From 2fc4a8fc3b752cab9b20e2aeb383c5ded9795f3d Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:24:26 +0900 Subject: [PATCH 0001/1029] =?UTF-8?q?[BE]=20Alarm=20=EB=A7=88=EC=BB=A4=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=B0=8F=20Use?= =?UTF-8?q?r=20=EC=97=94=ED=8B=B0=ED=8B=B0=EC=97=90=20device=20token=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/ftclub/cabinet/alarm/domain/Alarm.java | 7 +++++++ .../src/main/java/org/ftclub/cabinet/user/domain/User.java | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/Alarm.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/Alarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/Alarm.java new file mode 100644 index 000000000..919659465 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/Alarm.java @@ -0,0 +1,7 @@ +package org.ftclub.cabinet.alarm.domain; + +/** + * Alarm 마커 인터페이스입니다. + */ +public interface Alarm { +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index fbb277336..ada33b735 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; +import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.utils.ExceptionUtil; @@ -14,7 +15,6 @@ import java.time.LocalDateTime; import java.util.Objects; import java.util.regex.Pattern; -import lombok.extern.log4j.Log4j2; @Entity @Table(name = "USER") @@ -37,6 +37,9 @@ public class User { @Column(name = "EMAIL", unique = true) private String email; + @Column(name = "DEVICE_TOKEN") + private String deviceToken; + @Column(name = "BLACKHOLED_AT") private LocalDateTime blackholedAt = null; From f255edb312911cd8209db7f057a80089c661b40b Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:31:22 +0900 Subject: [PATCH 0002/1029] =?UTF-8?q?[BE]=20AlarmEvent=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/alarm/domain/AlarmEvent.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java new file mode 100644 index 000000000..b769eabb1 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java @@ -0,0 +1,18 @@ +package org.ftclub.cabinet.alarm.domain; + +import lombok.Getter; + +@Getter +public class AlarmEvent { + private final Long id; + private final Alarm alarm; + + private AlarmEvent(Long id, Alarm alarm) { + this.id = id; + this.alarm = alarm; + } + + public static AlarmEvent of(Long id, Alarm alarm) { + return new AlarmEvent(id, alarm); + } +} From c37bf146ae353226389520413abc5ffe0c8ceb11 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:34:41 +0900 Subject: [PATCH 0003/1029] =?UTF-8?q?[BE]=20AlarmOptOut=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/alarm/domain/AlarmType.java | 8 +++++ .../cabinet/user/domain/AlarmOptOut.java | 33 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmType.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmType.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmType.java new file mode 100644 index 000000000..183dd9f51 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmType.java @@ -0,0 +1,8 @@ +package org.ftclub.cabinet.alarm.domain; + +public enum AlarmType { + SLACK, + EMAIL, + PUSH, + ; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java new file mode 100644 index 000000000..01fcb6447 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java @@ -0,0 +1,33 @@ +package org.ftclub.cabinet.user.domain; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.ftclub.cabinet.alarm.domain.AlarmType; + +import javax.persistence.*; + +/** + * 유저의 알람 수신 거부 정보 엔티티입니다. + */ +@Entity +@Table(name = "ALARM_OPT_OUT") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@ToString +public class AlarmOptOut { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private Long id; + + @ManyToOne + @JoinColumn(name = "USER_ID", nullable = false) + private User user; + + @Enumerated(value = EnumType.STRING) + @Column(name = "ALARM_TYPE", length = 32) + private AlarmType alarmType; +} From e31d65d0ac58fa5ec34c5f176f022b0f2fc67cc6 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:36:45 +0900 Subject: [PATCH 0004/1029] =?UTF-8?q?[BE]=20AlarmOptOut=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftclub/cabinet/user/domain/AlarmOptOut.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java index 01fcb6447..5045a5ff5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java @@ -30,4 +30,18 @@ public class AlarmOptOut { @Enumerated(value = EnumType.STRING) @Column(name = "ALARM_TYPE", length = 32) private AlarmType alarmType; + + protected AlarmOptOut(User user, AlarmType alarmType) { + this.user = user; + this.alarmType = alarmType; + } + + public static AlarmOptOut of(User user, AlarmType alarmType) { + return new AlarmOptOut(user, alarmType); + } + + private boolean isValid() { + return user != null && alarmType != null; + } + } From dd899fe3fe9a6fbca1b70237ed2e7f2b8336a7b0 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:37:03 +0900 Subject: [PATCH 0005/1029] =?UTF-8?q?[BE]=20AlarmOptOut=20private=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9E=90=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java index 5045a5ff5..0399340c1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java @@ -31,7 +31,7 @@ public class AlarmOptOut { @Column(name = "ALARM_TYPE", length = 32) private AlarmType alarmType; - protected AlarmOptOut(User user, AlarmType alarmType) { + private AlarmOptOut(User user, AlarmType alarmType) { this.user = user; this.alarmType = alarmType; } From 64cd155d2f9bc96e87a2927533a11e2b9cc16db2 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:37:52 +0900 Subject: [PATCH 0006/1029] [BE] AlarmOptOut isvalid --- .../java/org/ftclub/cabinet/user/domain/AlarmOptOut.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java index 0399340c1..774ffcd62 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java @@ -5,6 +5,9 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.utils.ExceptionUtil; import javax.persistence.*; @@ -37,7 +40,10 @@ private AlarmOptOut(User user, AlarmType alarmType) { } public static AlarmOptOut of(User user, AlarmType alarmType) { - return new AlarmOptOut(user, alarmType); + AlarmOptOut alarmOptOut = new AlarmOptOut(user, alarmType); + ExceptionUtil.throwIfFalse(alarmOptOut.isValid(), + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + return alarmOptOut; } private boolean isValid() { From 8bae3406c54118db7893455a29a4885e6d74ec92 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:40:32 +0900 Subject: [PATCH 0007/1029] =?UTF-8?q?[BE]=20alarmsender,=20alarmeventhandl?= =?UTF-8?q?er=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/service/AlarmEventHandler.java | 22 +++++++++++++++++++ .../alarm/service/EmailAlarmSender.java | 10 +++++++++ .../alarm/service/PushAlarmSender.java | 10 +++++++++ .../alarm/service/SlackAlarmSender.java | 11 ++++++++++ 4 files changed, 53 insertions(+) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java new file mode 100644 index 000000000..e06109e86 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java @@ -0,0 +1,22 @@ +package org.ftclub.cabinet.alarm.service; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +public class AlarmEventHandler { + private final SlackAlarmSender slackAlarmSender; + private final EmailAlarmSender emailAlarmSender; + private final PushAlarmSender pushAlarmSender; + + @TransactionalEventListener + public void handleAlarmEvent(AlarmEvent alarmEvent) { + //TODO sender별로 매개변수 다름. + slackAlarmSender.send(alarmEvent); + emailAlarmSender.send(alarmEvent); + pushAlarmSender.send(alarmEvent); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java new file mode 100644 index 000000000..8ad8afd74 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java @@ -0,0 +1,10 @@ +package org.ftclub.cabinet.alarm.service; + +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.springframework.stereotype.Component; + +@Component +public class EmailAlarmSender { + void send(AlarmEvent alarmEvent) { + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java new file mode 100644 index 000000000..6f5e10dae --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java @@ -0,0 +1,10 @@ +package org.ftclub.cabinet.alarm.service; + +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.springframework.stereotype.Component; + +@Component +public class PushAlarmSender { + void send(AlarmEvent alarmEvent) { + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java new file mode 100644 index 000000000..979be66d0 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java @@ -0,0 +1,11 @@ +package org.ftclub.cabinet.alarm.service; + +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.springframework.stereotype.Component; + +@Component +public class SlackAlarmSender { + + void send(AlarmEvent alarmEvent) { + } +} From a2a79641c45f029466e7c409b0961e8e95c5f388 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:41:56 +0900 Subject: [PATCH 0008/1029] =?UTF-8?q?[BE]=20alarmeventhandler=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java | 6 +++--- .../ftclub/cabinet/alarm/service/AlarmEventHandler.java | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java index b769eabb1..9b3994677 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java @@ -4,11 +4,11 @@ @Getter public class AlarmEvent { - private final Long id; + private final Long receiverId; private final Alarm alarm; - private AlarmEvent(Long id, Alarm alarm) { - this.id = id; + private AlarmEvent(Long receiverId, Alarm alarm) { + this.receiverId = receiverId; this.alarm = alarm; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java index e06109e86..64788e242 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java @@ -2,19 +2,24 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; @Component @RequiredArgsConstructor public class AlarmEventHandler { + private final UserRepository userRepository; private final SlackAlarmSender slackAlarmSender; private final EmailAlarmSender emailAlarmSender; private final PushAlarmSender pushAlarmSender; @TransactionalEventListener public void handleAlarmEvent(AlarmEvent alarmEvent) { - //TODO sender별로 매개변수 다름. + User receiver = userRepository.findById(alarmEvent.getReceiverId()).orElseThrow(); + //TODO id로 멤버 조회 및 OPTIONS 조회 + //TODO sender별로 매개변수 다름. + 조건분기 slackAlarmSender.send(alarmEvent); emailAlarmSender.send(alarmEvent); pushAlarmSender.send(alarmEvent); From a8a6e0c4491e79131540cff3f35d25f2117574be Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:51:05 +0900 Subject: [PATCH 0009/1029] =?UTF-8?q?[BE]=20FEAT:=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=95=8C=EB=9E=8C=20=EC=9A=B0=EC=84=A0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/domain/ExtensionExpirationImminentAlarm.java | 7 +++++++ .../cabinet/alarm/domain/ExtensionIssuanceAlarm.java | 7 +++++++ .../ftclub/cabinet/alarm/domain/LentExpirationAlarm.java | 7 +++++++ .../cabinet/alarm/domain/LentExpirationImminentAlarm.java | 7 +++++++ .../org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java | 7 +++++++ 5 files changed, 35 insertions(+) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java new file mode 100644 index 000000000..3f962cc6c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java @@ -0,0 +1,7 @@ +package org.ftclub.cabinet.alarm.domain; + +/** + * 연장 만료 임박 알람 + */ +public class ExtensionExpirationImminentAlarm implements Alarm { +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java new file mode 100644 index 000000000..873e0ba45 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java @@ -0,0 +1,7 @@ +package org.ftclub.cabinet.alarm.domain; + +/** + * 연장권 발급 알람 + */ +public class ExtensionIssuanceAlarm implements Alarm { +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java new file mode 100644 index 000000000..314d304ec --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java @@ -0,0 +1,7 @@ +package org.ftclub.cabinet.alarm.domain; + +/** + * 대여 만료 알람 + */ +public class LentExpirationAlarm implements Alarm { +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java new file mode 100644 index 000000000..b68be2a48 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -0,0 +1,7 @@ +package org.ftclub.cabinet.alarm.domain; + +/** + * 대여 만료 임박 알람 + */ +public class LentExpirationImminentAlarm implements Alarm { +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java new file mode 100644 index 000000000..ccaa03968 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java @@ -0,0 +1,7 @@ +package org.ftclub.cabinet.alarm.domain; + +/** + * 대여 성공 알람 + */ +public class LentSuccessAlarm implements Alarm { +} From 77dceaafbc80b0e02b888e594e4a2d6188f44517 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:56:50 +0900 Subject: [PATCH 0010/1029] =?UTF-8?q?[BE]=20FEAT:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EB=90=A0=EB=B2=95=ED=95=9C=20=ED=94=84=EB=A1=9C=ED=8D=BC?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/domain/ExtensionExpirationImminentAlarm.java | 9 +++++++++ .../cabinet/alarm/domain/ExtensionIssuanceAlarm.java | 8 ++++++++ .../ftclub/cabinet/alarm/domain/LentExpirationAlarm.java | 9 +++++++++ .../alarm/domain/LentExpirationImminentAlarm.java | 8 ++++++++ .../ftclub/cabinet/alarm/domain/LentSuccessAlarm.java | 8 ++++++++ 5 files changed, 42 insertions(+) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java index 3f962cc6c..f1dd1cedb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java @@ -1,7 +1,16 @@ package org.ftclub.cabinet.alarm.domain; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDateTime; + /** * 연장 만료 임박 알람 */ +@AllArgsConstructor +@Getter public class ExtensionExpirationImminentAlarm implements Alarm { + private final String extensionName; + private final LocalDateTime extensionExpirationDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java index 873e0ba45..e0d087960 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java @@ -1,7 +1,15 @@ package org.ftclub.cabinet.alarm.domain; +import lombok.AllArgsConstructor; + +import java.time.LocalDateTime; + /** * 연장권 발급 알람 */ +@AllArgsConstructor public class ExtensionIssuanceAlarm implements Alarm { + private final String extensionName; + private final LocalDateTime extensionExpirationDate; + private final int daysToExtend; // .. ? } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java index 314d304ec..050dceef5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java @@ -1,7 +1,16 @@ package org.ftclub.cabinet.alarm.domain; +import lombok.AllArgsConstructor; +import org.ftclub.cabinet.cabinet.domain.CabinetPlace; + +import java.time.LocalDateTime; + /** * 대여 만료 알람 */ +@AllArgsConstructor public class LentExpirationAlarm implements Alarm { + private final CabinetPlace cabinetPlace; + private final LocalDateTime lentExpirationDate; + private final int daysAfterExpiration; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index b68be2a48..7ac539644 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -1,7 +1,15 @@ package org.ftclub.cabinet.alarm.domain; +import lombok.AllArgsConstructor; +import org.ftclub.cabinet.cabinet.domain.CabinetPlace; + +import java.time.LocalDateTime; + /** * 대여 만료 임박 알람 */ +@AllArgsConstructor public class LentExpirationImminentAlarm implements Alarm { + private final CabinetPlace cabinetPlace; + private final LocalDateTime lentExpirationDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java index ccaa03968..d9020b736 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java @@ -1,7 +1,15 @@ package org.ftclub.cabinet.alarm.domain; +import lombok.AllArgsConstructor; +import org.ftclub.cabinet.cabinet.domain.CabinetPlace; + +import java.time.LocalDateTime; + /** * 대여 성공 알람 */ +@AllArgsConstructor public class LentSuccessAlarm implements Alarm { + private final CabinetPlace cabinetPlace; + private final LocalDateTime lentExpirationDate; } From 733e919c728eb16bdfb13eaa582763e94f78ca9f Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 19 Oct 2023 17:58:03 +0900 Subject: [PATCH 0011/1029] =?UTF-8?q?[BE]=20DOCS:=20TODO=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java index 050dceef5..2448e1242 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java @@ -10,6 +10,7 @@ */ @AllArgsConstructor public class LentExpirationAlarm implements Alarm { + //TODO : CabinetPlace는 Dto로 전환이 필요함(엔티티임) private final CabinetPlace cabinetPlace; private final LocalDateTime lentExpirationDate; private final int daysAfterExpiration; From fcc8847200a4ebe7e9d8d2d57fd2fb9d0b6c96ae Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 19 Oct 2023 18:25:10 +0900 Subject: [PATCH 0012/1029] [BE] FEAT: slack properties added --- .../alarm/service/SlackAlarmSender.java | 9 +++++-- .../cabinet/config/SlackProperties.java | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/config/SlackProperties.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java index 979be66d0..503bc175a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java @@ -1,11 +1,16 @@ package org.ftclub.cabinet.alarm.service; +import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.config.SlackProperties; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class SlackAlarmSender { - void send(AlarmEvent alarmEvent) { - } + private final SlackProperties slackProperties; + + void send(AlarmEvent alarmEvent) { + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/config/SlackProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/SlackProperties.java new file mode 100644 index 000000000..9b05e9b56 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/config/SlackProperties.java @@ -0,0 +1,26 @@ +package org.ftclub.cabinet.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Getter +public class SlackProperties { + + @Value("${slack.token.singing-secret}") + private String singingSecert; + + @Value("${slack.token.bot-token") + private String botToken; + + @Value("${slack.token.app-token") + private String appToken; + + @Value("${slack.channel.cabi") + private String cabiChannelId; + + @Value("${slack.channel.ramdom") + private String randomChannelId; + +} From d1d4418cc327d2fa4e8cb41cc72361ffbf4d5eef Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 19 Oct 2023 19:58:41 +0900 Subject: [PATCH 0013/1029] [BE] FEAT: openfeign & slack bolt add --- backend/build.gradle | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/backend/build.gradle b/backend/build.gradle index 93a68ad98..63d8905d8 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -18,6 +18,7 @@ version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' ext { + set('springCloudVersion', "2021.0.8") snippetsDir = file('build/generated-snippets') snippetsDir.mkdirs() outputDocs = file('src/main/resources/static/docs') @@ -56,6 +57,14 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // spring-cloud - openfeign + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + + // slack + implementation group: 'com.slack.api', name: 'slack-api-client', version: '1.30.0' + implementation("com.slack.api:bolt-jetty:1.30.0") + + // querydsl implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}" @@ -97,6 +106,13 @@ dependencies { testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' } +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + + tasks.register('copyMainConfig', Copy) { from '../config/backend/src/main/resources' into 'src/main/resources' From 1452cb84d994d3e253b5fe629807c3de2cc95a26 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 19 Oct 2023 19:58:53 +0900 Subject: [PATCH 0014/1029] [BE] FEAT: feignclient enable --- .../java/org/ftclub/cabinet/CabinetApplication.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java b/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java index 0addc2169..79d4b3e2c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java +++ b/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java @@ -1,18 +1,20 @@ - package org.ftclub.cabinet; +package org.ftclub.cabinet; import org.ftclub.cabinet.config.CorsConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Import; +@EnableFeignClients @SpringBootApplication(exclude = SecurityAutoConfiguration.class) @Import({CorsConfig.class}) // @CrossOrigin(origins = {"*"}, allowedHeaders = {"*"}, originPatterns = {"*"}) public class CabinetApplication { - public static void main(String[] args) { - SpringApplication.run(CabinetApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(CabinetApplication.class, args); + } } From 9b909fe1fee73a3b1bf77ae87ad592ec6d4a6f68 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 19 Oct 2023 19:59:17 +0900 Subject: [PATCH 0015/1029] [BE] FEAT: slack request add --- .../cabinet/alarm/slack/SlackFeignClient.java | 18 ++++++++++++++++++ .../slack}/config/SlackProperties.java | 14 +++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackFeignClient.java rename backend/src/main/java/org/ftclub/cabinet/{ => alarm/slack}/config/SlackProperties.java (52%) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackFeignClient.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackFeignClient.java new file mode 100644 index 000000000..123b939d3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackFeignClient.java @@ -0,0 +1,18 @@ +package org.ftclub.cabinet.alarm.slack; + +import feign.FeignException.FeignClientException; +import org.ftclub.cabinet.alarm.slack.dto.SlackResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "slack-client", url = "https://slack.com/api/") +public interface SlackFeignClient { + + @GetMapping("/users.lookupByEmail") + SlackResponse getUserInfoByEmail( + @RequestHeader("Content-Type") String contentType, + @RequestHeader("Authorization") String bearerToken, + @RequestParam("email") String email) throws FeignClientException; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/config/SlackProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java similarity index 52% rename from backend/src/main/java/org/ftclub/cabinet/config/SlackProperties.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java index 9b05e9b56..8308504b6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/SlackProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.config; +package org.ftclub.cabinet.alarm.slack.config; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; @@ -8,18 +8,22 @@ @Getter public class SlackProperties { + private final String authorization = "Authorization"; + private final String contentType = "CONTENT_TYPE"; + private final String applicationForm = "application/x-www-form-urlencoded"; + private final String bearer = "Bearer "; + // private final String SLACK_ERROR_MESSAGE = "Slack API Response Error"; +// private final String ERROR_RESPONSE = "error"; + private final String INTRA42_EMAIL_DOMAIN = "@student.42seoul.kr"; + @Value("${slack.token.singing-secret}") private String singingSecert; - @Value("${slack.token.bot-token") private String botToken; - @Value("${slack.token.app-token") private String appToken; - @Value("${slack.channel.cabi") private String cabiChannelId; - @Value("${slack.channel.ramdom") private String randomChannelId; From d92516e846323f3b0ff4bc4abb05e522a9eca270 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 19 Oct 2023 20:20:50 +0900 Subject: [PATCH 0016/1029] =?UTF-8?q?[BE]=20FEAT:=20handler=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20LentSuccessAlarm=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/domain/LentExpirationAlarm.java | 11 +-- .../domain/LentExpirationImminentAlarm.java | 9 +-- .../alarm/domain/LentSuccessAlarm.java | 10 ++- .../alarm/service/AlarmEventHandler.java | 26 +++++-- .../alarm/service/EmailAlarmSender.java | 14 +++- .../alarm/service/PushAlarmSender.java | 4 +- .../alarm/service/SlackAlarmSender.java | 3 +- .../cabinet/lent/service/LentServiceImpl.java | 6 ++ .../cabinet/user/domain/AlarmOptOut.java | 9 +-- .../org/ftclub/cabinet/user/domain/User.java | 26 +++++-- .../user/repository/UserRepository.java | 8 ++ .../utils/overdue/manager/OverdueManager.java | 21 +++-- .../ftclub/cabinet/user/domain/UserTest.java | 2 + backend/src/test/resources/application.yml | 78 +++++++++++++++++++ 14 files changed, 178 insertions(+), 49 deletions(-) create mode 100644 backend/src/test/resources/application.yml diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java index 2448e1242..0b08d56c0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java @@ -1,17 +1,14 @@ package org.ftclub.cabinet.alarm.domain; import lombok.AllArgsConstructor; -import org.ftclub.cabinet.cabinet.domain.CabinetPlace; - -import java.time.LocalDateTime; +import lombok.Getter; /** * 대여 만료 알람 */ +@Getter @AllArgsConstructor public class LentExpirationAlarm implements Alarm { - //TODO : CabinetPlace는 Dto로 전환이 필요함(엔티티임) - private final CabinetPlace cabinetPlace; - private final LocalDateTime lentExpirationDate; - private final int daysAfterExpiration; + + private final Long daysLeftFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index 7ac539644..c8c5ce586 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -1,15 +1,14 @@ package org.ftclub.cabinet.alarm.domain; import lombok.AllArgsConstructor; -import org.ftclub.cabinet.cabinet.domain.CabinetPlace; - -import java.time.LocalDateTime; +import lombok.Getter; /** * 대여 만료 임박 알람 */ +@Getter @AllArgsConstructor public class LentExpirationImminentAlarm implements Alarm { - private final CabinetPlace cabinetPlace; - private final LocalDateTime lentExpirationDate; + + private final Long daysLeftFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java index d9020b736..19765876b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java @@ -1,15 +1,17 @@ package org.ftclub.cabinet.alarm.domain; -import lombok.AllArgsConstructor; -import org.ftclub.cabinet.cabinet.domain.CabinetPlace; - import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ftclub.cabinet.cabinet.domain.Location; /** * 대여 성공 알람 */ +@Getter @AllArgsConstructor public class LentSuccessAlarm implements Alarm { - private final CabinetPlace cabinetPlace; + + private final Location location; private final LocalDateTime lentExpirationDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java index 64788e242..4b88bf5b7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java @@ -1,7 +1,17 @@ package org.ftclub.cabinet.alarm.service; +import static org.ftclub.cabinet.alarm.domain.AlarmType.EMAIL; +import static org.ftclub.cabinet.alarm.domain.AlarmType.PUSH; +import static org.ftclub.cabinet.alarm.domain.AlarmType.SLACK; +import static org.ftclub.cabinet.exception.ExceptionStatus.NOT_FOUND_USER; + +import java.util.Set; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.user.domain.AlarmOptOut; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.stereotype.Component; @@ -17,11 +27,15 @@ public class AlarmEventHandler { @TransactionalEventListener public void handleAlarmEvent(AlarmEvent alarmEvent) { - User receiver = userRepository.findById(alarmEvent.getReceiverId()).orElseThrow(); - //TODO id로 멤버 조회 및 OPTIONS 조회 - //TODO sender별로 매개변수 다름. + 조건분기 - slackAlarmSender.send(alarmEvent); - emailAlarmSender.send(alarmEvent); - pushAlarmSender.send(alarmEvent); + User receiver = userRepository.findUserWithOptOutById(alarmEvent.getReceiverId()) + .orElseThrow(() -> new ServiceException(NOT_FOUND_USER)); + Set alarmOptOuts = receiver.getAlarmOptOuts() + .stream().map(AlarmOptOut::getAlarmType).collect(Collectors.toSet()); + if (alarmOptOuts.contains(SLACK)) + slackAlarmSender.send(receiver, alarmEvent); + else if (alarmOptOuts.contains(EMAIL)) + emailAlarmSender.send(receiver, alarmEvent); + else if (alarmOptOuts.contains(PUSH)) + pushAlarmSender.send(receiver, alarmEvent); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java index 8ad8afd74..948aca00b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java @@ -1,10 +1,22 @@ package org.ftclub.cabinet.alarm.service; +import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.config.MailOverdueProperties; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.utils.mail.EmailSender; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class EmailAlarmSender { - void send(AlarmEvent alarmEvent) { + + private final EmailSender emailSender; + private final MailOverdueProperties mailOverdueProperties; + + void send(User user, AlarmEvent alarmEvent) { + String subject = mailOverdueProperties.getSoonOverdueMailSubject(); + String template = mailOverdueProperties.getSoonOverdueMailTemplateUrl(); +// emailSender.sendMail(activeLent.getName(), activeLent.getEmail(), subject, template); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java index 6f5e10dae..34416ae2f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java @@ -1,10 +1,12 @@ package org.ftclub.cabinet.alarm.service; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Component; @Component public class PushAlarmSender { - void send(AlarmEvent alarmEvent) { + void send(User user, AlarmEvent alarmEvent) { +// fcmService.sendPushMessage(activeLent.getName(), overdueType, activeLent.getDaysLeftFromExpireDate()); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java index 979be66d0..d95f3e8e6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java @@ -1,11 +1,12 @@ package org.ftclub.cabinet.alarm.service; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Component; @Component public class SlackAlarmSender { - void send(AlarmEvent alarmEvent) { + void send(User user, AlarmEvent alarmEvent) { } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java index 9dcd7d516..abc361554 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java @@ -7,6 +7,8 @@ import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; @@ -25,6 +27,7 @@ import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; import org.ftclub.cabinet.user.service.UserService; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @Service @@ -41,6 +44,7 @@ public class LentServiceImpl implements LentService { private final UserService userService; private final BanHistoryRepository banHistoryRepository; private final LentMapper lentMapper; + private final ApplicationEventPublisher eventPublisher; @Override public void startLentCabinet(Long userId, Long cabinetId) { @@ -75,6 +79,8 @@ public void startLentCabinet(Long userId, Long cabinetId) { // 연체 시간 적용 lentPolicy.applyExpirationDate(lentHistory, cabinetActiveLentHistories, expiredAt); lentRepository.save(lentHistory); + eventPublisher.publishEvent(AlarmEvent.of(userId, + new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), expiredAt))); } @Override diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java index 774ffcd62..690993871 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java @@ -26,14 +26,14 @@ public class AlarmOptOut { @Column(name = "ID") private Long id; - @ManyToOne - @JoinColumn(name = "USER_ID", nullable = false) - private User user; - @Enumerated(value = EnumType.STRING) @Column(name = "ALARM_TYPE", length = 32) private AlarmType alarmType; + @ManyToOne + @JoinColumn(name = "USER_ID", nullable = false) + private User user; + private AlarmOptOut(User user, AlarmType alarmType) { this.user = user; this.alarmType = alarmType; @@ -49,5 +49,4 @@ public static AlarmOptOut of(User user, AlarmType alarmType) { private boolean isValid() { return user != null && alarmType != null; } - } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index ada33b735..0aa6cf0ed 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -1,5 +1,21 @@ package org.ftclub.cabinet.user.domain; +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -9,13 +25,6 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.utils.ExceptionUtil; -import javax.persistence.*; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotNull; -import java.time.LocalDateTime; -import java.util.Objects; -import java.util.regex.Pattern; - @Entity @Table(name = "USER") @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -50,6 +59,9 @@ public class User { @Column(name = "ROLE", length = 32, nullable = false) private UserRole role; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private Set alarmOptOuts; + protected User(String name, String email, LocalDateTime blackholedAt, UserRole userRole) { this.name = name; this.email = email; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index 9aff10154..872fa4762 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -72,4 +72,12 @@ public interface UserRepository extends JpaRepository { */ @Query("SELECT u FROM User u WHERE u.blackholedAt IS NULL OR u.blackholedAt > CURRENT_TIMESTAMP") List findByNoRiskOfFallingIntoBlackholeUsers(); + + /** + * 유저의 id로 OptOut 테이블과 결합된 유저 정보를 찾습니다. + */ + @Query("SELECT u FROM User u " + + "JOIN AlarmOptOut o ON u.userId = o.user.userId " + + "WHERE u.userId = :id") + Optional findUserWithOptOutById(@Param("id") Long id); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java index 3e76b1043..6bb2b5629 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java @@ -2,12 +2,16 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.service.CabinetService; import org.ftclub.cabinet.config.MailOverdueProperties; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.firebase.fcm.service.FCMService; import org.ftclub.cabinet.utils.mail.EmailSender; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; @Component @@ -25,6 +29,7 @@ public class OverdueManager { private final FCMService fcmService; private final CabinetService cabinetService; private final MailOverdueProperties mailOverdueProperties; + private final ApplicationEventPublisher eventPublisher; /** @@ -48,7 +53,6 @@ public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate public void handleOverdue(ActiveLentHistoryDto activeLent) { log.info("called handleOverdue with {}", activeLent); - String subject = null, template = null; OverdueType overdueType = getOverdueType(activeLent.getIsExpired(), activeLent.getDaysLeftFromExpireDate()); @@ -56,22 +60,15 @@ public void handleOverdue(ActiveLentHistoryDto activeLent) { case NONE: return; case SOON_OVERDUE: - subject = mailOverdueProperties.getSoonOverdueMailSubject(); - template = mailOverdueProperties.getSoonOverdueMailTemplateUrl(); + eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), + new LentExpirationImminentAlarm(activeLent.getDaysLeftFromExpireDate()))); break; case OVERDUE: cabinetService.updateStatus(activeLent.getCabinetId(), CabinetStatus.OVERDUE); - subject = mailOverdueProperties.getOverdueMailSubject(); - template = mailOverdueProperties.getOverdueMailTemplateUrl(); + eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), + new LentExpirationAlarm(activeLent.getDaysLeftFromExpireDate()))); break; } - try { - emailSender.sendMail(activeLent.getName(), activeLent.getEmail(), subject, - template); - fcmService.sendPushMessage(activeLent.getName(), overdueType, activeLent.getDaysLeftFromExpireDate()); - } catch (Exception e) { - e.printStackTrace(); - } } } diff --git a/backend/src/test/java/org/ftclub/cabinet/user/domain/UserTest.java b/backend/src/test/java/org/ftclub/cabinet/user/domain/UserTest.java index 719324a7b..7e05bea0d 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/domain/UserTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/domain/UserTest.java @@ -8,10 +8,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.exception.DomainException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +@RequiredArgsConstructor public class UserTest { @Test diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml new file mode 100644 index 000000000..26cf01dab --- /dev/null +++ b/backend/src/test/resources/application.yml @@ -0,0 +1,78 @@ +#directory -> "/src/test/resources" +spring: + production: false # 배포 환경에서는 true + config: + import: classpath:application-auth.yml + server: + fe-host: http://localhost + port: 2424 + + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:;MODE=MySQL + username: root + password: test_password + + sql: + init: + mode: embedded + + jpa: + hibernate: + ddl-auto: none + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + properties: + hibernate: + globally_quoted_identifiers: true + dialect: org.hibernate.dialect.MySQL5InnoDBDialect + format_sql: true + defer-datasource-initialization: true + + cabinet: + lent: + term: + private: 21 + share: 42 + penalty: + day: + share: 3 + padding: 2 + auth: + ft: + client-id: cliend_id + client-secret: client_secret + google: + client-id: client_id + client-secret: client_secret + jwt-secret-key: jwt_secret_key_must_be_long_jwt_secret_key_must_be_long_jwt_secret_key_must_be_long_jwt_secret_key_must_be_long + urls: + admin-login-callback: ${spring.server.fe-host}/v4/admin/auth/login/callback + user-login-callback: ${spring.server.fe-host}/v4/auth/login/callback + + schedule: + cron: + leave-absence: 0 0 0 * * * # 매일 0시 0분 0초 + risk-of-blackhole: 0 42 0 * * MON # 매주 월요일 0시 42분 0초 + no-risk-of-blackhole: 0 42 1 1 * * # 매월 1일 1시 42분 0초 + + mail: + display-sender-name: "42CABI" + soonoverdue: + term: -1 + subject: "42CABI 사물함 연체 예정 알림" + template: "mail/soonoverdue" + overdue: + term: 1 + subject: "42CABI 사물함 연체 알림" + template: "mail/overdue" + + host: smtp.gmail.com + port: 587 + username: test_email@gmail.com + password: test_password + properties: + mail: + smtp: + auth: true + starttls: + enable: true From ec0cb34ad99015e23a48de66476094345b4490d8 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 19 Oct 2023 20:29:47 +0900 Subject: [PATCH 0017/1029] [BE] FEAT: slack Request added --- .../cabinet/alarm/domain/SlackAlarm.java | 15 +++++++ .../alarm/service/SlackAlarmSender.java | 3 -- .../cabinet/alarm/slack/SlackApiManager.java | 40 +++++++++++++++++++ .../alarm/slack/dto/SlackResponse.java | 15 +++++++ .../alarm/slack/dto/SlackUserInfo.java | 23 +++++++++++ config | 2 +- 6 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/SlackAlarm.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/SlackAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/SlackAlarm.java new file mode 100644 index 000000000..cee68c6dd --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/SlackAlarm.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.alarm.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@RequiredArgsConstructor +@ToString +@Getter +public class SlackAlarm implements Alarm { + + private String receiverName; + private String receiverEmail; + private String receiverPhone; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java index f266b18b5..ab94532d7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java @@ -2,7 +2,6 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.domain.AlarmEvent; -import org.ftclub.cabinet.alarm.slack.config.SlackProperties; import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Component; @@ -10,8 +9,6 @@ @RequiredArgsConstructor public class SlackAlarmSender { - private final SlackProperties slackProperties; - void send(User user, AlarmEvent alarmEvent) { } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java new file mode 100644 index 000000000..590bad651 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java @@ -0,0 +1,40 @@ +package org.ftclub.cabinet.alarm.slack; + +import feign.FeignException.FeignClientException; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.slack.config.SlackProperties; +import org.ftclub.cabinet.alarm.slack.dto.SlackResponse; +import org.ftclub.cabinet.alarm.slack.dto.SlackUserInfo; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class SlackApiManager { + + private final SlackProperties slackProperties; + private final SlackFeignClient slackFeignClient; + + + public SlackUserInfo requestSlackUserInfo(String email) { + log.info("Called requestSlackUserInfo email={}", email); + try { + SlackResponse slackResponse = slackFeignClient.getUserInfoByEmail( + slackProperties.getContentType(), + slackProperties.getBearer() + slackProperties.getAppToken(), email); + String RESPONSE_ERROR_MSG = "error"; + if (slackResponse.getOk().equals(RESPONSE_ERROR_MSG)) { + log.error("Slack Response ERROR Error {} ", slackResponse); + throw new ServiceException(ExceptionStatus.NOT_FOUND_USER); + } + return slackResponse.getSlackUserInfo(); + } catch (FeignClientException e) { + log.error("{}", e.getMessage()); + throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); + } + } + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java new file mode 100644 index 000000000..73a6c7230 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.alarm.slack.dto; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class SlackResponse { + + private final String ok; + @JsonAlias("user") + private final SlackUserInfo slackUserInfo; +} + diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java new file mode 100644 index 000000000..8132167f5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java @@ -0,0 +1,23 @@ +package org.ftclub.cabinet.alarm.slack.dto; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class SlackUserInfo { + + String id; + String name; + @JsonAlias("real_name") + String realName; + @JsonAlias("team_id") + String teamId; + Boolean deleted; +} + diff --git a/config b/config index 0d2196d66..51641b05d 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 0d2196d668c90f898cdf6090c46bc6f65fa2df90 +Subproject commit 51641b05d864202651b8d3fe6c1e524ff1100da3 From 7cfe3be5806079b661052b5ae1754cfa5dead10f Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 19 Oct 2023 20:50:25 +0900 Subject: [PATCH 0018/1029] [BE] FEAT: Slack api management added --- .../cabinet/alarm/slack/SlackApiManager.java | 23 +++++- .../cabinet/exception/ExceptionStatus.java | 75 ++++++++++--------- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java index 590bad651..60ed06a5e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java @@ -1,6 +1,11 @@ package org.ftclub.cabinet.alarm.slack; +import com.slack.api.Slack; +import com.slack.api.methods.MethodsClient; +import com.slack.api.methods.SlackApiException; +import com.slack.api.methods.request.chat.ChatPostMessageRequest; import feign.FeignException.FeignClientException; +import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.slack.config.SlackProperties; @@ -18,7 +23,6 @@ public class SlackApiManager { private final SlackProperties slackProperties; private final SlackFeignClient slackFeignClient; - public SlackUserInfo requestSlackUserInfo(String email) { log.info("Called requestSlackUserInfo email={}", email); try { @@ -33,8 +37,23 @@ public SlackUserInfo requestSlackUserInfo(String email) { return slackResponse.getSlackUserInfo(); } catch (FeignClientException e) { log.error("{}", e.getMessage()); - throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); + throw new ServiceException(ExceptionStatus.SLACK_BAD_GATEWAY); } } + public void sendMessage(String channelId, String message) { + log.info("Called sendMessage channelId={}, message={}", channelId, message); + try { + MethodsClient methods = Slack.getInstance().methods(slackProperties.getAppToken()); + + ChatPostMessageRequest request = ChatPostMessageRequest.builder() + .channel(channelId) // DM & channel + .text(message) + .build(); + methods.chatPostMessage(request); + } catch (SlackApiException | IOException e) { + log.error("{}", e.getMessage()); + throw new ServiceException(ExceptionStatus.SLACK_BAD_GATEWAY); + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index 97b10e89b..bf6632b8c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -12,43 +12,44 @@ @RequiredArgsConstructor @Getter public enum ExceptionStatus { - NOT_FOUND_USER(HttpStatus.NOT_FOUND, "유저가 존재하지 않습니다"), - NOT_FOUND_ADMIN_USER(HttpStatus.NOT_FOUND, "어드민이 존재하지 않습니다"), - NOT_FOUND_CABINET(HttpStatus.NOT_FOUND, "사물함이 존재하지 않습니다."), - LENT_CLUB(HttpStatus.I_AM_A_TEAPOT, "동아리 전용 사물함입니다"), - LENT_EXPIRE_IMMINENT(HttpStatus.I_AM_A_TEAPOT, "만료가 임박한 공유 사물함입니다\n해당 사물함은 대여할 수 없습니다"), - LENT_FULL(HttpStatus.CONFLICT, "사물함에 잔여 자리가 없습니다"), - LENT_EXPIRED(HttpStatus.FORBIDDEN, "연체된 사물함은 대여할 수 없습니다"), - LENT_BROKEN(HttpStatus.FORBIDDEN, "고장난 사물함은 대여할 수 없습니다"), - NO_LENT_CABINET(HttpStatus.FORBIDDEN, "대여한 사물함이 없습니다"), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러가 발생했습니다"), - OAUTH_BAD_GATEWAY(HttpStatus.BAD_GATEWAY, "인증 서버와 통신 중 에러가 발생했습니다"), - UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "로그인 정보가 유효하지 않습니다\n다시 로그인해주세요"), - UNCHANGEABLE_CABINET(HttpStatus.BAD_REQUEST, "사물함의 상태를 변경할 수 없습니다."), - LENT_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 대여중인 사물함이 있습니다"), - INVALID_ARGUMENT(HttpStatus.BAD_REQUEST, "유효하지 않은 입력입니다"), - INVALID_STATUS(HttpStatus.BAD_REQUEST, "유효하지 않은 상태변경입니다"), - INVALID_EXPIRED_AT(HttpStatus.BAD_REQUEST, "잘못된 만료일 입니다"), - INCORRECT_ARGUMENT(HttpStatus.BAD_REQUEST, "잘못된 입력입니다"), - ALL_BANNED_USER(HttpStatus.BAD_REQUEST, "ALL 밴 상태의 유저입니다."), - SHARE_BANNED_USER(HttpStatus.BAD_REQUEST, "SHARE 밴 상태의 유저입니다."), - NOT_FOUND_BAN_HISTORY(HttpStatus.NOT_FOUND, "현재 정지 상태인 유저가 아닙니다."), - BLACKHOLED_USER(HttpStatus.BAD_REQUEST, "블랙홀 상태의 유저입니다."), - BLACKHOLE_REFRESHING(HttpStatus.BAD_REQUEST, "블랙홀 갱신 중 입니다.\n잠시 후에 다시 시도해주세요."), - UNAUTHORIZED_ADMIN(HttpStatus.UNAUTHORIZED, "관리자 로그인 정보가 유효하지 않습니다\n다시 로그인해주세요"), - UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "사용자 로그인 정보가 유효하지 않습니다\n다시 로그인해주세요"), - EXTERNAL_API_EXCEPTION(HttpStatus.BAD_REQUEST, "외부 API와 통신 중 에러가 발생했습니다"), - EXISTED_CLUB_USER(HttpStatus.CONFLICT, "이미 존재하는 동아리 유저입니다"), - CLUB_HAS_LENT_CABINET(HttpStatus.NOT_ACCEPTABLE, "대여 중인 사물함을 반납 후 삭제할 수 있습니다."), - ; + NOT_FOUND_USER(HttpStatus.NOT_FOUND, "유저가 존재하지 않습니다"), + NOT_FOUND_ADMIN_USER(HttpStatus.NOT_FOUND, "어드민이 존재하지 않습니다"), + NOT_FOUND_CABINET(HttpStatus.NOT_FOUND, "사물함이 존재하지 않습니다."), + LENT_CLUB(HttpStatus.I_AM_A_TEAPOT, "동아리 전용 사물함입니다"), + LENT_EXPIRE_IMMINENT(HttpStatus.I_AM_A_TEAPOT, "만료가 임박한 공유 사물함입니다\n해당 사물함은 대여할 수 없습니다"), + LENT_FULL(HttpStatus.CONFLICT, "사물함에 잔여 자리가 없습니다"), + LENT_EXPIRED(HttpStatus.FORBIDDEN, "연체된 사물함은 대여할 수 없습니다"), + LENT_BROKEN(HttpStatus.FORBIDDEN, "고장난 사물함은 대여할 수 없습니다"), + NO_LENT_CABINET(HttpStatus.FORBIDDEN, "대여한 사물함이 없습니다"), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러가 발생했습니다"), + OAUTH_BAD_GATEWAY(HttpStatus.BAD_GATEWAY, "인증 서버와 통신 중 에러가 발생했습니다"), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "로그인 정보가 유효하지 않습니다\n다시 로그인해주세요"), + UNCHANGEABLE_CABINET(HttpStatus.BAD_REQUEST, "사물함의 상태를 변경할 수 없습니다."), + LENT_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 대여중인 사물함이 있습니다"), + INVALID_ARGUMENT(HttpStatus.BAD_REQUEST, "유효하지 않은 입력입니다"), + INVALID_STATUS(HttpStatus.BAD_REQUEST, "유효하지 않은 상태변경입니다"), + INVALID_EXPIRED_AT(HttpStatus.BAD_REQUEST, "잘못된 만료일 입니다"), + INCORRECT_ARGUMENT(HttpStatus.BAD_REQUEST, "잘못된 입력입니다"), + ALL_BANNED_USER(HttpStatus.BAD_REQUEST, "ALL 밴 상태의 유저입니다."), + SHARE_BANNED_USER(HttpStatus.BAD_REQUEST, "SHARE 밴 상태의 유저입니다."), + NOT_FOUND_BAN_HISTORY(HttpStatus.NOT_FOUND, "현재 정지 상태인 유저가 아닙니다."), + BLACKHOLED_USER(HttpStatus.BAD_REQUEST, "블랙홀 상태의 유저입니다."), + BLACKHOLE_REFRESHING(HttpStatus.BAD_REQUEST, "블랙홀 갱신 중 입니다.\n잠시 후에 다시 시도해주세요."), + UNAUTHORIZED_ADMIN(HttpStatus.UNAUTHORIZED, "관리자 로그인 정보가 유효하지 않습니다\n다시 로그인해주세요"), + UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "사용자 로그인 정보가 유효하지 않습니다\n다시 로그인해주세요"), + EXTERNAL_API_EXCEPTION(HttpStatus.BAD_REQUEST, "외부 API와 통신 중 에러가 발생했습니다"), + EXISTED_CLUB_USER(HttpStatus.CONFLICT, "이미 존재하는 동아리 유저입니다"), + CLUB_HAS_LENT_CABINET(HttpStatus.NOT_ACCEPTABLE, "대여 중인 사물함을 반납 후 삭제할 수 있습니다."), + SLACK_BAD_GATEWAY(HttpStatus.BAD_GATEWAY, "슬랙과 통신 중 에러가 발생했습니다"), + ; - final private int statusCode; - final private String message; - final private String error; + final private int statusCode; + final private String message; + final private String error; - ExceptionStatus(HttpStatus status, String message) { - this.statusCode = status.value(); - this.message = message; - this.error = status.getReasonPhrase(); - } + ExceptionStatus(HttpStatus status, String message) { + this.statusCode = status.value(); + this.message = message; + this.error = status.getReasonPhrase(); + } } From 5a786dc44448e86492ab34171e89ec03b379902d Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 19 Oct 2023 21:25:26 +0900 Subject: [PATCH 0019/1029] [BE] FEAT: Slack api message send added --- .../ftclub/cabinet/alarm/domain/SlackAlarm.java | 15 --------------- .../cabinet/alarm/service/SlackAlarmSender.java | 14 ++++++++++++++ .../cabinet/alarm/slack/SlackApiManager.java | 15 ++++++++++----- .../alarm/slack/config/SlackProperties.java | 5 ----- .../ftclub/cabinet/exception/ExceptionStatus.java | 3 +++ 5 files changed, 27 insertions(+), 25 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/SlackAlarm.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/SlackAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/SlackAlarm.java deleted file mode 100644 index cee68c6dd..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/SlackAlarm.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.ftclub.cabinet.alarm.domain; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -@RequiredArgsConstructor -@ToString -@Getter -public class SlackAlarm implements Alarm { - - private String receiverName; - private String receiverEmail; - private String receiverPhone; -} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java index ab94532d7..cce505ef1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java @@ -2,13 +2,27 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.slack.SlackApiManager; +import org.ftclub.cabinet.alarm.slack.dto.SlackUserInfo; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Component; +import org.thymeleaf.util.StringUtils; @Component @RequiredArgsConstructor public class SlackAlarmSender { + private final SlackApiManager slackApiManager; + void send(User user, AlarmEvent alarmEvent) { + SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); + String id = slackUserInfo.getId(); + if (StringUtils.isEmpty(id)) { + throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); + } + + slackApiManager.sendMessage(alarmEvent.getAlarm().toString(), id); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java index 60ed06a5e..e7de7ccc3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java @@ -24,20 +24,25 @@ public class SlackApiManager { private final SlackFeignClient slackFeignClient; public SlackUserInfo requestSlackUserInfo(String email) { + log.info("Called requestSlackUserInfo email={}", email); + try { SlackResponse slackResponse = slackFeignClient.getUserInfoByEmail( - slackProperties.getContentType(), - slackProperties.getBearer() + slackProperties.getAppToken(), email); + slackProperties.getApplicationForm(), + slackProperties.getBearer() + slackProperties.getAppToken(), + email); + String RESPONSE_ERROR_MSG = "error"; if (slackResponse.getOk().equals(RESPONSE_ERROR_MSG)) { log.error("Slack Response ERROR Error {} ", slackResponse); - throw new ServiceException(ExceptionStatus.NOT_FOUND_USER); + throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); } + return slackResponse.getSlackUserInfo(); } catch (FeignClientException e) { log.error("{}", e.getMessage()); - throw new ServiceException(ExceptionStatus.SLACK_BAD_GATEWAY); + throw new ServiceException(ExceptionStatus.SLACK_REQUEST_BAD_GATEWAY); } } @@ -53,7 +58,7 @@ public void sendMessage(String channelId, String message) { methods.chatPostMessage(request); } catch (SlackApiException | IOException e) { log.error("{}", e.getMessage()); - throw new ServiceException(ExceptionStatus.SLACK_BAD_GATEWAY); + throw new ServiceException(ExceptionStatus.SLACK_MESSAGE_SEND_BAD_GATEWAY); } } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java index 8308504b6..72989d1ac 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java @@ -8,13 +8,8 @@ @Getter public class SlackProperties { - private final String authorization = "Authorization"; - private final String contentType = "CONTENT_TYPE"; private final String applicationForm = "application/x-www-form-urlencoded"; private final String bearer = "Bearer "; - // private final String SLACK_ERROR_MESSAGE = "Slack API Response Error"; -// private final String ERROR_RESPONSE = "error"; - private final String INTRA42_EMAIL_DOMAIN = "@student.42seoul.kr"; @Value("${slack.token.singing-secret}") private String singingSecert; diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index bf6632b8c..79395a13d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -41,6 +41,9 @@ public enum ExceptionStatus { EXISTED_CLUB_USER(HttpStatus.CONFLICT, "이미 존재하는 동아리 유저입니다"), CLUB_HAS_LENT_CABINET(HttpStatus.NOT_ACCEPTABLE, "대여 중인 사물함을 반납 후 삭제할 수 있습니다."), SLACK_BAD_GATEWAY(HttpStatus.BAD_GATEWAY, "슬랙과 통신 중 에러가 발생했습니다"), + SLACK_REQUEST_BAD_GATEWAY(HttpStatus.BAD_GATEWAY, "슬랙 인증 중 에러가 발생했습니다"), + SLACK_MESSAGE_SEND_BAD_GATEWAY(HttpStatus.BAD_GATEWAY, "슬랙 메세지 전송 중 에러가 발생했습니다"), + SLACK_ID_NOT_FOUND(HttpStatus.NOT_FOUND, "슬랙 아이디를 찾을 수 없습니다."), ; final private int statusCode; From a3d7e8c157f9e9b3b1b2b929c6717ac675d1ebd7 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 19 Oct 2023 22:40:25 +0900 Subject: [PATCH 0020/1029] =?UTF-8?q?[BE]=20FEAT:=20=EC=82=AC=EB=AC=BC?= =?UTF-8?q?=ED=95=A8=20=EA=B4=80=EB=A0=A8=20=EB=8C=80=EC=97=AC=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5,=20=EB=A7=8C=EB=A3=8C=20=EC=9E=84=EB=B0=95,=20?= =?UTF-8?q?=EC=97=B0=EC=B2=B4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20Email=20=ED=85=9C=ED=94=8C=EB=A6=BF=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/LentExpirationImminentAlarm.java | 2 +- .../ftclub/cabinet/alarm/domain/MailDto.java | 12 +++++ .../alarm/service/EmailAlarmSender.java | 49 +++++++++++++++++-- .../cabinet/config/AlarmProperties.java | 49 +++++++++++++++++++ .../cabinet/config/MailOverdueProperties.java | 29 +++++++++-- .../cabinet/exception/ExceptionStatus.java | 2 + .../cabinet/utils/mail/EmailSender.java | 28 ++++++++--- .../utils/overdue/manager/OverdueManager.java | 2 - .../resources/templates/mail/lentsuccess.html | 33 +++++++++++++ .../resources/templates/mail/overdue.html | 6 +-- .../resources/templates/mail/soonoverdue.html | 7 +-- .../utils/mail/EmailSenderUnitTest.java | 4 +- .../manager/OverdueManagerUnitTest.java | 17 +++++-- config | 2 +- 14 files changed, 212 insertions(+), 30 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/MailDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/config/AlarmProperties.java create mode 100644 backend/src/main/resources/templates/mail/lentsuccess.html diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index c8c5ce586..3edc3f95b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -10,5 +10,5 @@ @AllArgsConstructor public class LentExpirationImminentAlarm implements Alarm { - private final Long daysLeftFromExpireDate; + private final Long daysAfterFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/MailDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/MailDto.java new file mode 100644 index 000000000..3a12ea2d3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/MailDto.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.alarm.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class MailDto { + + String subject; + String template; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java index 948aca00b..ae5f3c57c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java @@ -1,12 +1,24 @@ package org.ftclub.cabinet.alarm.service; +import javax.mail.MessagingException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.alarm.domain.Alarm; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; +import org.ftclub.cabinet.alarm.domain.MailDto; import org.ftclub.cabinet.config.MailOverdueProperties; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.utils.mail.EmailSender; import org.springframework.stereotype.Component; +@Slf4j @Component @RequiredArgsConstructor public class EmailAlarmSender { @@ -15,8 +27,39 @@ public class EmailAlarmSender { private final MailOverdueProperties mailOverdueProperties; void send(User user, AlarmEvent alarmEvent) { - String subject = mailOverdueProperties.getSoonOverdueMailSubject(); - String template = mailOverdueProperties.getSoonOverdueMailTemplateUrl(); -// emailSender.sendMail(activeLent.getName(), activeLent.getEmail(), subject, template); + Alarm alarm = alarmEvent.getAlarm(); + MailDto mailDto = getMailDto(alarm); + try { + emailSender.sendMail(user.getName(), user.getEmail(), mailDto.getSubject(), + mailDto.getTemplate(), alarm); + log.info("{} ({})에게 메일을 성공적으로 보냈습니다.", user.getName(), user.getEmail()); + } catch (MessagingException e) { + log.error("메일 전송 중 오류가 발생했습니다: {}", e.getMessage()); + throw new ServiceException(ExceptionStatus.MAIL_SEND_FAIL); + } + } + + private MailDto getMailDto(Alarm alarm) { + if (alarm instanceof LentSuccessAlarm) { + return new MailDto(mailOverdueProperties.getSoonOverdueMailSubject(), + mailOverdueProperties.getSoonOverdueMailTemplateUrl()); + } + else if (alarm instanceof LentExpirationAlarm) { + return new MailDto(mailOverdueProperties.getOverdueMailSubject(), + mailOverdueProperties.getOverdueMailTemplateUrl()); + } + else if (alarm instanceof LentExpirationImminentAlarm) { + return new MailDto(mailOverdueProperties.getSoonOverdueMailSubject(), + mailOverdueProperties.getSoonOverdueMailTemplateUrl()); + } + else if (alarm instanceof ExtensionIssuanceAlarm) { + return new MailDto(mailOverdueProperties.getExtensionIssuanceMailSubject(), + mailOverdueProperties.getExtensionIssuanceMailTemplateUrl()); + } + else if (alarm instanceof ExtensionExpirationImminentAlarm) { + return new MailDto(mailOverdueProperties.getExtensionExpirationImminentMailSubject(), + mailOverdueProperties.getExtensionExpirationImminentMailTemplateUrl()); + } + else throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/AlarmProperties.java new file mode 100644 index 000000000..41f00c081 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/config/AlarmProperties.java @@ -0,0 +1,49 @@ +package org.ftclub.cabinet.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Getter +public class AlarmProperties { + + @Value("${alarm.lentSuccess.subject}") + private String lentSuccessSubject; + + @Value("${alarm.lentSuccess.template}") + private String lentSuccessTemplate; + + @Value("${alarm.lentExpiration.term}") + private Long lentExpirationTerm; + + @Value("${alarm.lentExpiration.subject}") + private String lentExpirationSubject; + + @Value("${alarm.lentExpiration.template}") + private String lentExpirationTemplate; + + @Value("${alarm.lentExpirationImminent.term}") + private Long lentExpirationImminentTerm; + + @Value("${alarm.lentExpirationImminent.subject}") + private String lentExpirationImminentSubject; + + @Value("${alarm.lentExpirationImminent.template}") + private String lentExpirationImminentTemplate; + + @Value("${alarm.extensionIssuance.subject}") + private String extensionIssuanceSubject; + + @Value("${alarm.extensionIssuance.template}") + private String extensionIssuanceTemplate; + + @Value("${alarm.extensionImminent.term}") + private Long extensionImminentTerm; + + @Value("${alarm.extensionImminent.subject}") + private String extensionImminentSubject; + + @Value("${alarm.extensionImminent.template}") + private String extensionImminentTemplate; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java index d5380d0af..c1cd8d9aa 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java @@ -8,8 +8,11 @@ @Getter public class MailOverdueProperties { - @Value("${spring.mail.soonoverdue.term}") - private Long soonOverdueTerm; + @Value("${spring.mail.lentSuccess.subject}") + private String lentSuccessMailSubject; + + @Value("${spring.mail.lentSuccess.template}") + private String lentSuccessMailTemplateUrl; @Value("${spring.mail.overdue.subject}") private String overdueMailSubject; @@ -17,9 +20,27 @@ public class MailOverdueProperties { @Value("${spring.mail.overdue.template}") private String overdueMailTemplateUrl; - @Value("${spring.mail.soonoverdue.subject}") + @Value("${spring.mail.soonOverdue.term}") + private Long soonOverdueTerm; + + @Value("${spring.mail.soonOverdue.subject}") private String soonOverdueMailSubject; - @Value("${spring.mail.soonoverdue.template}") + @Value("${spring.mail.soonOverdue.template}") private String soonOverdueMailTemplateUrl; + + @Value("${spring.mail.extensionIssuance.subject}") + private String extensionIssuanceMailSubject; + + @Value("${spring.mail.extensionIssuance.template}") + private String extensionIssuanceMailTemplateUrl; + + @Value("${spring.mail.extensionExpiration.term}") + private Long extensionExpirationTerm; + + @Value("${spring.mail.extensionExpiration.subject}") + private String extensionExpirationImminentMailSubject; + + @Value("${spring.mail.extensionExpiration.template}") + private String extensionExpirationImminentMailTemplateUrl; } diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index 97b10e89b..c52b09f68 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -40,6 +40,8 @@ public enum ExceptionStatus { EXTERNAL_API_EXCEPTION(HttpStatus.BAD_REQUEST, "외부 API와 통신 중 에러가 발생했습니다"), EXISTED_CLUB_USER(HttpStatus.CONFLICT, "이미 존재하는 동아리 유저입니다"), CLUB_HAS_LENT_CABINET(HttpStatus.NOT_ACCEPTABLE, "대여 중인 사물함을 반납 후 삭제할 수 있습니다."), + MAIL_SEND_FAIL(HttpStatus.BAD_GATEWAY, "메일 전송 중 에러가 발생했습니다"), + NOT_FOUND_ALARM(HttpStatus.BAD_REQUEST, "알람이 존재하지 않습니다"), ; final private int statusCode; diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java b/backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java index 289656cd2..243914410 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java @@ -4,6 +4,11 @@ import javax.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; +import org.ftclub.cabinet.cabinet.domain.Location; import org.ftclub.cabinet.config.GmailProperties; import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; @@ -21,7 +26,7 @@ public class EmailSender { private final ITemplateEngine templateEngine; private final GmailProperties gmailProperties; - public void sendMail(String name, String to, String subject, String template) + public void sendMail(String name, String to, String subject, String template, Alarm alarm) throws MessagingException, MailException { log.info("called EmailSender for {}, {}, {}", name, to, subject); if (gmailProperties.getIsProduction() == false) { @@ -37,15 +42,24 @@ public void sendMail(String name, String to, String subject, String template) Context context = new Context(); context.setVariable("name", name); + if (alarm instanceof LentSuccessAlarm) { + Location location = ((LentSuccessAlarm) alarm).getLocation(); + String locationString = location.getBuilding() + " " + + location.getFloor() + "층 " + location.getSection(); + context.setVariable("location", locationString); + context.setVariable("expireDate", ((LentSuccessAlarm) alarm).getLentExpirationDate()); + } + else if (alarm instanceof LentExpirationAlarm) { + context.setVariable("expireDate", + ((LentExpirationAlarm) alarm).getDaysLeftFromExpireDate()); + } else if (alarm instanceof LentExpirationImminentAlarm) { + long overdueDays = ((LentExpirationImminentAlarm) alarm).getDaysAfterFromExpireDate(); + context.setVariable("overdueDays", overdueDays); + } String htmlContent = templateEngine.process(template, context); helper.setText(htmlContent, true); - try { - javaMailSender.send(message); - log.info("{} ({})에게 메일을 성공적으로 보냈습니다.", name, to); - } catch (MailException e) { - log.error("메일 전송 중 오류가 발생했습니다: {}", e.getMessage()); - } + javaMailSender.send(message); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java index 6bb2b5629..3ac79ee56 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java @@ -25,8 +25,6 @@ */ public class OverdueManager { - private final EmailSender emailSender; - private final FCMService fcmService; private final CabinetService cabinetService; private final MailOverdueProperties mailOverdueProperties; private final ApplicationEventPublisher eventPublisher; diff --git a/backend/src/main/resources/templates/mail/lentsuccess.html b/backend/src/main/resources/templates/mail/lentsuccess.html new file mode 100644 index 000000000..1be585ffd --- /dev/null +++ b/backend/src/main/resources/templates/mail/lentsuccess.html @@ -0,0 +1,33 @@ + + + + + + + Document + + + +
+

🚨 사물함 대여 성공 알림 🚨

+
+
+
+ 님, 의 사물함 대여에 성공했습니다.

+ + 대여 기간은 까지 이며 기한 만료 전 반납 부탁드립니다.

+ + 연체한 일 수의 제곱 일수 만큼 패널티가 주어집니다.
+ 패널티 일수 만큼 사물함 이용이 불가합니다.
+ 반복적인 연체 발생 시 TIG가 부여될 수 있습니다.
+
+
+
+ 사물함 대여 서비스 바로가기 ➡ https://cabi.42seoul.io
+ 사물함 서비스 관련 문의사항 ➡ https://42born2code.slack.com/archives/C02V6GE8LD7 +
+
+ + + \ No newline at end of file diff --git a/backend/src/main/resources/templates/mail/overdue.html b/backend/src/main/resources/templates/mail/overdue.html index 21fe7816c..eee1a4418 100644 --- a/backend/src/main/resources/templates/mail/overdue.html +++ b/backend/src/main/resources/templates/mail/overdue.html @@ -16,10 +16,10 @@

🚨 사물함 대여 연체 알림 🚨

님, 이용 중인 사물함이 연체되었습니다.

- 사물함의 대여 기간을 확인하신 후 반납 부탁드립니다.

+ 현재 일 연체 되었으며, 확인 후 반납 부탁드립니다.

- 42CABI 서비스 사용 이후부터 현재까지 연체한 일 수만큼 연체일이 누적됩니다.
- 누적된 연체일만큼 사물함 이용이 불가합니다.
+ 연체한 일 수의 제곱 일수 만큼 패널티가 주어집니다.
+ 패널티 일수 만큼 사물함 이용이 불가합니다.
반복적인 연체 발생 시 TIG가 부여될 수 있습니다.

diff --git a/backend/src/main/resources/templates/mail/soonoverdue.html b/backend/src/main/resources/templates/mail/soonoverdue.html index e57246d54..f5e81139e 100644 --- a/backend/src/main/resources/templates/mail/soonoverdue.html +++ b/backend/src/main/resources/templates/mail/soonoverdue.html @@ -14,11 +14,12 @@

🚨 사물함 대여 기간 만료 예정 알림 🚨


- 님, 사물함 사용 기간이 만료될 예정입니다.

+ 님, 사물함 사용 기간이 곧 만료될 예정입니다.

- 사물함의 대여 기간을 확인하신 후 반납 부탁드립니다.

+ 대여 기간은 까지 이며 기한 만료 전 반납 부탁드립니다.

- 누적된 연체일만큼 사물함 이용이 불가합니다.
+ 연체한 일 수의 제곱 일수 만큼 패널티가 주어집니다.
+ 패널티 일수 만큼 사물함 이용이 불가합니다.
반복적인 연체 발생 시 TIG가 부여될 수 있습니다.

diff --git a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java index f37594a86..23df3f780 100644 --- a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java @@ -77,7 +77,7 @@ void setupBeforeEach() { void 실패_sendMail_개발환경() throws MessagingException, MailException { given(gmailProperties.getIsProduction()).willReturn(false); - emailSender.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate()); + emailSender.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate(), null); then(javaMailSender).should(never()).send( any(MimeMessage.class) @@ -92,7 +92,7 @@ void setupBeforeEach() { MimeMessage mimeMessage = new MimeMessage((javax.mail.Session) null); given(javaMailSender.createMimeMessage()).willReturn(mimeMessage); - emailSender.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate()); + emailSender.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate(), null); then(javaMailSender).should().send( any(MimeMessage.class) diff --git a/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java index f2bec49d9..6fda55a77 100644 --- a/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java @@ -6,6 +6,8 @@ import static org.mockito.Mockito.never; import javax.mail.MessagingException; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.service.CabinetService; import org.ftclub.cabinet.config.GmailProperties; @@ -15,6 +17,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -23,7 +26,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mail.MailException; +// TODO: 2021-10-07 알람 이벤트 핸들러 방식으로 변경 필요 @ExtendWith(MockitoExtension.class) +@Disabled public class OverdueManagerUnitTest { @Mock @@ -126,7 +131,8 @@ void setUp() { activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), mailOverdueProperties.getOverdueMailSubject(), - mailOverdueProperties.getOverdueMailTemplateUrl() + mailOverdueProperties.getOverdueMailTemplateUrl(), + new LentExpirationAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) ); } @@ -149,7 +155,8 @@ void setUp() { activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), mailOverdueProperties.getSoonOverdueMailSubject(), - mailOverdueProperties.getSoonOverdueMailTemplateUrl() + mailOverdueProperties.getSoonOverdueMailTemplateUrl(), + new LentExpirationImminentAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) ); } @@ -174,13 +181,15 @@ void setUp() { activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), mailOverdueProperties.getSoonOverdueMailSubject(), - mailOverdueProperties.getSoonOverdueMailTemplateUrl() + mailOverdueProperties.getSoonOverdueMailTemplateUrl(), + null ); then(emailSender).should(never()).sendMail( activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), mailOverdueProperties.getOverdueMailSubject(), - mailOverdueProperties.getOverdueMailTemplateUrl() + mailOverdueProperties.getOverdueMailTemplateUrl(), + null ); } } diff --git a/config b/config index 51641b05d..23c8d65ee 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 51641b05d864202651b8d3fe6c1e524ff1100da3 +Subproject commit 23c8d65eeb5a810c2e50d6b14ad31367d0327049 From 1d77a581940c4b76315fc126f7ef118fb9ee82f6 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 19 Oct 2023 22:57:17 +0900 Subject: [PATCH 0021/1029] [BE] FEAT: file names refactoring --- .../alarm/domain/ExtensionIssuanceAlarm.java | 2 + .../{utils => alarm}/mail/EmailSender.java | 2 +- .../alarm/{domain => mail}/MailDto.java | 2 +- .../mail/config/MailAlarmProperties.java} | 4 +- .../alarm/service/EmailAlarmSender.java | 28 +++++------ .../utils/overdue/manager/OverdueManager.java | 8 ++-- .../utils/mail/EmailSenderUnitTest.java | 1 + .../manager/OverdueManagerUnitTest.java | 48 +++++++++---------- 8 files changed, 48 insertions(+), 47 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/{utils => alarm}/mail/EmailSender.java (98%) rename backend/src/main/java/org/ftclub/cabinet/alarm/{domain => mail}/MailDto.java (78%) rename backend/src/main/java/org/ftclub/cabinet/{config/MailOverdueProperties.java => alarm/mail/config/MailAlarmProperties.java} (94%) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java index e0d087960..c9e7b3a6f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java @@ -3,11 +3,13 @@ import lombok.AllArgsConstructor; import java.time.LocalDateTime; +import lombok.Getter; /** * 연장권 발급 알람 */ @AllArgsConstructor +@Getter public class ExtensionIssuanceAlarm implements Alarm { private final String extensionName; private final LocalDateTime extensionExpirationDate; diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailSender.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailSender.java index 243914410..6a828659f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailSender.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.utils.mail; +package org.ftclub.cabinet.alarm.mail; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/MailDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/MailDto.java similarity index 78% rename from backend/src/main/java/org/ftclub/cabinet/alarm/domain/MailDto.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/mail/MailDto.java index 3a12ea2d3..b07964a26 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/MailDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/MailDto.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.alarm.domain; +package org.ftclub.cabinet.alarm.mail; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/MailAlarmProperties.java similarity index 94% rename from backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/MailAlarmProperties.java index c1cd8d9aa..de6f60529 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/MailAlarmProperties.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.config; +package org.ftclub.cabinet.alarm.mail.config; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; @@ -6,7 +6,7 @@ @Component @Getter -public class MailOverdueProperties { +public class MailAlarmProperties { @Value("${spring.mail.lentSuccess.subject}") private String lentSuccessMailSubject; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java index 1b866d539..240ac198e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java @@ -10,12 +10,12 @@ import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; -import org.ftclub.cabinet.alarm.domain.MailDto; -import org.ftclub.cabinet.config.MailOverdueProperties; +import org.ftclub.cabinet.alarm.mail.MailDto; +import org.ftclub.cabinet.alarm.mail.config.MailAlarmProperties; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.utils.mail.EmailSender; +import org.ftclub.cabinet.alarm.mail.EmailSender; import org.springframework.stereotype.Component; @Slf4j @@ -24,7 +24,7 @@ public class EmailAlarmSender { private final EmailSender emailSender; - private final MailOverdueProperties mailOverdueProperties; + private final MailAlarmProperties mailAlarmProperties; void send(User user, AlarmEvent alarmEvent) { Alarm alarm = alarmEvent.getAlarm(); @@ -41,24 +41,24 @@ void send(User user, AlarmEvent alarmEvent) { private MailDto getMailDto(Alarm alarm) { if (alarm instanceof LentSuccessAlarm) { - return new MailDto(mailOverdueProperties.getSoonOverdueMailSubject(), - mailOverdueProperties.getSoonOverdueMailTemplateUrl()); + return new MailDto(mailAlarmProperties.getSoonOverdueMailSubject(), + mailAlarmProperties.getSoonOverdueMailTemplateUrl()); } else if (alarm instanceof LentExpirationAlarm) { - return new MailDto(mailOverdueProperties.getOverdueMailSubject(), - mailOverdueProperties.getOverdueMailTemplateUrl()); + return new MailDto(mailAlarmProperties.getOverdueMailSubject(), + mailAlarmProperties.getOverdueMailTemplateUrl()); } else if (alarm instanceof LentExpirationImminentAlarm) { - return new MailDto(mailOverdueProperties.getSoonOverdueMailSubject(), - mailOverdueProperties.getSoonOverdueMailTemplateUrl()); + return new MailDto(mailAlarmProperties.getSoonOverdueMailSubject(), + mailAlarmProperties.getSoonOverdueMailTemplateUrl()); } else if (alarm instanceof ExtensionIssuanceAlarm) { - return new MailDto(mailOverdueProperties.getExtensionIssuanceMailSubject(), - mailOverdueProperties.getExtensionIssuanceMailTemplateUrl()); + return new MailDto(mailAlarmProperties.getExtensionIssuanceMailSubject(), + mailAlarmProperties.getExtensionIssuanceMailTemplateUrl()); } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - return new MailDto(mailOverdueProperties.getExtensionExpirationImminentMailSubject(), - mailOverdueProperties.getExtensionExpirationImminentMailTemplateUrl()); + return new MailDto(mailAlarmProperties.getExtensionExpirationImminentMailSubject(), + mailAlarmProperties.getExtensionExpirationImminentMailTemplateUrl()); } else throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java index 3ac79ee56..ea4a31393 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java @@ -7,10 +7,8 @@ import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.service.CabinetService; -import org.ftclub.cabinet.config.MailOverdueProperties; +import org.ftclub.cabinet.alarm.mail.config.MailAlarmProperties; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; -import org.ftclub.cabinet.firebase.fcm.service.FCMService; -import org.ftclub.cabinet.utils.mail.EmailSender; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; @@ -26,7 +24,7 @@ public class OverdueManager { private final CabinetService cabinetService; - private final MailOverdueProperties mailOverdueProperties; + private final MailAlarmProperties mailAlarmProperties; private final ApplicationEventPublisher eventPublisher; @@ -43,7 +41,7 @@ public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate if (isExpired) { return OverdueType.OVERDUE; } - if (mailOverdueProperties.getSoonOverdueTerm().equals(daysLeftFromExpireDate)) { + if (mailAlarmProperties.getSoonOverdueTerm().equals(daysLeftFromExpireDate)) { return OverdueType.SOON_OVERDUE; } return OverdueType.NONE; diff --git a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java index 23df3f780..e733a4a80 100644 --- a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java @@ -11,6 +11,7 @@ import javax.mail.internet.MimeMessage; import lombok.AllArgsConstructor; import lombok.Getter; +import org.ftclub.cabinet.alarm.mail.EmailSender; import org.ftclub.cabinet.config.GmailProperties; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; diff --git a/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java index 6fda55a77..143496fad 100644 --- a/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java @@ -11,9 +11,9 @@ import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.service.CabinetService; import org.ftclub.cabinet.config.GmailProperties; -import org.ftclub.cabinet.config.MailOverdueProperties; +import org.ftclub.cabinet.alarm.mail.config.MailAlarmProperties; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; -import org.ftclub.cabinet.utils.mail.EmailSender; +import org.ftclub.cabinet.alarm.mail.EmailSender; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -36,7 +36,7 @@ public class OverdueManagerUnitTest { @Mock private EmailSender emailSender = mock(EmailSender.class); @Mock(lenient = true) - private MailOverdueProperties mailOverdueProperties = mock(MailOverdueProperties.class); + private MailAlarmProperties mailAlarmProperties = mock(MailAlarmProperties.class); @InjectMocks private OverdueManager overdueManager; private static ActiveLentHistoryDto activeLentHistoryDto; @@ -64,12 +64,12 @@ static void setupBeforeAll() { @BeforeEach @DisplayName("테스트 전에 mailOverdueProperties를 설정한다.") void setUp() { - given(mailOverdueProperties.getSoonOverdueMailSubject()).willReturn("42CABI 사물함 연체 예정 알림"); - given(mailOverdueProperties.getSoonOverdueMailTemplateUrl()).willReturn( + given(mailAlarmProperties.getSoonOverdueMailSubject()).willReturn("42CABI 사물함 연체 예정 알림"); + given(mailAlarmProperties.getSoonOverdueMailTemplateUrl()).willReturn( "mail/soonOverdue.html"); - given(mailOverdueProperties.getOverdueMailSubject()).willReturn("42CABI 사물함 연체 알림"); - given(mailOverdueProperties.getOverdueMailTemplateUrl()).willReturn("mail/overdue.html"); - given(mailOverdueProperties.getSoonOverdueTerm()).willReturn(-1L); + given(mailAlarmProperties.getOverdueMailSubject()).willReturn("42CABI 사물함 연체 알림"); + given(mailAlarmProperties.getOverdueMailTemplateUrl()).willReturn("mail/overdue.html"); + given(mailAlarmProperties.getSoonOverdueTerm()).willReturn(-1L); } @Test @@ -125,13 +125,13 @@ void setUp() { CabinetStatus.OVERDUE ); - then(mailOverdueProperties).should().getOverdueMailSubject(); - then(mailOverdueProperties).should().getOverdueMailTemplateUrl(); + then(mailAlarmProperties).should().getOverdueMailSubject(); + then(mailAlarmProperties).should().getOverdueMailTemplateUrl(); then(emailSender).should().sendMail( activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), - mailOverdueProperties.getOverdueMailSubject(), - mailOverdueProperties.getOverdueMailTemplateUrl(), + mailAlarmProperties.getOverdueMailSubject(), + mailAlarmProperties.getOverdueMailTemplateUrl(), new LentExpirationAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) ); } @@ -149,13 +149,13 @@ void setUp() { CabinetStatus.OVERDUE ); - then(mailOverdueProperties).should().getSoonOverdueMailSubject(); - then(mailOverdueProperties).should().getSoonOverdueMailTemplateUrl(); + then(mailAlarmProperties).should().getSoonOverdueMailSubject(); + then(mailAlarmProperties).should().getSoonOverdueMailTemplateUrl(); then(emailSender).should().sendMail( activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), - mailOverdueProperties.getSoonOverdueMailSubject(), - mailOverdueProperties.getSoonOverdueMailTemplateUrl(), + mailAlarmProperties.getSoonOverdueMailSubject(), + mailAlarmProperties.getSoonOverdueMailTemplateUrl(), new LentExpirationImminentAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) ); } @@ -173,22 +173,22 @@ void setUp() { CabinetStatus.OVERDUE ); - then(mailOverdueProperties).should(never()).getSoonOverdueMailSubject(); - then(mailOverdueProperties).should(never()).getSoonOverdueMailTemplateUrl(); - then(mailOverdueProperties).should(never()).getOverdueMailSubject(); - then(mailOverdueProperties).should(never()).getOverdueMailTemplateUrl(); + then(mailAlarmProperties).should(never()).getSoonOverdueMailSubject(); + then(mailAlarmProperties).should(never()).getSoonOverdueMailTemplateUrl(); + then(mailAlarmProperties).should(never()).getOverdueMailSubject(); + then(mailAlarmProperties).should(never()).getOverdueMailTemplateUrl(); then(emailSender).should(never()).sendMail( activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), - mailOverdueProperties.getSoonOverdueMailSubject(), - mailOverdueProperties.getSoonOverdueMailTemplateUrl(), + mailAlarmProperties.getSoonOverdueMailSubject(), + mailAlarmProperties.getSoonOverdueMailTemplateUrl(), null ); then(emailSender).should(never()).sendMail( activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), - mailOverdueProperties.getOverdueMailSubject(), - mailOverdueProperties.getOverdueMailTemplateUrl(), + mailAlarmProperties.getOverdueMailSubject(), + mailAlarmProperties.getOverdueMailTemplateUrl(), null ); } From 7fb97a5a0cb75e3089a22ae16764d5bb336a0663 Mon Sep 17 00:00:00 2001 From: jiwon Date: Fri, 20 Oct 2023 02:01:50 +0900 Subject: [PATCH 0022/1029] =?UTF-8?q?[BE]=20FEAT:=20fcm=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 2 +- .../cabinet/alarm/config/AlarmProperties.java | 76 ++++++++++++++++ .../{ => discord}/DiscordAlarmMessage.java | 2 +- .../DiscordWebHookMessenger.java | 2 +- .../alarm/domain/AnnouncementAlarm.java | 14 +++ .../ExtensionExpirationImminentAlarm.java | 1 + .../alarm/domain/ExtensionIssuanceAlarm.java | 3 +- .../alarm/domain/LentSuccessAlarm.java | 1 + .../org/ftclub/cabinet/alarm/fcm/FCMDto.java | 12 +++ .../fcm}/FCMInitializer.java | 2 +- .../ftclub/cabinet/alarm/fcm/FCMService.java | 86 +++++++++++++++++++ .../fcm}/FCMTestController.java | 7 +- .../AlarmEventHandler.java | 2 +- .../EmailAlarmSender.java | 39 +++++---- .../alarm/handler/PushAlarmSender.java | 56 ++++++++++++ .../SlackAlarmSender.java | 2 +- .../{EmailSender.java => EmailService.java} | 23 +++-- .../mail}/config/GmailProperties.java | 2 +- .../mail/config/MailAlarmProperties.java | 46 ---------- .../alarm/service/PushAlarmSender.java | 12 --- .../firebase/fcm/service/FCMService.java | 79 ----------------- .../cabinet/lent/service/LentServiceImpl.java | 2 +- .../ftclub/cabinet/log/AdminApiLogAspect.java | 4 +- .../org/ftclub/cabinet/log/LogParser.java | 2 +- .../utils/overdue/manager/OverdueManager.java | 6 +- .../templates/mail/announcement.html | 30 +++++++ .../templates/mail/extensionExpiration.html | 28 ++++++ .../templates/mail/extensionIssuance.html | 29 +++++++ ...nitTest.java => EmailServiceUnitTest.java} | 12 +-- .../manager/OverdueManagerUnitTest.java | 60 ++++++------- 30 files changed, 429 insertions(+), 213 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java rename backend/src/main/java/org/ftclub/cabinet/alarm/{ => discord}/DiscordAlarmMessage.java (95%) rename backend/src/main/java/org/ftclub/cabinet/alarm/{ => discord}/DiscordWebHookMessenger.java (95%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java rename backend/src/main/java/org/ftclub/cabinet/{firebase => alarm/fcm}/FCMInitializer.java (96%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMService.java rename backend/src/main/java/org/ftclub/cabinet/{firebase => alarm/fcm}/FCMTestController.java (81%) rename backend/src/main/java/org/ftclub/cabinet/alarm/{service => handler}/AlarmEventHandler.java (97%) rename backend/src/main/java/org/ftclub/cabinet/alarm/{service => handler}/EmailAlarmSender.java (57%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java rename backend/src/main/java/org/ftclub/cabinet/alarm/{service => handler}/SlackAlarmSender.java (95%) rename backend/src/main/java/org/ftclub/cabinet/alarm/mail/{EmailSender.java => EmailService.java} (63%) rename backend/src/main/java/org/ftclub/cabinet/{ => alarm/mail}/config/GmailProperties.java (94%) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/MailAlarmProperties.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/firebase/fcm/service/FCMService.java create mode 100644 backend/src/main/resources/templates/mail/announcement.html create mode 100644 backend/src/main/resources/templates/mail/extensionExpiration.html create mode 100644 backend/src/main/resources/templates/mail/extensionIssuance.html rename backend/src/test/java/org/ftclub/cabinet/utils/mail/{EmailSenderUnitTest.java => EmailServiceUnitTest.java} (88%) diff --git a/.idea/misc.xml b/.idea/misc.xml index b237832f3..5e319ef27 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java new file mode 100644 index 000000000..0f7003c8b --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -0,0 +1,76 @@ +package org.ftclub.cabinet.alarm.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Getter +public class AlarmProperties { + + /*===================== lentSuccess =========================*/ + @Value("${spring.subject.lentSuccess}") + private String lentSuccessSubject; + + @Value("${spring.mail.lentSuccess.template}") + private String lentSuccessMailTemplateUrl; + + @Value("${spring.fcm.lentSuccess.template}") + private String lentSuccessFcmTemplate; + + /*======================= overdue ===========================*/ + @Value("${spring.subject.overdue}") + private String overdueSubject; + + @Value("${spring.mail.overdue.template}") + private String overdueMailTemplateUrl; + + @Value("${spring.fcm.overdue.template}") + private String overdueFcmTemplate; + + /*===================== soonOverdue =========================*/ + @Value("${spring.mail.soonOverdue.term}") + private Long soonOverdueTerm; + + @Value("${spring.subject.soonOverdue}") + private String soonOverdueSubject; + + @Value("${spring.mail.soonOverdue.template}") + private String soonOverdueMailTemplateUrl; + + @Value("${spring.fcm.soonOverdue.template}") + private String soonOverdueFcmTemplate; + + /*================== extensionIssuance ======================*/ + @Value("${spring.subject.extensionIssuance}") + private String extensionIssuanceSubject; + + @Value("${spring.mail.extensionIssuance.template}") + private String extensionIssuanceMailTemplateUrl; + + @Value("${spring.fcm.extensionIssuance.template}") + private String extensionIssuanceFcmTemplate; + + /*================= extensionExpiration =====================*/ + @Value("${spring.mail.extensionExpiration.term}") + private Long extensionExpirationTerm; + + @Value("${spring.subject.extensionExpiration}") + private String extensionExpirationImminentSubject; + + @Value("${spring.mail.extensionExpiration.template}") + private String extensionExpirationImminentMailTemplateUrl; + + @Value("${spring.fcm.extensionExpiration.template}") + private String extensionExpirationImminentFcmTemplate; + + /*==================== announcement =========================*/ + @Value("${spring.subject.announcement}") + private String announcementSubject; + + @Value("${spring.mail.announcement.template}") + private String announcementMailTemplateUrl; + + @Value("${spring.fcm.announcement.template}") + private String announcementFcmTemplate; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/DiscordAlarmMessage.java b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordAlarmMessage.java similarity index 95% rename from backend/src/main/java/org/ftclub/cabinet/alarm/DiscordAlarmMessage.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordAlarmMessage.java index 4ad5ebb12..b106d1a20 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/DiscordAlarmMessage.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordAlarmMessage.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.alarm; +package org.ftclub.cabinet.alarm.discord; import lombok.Builder; import lombok.Getter; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/DiscordWebHookMessenger.java b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java similarity index 95% rename from backend/src/main/java/org/ftclub/cabinet/alarm/DiscordWebHookMessenger.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java index af30a714b..575a9b358 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/DiscordWebHookMessenger.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.alarm; +package org.ftclub.cabinet.alarm.discord; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java new file mode 100644 index 000000000..884c2a5d9 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java @@ -0,0 +1,14 @@ +package org.ftclub.cabinet.alarm.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 공지사항 알람 + */ +@Getter +@AllArgsConstructor +public class AnnouncementAlarm { + + private final String announcementContent; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java index f1dd1cedb..c6916aad5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java @@ -11,6 +11,7 @@ @AllArgsConstructor @Getter public class ExtensionExpirationImminentAlarm implements Alarm { + private final String extensionName; private final LocalDateTime extensionExpirationDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java index c9e7b3a6f..208088919 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java @@ -11,7 +11,8 @@ @AllArgsConstructor @Getter public class ExtensionIssuanceAlarm implements Alarm { + private final String extensionName; private final LocalDateTime extensionExpirationDate; - private final int daysToExtend; // .. ? + private final Integer daysToExtend; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java index 19765876b..94b706679 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java @@ -13,5 +13,6 @@ public class LentSuccessAlarm implements Alarm { private final Location location; + private final Integer visibleNum; private final LocalDateTime lentExpirationDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java new file mode 100644 index 000000000..e442716a0 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.alarm.fcm; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class FCMDto { + + private final String title; + private final String format; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMInitializer.java similarity index 96% rename from backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMInitializer.java index 8e7321b67..43f79222b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMInitializer.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.firebase; +package org.ftclub.cabinet.alarm.fcm; import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.FirebaseApp; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMService.java b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMService.java new file mode 100644 index 000000000..815012441 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMService.java @@ -0,0 +1,86 @@ +package org.ftclub.cabinet.alarm.fcm; + +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.Message; +import java.time.LocalDateTime; +import java.util.Formatter; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.config.DomainProperties; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.redis.service.RedisService; +import org.ftclub.cabinet.utils.overdue.manager.OverdueType; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class FCMService { + private final RedisService redisService; + private final DomainProperties domainProperties; + private static final String ICON_FILE_PATH = "/src/assets/images/logo.svg"; + + + public void sendPushMessage(String name, Alarm alarm, FCMDto fcmDto) { + log.info("called sendPushMessage name = {}, alarm = {}", name, alarm); + + Optional token = redisService.findByKey(name, String.class); + if (token.isEmpty()) { + log.warn("\"{}\"에 해당하는 디바이스 토큰이 존재하지 않습니다.", name); + return; + } + + if (alarm instanceof LentExpirationImminentAlarm) { + Long daysAfterFromExpireDate = ((LentExpirationImminentAlarm) alarm).getDaysAfterFromExpireDate(); + String title = fcmDto.getTitle(); + String body = String.format(fcmDto.getFormat(), Math.abs(daysAfterFromExpireDate)); + sendMessage(token.get(), name, title, body); + } else if (alarm instanceof LentExpirationAlarm) { + Long daysLeftFromExpireDate = ((LentExpirationAlarm) alarm).getDaysLeftFromExpireDate(); + String title = fcmDto.getTitle(); + String body = String.format(fcmDto.getFormat(), Math.abs(daysLeftFromExpireDate)); + sendMessage(token.get(), name, title, body); + } else if (alarm instanceof ExtensionIssuanceAlarm) { + Integer daysToExtend = ((ExtensionIssuanceAlarm) alarm).getDaysToExtend(); + String extensionName = ((ExtensionIssuanceAlarm) alarm).getExtensionName(); + String title = fcmDto.getTitle(); + String body = String.format(fcmDto.getFormat(), daysToExtend, extensionName); + sendMessage(token.get(), name, title, body); + } else if (alarm instanceof ExtensionExpirationImminentAlarm) { + String extensionName = ((ExtensionExpirationImminentAlarm) alarm).getExtensionName(); + LocalDateTime extensionExpireDate = ((ExtensionExpirationImminentAlarm) alarm).getExtensionExpirationDate(); + String title = fcmDto.getTitle(); + String body = String.format(fcmDto.getFormat(), extensionName, extensionExpireDate); + sendMessage(token.get(), name, title, body); + } else if (alarm instanceof AnnouncementAlarm) { + String title = fcmDto.getTitle(); + String body = fcmDto.getFormat(); + sendMessage(token.get(), name, title, body); + } else { + throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); + } + } + + private void sendMessage(String token, String name, String title, String body) { + log.info( + "called sendOverdueMessage token = {}, name = {}, title = {}, body = {}", + token, name, title, body); + Message message = Message.builder() + .putData("title", title) + .putData("body", body) + .putData("icon", domainProperties.getFeHost() + ICON_FILE_PATH) + .putData("click_action", domainProperties.getFeHost()) + .setToken(token) + .build(); + + FirebaseMessaging.getInstance().sendAsync(message); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMTestController.java b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMTestController.java similarity index 81% rename from backend/src/main/java/org/ftclub/cabinet/firebase/FCMTestController.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMTestController.java index 4db09bd0b..3b2569840 100644 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMTestController.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMTestController.java @@ -1,10 +1,10 @@ -package org.ftclub.cabinet.firebase; +package org.ftclub.cabinet.alarm.fcm; import java.time.Duration; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.firebase.fcm.service.FCMService; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; import org.ftclub.cabinet.redis.service.RedisService; import org.ftclub.cabinet.utils.overdue.manager.OverdueType; import org.springframework.web.bind.annotation.PathVariable; @@ -29,6 +29,7 @@ public void test(@PathVariable("name") String name, @PathVariable("token") Strin @PostMapping("test2/{name}") public void test2(@PathVariable("name") String name) { log.info("called test2"); - fcmService.sendPushMessage(name, OverdueType.OVERDUE, 1L); + LentExpirationAlarm alarm = new LentExpirationAlarm(1L); + fcmService.sendPushMessage(name, alarm, new FCMDto("title", "%d")); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java similarity index 97% rename from backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index 4b88bf5b7..fc1f1dd6f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.alarm.service; +package org.ftclub.cabinet.alarm.handler; import static org.ftclub.cabinet.alarm.domain.AlarmType.EMAIL; import static org.ftclub.cabinet.alarm.domain.AlarmType.PUSH; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java similarity index 57% rename from backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java index 240ac198e..8b4ee5eda 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java @@ -1,21 +1,22 @@ -package org.ftclub.cabinet.alarm.service; +package org.ftclub.cabinet.alarm.handler; import javax.mail.MessagingException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.domain.Alarm; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.alarm.mail.MailDto; -import org.ftclub.cabinet.alarm.mail.config.MailAlarmProperties; +import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.alarm.mail.EmailSender; +import org.ftclub.cabinet.alarm.mail.EmailService; import org.springframework.stereotype.Component; @Slf4j @@ -23,14 +24,14 @@ @RequiredArgsConstructor public class EmailAlarmSender { - private final EmailSender emailSender; - private final MailAlarmProperties mailAlarmProperties; + private final EmailService emailService; + private final AlarmProperties alarmProperties; void send(User user, AlarmEvent alarmEvent) { Alarm alarm = alarmEvent.getAlarm(); MailDto mailDto = getMailDto(alarm); try { - emailSender.sendMail(user.getName(), user.getEmail(), mailDto.getSubject(), + emailService.sendMail(user.getName(), user.getEmail(), mailDto.getSubject(), mailDto.getTemplate(), alarm); log.info("{} ({})에게 메일을 성공적으로 보냈습니다.", user.getName(), user.getEmail()); } catch (MessagingException e) { @@ -41,25 +42,29 @@ void send(User user, AlarmEvent alarmEvent) { private MailDto getMailDto(Alarm alarm) { if (alarm instanceof LentSuccessAlarm) { - return new MailDto(mailAlarmProperties.getSoonOverdueMailSubject(), - mailAlarmProperties.getSoonOverdueMailTemplateUrl()); + return new MailDto(alarmProperties.getLentSuccessSubject(), + alarmProperties.getLentSuccessMailTemplateUrl()); } else if (alarm instanceof LentExpirationAlarm) { - return new MailDto(mailAlarmProperties.getOverdueMailSubject(), - mailAlarmProperties.getOverdueMailTemplateUrl()); + return new MailDto(alarmProperties.getOverdueSubject(), + alarmProperties.getOverdueMailTemplateUrl()); } else if (alarm instanceof LentExpirationImminentAlarm) { - return new MailDto(mailAlarmProperties.getSoonOverdueMailSubject(), - mailAlarmProperties.getSoonOverdueMailTemplateUrl()); + return new MailDto(alarmProperties.getSoonOverdueSubject(), + alarmProperties.getSoonOverdueMailTemplateUrl()); } else if (alarm instanceof ExtensionIssuanceAlarm) { - return new MailDto(mailAlarmProperties.getExtensionIssuanceMailSubject(), - mailAlarmProperties.getExtensionIssuanceMailTemplateUrl()); + return new MailDto(alarmProperties.getExtensionIssuanceSubject(), + alarmProperties.getExtensionIssuanceMailTemplateUrl()); } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - return new MailDto(mailAlarmProperties.getExtensionExpirationImminentMailSubject(), - mailAlarmProperties.getExtensionExpirationImminentMailTemplateUrl()); + return new MailDto(alarmProperties.getExtensionExpirationImminentSubject(), + alarmProperties.getExtensionExpirationImminentMailTemplateUrl()); + } else if (alarm instanceof AnnouncementAlarm) { + return new MailDto(alarmProperties.getAnnouncementSubject(), + alarmProperties.getAnnouncementMailTemplateUrl()); + } else { + throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); } - else throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java new file mode 100644 index 000000000..3e89e63ac --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java @@ -0,0 +1,56 @@ +package org.ftclub.cabinet.alarm.handler; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.alarm.config.AlarmProperties; +import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; +import org.ftclub.cabinet.alarm.fcm.FCMDto; +import org.ftclub.cabinet.alarm.fcm.FCMService; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.user.domain.User; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PushAlarmSender { + + private final AlarmProperties alarmProperties; + private final FCMService fcmService; + + void send(User user, AlarmEvent alarmEvent) { + Alarm alarm = alarmEvent.getAlarm(); + FCMDto fcmDto = getFCMDto(alarm); + fcmService.sendPushMessage(user.getName(), alarm, fcmDto); + } + + private FCMDto getFCMDto(Alarm alarm) { + if (alarm instanceof LentSuccessAlarm) { + return new FCMDto(alarmProperties.getLentSuccessSubject(), + alarmProperties.getLentSuccessFcmTemplate()); + } else if (alarm instanceof LentExpirationImminentAlarm) { + return new FCMDto(alarmProperties.getSoonOverdueSubject(), + alarmProperties.getSoonOverdueFcmTemplate()); + } else if (alarm instanceof LentExpirationAlarm) { + return new FCMDto(alarmProperties.getOverdueSubject(), + alarmProperties.getOverdueFcmTemplate()); + } else if (alarm instanceof ExtensionIssuanceAlarm) { + return new FCMDto(alarmProperties.getExtensionIssuanceSubject(), + alarmProperties.getExtensionIssuanceFcmTemplate()); + } else if (alarm instanceof ExtensionExpirationImminentAlarm) { + return new FCMDto(alarmProperties.getExtensionExpirationImminentSubject(), + alarmProperties.getExtensionExpirationImminentFcmTemplate()); + } else if (alarm instanceof AnnouncementAlarm) { + return new FCMDto(alarmProperties.getAnnouncementSubject(), + alarmProperties.getAnnouncementFcmTemplate()); + } else { + throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); + } + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java similarity index 95% rename from backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index cce505ef1..428ef8c6c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.alarm.service; +package org.ftclub.cabinet.alarm.handler; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.domain.AlarmEvent; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java similarity index 63% rename from backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailSender.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java index 6a828659f..203222872 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java @@ -5,11 +5,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.cabinet.domain.Location; -import org.ftclub.cabinet.config.GmailProperties; +import org.ftclub.cabinet.alarm.mail.config.GmailProperties; import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; @@ -20,7 +23,7 @@ @Component @RequiredArgsConstructor @Log4j2 -public class EmailSender { +public class EmailService { private final JavaMailSender javaMailSender; private final ITemplateEngine templateEngine; @@ -43,9 +46,10 @@ public void sendMail(String name, String to, String subject, String template, Al Context context = new Context(); context.setVariable("name", name); if (alarm instanceof LentSuccessAlarm) { - Location location = ((LentSuccessAlarm) alarm).getLocation(); - String locationString = location.getBuilding() + " " - + location.getFloor() + "층 " + location.getSection(); + String building = ((LentSuccessAlarm) alarm).getLocation().getBuilding(); + Integer floor = ((LentSuccessAlarm) alarm).getLocation().getFloor(); + Integer visibleNum = ((LentSuccessAlarm) alarm).getVisibleNum(); + String locationString = building + " " + floor + "층 " + visibleNum + "번"; context.setVariable("location", locationString); context.setVariable("expireDate", ((LentSuccessAlarm) alarm).getLentExpirationDate()); } @@ -55,6 +59,15 @@ else if (alarm instanceof LentExpirationAlarm) { } else if (alarm instanceof LentExpirationImminentAlarm) { long overdueDays = ((LentExpirationImminentAlarm) alarm).getDaysAfterFromExpireDate(); context.setVariable("overdueDays", overdueDays); + } else if (alarm instanceof ExtensionIssuanceAlarm) { + context.setVariable("extensionName", ((ExtensionIssuanceAlarm) alarm).getExtensionName()); + context.setVariable("expireDate", ((ExtensionIssuanceAlarm) alarm).getExtensionExpirationDate()); + context.setVariable("daysToExtend", ((ExtensionIssuanceAlarm) alarm).getDaysToExtend()); + } else if (alarm instanceof ExtensionExpirationImminentAlarm) { + context.setVariable("extensionName", ((ExtensionExpirationImminentAlarm) alarm).getExtensionName()); + context.setVariable("expireDate", ((ExtensionExpirationImminentAlarm) alarm).getExtensionExpirationDate()); + } else if (alarm instanceof AnnouncementAlarm) { + context.setVariable("announcementContent", ((AnnouncementAlarm) alarm).getAnnouncementContent()); } String htmlContent = templateEngine.process(template, context); diff --git a/backend/src/main/java/org/ftclub/cabinet/config/GmailProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/GmailProperties.java similarity index 94% rename from backend/src/main/java/org/ftclub/cabinet/config/GmailProperties.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/GmailProperties.java index 51b22f28d..f233e885d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/GmailProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/GmailProperties.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.config; +package org.ftclub.cabinet.alarm.mail.config; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/MailAlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/MailAlarmProperties.java deleted file mode 100644 index de6f60529..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/MailAlarmProperties.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.ftclub.cabinet.alarm.mail.config; - -import lombok.Getter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -@Getter -public class MailAlarmProperties { - - @Value("${spring.mail.lentSuccess.subject}") - private String lentSuccessMailSubject; - - @Value("${spring.mail.lentSuccess.template}") - private String lentSuccessMailTemplateUrl; - - @Value("${spring.mail.overdue.subject}") - private String overdueMailSubject; - - @Value("${spring.mail.overdue.template}") - private String overdueMailTemplateUrl; - - @Value("${spring.mail.soonOverdue.term}") - private Long soonOverdueTerm; - - @Value("${spring.mail.soonOverdue.subject}") - private String soonOverdueMailSubject; - - @Value("${spring.mail.soonOverdue.template}") - private String soonOverdueMailTemplateUrl; - - @Value("${spring.mail.extensionIssuance.subject}") - private String extensionIssuanceMailSubject; - - @Value("${spring.mail.extensionIssuance.template}") - private String extensionIssuanceMailTemplateUrl; - - @Value("${spring.mail.extensionExpiration.term}") - private Long extensionExpirationTerm; - - @Value("${spring.mail.extensionExpiration.subject}") - private String extensionExpirationImminentMailSubject; - - @Value("${spring.mail.extensionExpiration.template}") - private String extensionExpirationImminentMailTemplateUrl; -} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java deleted file mode 100644 index 34416ae2f..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/PushAlarmSender.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ftclub.cabinet.alarm.service; - -import org.ftclub.cabinet.alarm.domain.AlarmEvent; -import org.ftclub.cabinet.user.domain.User; -import org.springframework.stereotype.Component; - -@Component -public class PushAlarmSender { - void send(User user, AlarmEvent alarmEvent) { -// fcmService.sendPushMessage(activeLent.getName(), overdueType, activeLent.getDaysLeftFromExpireDate()); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/fcm/service/FCMService.java b/backend/src/main/java/org/ftclub/cabinet/firebase/fcm/service/FCMService.java deleted file mode 100644 index d8cd9bfb2..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/fcm/service/FCMService.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.ftclub.cabinet.firebase.fcm.service; - -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.Message; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.config.DomainProperties; -import org.ftclub.cabinet.redis.service.RedisService; -import org.ftclub.cabinet.utils.overdue.manager.OverdueType; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Slf4j -public class FCMService { - private final RedisService redisService; - private final DomainProperties domainProperties; - private static final String ICON_FILE_PATH = "/src/assets/images/logo.svg"; - - - public void sendPushMessage(String name, OverdueType overdueType, Long daysLeftFromExpireDate) { - log.info("called sendPushMessage name = {}, overdueType = {}, daysLeftFromExpireDate = {}", name, overdueType, - daysLeftFromExpireDate); - - Optional token = redisService.findByKey(name, String.class); - if (token.isEmpty()) { - log.warn("\"{}\"에 해당하는 디바이스 토큰이 존재하지 않습니다.", name); - return; - } - - switch (overdueType) { - case NONE: - log.warn("overdueType이 NONE입니다. name = {}, overdueType = {}, daysLeftFromExpireDate = {}", name, overdueType, - daysLeftFromExpireDate); - break; - case SOON_OVERDUE: - sendSoonOverdueMessage(token.get(), name, daysLeftFromExpireDate); - break; - case OVERDUE: - sendOverdueMessage(token.get(), name, daysLeftFromExpireDate); - break; - } - } - - private void sendOverdueMessage(String token, String name, Long daysLeftFromExpireDate) { - log.info( - "called sendOverdueMessage token = {}, name = {}, daysLeftFromExpireDate = {}", - token, name, daysLeftFromExpireDate); - Message message = Message.builder() - .putData("title", " 연체 알림") - .putData("body", name + "님, 대여한 사물함이 " + Math.abs(daysLeftFromExpireDate) + "일 연체되었습니다.") - .putData("icon", domainProperties.getFeHost() + ICON_FILE_PATH) - .putData("click_action", domainProperties.getFeHost()) - .setToken(token) - .build(); - - FirebaseMessaging.getInstance().sendAsync(message); - } - - private void sendSoonOverdueMessage(String token, String name, Long daysLeftFromExpireDate) { - log.info( - "called sendSoonOverdueMessage token = {}, name = {}, daysLeftFromExpireDate = {}", - token, name, daysLeftFromExpireDate); - if (token.isEmpty()) { - log.warn("\"{}\"에 해당하는 디바이스 토큰이 존재하지 않습니다.", name); - return; - } - Message message = Message.builder() - .putData("title", " 연체 예정 알림") - .putData("body", "대여한 사물함이 " + daysLeftFromExpireDate + "일 후 연체됩니다.") - .putData("icon", domainProperties.getFeHost() + ICON_FILE_PATH) - .putData("click_action", domainProperties.getFeHost()) - .setToken(token) - .build(); - - FirebaseMessaging.getInstance().sendAsync(message); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java index abc361554..5abc68552 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java @@ -80,7 +80,7 @@ public void startLentCabinet(Long userId, Long cabinetId) { lentPolicy.applyExpirationDate(lentHistory, cabinetActiveLentHistories, expiredAt); lentRepository.save(lentHistory); eventPublisher.publishEvent(AlarmEvent.of(userId, - new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), expiredAt))); + new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), cabinet.getVisibleNum(), expiredAt))); } @Override diff --git a/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java b/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java index 996d6da41..835ce990a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java +++ b/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java @@ -14,8 +14,8 @@ import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; -import org.ftclub.cabinet.alarm.DiscordAlarmMessage; -import org.ftclub.cabinet.alarm.DiscordWebHookMessenger; +import org.ftclub.cabinet.alarm.discord.DiscordAlarmMessage; +import org.ftclub.cabinet.alarm.discord.DiscordWebHookMessenger; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.auth.domain.CookieManager; diff --git a/backend/src/main/java/org/ftclub/cabinet/log/LogParser.java b/backend/src/main/java/org/ftclub/cabinet/log/LogParser.java index 7fb01a020..a51580304 100644 --- a/backend/src/main/java/org/ftclub/cabinet/log/LogParser.java +++ b/backend/src/main/java/org/ftclub/cabinet/log/LogParser.java @@ -1,6 +1,6 @@ package org.ftclub.cabinet.log; -import org.ftclub.cabinet.alarm.DiscordAlarmMessage; +import org.ftclub.cabinet.alarm.discord.DiscordAlarmMessage; import org.springframework.stereotype.Component; @Component diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java index ea4a31393..ee639be3f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java @@ -7,7 +7,7 @@ import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.service.CabinetService; -import org.ftclub.cabinet.alarm.mail.config.MailAlarmProperties; +import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; @@ -24,7 +24,7 @@ public class OverdueManager { private final CabinetService cabinetService; - private final MailAlarmProperties mailAlarmProperties; + private final AlarmProperties alarmProperties; private final ApplicationEventPublisher eventPublisher; @@ -41,7 +41,7 @@ public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate if (isExpired) { return OverdueType.OVERDUE; } - if (mailAlarmProperties.getSoonOverdueTerm().equals(daysLeftFromExpireDate)) { + if (alarmProperties.getSoonOverdueTerm().equals(daysLeftFromExpireDate)) { return OverdueType.SOON_OVERDUE; } return OverdueType.NONE; diff --git a/backend/src/main/resources/templates/mail/announcement.html b/backend/src/main/resources/templates/mail/announcement.html new file mode 100644 index 000000000..1ba11c9da --- /dev/null +++ b/backend/src/main/resources/templates/mail/announcement.html @@ -0,0 +1,30 @@ + + + + + + + Document + + + +
+

🚨 사물함 공지사항 안내 🚨

+
+
+
+ 안녕하세요 님, Cabi 팀에서 공지사항이 있어 안내드립니다.

+ +

+ + 감사합니다.
+
+
+
+ 사물함 대여 서비스 바로가기 ➡ https://cabi.42seoul.io
+ 사물함 서비스 관련 문의사항 ➡ https://42born2code.slack.com/archives/C02V6GE8LD7 +
+
+ + \ No newline at end of file diff --git a/backend/src/main/resources/templates/mail/extensionExpiration.html b/backend/src/main/resources/templates/mail/extensionExpiration.html new file mode 100644 index 000000000..0493c761f --- /dev/null +++ b/backend/src/main/resources/templates/mail/extensionExpiration.html @@ -0,0 +1,28 @@ + + + + + + + Document + + + +
+

🚨 사물함 연장권 만료 예정 알림 🚨

+
+
+
+ 님, 연장권 만료 예정 안내드립니다.

+ + 연장권의 사용 기간은 까지 이며 기한이 만료 되면 사라지니 주의해주세요.

+
+
+
+ 사물함 대여 서비스 바로가기 ➡ https://cabi.42seoul.io
+ 사물함 서비스 관련 문의사항 ➡ https://42born2code.slack.com/archives/C02V6GE8LD7 +
+
+ + \ No newline at end of file diff --git a/backend/src/main/resources/templates/mail/extensionIssuance.html b/backend/src/main/resources/templates/mail/extensionIssuance.html new file mode 100644 index 000000000..151b696c2 --- /dev/null +++ b/backend/src/main/resources/templates/mail/extensionIssuance.html @@ -0,0 +1,29 @@ + + + + + + + Document + + + +
+

🚨 사물함 연장권 발급 알림 🚨

+
+
+
+ 님, 연장권 발급 안내드립니다.

+ + 연장권은 사용하신 사물함의 대여 일수를 만큼 늘려줍니다.

+ 연장권의 사용 기간은 까지 이며 기한이 만료 되면 사라지니 주의해주세요.

+
+
+
+ 사물함 대여 서비스 바로가기 ➡ https://cabi.42seoul.io
+ 사물함 서비스 관련 문의사항 ➡ https://42born2code.slack.com/archives/C02V6GE8LD7 +
+
+ + \ No newline at end of file diff --git a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java similarity index 88% rename from backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java rename to backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java index e733a4a80..6dc259256 100644 --- a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailSenderUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java @@ -11,8 +11,8 @@ import javax.mail.internet.MimeMessage; import lombok.AllArgsConstructor; import lombok.Getter; -import org.ftclub.cabinet.alarm.mail.EmailSender; -import org.ftclub.cabinet.config.GmailProperties; +import org.ftclub.cabinet.alarm.mail.EmailService; +import org.ftclub.cabinet.alarm.mail.config.GmailProperties; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -27,7 +27,7 @@ import org.thymeleaf.context.Context; @ExtendWith(MockitoExtension.class) -public class EmailSenderUnitTest { +public class EmailServiceUnitTest { @Getter @AllArgsConstructor @@ -48,7 +48,7 @@ private static class Mail { private GmailProperties gmailProperties = mock(GmailProperties.class); @InjectMocks - private EmailSender emailSender; + private EmailService emailService; @BeforeAll @@ -78,7 +78,7 @@ void setupBeforeEach() { void 실패_sendMail_개발환경() throws MessagingException, MailException { given(gmailProperties.getIsProduction()).willReturn(false); - emailSender.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate(), null); + emailService.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate(), null); then(javaMailSender).should(never()).send( any(MimeMessage.class) @@ -93,7 +93,7 @@ void setupBeforeEach() { MimeMessage mimeMessage = new MimeMessage((javax.mail.Session) null); given(javaMailSender.createMimeMessage()).willReturn(mimeMessage); - emailSender.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate(), null); + emailService.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate(), null); then(javaMailSender).should().send( any(MimeMessage.class) diff --git a/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java index 143496fad..72f07d963 100644 --- a/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java @@ -10,10 +10,10 @@ import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.service.CabinetService; -import org.ftclub.cabinet.config.GmailProperties; -import org.ftclub.cabinet.alarm.mail.config.MailAlarmProperties; +import org.ftclub.cabinet.alarm.mail.config.GmailProperties; +import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; -import org.ftclub.cabinet.alarm.mail.EmailSender; +import org.ftclub.cabinet.alarm.mail.EmailService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -34,9 +34,9 @@ public class OverdueManagerUnitTest { @Mock private CabinetService cabinetService = mock(CabinetService.class); @Mock - private EmailSender emailSender = mock(EmailSender.class); + private EmailService emailService = mock(EmailService.class); @Mock(lenient = true) - private MailAlarmProperties mailAlarmProperties = mock(MailAlarmProperties.class); + private AlarmProperties alarmProperties = mock(AlarmProperties.class); @InjectMocks private OverdueManager overdueManager; private static ActiveLentHistoryDto activeLentHistoryDto; @@ -64,12 +64,12 @@ static void setupBeforeAll() { @BeforeEach @DisplayName("테스트 전에 mailOverdueProperties를 설정한다.") void setUp() { - given(mailAlarmProperties.getSoonOverdueMailSubject()).willReturn("42CABI 사물함 연체 예정 알림"); - given(mailAlarmProperties.getSoonOverdueMailTemplateUrl()).willReturn( + given(alarmProperties.getSoonOverdueSubject()).willReturn("42CABI 사물함 연체 예정 알림"); + given(alarmProperties.getSoonOverdueMailTemplateUrl()).willReturn( "mail/soonOverdue.html"); - given(mailAlarmProperties.getOverdueMailSubject()).willReturn("42CABI 사물함 연체 알림"); - given(mailAlarmProperties.getOverdueMailTemplateUrl()).willReturn("mail/overdue.html"); - given(mailAlarmProperties.getSoonOverdueTerm()).willReturn(-1L); + given(alarmProperties.getOverdueSubject()).willReturn("42CABI 사물함 연체 알림"); + given(alarmProperties.getOverdueMailTemplateUrl()).willReturn("mail/overdue.html"); + given(alarmProperties.getSoonOverdueTerm()).willReturn(-1L); } @Test @@ -125,13 +125,13 @@ void setUp() { CabinetStatus.OVERDUE ); - then(mailAlarmProperties).should().getOverdueMailSubject(); - then(mailAlarmProperties).should().getOverdueMailTemplateUrl(); - then(emailSender).should().sendMail( + then(alarmProperties).should().getOverdueSubject(); + then(alarmProperties).should().getOverdueMailTemplateUrl(); + then(emailService).should().sendMail( activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), - mailAlarmProperties.getOverdueMailSubject(), - mailAlarmProperties.getOverdueMailTemplateUrl(), + alarmProperties.getOverdueSubject(), + alarmProperties.getOverdueMailTemplateUrl(), new LentExpirationAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) ); } @@ -149,13 +149,13 @@ void setUp() { CabinetStatus.OVERDUE ); - then(mailAlarmProperties).should().getSoonOverdueMailSubject(); - then(mailAlarmProperties).should().getSoonOverdueMailTemplateUrl(); - then(emailSender).should().sendMail( + then(alarmProperties).should().getSoonOverdueSubject(); + then(alarmProperties).should().getSoonOverdueMailTemplateUrl(); + then(emailService).should().sendMail( activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), - mailAlarmProperties.getSoonOverdueMailSubject(), - mailAlarmProperties.getSoonOverdueMailTemplateUrl(), + alarmProperties.getSoonOverdueSubject(), + alarmProperties.getSoonOverdueMailTemplateUrl(), new LentExpirationImminentAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) ); } @@ -173,22 +173,22 @@ void setUp() { CabinetStatus.OVERDUE ); - then(mailAlarmProperties).should(never()).getSoonOverdueMailSubject(); - then(mailAlarmProperties).should(never()).getSoonOverdueMailTemplateUrl(); - then(mailAlarmProperties).should(never()).getOverdueMailSubject(); - then(mailAlarmProperties).should(never()).getOverdueMailTemplateUrl(); - then(emailSender).should(never()).sendMail( + then(alarmProperties).should(never()).getSoonOverdueSubject(); + then(alarmProperties).should(never()).getSoonOverdueMailTemplateUrl(); + then(alarmProperties).should(never()).getOverdueSubject(); + then(alarmProperties).should(never()).getOverdueMailTemplateUrl(); + then(emailService).should(never()).sendMail( activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), - mailAlarmProperties.getSoonOverdueMailSubject(), - mailAlarmProperties.getSoonOverdueMailTemplateUrl(), + alarmProperties.getSoonOverdueSubject(), + alarmProperties.getSoonOverdueMailTemplateUrl(), null ); - then(emailSender).should(never()).sendMail( + then(emailService).should(never()).sendMail( activeLentHistoryDto.getName(), activeLentHistoryDto.getEmail(), - mailAlarmProperties.getOverdueMailSubject(), - mailAlarmProperties.getOverdueMailTemplateUrl(), + alarmProperties.getOverdueSubject(), + alarmProperties.getOverdueMailTemplateUrl(), null ); } From 11b1af5dd1c064ee6a3c6c17b8521c684bd5c42c Mon Sep 17 00:00:00 2001 From: jiwon Date: Fri, 20 Oct 2023 02:05:13 +0900 Subject: [PATCH 0023/1029] [BE] FEAT: fix application.yml to alarm yml file --- backend/src/main/resources/application.yml | 6 +++--- config | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 2e00e6d9f..d3bf9b1da 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -4,7 +4,7 @@ server: port: 4242 spring: config: - import: classpath:application-auth.yml, classpath:application-mail.yml + import: classpath:application-auth.yml, classpath:application-alarm.yml activate: on-profile: prod logging: @@ -16,7 +16,7 @@ server: port: 4242 spring: config: - import: classpath:application-auth.yml, classpath:application-mail.yml + import: classpath:application-auth.yml, classpath:application-alarm.yml activate: on-profile: dev logging: @@ -28,7 +28,7 @@ server: port: 2424 spring: config: - import: classpath:application-auth.yml, classpath:application-mail.yml + import: classpath:application-auth.yml, classpath:application-alarm.yml activate: on-profile: local logging: diff --git a/config b/config index 23c8d65ee..38ef2be7f 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 23c8d65eeb5a810c2e50d6b14ad31367d0327049 +Subproject commit 38ef2be7f4b2974735f91e72caebcc37202147f0 From 669b6bc4c893a4b80789c0b489edfec84e694919 Mon Sep 17 00:00:00 2001 From: jiwon Date: Fri, 20 Oct 2023 09:47:13 +0900 Subject: [PATCH 0024/1029] =?UTF-8?q?[BE]=20FIX:=20fcm=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A4=91=EB=B3=B5=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/alarm/fcm/FCMDto.java | 2 +- .../ftclub/cabinet/alarm/fcm/FCMService.java | 86 ------------------- .../cabinet/alarm/fcm/FCMTestController.java | 11 ++- .../alarm/handler/PushAlarmSender.java | 86 +++++++++++++++---- .../cabinet/alarm/mail/EmailService.java | 3 +- 5 files changed, 77 insertions(+), 111 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMService.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java index e442716a0..b44a45349 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java @@ -8,5 +8,5 @@ public class FCMDto { private final String title; - private final String format; + private final String body; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMService.java b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMService.java deleted file mode 100644 index 815012441..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMService.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.ftclub.cabinet.alarm.fcm; - -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.Message; -import java.time.LocalDateTime; -import java.util.Formatter; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.alarm.domain.Alarm; -import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; -import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; -import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; -import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; -import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; -import org.ftclub.cabinet.config.DomainProperties; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.redis.service.RedisService; -import org.ftclub.cabinet.utils.overdue.manager.OverdueType; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Slf4j -public class FCMService { - private final RedisService redisService; - private final DomainProperties domainProperties; - private static final String ICON_FILE_PATH = "/src/assets/images/logo.svg"; - - - public void sendPushMessage(String name, Alarm alarm, FCMDto fcmDto) { - log.info("called sendPushMessage name = {}, alarm = {}", name, alarm); - - Optional token = redisService.findByKey(name, String.class); - if (token.isEmpty()) { - log.warn("\"{}\"에 해당하는 디바이스 토큰이 존재하지 않습니다.", name); - return; - } - - if (alarm instanceof LentExpirationImminentAlarm) { - Long daysAfterFromExpireDate = ((LentExpirationImminentAlarm) alarm).getDaysAfterFromExpireDate(); - String title = fcmDto.getTitle(); - String body = String.format(fcmDto.getFormat(), Math.abs(daysAfterFromExpireDate)); - sendMessage(token.get(), name, title, body); - } else if (alarm instanceof LentExpirationAlarm) { - Long daysLeftFromExpireDate = ((LentExpirationAlarm) alarm).getDaysLeftFromExpireDate(); - String title = fcmDto.getTitle(); - String body = String.format(fcmDto.getFormat(), Math.abs(daysLeftFromExpireDate)); - sendMessage(token.get(), name, title, body); - } else if (alarm instanceof ExtensionIssuanceAlarm) { - Integer daysToExtend = ((ExtensionIssuanceAlarm) alarm).getDaysToExtend(); - String extensionName = ((ExtensionIssuanceAlarm) alarm).getExtensionName(); - String title = fcmDto.getTitle(); - String body = String.format(fcmDto.getFormat(), daysToExtend, extensionName); - sendMessage(token.get(), name, title, body); - } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - String extensionName = ((ExtensionExpirationImminentAlarm) alarm).getExtensionName(); - LocalDateTime extensionExpireDate = ((ExtensionExpirationImminentAlarm) alarm).getExtensionExpirationDate(); - String title = fcmDto.getTitle(); - String body = String.format(fcmDto.getFormat(), extensionName, extensionExpireDate); - sendMessage(token.get(), name, title, body); - } else if (alarm instanceof AnnouncementAlarm) { - String title = fcmDto.getTitle(); - String body = fcmDto.getFormat(); - sendMessage(token.get(), name, title, body); - } else { - throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); - } - } - - private void sendMessage(String token, String name, String title, String body) { - log.info( - "called sendOverdueMessage token = {}, name = {}, title = {}, body = {}", - token, name, title, body); - Message message = Message.builder() - .putData("title", title) - .putData("body", body) - .putData("icon", domainProperties.getFeHost() + ICON_FILE_PATH) - .putData("click_action", domainProperties.getFeHost()) - .setToken(token) - .build(); - - FirebaseMessaging.getInstance().sendAsync(message); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMTestController.java b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMTestController.java index 3b2569840..0772852f8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMTestController.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMTestController.java @@ -4,8 +4,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.handler.PushAlarmSender; import org.ftclub.cabinet.redis.service.RedisService; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.utils.overdue.manager.OverdueType; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -18,7 +22,7 @@ @Log4j2 public class FCMTestController { private final RedisService redisService; - private final FCMService fcmService; + private final PushAlarmSender pushAlarmSender; @PostMapping("test/{name}/{token}") public void test(@PathVariable("name") String name, @PathVariable("token") String token) { @@ -29,7 +33,8 @@ public void test(@PathVariable("name") String name, @PathVariable("token") Strin @PostMapping("test2/{name}") public void test2(@PathVariable("name") String name) { log.info("called test2"); - LentExpirationAlarm alarm = new LentExpirationAlarm(1L); - fcmService.sendPushMessage(name, alarm, new FCMDto("title", "%d")); + pushAlarmSender.send( + User.of(name, name + "@studuent.42seoul.kr", null, UserRole.USER), + AlarmEvent.of(1L, new LentExpirationAlarm(1L))); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java index 3e89e63ac..a066f1a70 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java @@ -1,6 +1,11 @@ package org.ftclub.cabinet.alarm.handler; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.Message; +import java.time.LocalDateTime; +import java.util.Optional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.alarm.domain.Alarm; import org.ftclub.cabinet.alarm.domain.AlarmEvent; @@ -11,46 +16,89 @@ import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.alarm.fcm.FCMDto; -import org.ftclub.cabinet.alarm.fcm.FCMService; +import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.redis.service.RedisService; import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Component; +@Slf4j @Component @RequiredArgsConstructor public class PushAlarmSender { private final AlarmProperties alarmProperties; - private final FCMService fcmService; + private final RedisService redisService; + private final DomainProperties domainProperties; + private static final String ICON_FILE_PATH = "/src/assets/images/logo.svg"; - void send(User user, AlarmEvent alarmEvent) { - Alarm alarm = alarmEvent.getAlarm(); - FCMDto fcmDto = getFCMDto(alarm); - fcmService.sendPushMessage(user.getName(), alarm, fcmDto); + public void send(User user, AlarmEvent alarmEvent) { + log.info("push alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); + + Optional token = redisService.findByKey(user.getName(), String.class); + if (token.isEmpty()) { + log.warn("\"{}\"에 해당하는 디바이스 토큰이 존재하지 않습니다.", user.getName()); + return; + } + + FCMDto fcmDto = messageParse(alarmEvent.getAlarm()); + sendMessage(token.get(), fcmDto); } - private FCMDto getFCMDto(Alarm alarm) { + private FCMDto messageParse(Alarm alarm) { if (alarm instanceof LentSuccessAlarm) { - return new FCMDto(alarmProperties.getLentSuccessSubject(), - alarmProperties.getLentSuccessFcmTemplate()); + String building = ((LentSuccessAlarm) alarm).getLocation().getBuilding(); + Integer floor = ((LentSuccessAlarm) alarm).getLocation().getFloor(); + Integer visibleNum = ((LentSuccessAlarm) alarm).getVisibleNum(); + String title = alarmProperties.getLentSuccessSubject(); + String body = String.format(alarmProperties.getLentSuccessMailTemplateUrl(), + building + " " + floor + "층 " + visibleNum + "번"); + return new FCMDto(title, body); } else if (alarm instanceof LentExpirationImminentAlarm) { - return new FCMDto(alarmProperties.getSoonOverdueSubject(), - alarmProperties.getSoonOverdueFcmTemplate()); + Long daysAfterFromExpireDate = ((LentExpirationImminentAlarm) alarm).getDaysAfterFromExpireDate(); + String title = alarmProperties.getSoonOverdueSubject(); + String body = String.format(alarmProperties.getSoonOverdueFcmTemplate(), + Math.abs(daysAfterFromExpireDate)); + return new FCMDto(title, body); } else if (alarm instanceof LentExpirationAlarm) { - return new FCMDto(alarmProperties.getOverdueSubject(), - alarmProperties.getOverdueFcmTemplate()); + Long daysLeftFromExpireDate = ((LentExpirationAlarm) alarm).getDaysLeftFromExpireDate(); + String title = alarmProperties.getOverdueSubject(); + String body = String.format(alarmProperties.getOverdueFcmTemplate(), + Math.abs(daysLeftFromExpireDate)); + return new FCMDto(title, body); } else if (alarm instanceof ExtensionIssuanceAlarm) { - return new FCMDto(alarmProperties.getExtensionIssuanceSubject(), - alarmProperties.getExtensionIssuanceFcmTemplate()); + Integer daysToExtend = ((ExtensionIssuanceAlarm) alarm).getDaysToExtend(); + String extensionName = ((ExtensionIssuanceAlarm) alarm).getExtensionName(); + String title = alarmProperties.getExtensionIssuanceSubject(); + String body = String.format(alarmProperties.getExtensionIssuanceMailTemplateUrl(), + daysToExtend, extensionName); + return new FCMDto(title, body); } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - return new FCMDto(alarmProperties.getExtensionExpirationImminentSubject(), - alarmProperties.getExtensionExpirationImminentFcmTemplate()); + String extensionName = ((ExtensionExpirationImminentAlarm) alarm).getExtensionName(); + LocalDateTime extensionExpireDate = ((ExtensionExpirationImminentAlarm) alarm).getExtensionExpirationDate(); + String title = alarmProperties.getExtensionExpirationImminentSubject(); + String body = String.format(alarmProperties.getExtensionExpirationImminentMailTemplateUrl(), + extensionName, extensionExpireDate); + return new FCMDto(title, body); } else if (alarm instanceof AnnouncementAlarm) { - return new FCMDto(alarmProperties.getAnnouncementSubject(), - alarmProperties.getAnnouncementFcmTemplate()); + String title = alarmProperties.getAnnouncementSubject(); + String body = alarmProperties.getAnnouncementMailTemplateUrl(); + return new FCMDto(title, body); } else { throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); } } + + private void sendMessage(String token, FCMDto fcmDto) { + log.info("send Message : token = {}, fcmDto = {}", token, fcmDto); + Message message = Message.builder() + .putData("title", fcmDto.getTitle()) + .putData("body", fcmDto.getBody()) + .putData("icon", domainProperties.getFeHost() + ICON_FILE_PATH) + .putData("click_action", domainProperties.getFeHost()) + .setToken(token) + .build(); + FirebaseMessaging.getInstance().sendAsync(message); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java index 203222872..ef1051c34 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java @@ -49,8 +49,7 @@ public void sendMail(String name, String to, String subject, String template, Al String building = ((LentSuccessAlarm) alarm).getLocation().getBuilding(); Integer floor = ((LentSuccessAlarm) alarm).getLocation().getFloor(); Integer visibleNum = ((LentSuccessAlarm) alarm).getVisibleNum(); - String locationString = building + " " + floor + "층 " + visibleNum + "번"; - context.setVariable("location", locationString); + context.setVariable("location", building + " " + floor + "층 " + visibleNum + "번"); context.setVariable("expireDate", ((LentSuccessAlarm) alarm).getLentExpirationDate()); } else if (alarm instanceof LentExpirationAlarm) { From 7e3be2625255e878b54d25d5fff1e2e0d3d64fb7 Mon Sep 17 00:00:00 2001 From: jiwon Date: Fri, 20 Oct 2023 10:18:48 +0900 Subject: [PATCH 0025/1029] =?UTF-8?q?[BE]=20FIX:=20email=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A4=91=EB=B3=B5=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{mail => }/config/GmailProperties.java | 2 +- .../cabinet/alarm/{fcm => dto}/FCMDto.java | 2 +- .../cabinet/alarm/{mail => dto}/MailDto.java | 4 +- .../alarm/handler/EmailAlarmSender.java | 85 ++++++++++++++----- .../alarm/handler/PushAlarmSender.java | 2 +- .../cabinet/alarm/mail/EmailService.java | 77 ----------------- .../utils/mail/EmailServiceUnitTest.java | 52 ++++-------- .../manager/OverdueManagerUnitTest.java | 65 +++++++------- 8 files changed, 114 insertions(+), 175 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/alarm/{mail => }/config/GmailProperties.java (94%) rename backend/src/main/java/org/ftclub/cabinet/alarm/{fcm => dto}/FCMDto.java (81%) rename backend/src/main/java/org/ftclub/cabinet/alarm/{mail => dto}/MailDto.java (60%) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/GmailProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java similarity index 94% rename from backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/GmailProperties.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java index f233e885d..09afbe0d6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/config/GmailProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.alarm.mail.config; +package org.ftclub.cabinet.alarm.config; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/FCMDto.java similarity index 81% rename from backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/dto/FCMDto.java index b44a45349..8fcc9eae1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/fcm/FCMDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/FCMDto.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.alarm.fcm; +package org.ftclub.cabinet.alarm.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/MailDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/MailDto.java similarity index 60% rename from backend/src/main/java/org/ftclub/cabinet/alarm/mail/MailDto.java rename to backend/src/main/java/org/ftclub/cabinet/alarm/dto/MailDto.java index b07964a26..352bb6eb6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/MailDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/MailDto.java @@ -1,7 +1,8 @@ -package org.ftclub.cabinet.alarm.mail; +package org.ftclub.cabinet.alarm.dto; import lombok.AllArgsConstructor; import lombok.Getter; +import org.thymeleaf.context.Context; @Getter @AllArgsConstructor @@ -9,4 +10,5 @@ public class MailDto { String subject; String template; + Context context; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java index 8b4ee5eda..abe57cb6d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.alarm.handler; import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.domain.Alarm; @@ -11,60 +12,98 @@ import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; -import org.ftclub.cabinet.alarm.mail.MailDto; +import org.ftclub.cabinet.alarm.dto.MailDto; import org.ftclub.cabinet.alarm.config.AlarmProperties; +import org.ftclub.cabinet.alarm.config.GmailProperties; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.alarm.mail.EmailService; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; +import org.thymeleaf.ITemplateEngine; +import org.thymeleaf.context.Context; @Slf4j @Component @RequiredArgsConstructor public class EmailAlarmSender { - private final EmailService emailService; + private final JavaMailSender javaMailSender; + private final ITemplateEngine templateEngine; + private final GmailProperties gmailProperties; private final AlarmProperties alarmProperties; - void send(User user, AlarmEvent alarmEvent) { - Alarm alarm = alarmEvent.getAlarm(); - MailDto mailDto = getMailDto(alarm); + public void send(User user, AlarmEvent alarmEvent) { + log.info("Email Alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); + if (gmailProperties.getIsProduction() == false) { + log.debug("개발 환경이므로 메일을 보내지 않습니다."); + return; + } + MailDto mailDto = messageParse(user.getName(), alarmEvent.getAlarm()); + try { - emailService.sendMail(user.getName(), user.getEmail(), mailDto.getSubject(), - mailDto.getTemplate(), alarm); - log.info("{} ({})에게 메일을 성공적으로 보냈습니다.", user.getName(), user.getEmail()); + sendMessage(user.getEmail(), mailDto); } catch (MessagingException e) { - log.error("메일 전송 중 오류가 발생했습니다: {}", e.getMessage()); throw new ServiceException(ExceptionStatus.MAIL_BAD_GATEWAY); } } - private MailDto getMailDto(Alarm alarm) { + private MailDto messageParse(String name, Alarm alarm) { + Context context = new Context(); + context.setVariable("name", name); if (alarm instanceof LentSuccessAlarm) { + LentSuccessAlarm lentSuccessAlarm = (LentSuccessAlarm) alarm; + String building = lentSuccessAlarm.getLocation().getBuilding(); + Integer floor = lentSuccessAlarm.getLocation().getFloor(); + Integer visibleNum = lentSuccessAlarm.getVisibleNum(); + context.setVariable("location", building + " " + floor + "층 " + visibleNum + "번"); + context.setVariable("expireDate", lentSuccessAlarm.getLentExpirationDate()); return new MailDto(alarmProperties.getLentSuccessSubject(), - alarmProperties.getLentSuccessMailTemplateUrl()); + alarmProperties.getLentSuccessMailTemplateUrl(), context); } else if (alarm instanceof LentExpirationAlarm) { + context.setVariable("expireDate", + ((LentExpirationAlarm) alarm).getDaysLeftFromExpireDate()); return new MailDto(alarmProperties.getOverdueSubject(), - alarmProperties.getOverdueMailTemplateUrl()); - } - else if (alarm instanceof LentExpirationImminentAlarm) { + alarmProperties.getOverdueMailTemplateUrl(), context); + } else if (alarm instanceof LentExpirationImminentAlarm) { + long overdueDays = ((LentExpirationImminentAlarm) alarm).getDaysAfterFromExpireDate(); + context.setVariable("overdueDays", overdueDays); return new MailDto(alarmProperties.getSoonOverdueSubject(), - alarmProperties.getSoonOverdueMailTemplateUrl()); - } - else if (alarm instanceof ExtensionIssuanceAlarm) { + alarmProperties.getSoonOverdueMailTemplateUrl(), context); + } else if (alarm instanceof ExtensionIssuanceAlarm) { + context.setVariable("extensionName", ((ExtensionIssuanceAlarm) alarm).getExtensionName()); + context.setVariable("expireDate", ((ExtensionIssuanceAlarm) alarm).getExtensionExpirationDate()); + context.setVariable("daysToExtend", ((ExtensionIssuanceAlarm) alarm).getDaysToExtend()); return new MailDto(alarmProperties.getExtensionIssuanceSubject(), - alarmProperties.getExtensionIssuanceMailTemplateUrl()); - } - else if (alarm instanceof ExtensionExpirationImminentAlarm) { + alarmProperties.getExtensionIssuanceMailTemplateUrl(), context); + } else if (alarm instanceof ExtensionExpirationImminentAlarm) { + context.setVariable("extensionName", ((ExtensionExpirationImminentAlarm) alarm).getExtensionName()); + context.setVariable("expireDate", ((ExtensionExpirationImminentAlarm) alarm).getExtensionExpirationDate()); return new MailDto(alarmProperties.getExtensionExpirationImminentSubject(), - alarmProperties.getExtensionExpirationImminentMailTemplateUrl()); + alarmProperties.getExtensionExpirationImminentMailTemplateUrl(), context); } else if (alarm instanceof AnnouncementAlarm) { + context.setVariable("announcementContent", ((AnnouncementAlarm) alarm).getAnnouncementContent()); return new MailDto(alarmProperties.getAnnouncementSubject(), - alarmProperties.getAnnouncementMailTemplateUrl()); + alarmProperties.getAnnouncementMailTemplateUrl(), context); } else { throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); } } + + private void sendMessage(String email, MailDto mailDto) throws MessagingException { + log.info("send Message : email = {}, mailDto = {}", email, mailDto); + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + + helper.setFrom(gmailProperties.getDisplaySenderName() + " <" + gmailProperties.getUsername() + ">"); + helper.setTo(email); + helper.setSubject(mailDto.getSubject()); + + String htmlContent = templateEngine.process(mailDto.getTemplate(), mailDto.getContext()); + helper.setText(htmlContent, true); + + javaMailSender.send(message); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java index a066f1a70..b013b220d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java @@ -15,7 +15,7 @@ import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; -import org.ftclub.cabinet.alarm.fcm.FCMDto; +import org.ftclub.cabinet.alarm.dto.FCMDto; import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java b/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java deleted file mode 100644 index ef1051c34..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/mail/EmailService.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.ftclub.cabinet.alarm.mail; - -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.alarm.domain.Alarm; -import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; -import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; -import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; -import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; -import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; -import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; -import org.ftclub.cabinet.cabinet.domain.Location; -import org.ftclub.cabinet.alarm.mail.config.GmailProperties; -import org.springframework.mail.MailException; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; -import org.springframework.stereotype.Component; -import org.thymeleaf.ITemplateEngine; -import org.thymeleaf.context.Context; - -@Component -@RequiredArgsConstructor -@Log4j2 -public class EmailService { - - private final JavaMailSender javaMailSender; - private final ITemplateEngine templateEngine; - private final GmailProperties gmailProperties; - - public void sendMail(String name, String to, String subject, String template, Alarm alarm) - throws MessagingException, MailException { - log.info("called EmailSender for {}, {}, {}", name, to, subject); - if (gmailProperties.getIsProduction() == false) { - log.debug("개발 환경이므로 메일을 보내지 않습니다."); - return; - } - MimeMessage message = javaMailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); - - helper.setFrom(gmailProperties.getDisplaySenderName() + " <" + gmailProperties.getUsername() + ">"); - helper.setTo(to); - helper.setSubject(subject); - - Context context = new Context(); - context.setVariable("name", name); - if (alarm instanceof LentSuccessAlarm) { - String building = ((LentSuccessAlarm) alarm).getLocation().getBuilding(); - Integer floor = ((LentSuccessAlarm) alarm).getLocation().getFloor(); - Integer visibleNum = ((LentSuccessAlarm) alarm).getVisibleNum(); - context.setVariable("location", building + " " + floor + "층 " + visibleNum + "번"); - context.setVariable("expireDate", ((LentSuccessAlarm) alarm).getLentExpirationDate()); - } - else if (alarm instanceof LentExpirationAlarm) { - context.setVariable("expireDate", - ((LentExpirationAlarm) alarm).getDaysLeftFromExpireDate()); - } else if (alarm instanceof LentExpirationImminentAlarm) { - long overdueDays = ((LentExpirationImminentAlarm) alarm).getDaysAfterFromExpireDate(); - context.setVariable("overdueDays", overdueDays); - } else if (alarm instanceof ExtensionIssuanceAlarm) { - context.setVariable("extensionName", ((ExtensionIssuanceAlarm) alarm).getExtensionName()); - context.setVariable("expireDate", ((ExtensionIssuanceAlarm) alarm).getExtensionExpirationDate()); - context.setVariable("daysToExtend", ((ExtensionIssuanceAlarm) alarm).getDaysToExtend()); - } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - context.setVariable("extensionName", ((ExtensionExpirationImminentAlarm) alarm).getExtensionName()); - context.setVariable("expireDate", ((ExtensionExpirationImminentAlarm) alarm).getExtensionExpirationDate()); - } else if (alarm instanceof AnnouncementAlarm) { - context.setVariable("announcementContent", ((AnnouncementAlarm) alarm).getAnnouncementContent()); - } - - String htmlContent = templateEngine.process(template, context); - helper.setText(htmlContent, true); - - javaMailSender.send(message); - } -} diff --git a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java index 6dc259256..559f74604 100644 --- a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java @@ -9,11 +9,12 @@ import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.ftclub.cabinet.alarm.mail.EmailService; -import org.ftclub.cabinet.alarm.mail.config.GmailProperties; -import org.junit.jupiter.api.BeforeAll; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.handler.EmailAlarmSender; +import org.ftclub.cabinet.alarm.config.GmailProperties; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserRole; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -29,18 +30,8 @@ @ExtendWith(MockitoExtension.class) public class EmailServiceUnitTest { - @Getter - @AllArgsConstructor - private static class Mail { - - String name; - String to; - String subject; - String template; - } - - private static Mail mail; - + private String name = "testUser"; + private String email = "testEamil@test.com"; private JavaMailSender javaMailSender = mock(JavaMailSender.class); @Mock private ITemplateEngine templateEngine; @@ -48,19 +39,8 @@ private static class Mail { private GmailProperties gmailProperties = mock(GmailProperties.class); @InjectMocks - private EmailService emailService; - + private EmailAlarmSender emailAlarmSender; - @BeforeAll - @DisplayName("테스트 전에 메일 대상에 대한 정보를 설정한다.") - static void setupBeforeAll() { - mail = new Mail( - "은빅임", - "은비킴의CPP.student.42seoul.kr", - "플랜비는없는데요.은비는있어요", - "mail/overdue" - ); - } @BeforeEach @DisplayName("테스트 전에 gmailProperties를 설정한다.") @@ -78,11 +58,10 @@ void setupBeforeEach() { void 실패_sendMail_개발환경() throws MessagingException, MailException { given(gmailProperties.getIsProduction()).willReturn(false); - emailService.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate(), null); + emailAlarmSender.send(User.of(name, email, null, UserRole.USER), + AlarmEvent.of(1L, new LentExpirationAlarm(1L))); - then(javaMailSender).should(never()).send( - any(MimeMessage.class) - ); + then(javaMailSender).should(never()).send(any(MimeMessage.class)); } @Test @@ -93,10 +72,9 @@ void setupBeforeEach() { MimeMessage mimeMessage = new MimeMessage((javax.mail.Session) null); given(javaMailSender.createMimeMessage()).willReturn(mimeMessage); - emailService.sendMail(mail.getName(), mail.getTo(), mail.getSubject(), mail.getTemplate(), null); + emailAlarmSender.send(User.of(name, email, null, UserRole.USER), + AlarmEvent.of(1L, new LentExpirationAlarm(1L))); - then(javaMailSender).should().send( - any(MimeMessage.class) - ); + then(javaMailSender).should().send(any(MimeMessage.class)); } } diff --git a/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java index 72f07d963..9e40d4e42 100644 --- a/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManagerUnitTest.java @@ -6,14 +6,11 @@ import static org.mockito.Mockito.never; import javax.mail.MessagingException; -import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; -import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.service.CabinetService; -import org.ftclub.cabinet.alarm.mail.config.GmailProperties; +import org.ftclub.cabinet.alarm.config.GmailProperties; import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; -import org.ftclub.cabinet.alarm.mail.EmailService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -33,8 +30,8 @@ public class OverdueManagerUnitTest { @Mock private CabinetService cabinetService = mock(CabinetService.class); - @Mock - private EmailService emailService = mock(EmailService.class); +// @Mock +// private EmailService emailService = mock(EmailService.class); @Mock(lenient = true) private AlarmProperties alarmProperties = mock(AlarmProperties.class); @InjectMocks @@ -127,13 +124,13 @@ void setUp() { then(alarmProperties).should().getOverdueSubject(); then(alarmProperties).should().getOverdueMailTemplateUrl(); - then(emailService).should().sendMail( - activeLentHistoryDto.getName(), - activeLentHistoryDto.getEmail(), - alarmProperties.getOverdueSubject(), - alarmProperties.getOverdueMailTemplateUrl(), - new LentExpirationAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) - ); +// then(emailService).should().sendMail( +// activeLentHistoryDto.getName(), +// activeLentHistoryDto.getEmail(), +// alarmProperties.getOverdueSubject(), +// alarmProperties.getOverdueMailTemplateUrl(), +// new LentExpirationAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) +// ); } @Test @@ -151,13 +148,13 @@ void setUp() { then(alarmProperties).should().getSoonOverdueSubject(); then(alarmProperties).should().getSoonOverdueMailTemplateUrl(); - then(emailService).should().sendMail( - activeLentHistoryDto.getName(), - activeLentHistoryDto.getEmail(), - alarmProperties.getSoonOverdueSubject(), - alarmProperties.getSoonOverdueMailTemplateUrl(), - new LentExpirationImminentAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) - ); +// then(emailService).should().sendMail( +// activeLentHistoryDto.getName(), +// activeLentHistoryDto.getEmail(), +// alarmProperties.getSoonOverdueSubject(), +// alarmProperties.getSoonOverdueMailTemplateUrl(), +// new LentExpirationImminentAlarm(activeLentHistoryDto.getDaysLeftFromExpireDate()) +// ); } @Test @@ -177,19 +174,19 @@ void setUp() { then(alarmProperties).should(never()).getSoonOverdueMailTemplateUrl(); then(alarmProperties).should(never()).getOverdueSubject(); then(alarmProperties).should(never()).getOverdueMailTemplateUrl(); - then(emailService).should(never()).sendMail( - activeLentHistoryDto.getName(), - activeLentHistoryDto.getEmail(), - alarmProperties.getSoonOverdueSubject(), - alarmProperties.getSoonOverdueMailTemplateUrl(), - null - ); - then(emailService).should(never()).sendMail( - activeLentHistoryDto.getName(), - activeLentHistoryDto.getEmail(), - alarmProperties.getOverdueSubject(), - alarmProperties.getOverdueMailTemplateUrl(), - null - ); +// then(emailService).should(never()).sendMail( +// activeLentHistoryDto.getName(), +// activeLentHistoryDto.getEmail(), +// alarmProperties.getSoonOverdueSubject(), +// alarmProperties.getSoonOverdueMailTemplateUrl(), +// null +// ); +// then(emailService).should(never()).sendMail( +// activeLentHistoryDto.getName(), +// activeLentHistoryDto.getEmail(), +// alarmProperties.getOverdueSubject(), +// alarmProperties.getOverdueMailTemplateUrl(), +// null +// ); } } From d31a31053274379a4ea869a2085809adbc50af35 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Tue, 28 Nov 2023 15:04:47 +0900 Subject: [PATCH 0026/1029] =?UTF-8?q?[BE]=20FIX:=20Properties=20yaml?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/modules.xml | 1 + .../ftclub/cabinet/alarm/config/AlarmProperties.java | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.idea/modules.xml b/.idea/modules.xml index 420fec42c..b1f67fbdc 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,7 @@ + diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java index 0f7003c8b..b46d6d810 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -9,7 +9,7 @@ public class AlarmProperties { /*===================== lentSuccess =========================*/ - @Value("${spring.subject.lentSuccess}") + @Value("${spring.mail.lentSuccess.subject}") private String lentSuccessSubject; @Value("${spring.mail.lentSuccess.template}") @@ -19,7 +19,7 @@ public class AlarmProperties { private String lentSuccessFcmTemplate; /*======================= overdue ===========================*/ - @Value("${spring.subject.overdue}") + @Value("${spring.mail.overdue.subject}") private String overdueSubject; @Value("${spring.mail.overdue.template}") @@ -32,7 +32,7 @@ public class AlarmProperties { @Value("${spring.mail.soonOverdue.term}") private Long soonOverdueTerm; - @Value("${spring.subject.soonOverdue}") + @Value("${spring.mail.soonOverdue.subject}") private String soonOverdueSubject; @Value("${spring.mail.soonOverdue.template}") @@ -42,7 +42,7 @@ public class AlarmProperties { private String soonOverdueFcmTemplate; /*================== extensionIssuance ======================*/ - @Value("${spring.subject.extensionIssuance}") + @Value("${spring.mail.extensionIssuance.subject}") private String extensionIssuanceSubject; @Value("${spring.mail.extensionIssuance.template}") @@ -55,7 +55,7 @@ public class AlarmProperties { @Value("${spring.mail.extensionExpiration.term}") private Long extensionExpirationTerm; - @Value("${spring.subject.extensionExpiration}") + @Value("${spring.mail.extensionExpiration.subject}") private String extensionExpirationImminentSubject; @Value("${spring.mail.extensionExpiration.template}") @@ -65,7 +65,7 @@ public class AlarmProperties { private String extensionExpirationImminentFcmTemplate; /*==================== announcement =========================*/ - @Value("${spring.subject.announcement}") + @Value("${spring.mail.announcement.subject}") private String announcementSubject; @Value("${spring.mail.announcement.template}") From da0d04406527f94cf0708e2b12582afb9985c79c Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Wed, 29 Nov 2023 16:38:55 +0900 Subject: [PATCH 0027/1029] [BE] DOCS: todo and question --- .../alarm/handler/AlarmEventHandler.java | 19 ++--- .../alarm/handler/EmailAlarmSender.java | 22 +++--- .../alarm/handler/SlackAlarmSender.java | 23 +++--- .../cabinet/alarm/slack/SlackApiManager.java | 73 ++++++++++--------- .../alarm/slack/dto/SlackUserInfo.java | 16 ++-- 5 files changed, 82 insertions(+), 71 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index fc1f1dd6f..6d350955c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -1,12 +1,5 @@ package org.ftclub.cabinet.alarm.handler; -import static org.ftclub.cabinet.alarm.domain.AlarmType.EMAIL; -import static org.ftclub.cabinet.alarm.domain.AlarmType.PUSH; -import static org.ftclub.cabinet.alarm.domain.AlarmType.SLACK; -import static org.ftclub.cabinet.exception.ExceptionStatus.NOT_FOUND_USER; - -import java.util.Set; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.AlarmType; @@ -17,6 +10,12 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.ftclub.cabinet.alarm.domain.AlarmType.*; +import static org.ftclub.cabinet.exception.ExceptionStatus.NOT_FOUND_USER; + @Component @RequiredArgsConstructor public class AlarmEventHandler { @@ -31,11 +30,13 @@ public void handleAlarmEvent(AlarmEvent alarmEvent) { .orElseThrow(() -> new ServiceException(NOT_FOUND_USER)); Set alarmOptOuts = receiver.getAlarmOptOuts() .stream().map(AlarmOptOut::getAlarmType).collect(Collectors.toSet()); + + // else-if가 아니어야 하는 것 아닌가? if (alarmOptOuts.contains(SLACK)) slackAlarmSender.send(receiver, alarmEvent); - else if (alarmOptOuts.contains(EMAIL)) + if (alarmOptOuts.contains(EMAIL)) emailAlarmSender.send(receiver, alarmEvent); - else if (alarmOptOuts.contains(PUSH)) + if (alarmOptOuts.contains(PUSH)) pushAlarmSender.send(receiver, alarmEvent); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java index abe57cb6d..ab305a453 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java @@ -1,20 +1,11 @@ package org.ftclub.cabinet.alarm.handler; -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.alarm.domain.Alarm; -import org.ftclub.cabinet.alarm.domain.AlarmEvent; -import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; -import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; -import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; -import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; -import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; -import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; -import org.ftclub.cabinet.alarm.dto.MailDto; import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.alarm.config.GmailProperties; +import org.ftclub.cabinet.alarm.domain.*; +import org.ftclub.cabinet.alarm.dto.MailDto; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; @@ -24,6 +15,9 @@ import org.thymeleaf.ITemplateEngine; import org.thymeleaf.context.Context; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + @Slf4j @Component @RequiredArgsConstructor @@ -40,6 +34,7 @@ public void send(User user, AlarmEvent alarmEvent) { log.debug("개발 환경이므로 메일을 보내지 않습니다."); return; } + // parseMessageToMailDto등과 같이 동사가 먼저오는 이름이어야 할 것 같습니다. MailDto mailDto = messageParse(user.getName(), alarmEvent.getAlarm()); try { @@ -52,6 +47,10 @@ public void send(User user, AlarmEvent alarmEvent) { private MailDto messageParse(String name, Alarm alarm) { Context context = new Context(); context.setVariable("name", name); + // private으로 각 알람별 메서드를 만들어서 호출하는 것이 좋을 것 같습니다. + // 지금 보니 기본적인 컨텐츠들 - 제목, 내용은 다른 sender에서도 동일하게 사용하는 경우가 많은 것 같은데, + // 이 부분을 Alarm 자체에서 toString과 같은 역할을 하는 템플리팅 메서드가 있으면 좋을 것 같습니다. + // 혹 각 sender별로 커스텀한 방식이 필요하다면 Alarm 자체에 각 알람에 맞게 구현되도록 하는 메서드를 갖게할 수도 있을 것 같습니다. if (alarm instanceof LentSuccessAlarm) { LentSuccessAlarm lentSuccessAlarm = (LentSuccessAlarm) alarm; String building = lentSuccessAlarm.getLocation().getBuilding(); @@ -62,6 +61,7 @@ private MailDto messageParse(String name, Alarm alarm) { return new MailDto(alarmProperties.getLentSuccessSubject(), alarmProperties.getLentSuccessMailTemplateUrl(), context); } + // 상속되는 것이 아닌 한 instanceof로 else는 필요 없지 않나요? else if (alarm instanceof LentExpirationAlarm) { context.setVariable("expireDate", ((LentExpirationAlarm) alarm).getDaysLeftFromExpireDate()); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index 428ef8c6c..8e05a9c09 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -14,15 +14,20 @@ @RequiredArgsConstructor public class SlackAlarmSender { - private final SlackApiManager slackApiManager; + private final SlackApiManager slackApiManager; - void send(User user, AlarmEvent alarmEvent) { - SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); - String id = slackUserInfo.getId(); - if (StringUtils.isEmpty(id)) { - throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); - } + // 접근지정자가 없습니다. + void send(User user, AlarmEvent alarmEvent) { + SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); + String id = slackUserInfo.getId(); + if (StringUtils.isEmpty(id)) { + throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); + } - slackApiManager.sendMessage(alarmEvent.getAlarm().toString(), id); - } + // toString을 이용하는 것이 아니라 직접적으로 메시지(String)으로 변환하는 메서드를 두는 게 좋을 것 같습니다. + // 개인적인 느낌으로는 toString은 내부적으로 보는 내용이고, 메시지는 외부적으로 보는 내용 같은 느낌이라... + // 이후에 작성하는 부분이 생길 때에도 toString을 override 하게하는 것 보다 인터페이스에서 명시적으로 강제하는게 좋을 것 같습니다. + // 그리고 파라미터 뒤집혀있네요 id, alarm.toString()이어야 함 + slackApiManager.sendMessage(alarmEvent.getAlarm().toString(), id); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java index e7de7ccc3..e908a8357 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java @@ -5,7 +5,6 @@ import com.slack.api.methods.SlackApiException; import com.slack.api.methods.request.chat.ChatPostMessageRequest; import feign.FeignException.FeignClientException; -import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.slack.config.SlackProperties; @@ -15,50 +14,54 @@ import org.ftclub.cabinet.exception.ServiceException; import org.springframework.stereotype.Service; +import java.io.IOException; + @Log4j2 @Service @RequiredArgsConstructor public class SlackApiManager { - private final SlackProperties slackProperties; - private final SlackFeignClient slackFeignClient; + private final SlackProperties slackProperties; + private final SlackFeignClient slackFeignClient; - public SlackUserInfo requestSlackUserInfo(String email) { + public SlackUserInfo requestSlackUserInfo(String email) { - log.info("Called requestSlackUserInfo email={}", email); + log.info("Called requestSlackUserInfo email={}", email); - try { - SlackResponse slackResponse = slackFeignClient.getUserInfoByEmail( - slackProperties.getApplicationForm(), - slackProperties.getBearer() + slackProperties.getAppToken(), - email); + try { + SlackResponse slackResponse = slackFeignClient.getUserInfoByEmail( + slackProperties.getApplicationForm(), + slackProperties.getBearer() + slackProperties.getAppToken(), + email); - String RESPONSE_ERROR_MSG = "error"; - if (slackResponse.getOk().equals(RESPONSE_ERROR_MSG)) { - log.error("Slack Response ERROR Error {} ", slackResponse); - throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); - } + // getOK()인데 Error일 수 있는 부분이 의아합니다. + String RESPONSE_ERROR_MSG = "error"; + if (slackResponse.getOk().equals(RESPONSE_ERROR_MSG)) { + log.error("Slack Response ERROR Error {} ", slackResponse); + throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); + } - return slackResponse.getSlackUserInfo(); - } catch (FeignClientException e) { - log.error("{}", e.getMessage()); - throw new ServiceException(ExceptionStatus.SLACK_REQUEST_BAD_GATEWAY); - } - } + return slackResponse.getSlackUserInfo(); + } catch (FeignClientException e) { + log.error("{}", e.getMessage()); + throw new ServiceException(ExceptionStatus.SLACK_REQUEST_BAD_GATEWAY); + } + } - public void sendMessage(String channelId, String message) { - log.info("Called sendMessage channelId={}, message={}", channelId, message); - try { - MethodsClient methods = Slack.getInstance().methods(slackProperties.getAppToken()); + public void sendMessage(String channelId, String message) { + log.info("Called sendMessage channelId={}, message={}", channelId, message); + try { + // token으로 그때그때 instance를 get해오는 게 아니고, 빈으로 등록해서 사용하는게 낫지 않나요? + MethodsClient methods = Slack.getInstance().methods(slackProperties.getAppToken()); - ChatPostMessageRequest request = ChatPostMessageRequest.builder() - .channel(channelId) // DM & channel - .text(message) - .build(); - methods.chatPostMessage(request); - } catch (SlackApiException | IOException e) { - log.error("{}", e.getMessage()); - throw new ServiceException(ExceptionStatus.SLACK_MESSAGE_SEND_BAD_GATEWAY); - } - } + ChatPostMessageRequest request = ChatPostMessageRequest.builder() + .channel(channelId) // DM & channel + .text(message) + .build(); + methods.chatPostMessage(request); + } catch (SlackApiException | IOException e) { + log.error("{}", e.getMessage()); + throw new ServiceException(ExceptionStatus.SLACK_MESSAGE_SEND_BAD_GATEWAY); + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java index 8132167f5..7da7fd2d2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java @@ -12,12 +12,14 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class SlackUserInfo { - String id; - String name; - @JsonAlias("real_name") - String realName; - @JsonAlias("team_id") - String teamId; - Boolean deleted; + // 접근 지정자가 없습니다. + String id; + String name; + // 얘는 뭔가요? + @JsonAlias("real_name") + String realName; + @JsonAlias("team_id") + String teamId; + Boolean deleted; } From 89d7f7377a59271e6fc5354a42945f682ed0216c Mon Sep 17 00:00:00 2001 From: hyungseok Date: Wed, 29 Nov 2023 22:02:05 +0900 Subject: [PATCH 0028/1029] =?UTF-8?q?[FE]=20REFACTOR:=20=EC=9D=BC=EB=8B=A8?= =?UTF-8?q?=20=EC=B4=88=EB=B3=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/axios/axios.custom.ts | 1 - frontend/src/pages/Layout.tsx | 4 +- .../src/pages/PendingPage/PendingPage.tsx | 68 +++++++++++++++--- .../PendingPage/components/FloorContainer.tsx | 4 -- .../components/MultiToggleSwitch.tsx | 69 +++++++++++++++++++ .../components/PendingCountdown.tsx | 66 ++++++++++++++++++ .../pages/PendingPage/components/Timer.tsx | 61 ---------------- frontend/src/types/dto/cabinet.dto.ts | 4 ++ frontend/src/types/enum/cabinet.type.enum.ts | 1 + frontend/src/types/enum/time.enum.ts | 7 ++ 10 files changed, 207 insertions(+), 78 deletions(-) create mode 100644 frontend/src/pages/PendingPage/components/MultiToggleSwitch.tsx create mode 100644 frontend/src/pages/PendingPage/components/PendingCountdown.tsx delete mode 100644 frontend/src/pages/PendingPage/components/Timer.tsx create mode 100644 frontend/src/types/enum/time.enum.ts diff --git a/frontend/src/api/axios/axios.custom.ts b/frontend/src/api/axios/axios.custom.ts index bfd6280ab..a01e0533b 100644 --- a/frontend/src/api/axios/axios.custom.ts +++ b/frontend/src/api/axios/axios.custom.ts @@ -17,7 +17,6 @@ const axiosMyInfoURL = "/v4/users/me"; export const axiosMyInfo = async (): Promise => { try { const response = await instance.get(axiosMyInfoURL); - console.log(response); return response; } catch (error) { throw error; diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index f19d4628c..dbbf5955a 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -47,8 +47,8 @@ const Layout = (): JSX.Element => { data: { date: serverTime }, } = await axiosMyInfo(); - const formattedServerTime = serverTime.split(" KST")[0]; - setServerTime(new Date(formattedServerTime)); // 접속 후 최초 서버 시간을 가져옴 + const formattedServerTime = serverTime.split(" KST")[0]; // 서버 시간을 Date 객체로 변환하기 위해 KST 제거 + setServerTime(new Date(formattedServerTime)); // 접속 후(세션 초기화 후) 최초 서버 시간을 가져옴 setMyInfoData(myInfo); setUser(myInfo); setIsValidToken(true); diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 0a76aa82e..31f0d673f 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -1,18 +1,30 @@ import { useEffect, useState } from "react"; +import { set } from "react-ga"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { isCurrentSectionRenderState } from "@/recoil/atoms"; import FloorContainer from "@/pages/PendingPage/components/FloorContainer"; -import Timer from "@/pages/PendingPage/components/Timer"; +import MultiToggleSwitch from "@/pages/PendingPage/components/MultiToggleSwitch"; +import PendingCountdown from "@/pages/PendingPage/components/PendingCountdown"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; +import { + CabinetPreviewInfo, + PendingCabinetsInfo, +} from "@/types/dto/cabinet.dto"; +import CabinetType from "@/types/enum/cabinet.type.enum"; import { axiosGetPendingCabinets } from "@/api/axios/axios.custom"; import useDebounce from "@/hooks/useDebounce"; const PendingPage = () => { - const [pendingCabinets, setPendingCabinets] = useState< - CabinetPreviewInfo[][] - >([[]]); + const [toggleType, setToggleType] = useState(CabinetType.ALL); + const [cabinets, setCabinets] = useState({}); + const [pendingCabinets, setPendingCabinets] = useState( + {} + ); + const [privateCabinets, setPrivateCabinets] = useState( + {} + ); + const [sharedCabinets, setSharedCabinets] = useState({}); const [isLoaded, setIsLoaded] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [isOpenTime, setIsOpenTime] = useState(false); @@ -27,7 +39,29 @@ const PendingPage = () => { try { const response = await axiosGetPendingCabinets(); const pendingCabinets = response.data.cabinetInfoResponseDtos; + + const filterCabinetsByType = (type: string) => + Object.fromEntries( + Object.entries(pendingCabinets).map(([key, cabinets]: any) => [ + key, + cabinets.filter( + (cabinet: CabinetPreviewInfo) => cabinet.lentType === type + ), + ]) + ); + + const privateCabinets = filterCabinetsByType(CabinetType.PRIVATE); + const sharedCabinets = filterCabinetsByType(CabinetType.SHARE); + + const updatedCabinets = + toggleType === CabinetType.ALL + ? pendingCabinets + : filterCabinetsByType(toggleType); + + setCabinets(updatedCabinets); setPendingCabinets(pendingCabinets); + setPrivateCabinets(privateCabinets); + setSharedCabinets(sharedCabinets); } catch (error) { throw error; } @@ -47,7 +81,7 @@ const PendingPage = () => { useEffect(() => { setTimeout(() => { - // 새로고침 광클 방지를 위한 딜레이 + // 새로고침 광클 방지를 위한 초기 로딩 딜레이 setIsLoaded(true); }, 500); }, []); @@ -65,8 +99,17 @@ const PendingPage = () => { } }, [isOpenTime]); + useEffect(() => { + if (toggleType === CabinetType.ALL) setCabinets(pendingCabinets); + else if (toggleType === CabinetType.PRIVATE) setCabinets(privateCabinets); + else if (toggleType === CabinetType.SHARE) setCabinets(sharedCabinets); + }, [toggleType]); + return ( + + + 사용 가능 사물함

@@ -76,9 +119,9 @@ const PendingPage = () => { 새로고침 - setIsOpenTime(true)} /> - {isShowingLoadingAnimation && pendingCabinets ? ( - Object.entries(pendingCabinets).map(([key, value]) => ( + setIsOpenTime(true)} /> + {isShowingLoadingAnimation && cabinets ? ( + Object.entries(cabinets).map(([key, value]) => ( ` } `; -const LoadingContainerStyled = styled.div` - margin-top: 30px; -`; - export default FloorContainer; diff --git a/frontend/src/pages/PendingPage/components/MultiToggleSwitch.tsx b/frontend/src/pages/PendingPage/components/MultiToggleSwitch.tsx new file mode 100644 index 000000000..f44147df5 --- /dev/null +++ b/frontend/src/pages/PendingPage/components/MultiToggleSwitch.tsx @@ -0,0 +1,69 @@ +import React, { useEffect } from "react"; +import styled from "styled-components"; +import CabinetType from "@/types/enum/cabinet.type.enum"; + +interface MultiToggleSwitchProps { + cabinetType: CabinetType; + onChange: (CabinetType: CabinetType) => void; +} + +const MultiToggleSwitch: React.FC = ({ + cabinetType, + onChange, +}) => { + useEffect(() => { + const buttons = document.querySelectorAll("button"); + + buttons.forEach((button) => { + if (button.className === cabinetType) { + button.style.color = "white"; + button.style.backgroundColor = "var(--main-color)"; + } + }); + }, []); + + function changeTargetButton(e: any) { + const target = e.target; + const buttons = document.querySelectorAll("button"); + + buttons.forEach((button) => { + button.style.color = "black"; + button.style.backgroundColor = "transparent"; + }); + + target.style.color = "white"; + target.style.backgroundColor = "var(--main-color)"; + + onChange(target.className); + } + + return ( + + + + + + ); +}; + +const WrapperStyled = styled.div` + width: fit-content; + display: flex; + align-items: center; + background-color: var(--lightgray-color); + border-radius: 10px; + button { + display: flex; + justify-content: center; + align-items: center; + border-radius: 10px; + font-size: 0.9rem; + height: 30px; + width: 50px; + font-weight: 500; + background-color: transparent; + color: black; + } +`; + +export default MultiToggleSwitch; diff --git a/frontend/src/pages/PendingPage/components/PendingCountdown.tsx b/frontend/src/pages/PendingPage/components/PendingCountdown.tsx new file mode 100644 index 000000000..51c2ed711 --- /dev/null +++ b/frontend/src/pages/PendingPage/components/PendingCountdown.tsx @@ -0,0 +1,66 @@ +import { useEffect, useState } from "react"; +import { useRecoilState } from "recoil"; +import styled from "styled-components"; +import { serverTimeState } from "@/recoil/atoms"; +import Time from "@/types/enum/time.enum"; + +const openTime = new Date(); +openTime.setHours(13, 0, 0, 0); // 오픈 시간인 13:00:00(오후 1시)로 설정 + +const hours24 = 86400000; // 24시간을 밀리초로 표현 + +const PendingCountdown = ({ + observeOpenTime, +}: { + observeOpenTime: () => void; +}) => { + const [serverTime] = useRecoilState(serverTimeState); + const [remainingTime, setRemainingTime] = useState(hours24); // 기본 24시로 초기화(처음 함수 호출을 위한 값. 큰 의미 없음) + + const hours = Math.floor(remainingTime / 3600000); + const minutes = Math.floor((remainingTime % 3600000) / 60000); + const seconds = Math.floor((remainingTime % 60000) / 1000); + + useEffect(() => { + if (serverTime.toLocaleTimeString() === Time.PENDING_OPEN) + observeOpenTime(); // 오픈 시간이 되면 업데이트 된 사물함 정보를 가져옴 + if (remainingTime !== 0) setRemainingTime(getRemainingTime()); // 이미 오픈했으면 OPEN으로 표시 + }, [serverTime]); + + function getRemainingTime() { + let timeRemains; + + timeRemains = openTime.getTime() - serverTime.getTime(); + + if (openTime.getTime() < serverTime.getTime()) timeRemains += hours24; // 24시간을 더해줌 + + if (timeRemains < 0) return -timeRemains; + return timeRemains; + } + + return ( + <> + + + {remainingTime === 0 + ? "OPEN" + : `${hours}시간 ${minutes}분 ${seconds}초`} + + + ); +}; + +const PendingCountdownIconStyled = styled.img` + height: 25px; + width: 25px; + margin-top: 50px; +`; + +const PendingCountdownStyled = styled.div` + margin-top: 5px; + color: var(--main-color); + font-size: 1.8rem; + font-weight: 600; +`; + +export default PendingCountdown; diff --git a/frontend/src/pages/PendingPage/components/Timer.tsx b/frontend/src/pages/PendingPage/components/Timer.tsx deleted file mode 100644 index 3091f5f48..000000000 --- a/frontend/src/pages/PendingPage/components/Timer.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useEffect, useState } from "react"; -import { useRecoilState } from "recoil"; -import styled from "styled-components"; -import { s } from "vitest/dist/env-afee91f0"; -import { serverTimeState } from "@/recoil/atoms"; - -const openTime = "오후 1:00:00"; - -const Timer = ({ observeOpenTime }: { observeOpenTime: () => void }) => { - const [serverTime] = useRecoilState(serverTimeState); - const [remainingTime, setRemainingTime] = useState(86400000); // 기본 24시로 초기화 - - useEffect(() => { - if (serverTime.toLocaleTimeString() === openTime) observeOpenTime(); - setRemainingTime(calculateRemainingTime()); - }, [serverTime]); - - function calculateRemainingTime() { - if (remainingTime === 0) return 0; // 이미 오픈했으면 OPEN으로 표시 - const openTime = new Date(); - openTime.setHours(13, 0, 0, 0); // 13:00:00로 설정 - - let timeDiff; - - timeDiff = openTime.getTime() - serverTime.getTime(); - if (openTime.getTime() < serverTime.getTime()) timeDiff += 86400000; // 24시간을 더해줌 - - if (timeDiff < 0) return -timeDiff; - return timeDiff; - } - - const hours = Math.floor(remainingTime / 3600000); - const minutes = Math.floor((remainingTime % 3600000) / 60000); - const seconds = Math.floor((remainingTime % 60000) / 1000); - - return ( - <> - - - {remainingTime === 0 - ? "OPEN" - : `${hours}시간 ${minutes}분 ${seconds}초`} - - - ); -}; - -const TimerIconStyled = styled.img` - height: 25px; - width: 25px; - margin-top: 50px; -`; - -const TimerStyled = styled.div` - margin-top: 5px; - color: var(--main-color); - font-size: 1.8rem; - font-weight: 600; -`; - -export default Timer; diff --git a/frontend/src/types/dto/cabinet.dto.ts b/frontend/src/types/dto/cabinet.dto.ts index e29e67987..3979d45ea 100644 --- a/frontend/src/types/dto/cabinet.dto.ts +++ b/frontend/src/types/dto/cabinet.dto.ts @@ -58,3 +58,7 @@ export interface CabinetInfoByBuildingFloorDto { section: string; // swagger의 CabinetPerSectionDto에 맞추어 object -> string으로 수정했습니다. cabinets: CabinetPreviewInfo[]; } + +export interface PendingCabinetsInfo { + [key: string]: CabinetPreviewInfo[]; +} diff --git a/frontend/src/types/enum/cabinet.type.enum.ts b/frontend/src/types/enum/cabinet.type.enum.ts index 380e1a20f..6806eabd8 100644 --- a/frontend/src/types/enum/cabinet.type.enum.ts +++ b/frontend/src/types/enum/cabinet.type.enum.ts @@ -2,6 +2,7 @@ enum CabinetType { PRIVATE = "PRIVATE", SHARE = "SHARE", CLUB = "CLUB", + ALL = "ALL", } export default CabinetType; diff --git a/frontend/src/types/enum/time.enum.ts b/frontend/src/types/enum/time.enum.ts new file mode 100644 index 000000000..421de833e --- /dev/null +++ b/frontend/src/types/enum/time.enum.ts @@ -0,0 +1,7 @@ +enum Time { + PENDING_OPEN = "오후 1:00:00", +} + +//Date 객체 .toLocaleTimeString() 반환값 기준 + +export default Time; From 585cd95fb9d7e82b47c6cbca59230926712ecacc Mon Sep 17 00:00:00 2001 From: hyungseok Date: Thu, 30 Nov 2023 12:29:29 +0900 Subject: [PATCH 0029/1029] =?UTF-8?q?[FE]=20REFACTOR:=20MultiToggleSwitch?= =?UTF-8?q?=20=EA=B3=B5=EC=9A=A9=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common}/MultiToggleSwitch.tsx | 45 ++++++++++++------- .../src/pages/PendingPage/PendingPage.tsx | 29 +++++++++--- .../PendingPage/components/FloorContainer.tsx | 5 +-- 3 files changed, 52 insertions(+), 27 deletions(-) rename frontend/src/{pages/PendingPage/components => components/Common}/MultiToggleSwitch.tsx (59%) diff --git a/frontend/src/pages/PendingPage/components/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx similarity index 59% rename from frontend/src/pages/PendingPage/components/MultiToggleSwitch.tsx rename to frontend/src/components/Common/MultiToggleSwitch.tsx index f44147df5..3f48ce838 100644 --- a/frontend/src/pages/PendingPage/components/MultiToggleSwitch.tsx +++ b/frontend/src/components/Common/MultiToggleSwitch.tsx @@ -1,29 +1,38 @@ import React, { useEffect } from "react"; import styled from "styled-components"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -interface MultiToggleSwitchProps { - cabinetType: CabinetType; - onChange: (CabinetType: CabinetType) => void; +interface toggleItem { + name: string; + key: string; } -const MultiToggleSwitch: React.FC = ({ - cabinetType, - onChange, -}) => { +interface MultiToggleSwitchProps { + initialState: T; // 초기값 + setState: React.Dispatch>; // 상태를 변경하는 dispatcher + toggleList: toggleItem[]; // 토글 리스트 +} + +const MultiToggleSwitch = ({ + initialState, + setState, + toggleList, +}: MultiToggleSwitchProps) => { useEffect(() => { const buttons = document.querySelectorAll("button"); buttons.forEach((button) => { - if (button.className === cabinetType) { + if (button.className === initialState) { button.style.color = "white"; button.style.backgroundColor = "var(--main-color)"; } }); }, []); - function changeTargetButton(e: any) { + function switchToggle(e: any) { const target = e.target; + + if (target === e.currentTarget) return; + const buttons = document.querySelectorAll("button"); buttons.forEach((button) => { @@ -34,14 +43,16 @@ const MultiToggleSwitch: React.FC = ({ target.style.color = "white"; target.style.backgroundColor = "var(--main-color)"; - onChange(target.className); + setState(target.className); } return ( - - - - + + {toggleList.map((item) => ( + + ))} ); }; @@ -51,15 +62,17 @@ const WrapperStyled = styled.div` display: flex; align-items: center; background-color: var(--lightgray-color); + border-radius: 10px; button { display: flex; justify-content: center; align-items: center; + width: fit-content; + min-width: 50px; border-radius: 10px; font-size: 0.9rem; height: 30px; - width: 50px; font-weight: 500; background-color: transparent; color: black; diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 31f0d673f..032597ee4 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -1,12 +1,11 @@ import { useEffect, useState } from "react"; -import { set } from "react-ga"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { isCurrentSectionRenderState } from "@/recoil/atoms"; import FloorContainer from "@/pages/PendingPage/components/FloorContainer"; -import MultiToggleSwitch from "@/pages/PendingPage/components/MultiToggleSwitch"; import PendingCountdown from "@/pages/PendingPage/components/PendingCountdown"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; +import MultiToggleSwitch from "@/components/Common/MultiToggleSwitch"; import { CabinetPreviewInfo, PendingCabinetsInfo, @@ -15,6 +14,12 @@ import CabinetType from "@/types/enum/cabinet.type.enum"; import { axiosGetPendingCabinets } from "@/api/axios/axios.custom"; import useDebounce from "@/hooks/useDebounce"; +const toggleList = [ + { name: "전체", key: CabinetType.ALL }, + { name: "개인", key: CabinetType.PRIVATE }, + { name: "공유", key: CabinetType.SHARE }, +]; + const PendingPage = () => { const [toggleType, setToggleType] = useState(CabinetType.ALL); const [cabinets, setCabinets] = useState({}); @@ -33,8 +38,6 @@ const PendingPage = () => { ); const { debounce } = useDebounce(); - const isShowingLoadingAnimation = !isRefreshing && isLoaded; - const getPendingCabinets = async () => { try { const response = await axiosGetPendingCabinets(); @@ -108,7 +111,11 @@ const PendingPage = () => { return ( - + 사용 가능 사물함 @@ -116,11 +123,15 @@ const PendingPage = () => { 매일 오후 1시 사용 가능한 사물함이 업데이트됩니다.

- 새로고침 + {isRefreshing ? ( + + ) : ( + 새로고침 + )}
setIsOpenTime(true)} /> - {isShowingLoadingAnimation && cabinets ? ( + {isLoaded && cabinets ? ( Object.entries(cabinets).map(([key, value]) => ( ` font-size: 1.1rem; color: var(--black); padding-left: 5px; - + padding-right: 5px; border-bottom: 1.5px solid #d9d9d9; - div { - } button { z-index: 2; height: 30px; @@ -69,7 +67,6 @@ const FloorTitleStyled = styled.h2<{ isToggled: boolean }>` background: url(/src/assets/images/select.svg) no-repeat 100%; transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; - margin-right: 5px; } `; From 8ce9afba0fb0b2ca9f699bf3cd560c356fa1d9c4 Mon Sep 17 00:00:00 2001 From: hyungseok Date: Thu, 30 Nov 2023 12:42:05 +0900 Subject: [PATCH 0030/1029] =?UTF-8?q?[FE]=20REFACTOR:=20pendingCabinetsTyp?= =?UTF-8?q?e=20enum=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CabinetListItem/CabinetListItem.tsx | 7 ++--- .../src/pages/PendingPage/PendingPage.tsx | 31 ++++++++++++------- frontend/src/types/enum/cabinet.type.enum.ts | 1 - 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx index 44cdebbd8..2417b760e 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -62,10 +62,9 @@ const CabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { ) { if (props.lentType === "PRIVATE") cabinetLabelText = props.name; else if (props.lentType === "SHARE") { - cabinetLabelText = - !!props.title - ? props.title - : `${props.userCount} / ${props.maxUser}`; + cabinetLabelText = !!props.title + ? props.title + : `${props.userCount} / ${props.maxUser}`; } else if (props.lentType === "CLUB") cabinetLabelText = props.title ? props.title : "동아리"; } else { diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 032597ee4..85f6fa1c6 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -10,18 +10,25 @@ import { CabinetPreviewInfo, PendingCabinetsInfo, } from "@/types/dto/cabinet.dto"; -import CabinetType from "@/types/enum/cabinet.type.enum"; import { axiosGetPendingCabinets } from "@/api/axios/axios.custom"; import useDebounce from "@/hooks/useDebounce"; +enum PendingCabinetsType { + ALL = "ALL", + PRIVATE = "PRIVATE", + SHARE = "SHARE", +} + const toggleList = [ - { name: "전체", key: CabinetType.ALL }, - { name: "개인", key: CabinetType.PRIVATE }, - { name: "공유", key: CabinetType.SHARE }, + { name: "전체", key: PendingCabinetsType.ALL }, + { name: "개인", key: PendingCabinetsType.PRIVATE }, + { name: "공유", key: PendingCabinetsType.SHARE }, ]; const PendingPage = () => { - const [toggleType, setToggleType] = useState(CabinetType.ALL); + const [toggleType, setToggleType] = useState( + PendingCabinetsType.ALL + ); const [cabinets, setCabinets] = useState({}); const [pendingCabinets, setPendingCabinets] = useState( {} @@ -53,11 +60,11 @@ const PendingPage = () => { ]) ); - const privateCabinets = filterCabinetsByType(CabinetType.PRIVATE); - const sharedCabinets = filterCabinetsByType(CabinetType.SHARE); + const privateCabinets = filterCabinetsByType(PendingCabinetsType.PRIVATE); + const sharedCabinets = filterCabinetsByType(PendingCabinetsType.SHARE); const updatedCabinets = - toggleType === CabinetType.ALL + toggleType === PendingCabinetsType.ALL ? pendingCabinets : filterCabinetsByType(toggleType); @@ -103,9 +110,11 @@ const PendingPage = () => { }, [isOpenTime]); useEffect(() => { - if (toggleType === CabinetType.ALL) setCabinets(pendingCabinets); - else if (toggleType === CabinetType.PRIVATE) setCabinets(privateCabinets); - else if (toggleType === CabinetType.SHARE) setCabinets(sharedCabinets); + if (toggleType === PendingCabinetsType.ALL) setCabinets(pendingCabinets); + else if (toggleType === PendingCabinetsType.PRIVATE) + setCabinets(privateCabinets); + else if (toggleType === PendingCabinetsType.SHARE) + setCabinets(sharedCabinets); }, [toggleType]); return ( diff --git a/frontend/src/types/enum/cabinet.type.enum.ts b/frontend/src/types/enum/cabinet.type.enum.ts index 6806eabd8..380e1a20f 100644 --- a/frontend/src/types/enum/cabinet.type.enum.ts +++ b/frontend/src/types/enum/cabinet.type.enum.ts @@ -2,7 +2,6 @@ enum CabinetType { PRIVATE = "PRIVATE", SHARE = "SHARE", CLUB = "CLUB", - ALL = "ALL", } export default CabinetType; From 8b9e383c92d347920c9ff889ed111df1eead5476 Mon Sep 17 00:00:00 2001 From: hyungseok Date: Fri, 1 Dec 2023 14:22:00 +0900 Subject: [PATCH 0031/1029] =?UTF-8?q?[FE]=20FIX:=20refreshButton=20?= =?UTF-8?q?=EB=AA=A8=EB=B0=94=EC=9D=BC=EC=97=90=EC=84=9C=20=EC=95=88?= =?UTF-8?q?=EB=B3=B4=EC=9D=B4=EB=8D=98=20=ED=98=84=EC=83=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PendingPage/PendingPage.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 85f6fa1c6..1cafbc3f0 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -194,18 +194,14 @@ const SubHeaderStyled = styled.div` `; const RefreshButtonStyled = styled.button` - margin-top: 30px; - margin-bottom: 20px; + margin-top: 40px; background-color: transparent; width: 35px; - height: 0px; + height: 35px; img { width: 35px; height: 35px; } - div { - margin-top: 11px; - } &:hover { opacity: 0.7; } From 7c8d657885a4226b634d1961e97e88e9cd95dcde Mon Sep 17 00:00:00 2001 From: hyungseok Date: Fri, 1 Dec 2023 16:17:01 +0900 Subject: [PATCH 0032/1029] =?UTF-8?q?[FE]=20FEAT:=20admin=20lineChart=20?= =?UTF-8?q?=EB=8C=80=EC=97=AC=20=EC=83=89=EA=B9=94=20main=20color=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/AdminInfo/Chart/LineChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/AdminInfo/Chart/LineChart.tsx b/frontend/src/components/AdminInfo/Chart/LineChart.tsx index f7b2fe829..f517bf3c9 100644 --- a/frontend/src/components/AdminInfo/Chart/LineChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/LineChart.tsx @@ -58,7 +58,7 @@ const LineChart = ({ data }: { data: IMonthlyData[] }) => ( reverse: false, }} curve={"linear"} - colors={["purple", "red"]} + colors={["var(--main-color)", "red"]} yFormat=" >0" axisRight={null} axisBottom={{ From e17f5907c31780cdf798d81e8fca1e04b6b6ab3a Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 2 Dec 2023 11:48:50 +0900 Subject: [PATCH 0033/1029] =?UTF-8?q?[BE]=20pending=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20cabine?= =?UTF-8?q?t,=20lent=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/controller/CabinetController.java | 8 +- .../repository/CabinetComplexRepository.java | 2 - .../repository/CabinetOptionalFetcher.java | 63 +--- .../cabinet/repository/CabinetRepository.java | 124 ++++---- .../cabinet/service/CabinetFacadeService.java | 16 +- .../service/CabinetFacadeServiceImpl.java | 210 ++++---------- .../cabinet/firebase/FCMInitializer.java | 6 +- .../cabinet/lent/domain/LentHistory.java | 2 + .../lent/repository/LentOptionalFetcher.java | 132 ++------- .../lent/repository/LentRepository.java | 217 ++++++-------- .../lent/service/LentFacadeService.java | 11 - .../lent/service/LentFacadeServiceImpl.java | 23 +- .../cabinet/lent/service/LentServiceImpl.java | 25 +- .../service/StatisticsFacadeServiceImpl.java | 2 +- .../service/LentExtensionServiceImpl.java | 2 +- .../service/CabinetFacadeServiceUnitTest.java | 272 ++++++++++-------- .../lent/repository/LentRepositoryTest.java | 33 ++- .../lent/service/LentServiceImplTest.java | 18 +- .../StatisticsFacadeServiceUnitTest.java | 2 +- .../service/LentExtensionServiceTest.java | 19 -- 20 files changed, 444 insertions(+), 743 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java index 4bed18d30..811c81fc4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java @@ -52,7 +52,7 @@ public List getBuildingFloorsResponse() { public List getCabinetsPerSection( @PathVariable("building") String building, @PathVariable("floor") Integer floor) { - return cabinetFacadeService.getCabinetsPerSectionDSL(building, floor); + return cabinetFacadeService.getCabinetsPerSection(building, floor); } /** @@ -75,10 +75,10 @@ public CabinetInfoResponseDto getCabinetInfo( * * @return 오픈 예정인 사물함들의 정보를 반환합니다. */ - @GetMapping("/pending") + @GetMapping("/buildings/{building}/pending") @AuthGuard(level = AuthLevel.USER_OR_ADMIN) - public CabinetPendingResponseDto getPendingCabinets() { + public CabinetPendingResponseDto getPendingCabinets(@PathVariable("building") String building) { log.info("Called getPendingCabinets"); - return cabinetFacadeService.getPendingCabinets(); + return cabinetFacadeService.getPendingCabinets(building); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepository.java index 9a4b03130..078f3d965 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepository.java @@ -16,7 +16,5 @@ public interface CabinetComplexRepository { List findCabinetsActiveLentHistoriesByBuildingAndFloor( String building, Integer floor); - List findAllCabinetsByBuildingAndFloor(String building, Integer floor); - List findAllCabinetsByCabinetStatusAndBeforeEndedAt(CabinetStatus cabinetStatus, LocalDateTime endedAt); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java index 0fae07959..f87822fa1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java @@ -2,19 +2,15 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.cabinet.domain.Location; import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.mapper.CabinetMapper; -import org.ftclub.cabinet.user.domain.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @@ -41,27 +37,9 @@ public Cabinet findCabinet(Long cabinetId) { public List findCabinetsActiveLentHistoriesByBuildingAndFloor( String building, Integer floor) { - log.debug("Called findCabinetsActiveLentHistoriesByBuildingAndFloor: {}, {}", building, - floor); - return cabinetRepository.findCabinetActiveLentHistoryUserListByBuildingAndFloor(building, - floor).stream() - .map(result -> { - Cabinet cabinet = (Cabinet) result[0]; - LentHistory lentHistory = (LentHistory) result[1]; - User user = (User) result[2]; - return cabinetMapper.toActiveCabinetInfoEntitiesDto(cabinet, lentHistory, user); - }).collect(Collectors.toList()); - } - - public List findCabinetsActiveLentHistoriesByBuildingAndFloor2( - String building, Integer floor) { return cabinetRepository.findCabinetsActiveLentHistoriesByBuildingAndFloor(building, floor); } - public List findCabinetsByBuildingAndFloor2(String building, Integer floor) { - return cabinetRepository.findAllCabinetsByBuildingAndFloor(building, floor); - } - /** * 유저 ID로 사물함을 찾습니다. * @@ -71,7 +49,7 @@ public List findCabinetsByBuildingAndFloor2(String building, Integer fl */ public Cabinet findLentCabinetByUserId(Long userId) { log.debug("Called findLentCabinetByUserId: {}", userId); - return cabinetRepository.findLentCabinetByUserId(userId).orElse(null); + return cabinetRepository.findByUserIdAndEndedAtIsNull(userId).orElse(null); } public List findAllBuildings() { @@ -84,12 +62,6 @@ public List findAllFloorsByBuilding(String building) { return cabinetRepository.findAllFloorsByBuilding(building); } - // deprecated - public List findAllSectionsByBuildingAndFloor(String building, Integer floor) { - log.debug("Called findAllSectionsByBuildingAndFloor: {}, {}", building, floor); - return cabinetRepository.findAllSectionsByBuildingAndFloor(building, floor); - } - public List findAllPendingCabinetsByCabinetStatusAndBeforeEndedAt( CabinetStatus cabinetStatus, LocalDateTime currentDate) { @@ -114,13 +86,8 @@ public Page findPaginationByVisibleNum(Integer visibleNum, PageRequest return cabinetRepository.findPaginationByVisibleNum(visibleNum, pageable); } - public List findAllCabinetsByLocation(Location location) { - log.debug("Called findAllCabinetsByLocation: {}", location); - return cabinetRepository.findAllCabinetsByLocation(location); - } - public List findAllCabinetsByBuildingAndFloor(String building, Integer floor) { - return cabinetRepository.findAllByBuildingAndFloor(building, floor); + return cabinetRepository.findAllByBuildingAndFloorOrderByVisibleNum(building, floor); } /*-------------------------------------------GET--------------------------------------------*/ @@ -136,7 +103,7 @@ public List findAllCabinetsByBuildingAndFloor(String building, Integer */ public Cabinet getCabinetForUpdate(Long cabinetId) { log.debug("Called getCabinetForUpdate: {}", cabinetId); - return cabinetRepository.findByIdForUpdate(cabinetId) + return cabinetRepository.findByCabinetIdForUpdate(cabinetId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } @@ -162,7 +129,7 @@ public Cabinet getCabinet(Long cabinetId) { */ public Cabinet getLentCabinetByUserId(Long userId) { log.debug("Called getLentCabinetByUserId: {}", userId); - return cabinetRepository.findLentCabinetByUserId(userId) + return cabinetRepository.findByUserIdAndEndedAtIsNull(userId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } @@ -175,27 +142,17 @@ public Cabinet getLentCabinetByUserId(Long userId) { */ public Cabinet getClubCabinet(Long cabinetId) { log.debug("Called getClubCabinet: {}", cabinetId); - Cabinet cabinet = getCabinet(cabinetId); + Cabinet cabinet = cabinetRepository.findById(cabinetId) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); if (!cabinet.isLentType(LentType.CLUB)) { throw new ServiceException(ExceptionStatus.NOT_FOUND_CABINET); } return cabinet; } - /** - * 사물함 ID로 위치(빌딩, 층, 섹션) 정보를 찾습니다. - * - * @param cabinetId 사물함 ID - * @return 위치 엔티티 - * @throws ServiceException 사물함을 찾을 수 없는 경우 - */ - public Location getLocation(Long cabinetId) { - log.debug("Called getLocation: {}", cabinetId); - return cabinetRepository.findLocationById(cabinetId) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); - } - - public List findAllCabinetsByBuilding(String building) { - return cabinetRepository.findAllCabinetsByBuilding(building); + public List findPendingCabinets( + String building, LentType lentType, List cabinetStatuses) { + return cabinetRepository.findAllByBuildingAndLentTypeNotAndStatusIn( + building, lentType, cabinetStatuses); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index cd2bbaec8..6319620ac 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -6,10 +6,8 @@ import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.cabinet.domain.Location; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; @@ -19,95 +17,71 @@ @Repository public interface CabinetRepository extends JpaRepository, CabinetComplexRepository { - @Lock(LockModeType.PESSIMISTIC_WRITE) - @Query("SELECT c " - + "FROM Cabinet c " - + "WHERE c.cabinetId = :cabinetId") - Optional findByIdForUpdate(@Param("cabinetId") Long cabinetId); + /** + * 모든 빌딩을 조회한다. + * + * @return 빌딩 {@link List} + */ + @Query("SELECT DISTINCT p.location.building " + + "FROM CabinetPlace p ") + List findAllBuildings(); + /** + * 빌딩의 모든 층을 조회한다. + * + * @param building 빌딩 + * @return 층 {@link List} + */ @Query("SELECT DISTINCT p.location.floor " - + "FROM Cabinet c " - + "JOIN c.cabinetPlace p " + + "FROM CabinetPlace p " + "WHERE p.location.building = :building") List findAllFloorsByBuilding(@Param("building") String building); - @Query("SELECT DISTINCT p.location.building " - + "FROM Cabinet c " - + "JOIN c.cabinetPlace p") - List findAllBuildings(); - - @Query("SELECT DISTINCT p.location.section " - + "FROM Cabinet c " - + "JOIN c.cabinetPlace p " - + "WHERE p.location.building = :building AND p.location.floor = :floor") - List findAllSectionsByBuildingAndFloor( - @Param("building") String building, - @Param("floor") Integer floor); - - @Query("SELECT p.location " + /** + * cabinetId로 사물함을 조회한다.(조회 이후 업데이트를 위해 X Lock을 건다.) + * + * @param cabinetId 사물함 ID + * @return 사물함 {@link Optional} + */ + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("SELECT c " + "FROM Cabinet c " - + "JOIN c.cabinetPlace p " + "WHERE c.cabinetId = :cabinetId") - Optional findLocationById(@Param("cabinetId") Long cabinetId); - - - @Query(value = "SELECT AUTO_INCREMENT " - + "FROM information_schema.TABLES " - + "WHERE TABLE_SCHEMA = (SELECT DATABASE()) AND TABLE_NAME = 'cabinet'", - nativeQuery = true) - Optional getNextCabinetId(); - + Optional findByCabinetIdForUpdate(@Param("cabinetId") Long cabinetId); + + /** + * userId로 현재 대여 중인 사물함을 조회한다. + * + * @param userId 사용자 ID + * @return 사물함 {@link Optional} + */ @Query("SELECT c " + "FROM Cabinet c " + "LEFT JOIN LentHistory lh ON c.cabinetId = lh.cabinetId " + "LEFT JOIN User u ON u.userId = lh.userId " + "WHERE u.userId = :userId AND lh.endedAt IS NULL") - Optional findLentCabinetByUserId(@Param("userId") Long userId); - - @Query("SELECT c " + - "FROM Cabinet c " + - "WHERE c.lentType = :lentType") - Page findPaginationByLentType(@Param("lentType") LentType lentType, - Pageable pageable); + Optional findByUserIdAndEndedAtIsNull(@Param("userId") Long userId); - @Query("SELECT c " + - "FROM Cabinet c " + - "WHERE c.status = :status") - Page findPaginationByStatus(@Param("status") CabinetStatus status, - Pageable pageable); + Page findPaginationByLentType(@Param("lentType") LentType lentType, Pageable pageable); - @Query("SELECT c " + - "FROM Cabinet c " + - "WHERE c.visibleNum = :visibleNum") - Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, - Pageable pageable); + Page findPaginationByStatus(@Param("status") CabinetStatus status, Pageable pageable); - @Query("SELECT c " + - "FROM Cabinet c " + - "WHERE c.cabinetPlace.location = :location") - List findAllCabinetsByLocation(@Param("location") Location location); + Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, Pageable pageable); - @EntityGraph(attributePaths = {"cabinetPlace"}) - @Query("SELECT DISTINCT c, lh, u " + - "FROM Cabinet c " + - "JOIN c.lentHistories lh ON lh.cabinetId = c.cabinetId " + - "JOIN lh.user u ON lh.userId = u.userId " + - "WHERE c.cabinetPlace.location.building = :building AND c.cabinetPlace.location.floor = :floor " - + - "AND lh.endedAt IS NULL") - List findCabinetActiveLentHistoryUserListByBuildingAndFloor( - @Param("building") String building, @Param("floor") Integer floor); - - @EntityGraph(attributePaths = {"cabinetPlace"}) - @Query("SELECT c " + - "FROM Cabinet c " + - "WHERE c.cabinetPlace.location.building = :building AND c.cabinetPlace.location.floor = :floor") - List findAllByBuildingAndFloor( + @Query("SELECT c " + + "FROM Cabinet c " + + "JOIN FETCH c.cabinetPlace p " + + "WHERE p.location.building = :building AND p.location.floor = :floor " + + "ORDER BY c.visibleNum ASC") + List findAllByBuildingAndFloorOrderByVisibleNum( @Param("building") String building, @Param("floor") Integer floor); - @EntityGraph(attributePaths = {"cabinetPlace"}) - @Query("SELECT c " + - "FROM Cabinet c " + - "WHERE c.cabinetPlace.location.building = :building") - List findAllCabinetsByBuilding(@Param("building") String building); + @Query("SELECT c " + + "FROM Cabinet c " + + "JOIN FETCH c.cabinetPlace p " + + "WHERE p.location.building = :building " + + "AND c.lentType <> :lentType " + + "AND c.status IN (:status)") + List findAllByBuildingAndLentTypeNotAndStatusIn(@Param("building") String building, + @Param("lentType") LentType lentType, @Param("status") List status); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index 1c1c19a89..769cce86d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -50,11 +50,6 @@ public interface CabinetFacadeService { */ List getCabinetsPerSection(String building, Integer floor); - List getCabinetsPerSectionRefactor(String building, - Integer floor); - - List getCabinetsPerSectionDSL(String building, Integer floor); - /** * 사물함의 상태 메모를 업데이트합니다. * @@ -95,15 +90,6 @@ List getCabinetsPerSectionRefactor(String buildin */ void updateCabinetBundleStatus(CabinetStatusRequestDto cabinetStatusRequestDto); -// /** -// * 사물함의 대여 타입을 업데이트합니다. -// * -// * @param cabinetIds 사물함 ID 리스트 -// * @param lentType 변경할 대여 타입 -// */ -// void updateCabinetBundleLentType(List cabinetIds, LentType lentType); - - /** * 대여 타입에 따른 사물함 페이지네이션을 가져옵니다. * @@ -158,6 +144,6 @@ LentHistoryPaginationDto getCabinetLentHistoriesPagination(Long cabinetId, * * @return 오픈 예정인 사물함 정보 */ - CabinetPendingResponseDto getPendingCabinets(); + CabinetPendingResponseDto getPendingCabinets(String building); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index 3c432cf41..76a671c5a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -1,5 +1,7 @@ package org.ftclub.cabinet.cabinet.service; +import java.time.LocalDateTime; +import java.time.LocalTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -23,18 +25,18 @@ import java.util.*; import java.util.stream.Collectors; -import java.util.stream.IntStream; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toMap; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; @Service @RequiredArgsConstructor @Log4j2 public class CabinetFacadeServiceImpl implements CabinetFacadeService { - private static final String BUILDING_SAEROM = "새롬관"; private final CabinetService cabinetService; private final CabinetOptionalFetcher cabinetOptionalFetcher; private final LentOptionalFetcher lentOptionalFetcher; @@ -70,13 +72,9 @@ public List getBuildingFloorsResponse() { public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { log.debug("getCabinetInfo"); List lentDtos = new ArrayList<>(); - List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( - cabinetId); + List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId(cabinetId); if (lentHistories.isEmpty()) { -// ArrayList users = ticketingSharedCabinet.findUsersInSessionByCabinetIdFromRedis( -// cabinetId); - ArrayList users = lentRedis.getUserIdsByCabinetIdInRedis( - cabinetId.toString()); + ArrayList users = lentRedis.getUserIdsByCabinetIdInRedis(cabinetId.toString()); for (String user : users) { String userName = userOptionalFetcher.findUser(Long.valueOf(user)).getName(); lentDtos.add(new LentDto(Long.valueOf(user), userName, null, null, null)); @@ -100,153 +98,51 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfoByVisibleNum(Integer visi PageRequest page = PageRequest.of(0, Integer.MAX_VALUE); Page allCabinetsByVisibleNum = cabinetOptionalFetcher.findPaginationByVisibleNum( visibleNum, page); - List cabinetsSimple = allCabinetsByVisibleNum.stream().map( - cabinetMapper::toCabinetSimpleDto).collect(Collectors.toList()); - return new CabinetSimplePaginationDto(cabinetsSimple, + List cabinetsSimpleDtoList = allCabinetsByVisibleNum.stream() + .map(cabinetMapper::toCabinetSimpleDto).collect(Collectors.toList()); + return new CabinetSimplePaginationDto(cabinetsSimpleDtoList, allCabinetsByVisibleNum.getTotalElements()); } - /** - * {@inheritDoc} - */ - @Override - @Transactional(readOnly = true) - public List getCabinetsPerSection(String building, - Integer floor) { - log.debug("getCabinetsPerSection"); - List currentLentCabinets = cabinetOptionalFetcher.findCabinetsActiveLentHistoriesByBuildingAndFloor( - building, floor); - List allCabinetsByBuildingAndFloor = cabinetOptionalFetcher.findAllCabinetsByBuildingAndFloor( - building, floor); - - Map> cabinetLentHistories = currentLentCabinets.stream(). - collect(groupingBy(ActiveCabinetInfoEntities::getCabinet, - mapping(ActiveCabinetInfoEntities::getLentHistory, - Collectors.toList()))); - - Map> cabinetPreviewsBySection = new HashMap<>(); - cabinetLentHistories.forEach((cabinet, lentHistories) -> { - String section = cabinet.getCabinetPlace().getLocation().getSection(); - CabinetPreviewDto preview = createCabinetPreviewDto(cabinet, lentHistories); - if (cabinetPreviewsBySection.containsKey(section)) { - cabinetPreviewsBySection.get(section).add(preview); - } else { - List previews = new ArrayList<>(); - previews.add(preview); - cabinetPreviewsBySection.put(section, previews); - } - }); - allCabinetsByBuildingAndFloor.forEach(cabinet -> { - if (!cabinetLentHistories.containsKey(cabinet)) { - String section = cabinet.getCabinetPlace().getLocation().getSection(); - CabinetPreviewDto preview = createCabinetPreviewDto(cabinet, - Collections.emptyList()); - if (cabinetPreviewsBySection.containsKey(section)) { - cabinetPreviewsBySection.get(section).add(preview); - } else { - List previews = new ArrayList<>(); - previews.add(preview); - cabinetPreviewsBySection.put(section, previews); - } - } - }); - cabinetPreviewsBySection.values().forEach(cabinetList -> cabinetList.sort( - Comparator.comparing(CabinetPreviewDto::getVisibleNum))); - return cabinetPreviewsBySection.entrySet().stream() - .sorted(Comparator.comparing(entry -> entry.getValue().get(0).getVisibleNum())) - .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), - entry.getValue())) - .collect(Collectors.toList()); - } - - @Override - public List getCabinetsPerSectionRefactor(String building, - Integer floor) { - List cabinets = cabinetOptionalFetcher.findAllCabinetsByBuildingAndFloor(building, - floor); - Map> map = new HashMap<>(); - cabinets.forEach(cabinet -> { - List lentHistories = lentOptionalFetcher - .findActiveLentByCabinetIdWithUser(cabinet.getCabinetId()); - String section = cabinet.getCabinetPlace().getLocation().getSection(); - if (map.containsKey(section)) { - map.get(section) - .add(cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), - lentHistories.isEmpty() ? null - : lentHistories.get(0).getUser().getName())); - } else { - List cabinetPreviewDtoList = new ArrayList<>(); - cabinetPreviewDtoList.add( - cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), - lentHistories.isEmpty() ? null - : lentHistories.get(0).getUser().getName())); - map.put(section, cabinetPreviewDtoList); - } - }); - map.forEach( - (key, value) -> value.sort(Comparator.comparing(CabinetPreviewDto::getVisibleNum))); - return map.entrySet().stream() - .sorted(Comparator.comparing(entry -> entry.getValue().get(0).getVisibleNum())) - .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), - entry.getValue())) - .collect(Collectors.toList()); - } - - private CabinetPreviewDto createCabinetPreviewDto(Cabinet cabinet, - List lentHistories) { + private CabinetPreviewDto createCabinetPreviewDto(Cabinet cabinet, List lentHistories) { String lentUserName = null; - if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { + if (cabinet.getTitle() != null && !cabinet.getTitle().isEmpty()) { + // 12/2 패치 : 개인 사물함도 타이틀이 있을 경우 타이틀을 노출합니다. + lentUserName = cabinet.getTitle(); + } + else if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { lentUserName = lentHistories.get(0).getUser().getName(); + } return cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), lentUserName); } + /** + * {@inheritDoc} + */ @Override - public List getCabinetsPerSectionDSL(String building, + public List getCabinetsPerSection(String building, Integer floor) { log.debug("getCabinetsPerSection"); - List currentLentCabinets = cabinetOptionalFetcher.findCabinetsActiveLentHistoriesByBuildingAndFloor2( - building, floor); - List allCabinetsByBuildingAndFloor = cabinetOptionalFetcher.findAllCabinetsByBuildingAndFloor( - building, floor); - + List currentLentCabinets = cabinetOptionalFetcher + .findCabinetsActiveLentHistoriesByBuildingAndFloor(building, floor); Map> cabinetLentHistories = currentLentCabinets.stream(). collect(groupingBy(ActiveCabinetInfoEntities::getCabinet, mapping(ActiveCabinetInfoEntities::getLentHistory, Collectors.toList()))); - Map> cabinetPreviewsBySection = new HashMap<>(); - cabinetLentHistories.forEach((cabinet, lentHistories) -> { + List allCabinetsOnSection = + cabinetOptionalFetcher.findAllCabinetsByBuildingAndFloor(building, floor); + Map> cabinetPreviewsBySection = new LinkedHashMap<>(); + allCabinetsOnSection.forEach(cabinet -> { String section = cabinet.getCabinetPlace().getLocation().getSection(); + List lentHistories = + cabinetLentHistories.getOrDefault(cabinet, Collections.emptyList()); CabinetPreviewDto preview = createCabinetPreviewDto(cabinet, lentHistories); - if (cabinetPreviewsBySection.containsKey(section)) { - cabinetPreviewsBySection.get(section).add(preview); - } else { - List previews = new ArrayList<>(); - previews.add(preview); - cabinetPreviewsBySection.put(section, previews); - } + cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()).add(preview); }); - allCabinetsByBuildingAndFloor.forEach(cabinet -> { - if (!cabinetLentHistories.containsKey(cabinet)) { - String section = cabinet.getCabinetPlace().getLocation().getSection(); - CabinetPreviewDto preview = createCabinetPreviewDto(cabinet, - Collections.emptyList()); - if (cabinetPreviewsBySection.containsKey(section)) { - cabinetPreviewsBySection.get(section).add(preview); - } else { - List previews = new ArrayList<>(); - previews.add(preview); - cabinetPreviewsBySection.put(section, previews); - } - } - }); - cabinetPreviewsBySection.values().forEach(cabinetList -> cabinetList.sort( - Comparator.comparing(CabinetPreviewDto::getVisibleNum))); return cabinetPreviewsBySection.entrySet().stream() - .sorted(Comparator.comparing(entry -> entry.getValue().get(0).getVisibleNum())) - .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), - entry.getValue())) + .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); } @@ -305,9 +201,8 @@ public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, PageRequest pageable = PageRequest.of(page, size); Page cabinets = cabinetOptionalFetcher.findPaginationByVisibleNum(visibleNum, pageable); - List cabinetDtos = cabinets.toList().stream() - .map((cabinet) -> cabinetMapper.toCabinetDto(cabinet)) - .collect(Collectors.toList()); + List cabinetDtos = cabinets.stream() + .map(cabinetMapper::toCabinetDto).collect(Collectors.toList()); return cabinetMapper.toCabinetPaginationDtoList(cabinetDtos, cabinets.getTotalElements()); } @@ -391,17 +286,38 @@ private List generateLentHistoryDtoList( */ @Override @Transactional - public CabinetPendingResponseDto getPendingCabinets() { + public CabinetPendingResponseDto getPendingCabinets(String building) { log.debug("getPendingCabinets"); - List allCabinets = cabinetOptionalFetcher.findAllCabinetsByBuilding(BUILDING_SAEROM); - Map> cabinetFloorMap = allCabinets.parallelStream() - .filter(cabinet -> !cabinet.isLentType(LentType.CLUB) && (cabinet.isStatus(AVAILABLE))) - .collect(groupingBy(cabinet -> cabinet.getCabinetPlace().getLocation().getFloor(), - mapping(cabinet -> cabinetMapper.toCabinetPreviewDto(cabinet, 0, null), - Collectors.toList()))); - if (cabinetFloorMap.size() != 5) { - IntStream.rangeClosed(2, 5).forEach(floor -> cabinetFloorMap.putIfAbsent(floor, new ArrayList<>())); - } + final LocalDateTime pendingLimit = LocalDateTime.of( + LocalDateTime.now().toLocalDate().minusDays(1), + LocalTime.of(13, 0, 0)); + List buildingCabinets = + cabinetOptionalFetcher.findPendingCabinets( + building, + LentType.CLUB, + List.of(CabinetStatus.AVAILABLE, CabinetStatus.PENDING)); + List cabinetIds = buildingCabinets.stream() + .map(Cabinet::getCabinetId).collect(Collectors.toList()); + Map> cabinetFloorMap = + cabinetOptionalFetcher.findAllFloorsByBuilding(building).stream() + .collect(toMap(key -> key, value -> new ArrayList<>())); + Map> lentHistoriesMap = + lentOptionalFetcher.findAllLentHistoriesByCabinetIds(cabinetIds) + .stream().collect(groupingBy(LentHistory::getCabinetId)); + buildingCabinets.forEach(cabinet -> { + Integer floor = cabinet.getCabinetPlace().getLocation().getFloor(); + if (cabinet.isStatus(AVAILABLE)) { + cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); + } + if (cabinet.isStatus(PENDING)) { + LocalDateTime latestEndedAt = lentHistoriesMap.get(cabinet.getCabinetId()).stream() + .map(LentHistory::getEndedAt) + .max(LocalDateTime::compareTo).orElse(null); + if (latestEndedAt != null && latestEndedAt.isAfter(pendingLimit)) { + cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); + } + } + }); return cabinetMapper.toCabinetPendingResponseDto(cabinetFloorMap); } diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java index 86b783c07..f5aa923fb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java +++ b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; @@ -27,7 +28,10 @@ public class FCMInitializer { @PostConstruct public void initialize() throws IOException { - Path currentPath = Paths.get("").toAbsolutePath().normalize(); + + Path currentPath = Paths.get(new ClassPathResource("/").getFile().getPath()) + .getParent().getParent().getParent().getParent().getParent() + .toAbsolutePath().normalize(); Resource resource = resourceLoader.getResource("file:" + currentPath + credentialsPath); try (InputStream inputStream = resource.getInputStream()) { FirebaseOptions options = FirebaseOptions.builder() diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java index 0321d2b50..a4865e931 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java @@ -23,6 +23,7 @@ import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.utils.DateUtil; import org.ftclub.cabinet.utils.ExceptionUtil; +import org.hibernate.annotations.BatchSize; /** * lent의 기록을 관리하기 위한 data mapper @@ -33,6 +34,7 @@ @Getter @ToString(exclude = {"user", "cabinet"}) @Log4j2 +@BatchSize(size = 200) public class LentHistory { @Id diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index ceb5cc40e..ea598a4da 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -27,17 +27,12 @@ public class LentOptionalFetcher { private final LentRepository lentRepository; private final CabinetRepository cabinetRepository; - private final CabinetOptionalFetcher cabinetExceptionHandler; + private final CabinetOptionalFetcher cabinetOptionalFetcher; private final LentRedis lentRedis; public List findAllActiveLentByCabinetId(Long cabinetId) { log.debug("Called findAllActiveLentByCabinetId: {}", cabinetId); - return lentRepository.findAllActiveLentByCabinetId(cabinetId); - } - - public List findActiveLentByCabinetIdWithUser(Long cabinetId) { - log.info("Called findActiveLentByCabinetIdWithUser: {}", cabinetId); - return lentRepository.findActiveLentHistoriesByCabinetIdWithUser(cabinetId); + return lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); } public Page findPaginationByCabinetId(Long cabinetId, PageRequest pageable) { @@ -51,46 +46,15 @@ public Page findPaginationByUserId(Long userId, PageRequest pageabl } /** - * 아직 반납하지 않은 {@link LentHistory} 중에서 user id와 cabinet id에 맞는 {@link LentHistory}를 찾습니다. - * - * @param userId 찾고 싶은 user id - * @param cabinetId 찾고 싶은 cabinet id - * @return user id와 cabinet id가 맞는 반납하지 않은 {@link LentHistory} - * @throws ServiceException NO_LENT_CABINET - */ - public LentHistory getActiveLentHistoryWithUserIdAndCabinetId(Long userId, Long cabinetId) { - log.debug("Called getActiveLentHistoryWithUserIdAndCabinetId: {}, {}", userId, cabinetId); - LentHistory ret = getActiveLentHistoryWithUserId(userId); - if (!ret.isCabinetIdEqual(cabinetId)) { - throw new ServiceException(ExceptionStatus.NO_LENT_CABINET); - } - return ret; - } - - /** - * 아직 반납하지 않은 {@link LentHistory} 중에서 cabinet id에 맞는 {@link LentHistory}를 찾습니다. - * - * @param cabinetId 찾고 싶은 cabinet id - * @return cabinet id가 맞는 반납하지 않은 {@link LentHistory} - * @throws ServiceException NO_LENT_CABINET - */ - public LentHistory getActiveLentHistoryWithCabinetId(Long cabinetId) { - log.debug("Called getActiveLentHistoryWithCabinetId: {}", cabinetId); - return lentRepository.findFirstByCabinetIdAndEndedAtIsNull(cabinetId) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NO_LENT_CABINET)); - } - - /** - * 아직 반납하지 않은 {@link LentHistory} 중에서 user id에 {@link LentHistory}를 찾습니다. + * 유저 id 로 대여 기록을 확인합니다. 없을경우, 빈 List 를 리턴합니다. 반납하지 않은 기록은 조회하지 않습니다. * - * @param userId 찾고 싶은 user id - * @return user id에 맞는 반납하지 않은 {@link LentHistory} - * @throws ServiceException NO_LENT_CABINET + * @param userId + * @param pageable + * @return */ - public LentHistory getActiveLentHistoryWithUserId(Long userId) { - log.debug("Called getActiveLentHistoryWithUserId: {}", userId); - return lentRepository.findFirstByUserIdAndEndedAtIsNull(userId) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NO_LENT_CABINET)); + public List findAllByUserIdAndEndedAtNotNull(Long userId, Pageable pageable) { + log.debug("Called findByUserId: {}", userId); + return lentRepository.findPaginationByUserIdAndEndedAtNotNull(userId, pageable).toList(); } /** @@ -103,7 +67,7 @@ public LentHistory getActiveLentHistoryWithUserId(Long userId) { */ public LentHistory getActiveLentHistoryWithUserIdForUpdate(Long userId) { log.debug("Called getActiveLentHistoryWithUserId: {}", userId); - return lentRepository.findFirstByUserIdAndEndedAtIsNullForUpdate(userId) + return lentRepository.findByUserIdAndEndedAtIsNullForUpdate(userId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NO_LENT_CABINET)); } @@ -116,37 +80,12 @@ public LentHistory getActiveLentHistoryWithUserIdForUpdate(Long userId) { */ public void checkExistedSpace(Long cabinetId) { log.debug("Called checkExistedSpace: {}", cabinetId); - Cabinet cabinet = cabinetExceptionHandler.getCabinet(cabinetId); - if (lentRepository.countCabinetActiveLent(cabinetId) == cabinet.getMaxUser()) { + Cabinet cabinet = cabinetOptionalFetcher.getCabinet(cabinetId); + if (lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId) == cabinet.getMaxUser()) { throw new ServiceException(ExceptionStatus.LENT_FULL); } } - /** - * 유저 id 로 대여 기록을 확인합니다. 없을경우, 빈 List 를 리턴합니다. - * - * @param userId 유저Id - * @param pageable - * @return - */ - public List findByUserId(Long userId, Pageable pageable) { - log.debug("Called findByUserId: {}", userId); - return lentRepository.findByUserId(userId, pageable); - } - - /** - * 유저 id 로 대여 기록을 확인합니다. 없을경우, 빈 List 를 리턴합니다. 반납하지 않은 기록은 조회하지 않습니다. - * - * @param userId - * @param pageable - * @return - */ - - public List findByUserIdAndEndedAtNotNull(Long userId, Pageable pageable) { - log.debug("Called findByUserId: {}", userId); - return lentRepository.findByUserIdAndEndedAtNotNull(userId, pageable); - } - /** * @param userId 유저 id @@ -155,40 +94,21 @@ public List findByUserIdAndEndedAtNotNull(Long userId, Pageable pag public int countUserAllLent(Long userId) { log.debug("Called countUserAllLent: {}", userId); - return lentRepository.countUserAllLent(userId); - } - - /** - * @param cabinetId 캐비넷 id - * @param pageable - * @return 캐비넷이 대여된 기록 - */ - public List findByCabinetId(Long cabinetId, Pageable pageable) { - log.debug("Called findByCabinetId: {}", cabinetId); - return lentRepository.findByCabinetId(cabinetId, pageable); + return lentRepository.countByUserId(userId); } /** * @param cabinetId 캐비넷 id * @return 캐비넷을 현재 대여 중인 기록(공유 사물함의 경우를 포함하여 리스트로 반환) */ - public List findActiveLentHistoriesByCabinetId(Long cabinetId) { + public List findAllActiveLentHistoriesByCabinetId(Long cabinetId) { log.debug("Called findByCabinetId: {}", cabinetId); - return lentRepository.findLentHistoriesByCabinetIdAndEndedAtIsNull(cabinetId); - } - - /** - * @param cabinetId 캐비넷 id - * @return 캐비넷이 대여된 총 횟수 - */ - public int countCabinetAllLent(Long cabinetId) { - log.debug("Called countCabinetAllLent: {}", cabinetId); - return lentRepository.countCabinetAllLent(cabinetId); + return lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); } public List findAllActiveLentHistories() { log.debug("Called findAllActiveLentHistories"); - return lentRepository.findAllActiveLentHistories(); + return lentRepository.findAllByEndedAtIsNull(); } /** @@ -197,7 +117,7 @@ public List findAllActiveLentHistories() { */ public Cabinet findActiveLentCabinetByUserId(Long userId) { log.debug("Called findActiveLentCabinetByUserId: {}", userId); - return cabinetRepository.findLentCabinetByUserId(userId).orElse(null); + return cabinetRepository.findByUserIdAndEndedAtIsNull(userId).orElse(null); } /** @@ -224,17 +144,7 @@ public List findUserIdsByCabinetIdFromRedis(Long cabinetId) { public List findAllOverdueLent(LocalDateTime date, Pageable pageable) { log.debug("Called findAllOverdueLent: {}", date); - return lentRepository.findAllOverdueLent(date, pageable); - } - - public Integer countCabinetAllActiveLent(Long cabinetId) { - log.debug("Called countCabinetAllActiveLent: {}", cabinetId); - return lentRepository.countCabinetAllActiveLent(cabinetId); - } - - public LocalDateTime getSessionExpiredAtFromRedis(Long cabinetId) { - log.debug("Called getSessionExpiredAtFromRedis: {}", cabinetId); - return lentRedis.getSessionExpiredAtInRedis(cabinetId); + return lentRepository.findAllOverdueLentHistories(date, pageable); } /** @@ -251,6 +161,10 @@ public List findAllActiveLentHistoriesByUserId(Long userId) { public Optional findPreviousLentHistoryByCabinetId(Long cabinetId) { log.debug("Called findPreviousLentUserNameByCabinetId: {}", cabinetId); - return lentRepository.findFirstByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc(cabinetId); + return lentRepository.findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc(cabinetId); + } + + public List findAllLentHistoriesByCabinetIds(List cabinetIds) { + return lentRepository.findAllByCabinetIdIn(cabinetIds); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index f313ec043..b0a985b9c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -20,153 +20,151 @@ public interface LentRepository extends JpaRepository { /** - * 사물함을 기준으로 아직 반납하지 않은 {@link LentHistory}중 하나를 가져옵니다. - * - * @param cabinetId 찾으려는 cabinet id - * @return 반납하지 않은 {@link LentHistory}의 {@link Optional} - */ - Optional findFirstByCabinetIdAndEndedAtIsNull(@Param("cabinetId") Long cabinetId); - - /** - * 유저를 기준으로 아직 반납하지 않은 {@link LentHistory}중 하나를 가져옵니다. + * 유저가 지금까지 빌렸던 사물함의 개수를 가져옵니다. * * @param userId 찾으려는 user id - * @return 반납하지 않은 {@link LentHistory}의 {@link Optional} + * @return 유저가 빌렸던 사물함의 개수 */ - Optional findFirstByUserIdAndEndedAtIsNull(@Param("userId") Long userId); + @Query("SELECT count(lh) " + + "FROM LentHistory lh " + + "WHERE lh.userId = :userId") + int countByUserId(@Param("userId") Long userId); /** - * 유저를 기준으로 아직 반납하지 않은 {@link LentHistory}중 하나를 가져옵니다. X Lock을 걸어서 가져옵니다. + * 유저가 빌리고 있는 사물함의 개수를 가져옵니다. * * @param userId 찾으려는 user id - * @return 반납하지 않은 {@link LentHistory}의 {@link Optional} - */ - @Lock(LockModeType.PESSIMISTIC_WRITE) - @Query("SELECT lh " + - "FROM LentHistory lh " + - "WHERE lh.userId = :userId AND lh.endedAt is null") - Optional findFirstByUserIdAndEndedAtIsNullForUpdate(@Param("userId") Long userId); - - - /** - * 유저가 지금까지 빌렸던 {@link LentHistory}들을 가져옵니다. {@link Pageable}이 적용되었습니다. - * - * @param userId 찾으려는 user id - * @param pageable pagination 정보 - * @return {@link LentHistory}들의 정보 + * @return 유저가 빌리고 있는 사물함 개수 */ - List findByUserId(@Param("userId") Long userId, Pageable pageable); + @Query("SELECT count(lh) " + + "FROM LentHistory lh " + + "WHERE lh.endedAt = null and lh.userId = :userId") + int countByUserIdAndEndedAtIsNull(@Param("userId") Long userId); /** - * 유저가 지금까지 빌렸던 {@link LentHistory}들을 가져옵니다.(현재 빌리고 반납하지 않은 기록은 표시하지 않습니다.) {@link Pageable}이 - * 적용되었습니다. + * 사물함을 빌렸던 유저의 수를 가져옵니다. * - * @param userId 찾으려는 user id - * @param pageable pagination 정보 - * @return + * @param cabinetId 찾으려는 cabinet id + * @return 사물함을 빌렸던 유저의 수 */ - List findByUserIdAndEndedAtNotNull(@Param("userId") Long userId, - Pageable pageable); + @Query("SELECT count(lh)" + + "FROM LentHistory lh " + + "WHERE lh.cabinetId = :cabinetId") + int countByCabinetId(@Param("cabinetId") Long cabinetId); /** - * 캐비넷의 {@link LentHistory}들을 가져옵니다. {@link Pageable}이 적용되었습니다. + * 사물함을 빌리고 있는 유저의 수를 가져옵니다. * * @param cabinetId 찾으려는 cabinet id - * @param pageable pagination 정보 - * @return {@link LentHistory}들의 정보 + * @return 사물함을 빌리고 있는 유저의 수 */ - List findByCabinetId(@Param("cabinetId") Long cabinetId, Pageable pageable); + @Query("SELECT count(lh) " + + "FROM LentHistory lh " + + "WHERE lh.endedAt = null and lh.cabinetId = :cabinetId") + int countByCabinetIdAndEndedAtIsNull(@Param("cabinetId") Long cabinetId); + + @Query("SELECT count(lh) " + + "FROM LentHistory lh " + + "WHERE lh.startedAt < :endDate AND lh.startedAt >= :startDate") + int countLentByTimeDuration(@Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate); + + @Query("SELECT count(lh) " + + "FROM LentHistory lh " + + "WHERE lh.endedAt < :endDate AND lh.endedAt >= :startDate") + int countReturnByTimeDuration(@Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate); /** - * 캐비넷의 대여 중인 {@link LentHistory}들을 가져옵니다.(공유 사물함의 경우를 포함하여 리스트로 반환) + * 사물함을 기준으로 아직 반납하지 않은 {@link LentHistory}중 하나를 가져옵니다. * * @param cabinetId 찾으려는 cabinet id - * @return {@link LentHistory}들의 정보 + * @return 반납하지 않은 {@link LentHistory}의 {@link Optional} */ - List findLentHistoriesByCabinetIdAndEndedAtIsNull(@Param("cabinetId") Long cabinetId); + Optional findByCabinetIdAndEndedAtIsNull(@Param("cabinetId") Long cabinetId); /*** * 사물함을 기준으로 가장 최근에 반납한 {@link LentHistory} 를 가져옵니다. * @param cabinetId 찾으려는 cabinet id * @return 반납한 {@link LentHistory}의 {@link Optional} */ - Optional findFirstByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc( + Optional findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc( @Param("cabinetId") Long cabinetId); /** - * 유저가 빌리고 있는 사물함의 개수를 가져옵니다. + * 유저를 기준으로 아직 반납하지 않은 {@link LentHistory}중 하나를 가져옵니다. * * @param userId 찾으려는 user id - * @return 유저가 빌리고 있는 사물함 개수 + * @return 반납하지 않은 {@link LentHistory}의 {@link Optional} */ - @Query("SELECT count(lh) " + - "FROM LentHistory lh " + - "WHERE lh.endedAt = null and lh.userId = :userId") - int countUserActiveLent(@Param("userId") Long userId); + Optional findByUserIdAndEndedAtIsNull(@Param("userId") Long userId); /** - * 사물함을 빌리고 있는 유저의 수를 가져옵니다. + * 유저를 기준으로 아직 반납하지 않은 {@link LentHistory}중 하나를 가져옵니다. X Lock을 걸어서 가져옵니다. * - * @param cabinetId 찾으려는 cabinet id - * @return 사물함을 빌리고 있는 유저의 수 + * @param userId 찾으려는 user id + * @return 반납하지 않은 {@link LentHistory}의 {@link Optional} */ - @Query("SELECT count(lh) " + - "FROM LentHistory lh " + - "WHERE lh.endedAt = null and lh.cabinetId = :cabinetId") - int countCabinetActiveLent(@Param("cabinetId") Long cabinetId); + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("SELECT lh " + + "FROM LentHistory lh " + + "WHERE lh.userId = :userId AND lh.endedAt is null") + Optional findByUserIdAndEndedAtIsNullForUpdate(@Param("userId") Long userId); /** - * 유저가 지금까지 빌렸던 사물함의 개수를 가져옵니다. + * 사물함의 대여기록 {@link LentHistory}들을 모두 가져옵니다. + * {@link Pageable}이 적용되었습니다. * - * @param userId 찾으려는 user id - * @return 유저가 빌렸던 사물함의 개수 + * @param cabinetId 찾으려는 cabinet id + * @param pageable pagination 정보 + * @return {@link LentHistory}의 {@link Page} */ - @Query("SELECT count(lh) " + - "FROM LentHistory lh " + - "WHERE lh.userId = :userId") - int countUserAllLent(@Param("userId") Long userId); + Page findPaginationByCabinetId( + @Param("cabinetId") Long cabinetId, Pageable pageable); /** - * 사물함을 빌렸던 유저의 수를 가져옵니다. + * 유저가 지금까지 빌렸던 {@link LentHistory}들을 모두 가져옵니다. + * {@link Pageable}이 적용되었습니다. * - * @param cabinetId 찾으려는 cabinet id - * @return 사물함을 빌렸던 유저의 수 + * @param userId 찾으려는 user id + * @param pageable pagination 정보 + * @return {@link LentHistory}의 {@link Page} */ - @Query("SELECT count(lh)" + - "FROM LentHistory lh " + - "WHERE lh.cabinetId = :cabinetId") - int countCabinetAllLent(@Param("cabinetId") Long cabinetId); + Page findPaginationByUserId(@Param("userId") Long userId, Pageable pageable); + /** + * 유저가 지금까지 빌렸던 {@link LentHistory}들을 가져옵니다.(현재 빌리고 반납하지 않은 기록은 표시하지 않습니다.) + * {@link Pageable}이 적용되었습니다. + * + * @param userId 찾으려는 user id + * @param pageable pagination 정보 + * @return 반납했던 {@link LentHistory}의 {@link Page} + */ + Page findPaginationByUserIdAndEndedAtNotNull( + @Param("userId") Long userId, Pageable pageable); /** - * 사물함을 기준으로 아직 반납하지 않은 {@link LentHistory}를 모두 가져옵니다.. + * 사물함을 기준으로 아직 반납하지 않은 {@link LentHistory}를 모두 가져옵니다. * * @param cabinetId 찾으려는 cabinet id * @return 반납하지 않은 {@link LentHistory}의 {@link List} */ - @Query("SELECT lh " + - "FROM LentHistory lh " + - "WHERE lh.cabinetId = :cabinetId and lh.endedAt is null") - List findAllActiveLentByCabinetId(@Param("cabinetId") Long cabinetId); - - @Query("SELECT lh " + - "FROM LentHistory lh " + - "LEFT JOIN FETCH lh.user " + - "WHERE lh.cabinetId = :cabinetId and lh.endedAt is null") - List findActiveLentHistoriesByCabinetIdWithUser( - @Param("cabinetId") Long cabinetId); + List findAllByCabinetIdAndEndedAtIsNull(@Param("cabinetId") Long cabinetId); - @Query("SELECT lh " + - "FROM LentHistory lh " + - "WHERE lh.cabinetId = :cabinetId") - Page findPaginationByCabinetId(@Param("cabinetId") Long cabinetId, - Pageable pageable); + /** + * 대여 중인 사물함을 모두 가져옵니다. + * + * @return 연체되어 있는 {@link LentHistory}의 {@link List} + */ + List findAllByEndedAtIsNull(); - @Query("SELECT lh " + - "FROM LentHistory lh " + - "WHERE lh.userId = :userId") - Page findPaginationByUserId(@Param("userId") Long userId, - Pageable pageable); + /** + * 여러 사물함들의 대여 기록을 모두 가져옵니다. + * + * @param cabinetIds 조회하고자 하는 사물함의 Id {@link List} + * @return 조회하고자 하는 사물함들 {@link LentHistory}의 {@link List} + */ + List findAllByCabinetIdIn(List cabinetIds); /** * 연체되어 있는 사물함을 모두 가져옵니다. @@ -174,35 +172,11 @@ Page findPaginationByUserId(@Param("userId") Long userId, * @param date 연체의 기준 날짜/시간 * @return 연체되어 있는 {@link LentHistory}의 {@link List} */ - @Query("SELECT lh " + - "FROM LentHistory lh " + - "WHERE lh.expiredAt < :date " + - "AND YEAR(lh.expiredAt) <> 9999 " + - "AND lh.endedAt is null " + - "ORDER BY lh.expiredAt ASC") - List findAllOverdueLent(@Param("date") LocalDateTime date, Pageable pageable); - - @Query("SELECT count(lh) " + - "FROM LentHistory lh " + - "WHERE lh.startedAt < :endDate AND lh.startedAt >= :startDate") - Integer countLentByTimeDuration(@Param("startDate") LocalDateTime startDate, - @Param("endDate") LocalDateTime endDate); - - @Query("SELECT count(lh) " + - "FROM LentHistory lh " + - "WHERE lh.endedAt < :endDate AND lh.endedAt >= :startDate") - Integer countReturnByTimeDuration(@Param("startDate") LocalDateTime startDate, - @Param("endDate") LocalDateTime endDate); - - @Query("SELECT count(lh) " + - "FROM LentHistory lh " + - "WHERE lh.cabinetId = :cabinetId AND lh.endedAt IS NULL") - Integer countCabinetAllActiveLent(@Param("cabinetId") Long cabinetId); - - @Query("SELECT lh " + - "FROM LentHistory lh " + - "WHERE lh.endedAt IS NULL") - List findAllActiveLentHistories(); + @Query("SELECT lh " + + "FROM LentHistory lh " + + "WHERE lh.expiredAt < :date AND lh.endedAt is null " + + "ORDER BY lh.expiredAt ASC") + List findAllOverdueLentHistories(@Param("date") LocalDateTime date, Pageable pageable); @Query("SELECT lh " + "FROM LentHistory lh " @@ -211,5 +185,4 @@ Integer countReturnByTimeDuration(@Param("startDate") LocalDateTime startDate, + "IN (SELECT lh2.cabinetId " + "FROM LentHistory lh2 WHERE lh2.userId = :userId AND lh2.endedAt IS NULL)") List findAllActiveLentHistoriesByUserId(@Param("userId") Long userId); - } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index e0a440e3e..ae9ab6466 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -97,17 +97,6 @@ public interface LentFacadeService { */ LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size); - /** - * 페이지네이션을 적용해서 지금까지 사물함의 모든 기록을 가져옵니다. - * - * @param cabinetId 찾으려는 cabinet id - * @param page 페이지네이션 offset - * @param size 페이지네이션 size - * @return {@link LentHistoryPaginationDto} - */ - LentHistoryPaginationDto getAllCabinetLentHistories(Long cabinetId, Integer page, - Integer size); - MyCabinetResponseDto getMyLentInfo(UserSessionDto user); /** diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java index 1722cc87f..342532941 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java @@ -13,7 +13,6 @@ import org.ftclub.cabinet.dto.CabinetInfoRequestDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.dto.LentEndMemoDto; -import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.dto.MyCabinetResponseDto; @@ -70,18 +69,6 @@ public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer pag lentHistories.getTotalElements()); } - @Override - public LentHistoryPaginationDto getAllCabinetLentHistories(Long cabinetId, Integer page, - Integer size) { - log.debug("Called getAllCabinetLentHistories: {}", cabinetId); - cabinetOptionalFetcher.getCabinet(cabinetId); - PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); - Page lentHistories = lentOptionalFetcher.findPaginationByCabinetId(cabinetId, - pageable); - return generateLentHistoryPaginationDto(lentHistories.toList(), - lentHistories.getTotalElements()); - } - @Override public List getLentDtoList(Long cabinetId) { log.debug("Called getLentDtoList: {}", cabinetId); @@ -131,9 +118,8 @@ public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, log.debug("Called getMyLentLog: {}", user.getName()); PageRequest pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "startedAt")); - List myLentHistories = lentOptionalFetcher.findByUserIdAndEndedAtNotNull( - user.getUserId(), - pageable); + List myLentHistories = lentOptionalFetcher.findAllByUserIdAndEndedAtNotNull( + user.getUserId(), pageable); List result = myLentHistories.stream() .map(lentHistory -> lentMapper.toLentHistoryDto( lentHistory, @@ -146,9 +132,7 @@ public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, private LentHistoryPaginationDto generateLentHistoryPaginationDto( List lentHistories, Long totalLength) { List lentHistoryDto = lentHistories.stream() - .map(e -> lentMapper.toLentHistoryDto(e, - e.getUser(), - e.getCabinet())) + .map(e -> lentMapper.toLentHistoryDto(e, e.getUser(), e.getCabinet())) .collect(Collectors.toList()); return new LentHistoryPaginationDto(lentHistoryDto, totalLength); } @@ -270,7 +254,6 @@ public void updateCabinetInfo(UserSessionDto user, cabinetInfoRequestDto.getMemo()); } - @Override public void assignLent(Long userId, Long cabinetId) { lentService.assignLent(userId, cabinetId); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java index a6c052541..17ad486a5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.lent.service; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -54,7 +55,7 @@ public void startLentCabinet(Long userId, Long cabinetId) { LocalDateTime now = LocalDateTime.now(); Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); User user = userOptionalFetcher.getUser(userId); - int userActiveLentCount = lentRepository.countUserActiveLent(userId); + int userActiveLentCount = lentRepository.countByUserIdAndEndedAtIsNull(userId); List userActiveBanList = banHistoryRepository.findUserActiveBanList(userId, now); // 대여 가능한 유저인지 확인 @@ -78,7 +79,7 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) LocalDateTime now = LocalDateTime.now(); Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); User user = userOptionalFetcher.getUser(userId); - int userActiveLentCount = lentRepository.countUserActiveLent(userId); + int userActiveLentCount = lentRepository.countByUserIdAndEndedAtIsNull(userId); List userActiveBanList = banHistoryRepository.findUserActiveBanList(userId, now); // 대여 가능한 유저인지 확인 @@ -126,8 +127,8 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { @Override public void endLentCabinet(Long userId) { log.debug("Called endLentCabinet: {}", userId); - List allActiveLentHistoriesByUserId = lentRepository.findAllActiveLentHistoriesByUserId( - userId); + List allActiveLentHistoriesByUserId = + lentOptionalFetcher.findAllActiveLentHistoriesByUserId(userId); LentHistory lentHistory = returnCabinetByUserId(userId); Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(lentHistory.getCabinetId()); // cabinetType도 인자로 전달하면 좋을 거 같습니다 (공유사물함 3일이내 반납 페널티) @@ -135,15 +136,14 @@ public void endLentCabinet(Long userId) { lentHistory.getEndedAt(), lentHistory.getExpiredAt()); // 공유 사물함 반납 시 남은 대여일 수 차감 (원래 남은 대여일 수 * (남은 인원 / 원래 있던 인원)) if (cabinet.getLentType().equals(LentType.SHARE)) { - Double usersInShareCabinet = lentRepository.countCabinetAllActiveLent( - cabinet.getCabinetId()).doubleValue(); - Double daysUntilExpiration = + double usersInShareCabinet = lentRepository.countByCabinetIdAndEndedAtIsNull( + cabinet.getCabinetId()); + double daysUntilExpiration = lentHistory.getDaysUntilExpiration(LocalDateTime.now()).doubleValue() * -1.0; - Double secondsRemaining = + double secondsRemaining = daysUntilExpiration * (usersInShareCabinet / (usersInShareCabinet + 1.0) * 24.0 * 60.0 * 60.0); - LocalDateTime expiredAt = LocalDateTime.now().plusSeconds( - secondsRemaining.longValue()) + LocalDateTime expiredAt = LocalDateTime.now().plusSeconds(Math.round(secondsRemaining)) .withHour(23) .withMinute(59) .withSecond(0); // 23:59:59 @@ -151,8 +151,7 @@ public void endLentCabinet(Long userId) { e.setExpiredAt(expiredAt); }); } - lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), - lentHistory.getUser().getName()); + lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), lentHistory.getUser().getName()); } @Override @@ -207,7 +206,7 @@ private LentHistory returnCabinetByUserId(Long userId) { LentHistory lentHistory = lentOptionalFetcher.getActiveLentHistoryWithUserIdForUpdate( userId); Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(lentHistory.getCabinetId()); - int activeLentCount = lentRepository.countCabinetActiveLent(lentHistory.getCabinetId()); + int activeLentCount = lentRepository.countByCabinetIdAndEndedAtIsNull(lentHistory.getCabinetId()); lentHistory.endLent(LocalDateTime.now()); cabinet.specifyStatusByUserCount(activeLentCount - 1); // policy로 빠질만한 부분인듯? if (activeLentCount - 1 == 0) { diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java index 114cddd1f..db9f7d312 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java @@ -42,7 +42,7 @@ public List getCabinetsCountOnAllFloors() { List availableCabinetsId = statisticsRepository.getAvailableCabinetsId(floor); Integer unused = 0; for (Long cabinetId : availableCabinetsId) { - if (lentRepository.countCabinetActiveLent(cabinetId) > 0) { + if (lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId) > 0) { used++; } else { unused++; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java index 073925f09..8d5231477 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java @@ -119,7 +119,7 @@ public void useLentExtension(Long userId, String username) { } Cabinet cabinet = cabinetOptionalFetcher.getLentCabinetByUserId(userId); - List activeLentHistories = lentOptionalFetcher.findActiveLentHistoriesByCabinetId( + List activeLentHistories = lentOptionalFetcher.findAllActiveLentHistoriesByCabinetId( cabinet.getCabinetId()); lentExtensionPolicy.verifyLentExtension(cabinet, activeLentHistories); diff --git a/backend/src/test/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceUnitTest.java index 1a8733caf..20f925ecd 100644 --- a/backend/src/test/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceUnitTest.java @@ -292,76 +292,76 @@ private ArrayList generateFloors(int limit) { assertEquals(0, result.getTotalLength()); } - @Test - @Disabled - @DisplayName("성공: building, floor로 캐비넷 section 과 정보 조회 - 결과 3개") - void 성공_simple_getCabinetsPerSection() { - String building = "새롬관"; - Integer floor = 2; - - List sectionList = List.of("1클러스터끝", "오아시스", "2클러스터끝"); - given(cabinetOptionalFetcher.findAllSectionsByBuildingAndFloor(building, floor)) - .willReturn(sectionList); - - CabinetPreviewDto cabinetPreviewDto1 = mock(CabinetPreviewDto.class); - CabinetPreviewDto cabinetPreviewDto2 = mock(CabinetPreviewDto.class); - CabinetPreviewDto cabinetPreviewDto3 = mock(CabinetPreviewDto.class); - List cabinetPreviewDtoList1 = new ArrayList<>( - List.of(cabinetPreviewDto1, cabinetPreviewDto2, cabinetPreviewDto3)); - List cabinetPreviewDtoList2 = new ArrayList<>( - List.of(cabinetPreviewDto2, cabinetPreviewDto1, cabinetPreviewDto3)); - List cabinetPreviewDtoList3 = new ArrayList<>( - List.of(cabinetPreviewDto3, cabinetPreviewDto2, cabinetPreviewDto1)); -/* -혹시 몰라서 작성 해놓은 mock - CabinetsPerSectionResponseDto mock1 = mock(CabinetsPerSectionResponseDto.class); - CabinetsPerSectionResponseDto mock2 = mock(CabinetsPerSectionResponseDto.class); - CabinetsPerSectionResponseDto mock3 = mock(CabinetsPerSectionResponseDto.class); - - given(mock1.getCabinets()).willReturn(cabinetPreviewDtoList1); - given(mock2.getCabinets()).willReturn(cabinetPreviewDtoList2); - given(mock3.getCabinets()).willReturn(cabinetPreviewDtoList3); - - given(mock1.getSection()).willReturn(sectionList.get(0)); - given(mock2.getSection()).willReturn(sectionList.get(1)); - given(mock3.getSection()).willReturn(sectionList.get(2)); -*/ - CabinetsPerSectionResponseDto cabinetsPerSectionResponseDto1 = new CabinetsPerSectionResponseDto( - sectionList.get(0), - cabinetPreviewDtoList1); - CabinetsPerSectionResponseDto cabinetsPerSectionResponseDto2 = new CabinetsPerSectionResponseDto( - sectionList.get(1), - cabinetPreviewDtoList2); - CabinetsPerSectionResponseDto cabinetsPerSectionResponseDto3 = new CabinetsPerSectionResponseDto( - sectionList.get(2), - cabinetPreviewDtoList3); - - given(cabinetMapper.toCabinetsPerSectionResponseDto(sectionList.get(0), - cabinetPreviewDtoList1)).willReturn(cabinetsPerSectionResponseDto1); - given(cabinetMapper.toCabinetsPerSectionResponseDto(sectionList.get(1), - cabinetPreviewDtoList2)).willReturn(cabinetsPerSectionResponseDto2); - given(cabinetMapper.toCabinetsPerSectionResponseDto(sectionList.get(2), - cabinetPreviewDtoList3)).willReturn(cabinetsPerSectionResponseDto3); - - //when - List result = cabinetFacadeService.getCabinetsPerSection( - building, floor); - - then(cabinetOptionalFetcher).should().findAllSectionsByBuildingAndFloor(building, floor); - then(cabinetMapper).should(times(3)).toCabinetsPerSectionResponseDto(any(), any()); - /* - then(cabinetMapper).should().toCabinetsPerSectionResponseDto(sectionList.get(0), - cabinetPreviewDtoList1); - then(cabinetMapper).should().toCabinetsPerSectionResponseDto(sectionList.get(1), - cabinetPreviewDtoList2); - then(cabinetMapper).should().toCabinetsPerSectionResponseDto(sectionList.get(2), - cabinetPreviewDtoList3); -*/ - assertEquals(3, result.size()); - assertEquals(sectionList.get(0), result.get(0).getSection()); - assertEquals(sectionList.get(1), result.get(1).getSection()); - assertEquals(sectionList.get(2), result.get(2).getSection()); - } +// @Test +// @Disabled +// @DisplayName("성공: building, floor로 캐비넷 section 과 정보 조회 - 결과 3개") +// void 성공_simple_getCabinetsPerSection() { +// String building = "새롬관"; +// Integer floor = 2; +// +// List sectionList = List.of("1클러스터끝", "오아시스", "2클러스터끝"); +// given(cabinetOptionalFetcher.findAllSectionsByBuildingAndFloor(building, floor)) +// .willReturn(sectionList); +// +// CabinetPreviewDto cabinetPreviewDto1 = mock(CabinetPreviewDto.class); +// CabinetPreviewDto cabinetPreviewDto2 = mock(CabinetPreviewDto.class); +// CabinetPreviewDto cabinetPreviewDto3 = mock(CabinetPreviewDto.class); +// List cabinetPreviewDtoList1 = new ArrayList<>( +// List.of(cabinetPreviewDto1, cabinetPreviewDto2, cabinetPreviewDto3)); +// List cabinetPreviewDtoList2 = new ArrayList<>( +// List.of(cabinetPreviewDto2, cabinetPreviewDto1, cabinetPreviewDto3)); +// List cabinetPreviewDtoList3 = new ArrayList<>( +// List.of(cabinetPreviewDto3, cabinetPreviewDto2, cabinetPreviewDto1)); +///* +//혹시 몰라서 작성 해놓은 mock +// CabinetsPerSectionResponseDto mock1 = mock(CabinetsPerSectionResponseDto.class); +// CabinetsPerSectionResponseDto mock2 = mock(CabinetsPerSectionResponseDto.class); +// CabinetsPerSectionResponseDto mock3 = mock(CabinetsPerSectionResponseDto.class); +// +// given(mock1.getCabinets()).willReturn(cabinetPreviewDtoList1); +// given(mock2.getCabinets()).willReturn(cabinetPreviewDtoList2); +// given(mock3.getCabinets()).willReturn(cabinetPreviewDtoList3); +// +// given(mock1.getSection()).willReturn(sectionList.get(0)); +// given(mock2.getSection()).willReturn(sectionList.get(1)); +// given(mock3.getSection()).willReturn(sectionList.get(2)); +//*/ +// CabinetsPerSectionResponseDto cabinetsPerSectionResponseDto1 = new CabinetsPerSectionResponseDto( +// sectionList.get(0), +// cabinetPreviewDtoList1); +// CabinetsPerSectionResponseDto cabinetsPerSectionResponseDto2 = new CabinetsPerSectionResponseDto( +// sectionList.get(1), +// cabinetPreviewDtoList2); +// CabinetsPerSectionResponseDto cabinetsPerSectionResponseDto3 = new CabinetsPerSectionResponseDto( +// sectionList.get(2), +// cabinetPreviewDtoList3); +// +// given(cabinetMapper.toCabinetsPerSectionResponseDto(sectionList.get(0), +// cabinetPreviewDtoList1)).willReturn(cabinetsPerSectionResponseDto1); +// given(cabinetMapper.toCabinetsPerSectionResponseDto(sectionList.get(1), +// cabinetPreviewDtoList2)).willReturn(cabinetsPerSectionResponseDto2); +// given(cabinetMapper.toCabinetsPerSectionResponseDto(sectionList.get(2), +// cabinetPreviewDtoList3)).willReturn(cabinetsPerSectionResponseDto3); +// +// //when +// List result = cabinetFacadeService.getCabinetsPerSection( +// building, floor); +// +// then(cabinetOptionalFetcher).should().findAllSectionsByBuildingAndFloor(building, floor); +// then(cabinetMapper).should(times(3)).toCabinetsPerSectionResponseDto(any(), any()); +// /* +// then(cabinetMapper).should().toCabinetsPerSectionResponseDto(sectionList.get(0), +// cabinetPreviewDtoList1); +// then(cabinetMapper).should().toCabinetsPerSectionResponseDto(sectionList.get(1), +// cabinetPreviewDtoList2); +// then(cabinetMapper).should().toCabinetsPerSectionResponseDto(sectionList.get(2), +// cabinetPreviewDtoList3); +//*/ +// assertEquals(3, result.size()); +// assertEquals(sectionList.get(0), result.get(0).getSection()); +// assertEquals(sectionList.get(1), result.get(1).getSection()); +// assertEquals(sectionList.get(2), result.get(2).getSection()); +// } @Test @DisplayName("성공: 빌딩, 층수로 section 별 캐비넷 정보 가져오기") @@ -372,6 +372,8 @@ private ArrayList generateFloors(int limit) { String section1 = "1클러스터끝"; String section2 = "오아시스"; String section3 = "2클러스터끝"; + String section4 = "테라스1"; + String section5 = "테라스2"; String lentUserName1 = "제발 그만해"; String lentUserName2 = "이러다"; String lentUserName3 = "다 죽어"; @@ -382,16 +384,24 @@ private ArrayList generateFloors(int limit) { Cabinet cabinet1 = mock(Cabinet.class); Cabinet cabinet2 = mock(Cabinet.class); Cabinet cabinet3 = mock(Cabinet.class); + Cabinet cabinet4 = mock(Cabinet.class); + Cabinet cabinet5 = mock(Cabinet.class); CabinetPlace cabinetPlace1 = mock(CabinetPlace.class); CabinetPlace cabinetPlace2 = mock(CabinetPlace.class); CabinetPlace cabinetPlace3 = mock(CabinetPlace.class); + CabinetPlace cabinetPlace4 = mock(CabinetPlace.class); + CabinetPlace cabinetPlace5 = mock(CabinetPlace.class); given(cabinetPlace1.getLocation()).willReturn(Location.of(building, floor, section1)); given(cabinetPlace2.getLocation()).willReturn(Location.of(building, floor, section2)); given(cabinetPlace3.getLocation()).willReturn(Location.of(building, floor, section3)); + given(cabinetPlace4.getLocation()).willReturn(Location.of(building, floor, section4)); + given(cabinetPlace5.getLocation()).willReturn(Location.of(building, floor, section5)); given(cabinet1.getCabinetPlace()).willReturn(cabinetPlace1); given(cabinet2.getCabinetPlace()).willReturn(cabinetPlace2); given(cabinet3.getCabinetPlace()).willReturn(cabinetPlace3); + given(cabinet4.getCabinetPlace()).willReturn(cabinetPlace4); + given(cabinet5.getCabinetPlace()).willReturn(cabinetPlace5); User user1 = mock(User.class); User user2 = mock(User.class); @@ -413,25 +423,32 @@ private ArrayList generateFloors(int limit) { ActiveCabinetInfoEntities info3 = new ActiveCabinetInfoEntities(cabinet3, lentHistory3, user3); given(cabinetOptionalFetcher.findCabinetsActiveLentHistoriesByBuildingAndFloor( building, floor)).willReturn(List.of(info1, info2, info3)); + given(cabinetOptionalFetcher.findAllCabinetsByBuildingAndFloor(building, floor)) + .willReturn(List.of(cabinet1, cabinet2, cabinet3, cabinet4, cabinet5)); given(cabinetMapper.toCabinetPreviewDto(eq(cabinet1), any(), any())).willReturn(mock(CabinetPreviewDto.class)); given(cabinetMapper.toCabinetPreviewDto(eq(cabinet2), any(), any())).willReturn(mock(CabinetPreviewDto.class)); given(cabinetMapper.toCabinetPreviewDto(eq(cabinet3), any(), any())).willReturn(mock(CabinetPreviewDto.class)); + given(cabinetMapper.toCabinetPreviewDto(eq(cabinet4), any(), any())).willReturn(mock(CabinetPreviewDto.class)); + given(cabinetMapper.toCabinetPreviewDto(eq(cabinet5), any(), any())).willReturn(mock(CabinetPreviewDto.class)); CabinetsPerSectionResponseDto result1 = mock(CabinetsPerSectionResponseDto.class); CabinetsPerSectionResponseDto result2 = mock(CabinetsPerSectionResponseDto.class); CabinetsPerSectionResponseDto result3 = mock(CabinetsPerSectionResponseDto.class); + CabinetsPerSectionResponseDto result4 = mock(CabinetsPerSectionResponseDto.class); + CabinetsPerSectionResponseDto result5 = mock(CabinetsPerSectionResponseDto.class); given(result1.getSection()).willReturn(section1); given(result2.getSection()).willReturn(section2); given(result3.getSection()).willReturn(section3); + given(result4.getSection()).willReturn(section4); + given(result5.getSection()).willReturn(section5); - given(cabinetMapper.toCabinetsPerSectionResponseDto(eq(section1), anyList())) - .willReturn(result1); - given(cabinetMapper.toCabinetsPerSectionResponseDto(eq(section2), anyList())) - .willReturn(result2); - given(cabinetMapper.toCabinetsPerSectionResponseDto(eq(section3), anyList())) - .willReturn(result3); + given(cabinetMapper.toCabinetsPerSectionResponseDto(eq(section1), anyList())).willReturn(result1); + given(cabinetMapper.toCabinetsPerSectionResponseDto(eq(section2), anyList())).willReturn(result2); + given(cabinetMapper.toCabinetsPerSectionResponseDto(eq(section3), anyList())).willReturn(result3); + given(cabinetMapper.toCabinetsPerSectionResponseDto(eq(section4), anyList())).willReturn(result4); + given(cabinetMapper.toCabinetsPerSectionResponseDto(eq(section5), anyList())).willReturn(result5); //============================== WHEN ============================== List result = cabinetFacadeService.getCabinetsPerSection( @@ -441,6 +458,8 @@ private ArrayList generateFloors(int limit) { assertEquals(section1, result.get(0).getSection()); assertEquals(section2, result.get(1).getSection()); assertEquals(section3, result.get(2).getSection()); + assertEquals(section4, result.get(3).getSection()); + assertEquals(section5, result.get(4).getSection()); } @@ -450,9 +469,12 @@ private ArrayList generateFloors(int limit) { String building = "유토피아"; Integer floor = 999; List emptyList = new ArrayList<>(); + List emptyCabinetList = new ArrayList<>(); given(cabinetOptionalFetcher.findCabinetsActiveLentHistoriesByBuildingAndFloor(building, floor)) .willReturn(emptyList); + given(cabinetOptionalFetcher.findAllCabinetsByBuildingAndFloor(building, floor)) + .willReturn(emptyCabinetList); // when List result = cabinetFacadeService @@ -462,55 +484,55 @@ private ArrayList generateFloors(int limit) { assertTrue(result.isEmpty()); } - @Test - @Disabled("해당하는 Location이 없는 경우에 빈 배열을 받게 됨") - @DisplayName("성공: 해당하는 Location 없음 - 결과 null") - void 성공_NULL_getCabinetsPerSection() { - String building = "유토피아"; - String section = "콜로라도"; - Integer floor = 999; - - given(cabinetOptionalFetcher.findAllSectionsByBuildingAndFloor(building, floor)) - .willReturn(new ArrayList(List.of(section))); - - given(cabinetOptionalFetcher.findAllCabinetsByLocation(any())) - .willReturn(new ArrayList()); - - // when - List result = cabinetFacadeService - .getCabinetsPerSection(building, floor); - - then(cabinetOptionalFetcher).should().findAllSectionsByBuildingAndFloor(building, floor); - then(cabinetOptionalFetcher).should().findAllCabinetsByLocation(any()); - assertNotNull(result); - } - - @Test - @Disabled("변경된 API에서는 반납하지 않은 캐비닛이 없는 경우는 가져오지 않음.") - @DisplayName("성공: 반납하지 않은 캐비넷이 없음 - 결과 null") - void 성공_findAllActiveLentByCabinetId_EMPTY_getCabinetsPerSection() { - String building = "유토피아"; - String section = "콜로라도"; - Integer floor = 999; - - given(cabinetOptionalFetcher.findAllSectionsByBuildingAndFloor(building, floor)) - .willReturn(new ArrayList(List.of(section))); - - given(cabinetOptionalFetcher.findAllCabinetsByLocation(any())) - .willReturn(new ArrayList(List.of(mock(Cabinet.class)))); - - given(lentOptionalFetcher.findAllActiveLentByCabinetId(any())) - .willReturn(new ArrayList()); - // when - List result = cabinetFacadeService - .getCabinetsPerSection(building, floor); - - then(cabinetOptionalFetcher).should().findAllSectionsByBuildingAndFloor(building, floor); - then(cabinetOptionalFetcher).should().findAllCabinetsByLocation(any()); - then(lentOptionalFetcher).should().findAllActiveLentByCabinetId(any()); - - assertNotNull(result); - } +// @Test +// @Disabled("해당하는 Location이 없는 경우에 빈 배열을 받게 됨") +// @DisplayName("성공: 해당하는 Location 없음 - 결과 null") +// void 성공_NULL_getCabinetsPerSection() { +// String building = "유토피아"; +// String section = "콜로라도"; +// Integer floor = 999; +// +// given(cabinetOptionalFetcher.findAllSectionsByBuildingAndFloor(building, floor)) +// .willReturn(new ArrayList(List.of(section))); +// +// given(cabinetOptionalFetcher.findAllCabinetsByLocation(any())) +// .willReturn(new ArrayList()); +// +// // when +// List result = cabinetFacadeService +// .getCabinetsPerSection(building, floor); +// +// then(cabinetOptionalFetcher).should().findAllSectionsByBuildingAndFloor(building, floor); +// then(cabinetOptionalFetcher).should().findAllCabinetsByLocation(any()); +// assertNotNull(result); +// } + +// @Test +// @Disabled("변경된 API에서는 반납하지 않은 캐비닛이 없는 경우는 가져오지 않음.") +// @DisplayName("성공: 반납하지 않은 캐비넷이 없음 - 결과 null") +// void 성공_findAllActiveLentByCabinetId_EMPTY_getCabinetsPerSection() { +// String building = "유토피아"; +// String section = "콜로라도"; +// Integer floor = 999; +// +// given(cabinetOptionalFetcher.findAllSectionsByBuildingAndFloor(building, floor)) +// .willReturn(new ArrayList(List.of(section))); +// +// given(cabinetOptionalFetcher.findAllCabinetsByLocation(any())) +// .willReturn(new ArrayList(List.of(mock(Cabinet.class)))); +// +// given(lentOptionalFetcher.findAllActiveLentByCabinetId(any())) +// .willReturn(new ArrayList()); +// // when +// List result = cabinetFacadeService +// .getCabinetsPerSection(building, floor); +// +// then(cabinetOptionalFetcher).should().findAllSectionsByBuildingAndFloor(building, floor); +// then(cabinetOptionalFetcher).should().findAllCabinetsByLocation(any()); +// then(lentOptionalFetcher).should().findAllActiveLentByCabinetId(any()); +// +// assertNotNull(result); +// } @Test @DisplayName("성공: 대여타입으로 캐비넷 정보 page로 가져오기 1페이지, 최대 10개 - 결과 3개 (개인 캐비넷)") diff --git a/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java index a2c7ccf3d..655f959c9 100644 --- a/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java @@ -28,14 +28,14 @@ void findFirstByCabinetIdAndEndedAtIsNull() { // 빌리고 있는 유저가 없는 cabinet id Long cabinetId = 2L; - Optional ent = lentRepository.findFirstByCabinetIdAndEndedAtIsNull(cabinetId); + Optional ent = lentRepository.findByCabinetIdAndEndedAtIsNull(cabinetId); assertTrue(ent.isEmpty()); LentHistory lentHistory = LentHistory.of(now, now.plusDays(3), 1L, cabinetId); LentHistory saved = lentRepository.save(lentHistory); - ent = lentRepository.findFirstByCabinetIdAndEndedAtIsNull(cabinetId); + ent = lentRepository.findByCabinetIdAndEndedAtIsNull(cabinetId); assertTrue(ent.isPresent()); assertEquals(saved, ent.get()); } @@ -46,14 +46,14 @@ void findFirstByUserIdAndEndedAtIsNull() { // 빌리고 있는 사물함이 없는 user id Long userId = 1L; - Optional ent = lentRepository.findFirstByUserIdAndEndedAtIsNull(userId); + Optional ent = lentRepository.findByUserIdAndEndedAtIsNull(userId); assertTrue(ent.isEmpty()); LentHistory lentHistory = LentHistory.of(now, now.plusDays(3), userId, 1L); LentHistory saved = lentRepository.save(lentHistory); - ent = lentRepository.findFirstByUserIdAndEndedAtIsNull(userId); + ent = lentRepository.findByUserIdAndEndedAtIsNull(userId); assertTrue(ent.isPresent()); assertEquals(saved, ent.get()); } @@ -62,12 +62,14 @@ void findFirstByUserIdAndEndedAtIsNull() { void findByUserId() { // 빌린 기록이 없는 user id long userId = 18L; - List lentHistories = lentRepository.findByUserId(userId, PageRequest.of(0, 1)); + List lentHistories = lentRepository.findPaginationByUserId(userId, + PageRequest.of(0, 1)).toList(); assertTrue(lentHistories.isEmpty()); // 빌린 기록이 12개 있는 user id userId = 5L; - lentHistories = lentRepository.findByUserId(userId, PageRequest.of(0, 20)); + lentHistories = lentRepository.findPaginationByUserId(userId, + PageRequest.of(0, 20)).toList(); assertEquals(12, lentHistories.size()); } @@ -75,13 +77,14 @@ void findByUserId() { void findByCabinetId() { // 빌린 기록이 없는 cabinet id long cabinetId = 1L; - List lentHistories = lentRepository.findByCabinetId(cabinetId, - PageRequest.of(0, 1)); + List lentHistories = lentRepository.findPaginationByCabinetId(cabinetId, + PageRequest.of(0, 1)).toList(); assertTrue(lentHistories.isEmpty()); // 빌린 기록이 6개 있는 cabinet id cabinetId = 3L; - lentHistories = lentRepository.findByCabinetId(cabinetId, PageRequest.of(0, 20)); + lentHistories = lentRepository.findPaginationByCabinetId(cabinetId, + PageRequest.of(0, 20)).toList(); assertEquals(6, lentHistories.size()); } @@ -89,7 +92,7 @@ void findByCabinetId() { void countUserActiveLent() { // 빌리고 잇는 기록이 1개인 user id Long userId = 5L; - int count = lentRepository.countUserActiveLent(userId); + int count = lentRepository.countByUserIdAndEndedAtIsNull(userId); assertEquals(1, count); } @@ -97,7 +100,7 @@ void countUserActiveLent() { void countCabinetActiveLent() { // 빌리고 있는 기록이 3개 있는 cabinet id Long cabinetId = 4L; - int count = lentRepository.countCabinetActiveLent(cabinetId); + int count = lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId); assertEquals(3, count); } @@ -105,7 +108,7 @@ void countCabinetActiveLent() { void countUserAllLent() { // 빌린 기록이 12개 있는 user id Long userId = 5L; - int count = lentRepository.countUserAllLent(userId); + int count = lentRepository.countByUserId(userId); assertEquals(12, count); } @@ -113,7 +116,7 @@ void countUserAllLent() { void countCabinetAllLent() { // 빌린 기록이 6개 있는 cabinet id Long cabinetId = 3L; - int count = lentRepository.countCabinetAllLent(cabinetId); + int count = lentRepository.countByCabinetId(cabinetId); assertEquals(6, count); } @@ -121,10 +124,10 @@ void countCabinetAllLent() { void findAllActiveLentByCabinetId() { // 빌리고 있는 기록이 3개 있는 cabinet id long cabinetId = 4L; - List lentHistories = lentRepository.findAllActiveLentByCabinetId(cabinetId); + List lentHistories = lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); assertEquals(3, lentHistories.size()); cabinetId = 2L; - lentHistories = lentRepository.findAllActiveLentByCabinetId(cabinetId); + lentHistories = lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); assertTrue(lentHistories.isEmpty()); } } \ No newline at end of file diff --git a/backend/src/test/java/org/ftclub/cabinet/lent/service/LentServiceImplTest.java b/backend/src/test/java/org/ftclub/cabinet/lent/service/LentServiceImplTest.java index c0c5545bd..22c0b3256 100644 --- a/backend/src/test/java/org/ftclub/cabinet/lent/service/LentServiceImplTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/lent/service/LentServiceImplTest.java @@ -157,18 +157,18 @@ void afterEach() throws NoSuchFieldException { @DisplayName("사물함 대여 가능한 사용자는 available, limited_available 상태의 사물함을 빌릴 수 있습니다 ") void generalLentSituation() { // given - int lentCountBefore = lentRepository.countUserActiveLent(normalUser1.getUserId()); + int lentCountBefore = lentRepository.countByUserIdAndEndedAtIsNull(normalUser1.getUserId()); // when lentService.startLentCabinet(normalUser1.getUserId(), privateAvailableCabinet1.getCabinetId()); // then - int lentCountAfter = lentRepository.countUserActiveLent(normalUser1.getUserId()); - LentHistory lentHistory = lentRepository.findFirstByUserIdAndEndedAtIsNull( + int lentCountAfter = lentRepository.countByUserIdAndEndedAtIsNull(normalUser1.getUserId()); + LentHistory lentHistory = lentRepository.findByUserIdAndEndedAtIsNull( normalUser1.getUserId()).orElseThrow(() -> new RuntimeException()); Assertions.assertEquals(lentCountAfter, lentCountBefore + 1L); Assertions.assertEquals(CabinetStatus.FULL, privateAvailableCabinet1.getStatus()); Assertions.assertNotNull( - lentRepository.findFirstByUserIdAndEndedAtIsNull(normalUser1.getUserId()) + lentRepository.findByUserIdAndEndedAtIsNull(normalUser1.getUserId()) .orElse(null)); } @@ -246,7 +246,7 @@ void sharedCabinetProperStatus1() { // when lentService.startLentCabinet(userId1, cabinetId); assertEquals(CabinetStatus.AVAILABLE, cabinet.getStatus()); - List activeLentHistory = lentRepository.findAllActiveLentByCabinetId( + List activeLentHistory = lentRepository.findAllByCabinetIdAndEndedAtIsNull( cabinetId); for (LentHistory lentHistory : activeLentHistory) { assertEquals(DateUtil.getInfinityDate(), lentHistory.getExpiredAt()); @@ -259,7 +259,7 @@ void sharedCabinetProperStatus1() { lentService.startLentCabinet(userId3, cabinetId); assertEquals(CabinetStatus.FULL, cabinet.getStatus()); - activeLentHistory = lentRepository.findAllActiveLentByCabinetId(cabinetId); + activeLentHistory = lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); // then for (LentHistory lentHistory : activeLentHistory) { assertNotEquals(DateUtil.getInfinityDate(), lentHistory.getExpiredAt()); @@ -282,7 +282,7 @@ void sharedCabinetProperStatus2() { lentService.startLentCabinet(userId2, cabinetId); lentService.startLentCabinet(userId3, cabinetId); assertEquals(CabinetStatus.FULL, cabinet.getStatus()); - List activeLentHistory = lentRepository.findAllActiveLentByCabinetId( + List activeLentHistory = lentRepository.findAllByCabinetIdAndEndedAtIsNull( cabinetId); for (LentHistory lentHistory : activeLentHistory) { assertNotEquals(DateUtil.getInfinityDate(), lentHistory.getExpiredAt()); @@ -293,9 +293,9 @@ void sharedCabinetProperStatus2() { lentService.startLentCabinet(userId4, cabinetId); assertEquals(CabinetStatus.FULL, cabinet.getStatus()); - LentHistory latestLentHistory = lentRepository.findFirstByCabinetIdAndEndedAtIsNull( + LentHistory latestLentHistory = lentRepository.findByCabinetIdAndEndedAtIsNull( cabinetId).orElse(null); - activeLentHistory = lentRepository.findAllActiveLentByCabinetId(cabinetId); + activeLentHistory = lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); for (LentHistory lentHistory : activeLentHistory) { assertEquals(latestLentHistory.getExpiredAt(), lentHistory.getExpiredAt()); } diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java index 69a8da07e..a8de4ae5f 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java @@ -57,7 +57,7 @@ class StatisticsFacadeServiceUnitTest { .willReturn(floors); given(statisticsRepository.getAvailableCabinetsId(any())) .willReturn(availableCabinetsId); - given(lentRepository.countCabinetActiveLent(cabinetId)) + given(lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId)) .willReturn(activeUserCount); List cabinetFloorStatisticsResponseDtosOrigin = new ArrayList<>(); for (Integer i = 2; i <= 5; i++) { diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/LentExtensionServiceTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/LentExtensionServiceTest.java index 2ab26e364..f74595f60 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/LentExtensionServiceTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/LentExtensionServiceTest.java @@ -41,24 +41,5 @@ void setUp() { em.persist(sanan); em.persist(jpark2); } - - @Test - @DisplayName("현재 시간을 기준으로 만료된 연장권들을 soft delete한다.") - void 삭제() { - LentExtension expired = LentExtension.of("extension", 31, now.minusDays(1), LentExtensionType.ALL, sanan.getUserId()); - LentExtension valid = LentExtension.of("extension", 31, now.plusDays(1), LentExtensionType.ALL, jpark2.getUserId()); - em.persist(expired); - em.persist(valid); - em.flush(); - em.clear(); - - lentExtensionService.deleteExpiredExtensions(); - - LentExtension mustDeleted = lentExtensionRepository.findById(expired.getLentExtensionId()).get(); - LentExtension mustValid = lentExtensionRepository.findById(valid.getLentExtensionId()).get(); - assertThat(mustDeleted.getDeletedAt()).isNotNull(); - assertThat(mustValid.getDeletedAt()).isNull(); - } - } } From 5aea841b84555f5c732c646e307df2720d36f902 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 2 Dec 2023 12:28:16 +0900 Subject: [PATCH 0034/1029] =?UTF-8?q?[BE]=20pending=20=EC=A0=95=EC=B1=85?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/cabinet/service/CabinetFacadeServiceImpl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index 76a671c5a..40bf55916 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.cabinet.service; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import lombok.RequiredArgsConstructor; @@ -288,9 +289,7 @@ private List generateLentHistoryDtoList( @Transactional public CabinetPendingResponseDto getPendingCabinets(String building) { log.debug("getPendingCabinets"); - final LocalDateTime pendingLimit = LocalDateTime.of( - LocalDateTime.now().toLocalDate().minusDays(1), - LocalTime.of(13, 0, 0)); + final LocalDate yesterday = LocalDateTime.now().minusDays(1).toLocalDate(); List buildingCabinets = cabinetOptionalFetcher.findPendingCabinets( building, @@ -313,7 +312,7 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { LocalDateTime latestEndedAt = lentHistoriesMap.get(cabinet.getCabinetId()).stream() .map(LentHistory::getEndedAt) .max(LocalDateTime::compareTo).orElse(null); - if (latestEndedAt != null && latestEndedAt.isAfter(pendingLimit)) { + if (latestEndedAt != null && latestEndedAt.toLocalDate().isEqual(yesterday)) { cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); } } From 258fdbb6de7f80c46e43c9312bbc4a20b61468a0 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 2 Dec 2023 16:20:30 +0900 Subject: [PATCH 0035/1029] =?UTF-8?q?[BE]=20section=20Cabinet=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=EC=BF=BC=EB=A6=AC=EC=97=90=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EC=A1=B0=EA=B1=B4=20=EC=A0=9C=EC=99=B8(=EB=A9=94?= =?UTF-8?q?=EB=AA=A8=EB=A6=AC=20=ED=9A=A8=EC=9C=A8=20=EA=B4=80=EB=A0=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CabinetOptionalFetcher.java | 2 +- .../cabinet/repository/CabinetRepository.java | 5 ++--- .../service/CabinetFacadeServiceImpl.java | 17 +++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java index f87822fa1..bf75efbe6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java @@ -87,7 +87,7 @@ public Page findPaginationByVisibleNum(Integer visibleNum, PageRequest } public List findAllCabinetsByBuildingAndFloor(String building, Integer floor) { - return cabinetRepository.findAllByBuildingAndFloorOrderByVisibleNum(building, floor); + return cabinetRepository.findAllByBuildingAndFloor(building, floor); } /*-------------------------------------------GET--------------------------------------------*/ diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 6319620ac..9daf9672a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -71,9 +71,8 @@ public interface CabinetRepository extends JpaRepository, Cabinet @Query("SELECT c " + "FROM Cabinet c " + "JOIN FETCH c.cabinetPlace p " - + "WHERE p.location.building = :building AND p.location.floor = :floor " - + "ORDER BY c.visibleNum ASC") - List findAllByBuildingAndFloorOrderByVisibleNum( + + "WHERE p.location.building = :building AND p.location.floor = :floor") + List findAllByBuildingAndFloor( @Param("building") String building, @Param("floor") Integer floor); @Query("SELECT c " diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index 40bf55916..5a3823ed6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -122,8 +122,7 @@ else if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { * {@inheritDoc} */ @Override - public List getCabinetsPerSection(String building, - Integer floor) { + public List getCabinetsPerSection(String building, Integer floor) { log.debug("getCabinetsPerSection"); List currentLentCabinets = cabinetOptionalFetcher .findCabinetsActiveLentHistoriesByBuildingAndFloor(building, floor); @@ -135,12 +134,14 @@ public List getCabinetsPerSection(String building List allCabinetsOnSection = cabinetOptionalFetcher.findAllCabinetsByBuildingAndFloor(building, floor); Map> cabinetPreviewsBySection = new LinkedHashMap<>(); - allCabinetsOnSection.forEach(cabinet -> { - String section = cabinet.getCabinetPlace().getLocation().getSection(); - List lentHistories = - cabinetLentHistories.getOrDefault(cabinet, Collections.emptyList()); - CabinetPreviewDto preview = createCabinetPreviewDto(cabinet, lentHistories); - cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()).add(preview); + allCabinetsOnSection.stream() + .sorted(Comparator.comparing(Cabinet::getVisibleNum)) + .forEach(cabinet -> { + String section = cabinet.getCabinetPlace().getLocation().getSection(); + List lentHistories = + cabinetLentHistories.getOrDefault(cabinet, Collections.emptyList()); + CabinetPreviewDto preview = createCabinetPreviewDto(cabinet, lentHistories); + cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()).add(preview); }); return cabinetPreviewsBySection.entrySet().stream() .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), entry.getValue())) From 6d36ffdaa3a2cd5686be2a0fd5f6da07207dc66a Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 2 Dec 2023 20:56:32 +0900 Subject: [PATCH 0036/1029] =?UTF-8?q?[BE]=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CabinetFacadeServiceImpl.java | 18 +++++++++--------- .../lent/repository/LentOptionalFetcher.java | 2 +- .../lent/repository/LentRepository.java | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index 5a3823ed6..1ffd242db 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -2,7 +2,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -105,17 +104,16 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfoByVisibleNum(Integer visi allCabinetsByVisibleNum.getTotalElements()); } - private CabinetPreviewDto createCabinetPreviewDto(Cabinet cabinet, List lentHistories) { - String lentUserName = null; + private String checkCabinetTitle(Cabinet cabinet, List lentHistories) { if (cabinet.getTitle() != null && !cabinet.getTitle().isEmpty()) { // 12/2 패치 : 개인 사물함도 타이틀이 있을 경우 타이틀을 노출합니다. - lentUserName = cabinet.getTitle(); + return cabinet.getTitle(); } else if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { - lentUserName = lentHistories.get(0).getUser().getName(); + return lentHistories.get(0).getUser().getName(); } - return cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), lentUserName); + return null; } /** @@ -140,8 +138,9 @@ public List getCabinetsPerSection(String building String section = cabinet.getCabinetPlace().getLocation().getSection(); List lentHistories = cabinetLentHistories.getOrDefault(cabinet, Collections.emptyList()); - CabinetPreviewDto preview = createCabinetPreviewDto(cabinet, lentHistories); - cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()).add(preview); + String title = checkCabinetTitle(cabinet, lentHistories); + cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()) + .add(cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), title)); }); return cabinetPreviewsBySection.entrySet().stream() .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), entry.getValue())) @@ -163,7 +162,7 @@ public CabinetPaginationDto getCabinetPaginationByLentType(LentType lentType, In Page cabinets = cabinetOptionalFetcher.findPaginationByLentType(lentType, pageable); List cabinetDtos = cabinets.toList().stream() - .map((cabinet) -> cabinetMapper.toCabinetDto(cabinet)) + .map(cabinetMapper::toCabinetDto) .collect(Collectors.toList()); return cabinetMapper.toCabinetPaginationDtoList(cabinetDtos, cabinets.getTotalElements()); @@ -297,6 +296,7 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { LentType.CLUB, List.of(CabinetStatus.AVAILABLE, CabinetStatus.PENDING)); List cabinetIds = buildingCabinets.stream() + .filter(cabinet -> cabinet.isStatus(PENDING)) .map(Cabinet::getCabinetId).collect(Collectors.toList()); Map> cabinetFloorMap = cabinetOptionalFetcher.findAllFloorsByBuilding(building).stream() diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index ea598a4da..8b3aac5e7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -144,7 +144,7 @@ public List findUserIdsByCabinetIdFromRedis(Long cabinetId) { public List findAllOverdueLent(LocalDateTime date, Pageable pageable) { log.debug("Called findAllOverdueLent: {}", date); - return lentRepository.findAllOverdueLentHistories(date, pageable); + return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNull(date, pageable); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index b0a985b9c..66bde5129 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -176,7 +176,8 @@ Page findPaginationByUserIdAndEndedAtNotNull( + "FROM LentHistory lh " + "WHERE lh.expiredAt < :date AND lh.endedAt is null " + "ORDER BY lh.expiredAt ASC") - List findAllOverdueLentHistories(@Param("date") LocalDateTime date, Pageable pageable); + List findAllExpiredAtBeforeAndEndedAtIsNull( + @Param("date") LocalDateTime date, Pageable pageable); @Query("SELECT lh " + "FROM LentHistory lh " From 5b9eee01e2aaab291baf64152ec1fd4684b691c9 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 2 Dec 2023 21:06:08 +0900 Subject: [PATCH 0037/1029] =?UTF-8?q?[BE]=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81(pending=20=EC=82=AC=EB=AC=BC=ED=95=A8=20fetc?= =?UTF-8?q?her=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/repository/CabinetOptionalFetcher.java | 10 +++++++++- .../cabinet/service/CabinetFacadeServiceImpl.java | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java index bf75efbe6..98834b716 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java @@ -150,7 +150,15 @@ public Cabinet getClubCabinet(Long cabinetId) { return cabinet; } - public List findPendingCabinets( + /** + * building과 status에 맞고 lentType이 아닌 사물함을 찾습니다. + * + * @param building 건물 이름 + * @param lentType 사물함 타입 + * @param cabinetStatuses 사물함 상태 {@link List} + * @return {@link Cabinet} {@link List} + */ + public List findPendingCabinetsNotLentTypeAndStatus( String building, LentType lentType, List cabinetStatuses) { return cabinetRepository.findAllByBuildingAndLentTypeNotAndStatusIn( building, lentType, cabinetStatuses); diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index 1ffd242db..4205ad84e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -291,7 +291,7 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { log.debug("getPendingCabinets"); final LocalDate yesterday = LocalDateTime.now().minusDays(1).toLocalDate(); List buildingCabinets = - cabinetOptionalFetcher.findPendingCabinets( + cabinetOptionalFetcher.findPendingCabinetsNotLentTypeAndStatus( building, LentType.CLUB, List.of(CabinetStatus.AVAILABLE, CabinetStatus.PENDING)); From 7211f5800a84114a748e4a5c43a062260e431db3 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 2 Dec 2023 23:30:54 +0900 Subject: [PATCH 0038/1029] =?UTF-8?q?[BE]=20=EB=A9=94=EB=AA=A8=EB=A6=AC=20?= =?UTF-8?q?=ED=9A=A8=EC=9C=A8=20=EA=B0=9C=EC=84=A0=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20pending=20cabinet=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C?= =?UTF-8?q?=20endedAt=EC=9D=B4=20=EC=96=B4=EC=A0=9C,=20=EC=98=A4=EB=8A=98?= =?UTF-8?q?=20=EB=82=A0=EC=A7=9C=EC=9D=B8=20lentHistory=EB=A7=8C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/service/CabinetFacadeServiceImpl.java | 6 ++---- .../cabinet/lent/repository/LentOptionalFetcher.java | 5 +++-- .../ftclub/cabinet/lent/repository/LentRepository.java | 9 ++++++++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index 4205ad84e..9c58c3fbd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -292,9 +292,7 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { final LocalDate yesterday = LocalDateTime.now().minusDays(1).toLocalDate(); List buildingCabinets = cabinetOptionalFetcher.findPendingCabinetsNotLentTypeAndStatus( - building, - LentType.CLUB, - List.of(CabinetStatus.AVAILABLE, CabinetStatus.PENDING)); + building, LentType.CLUB, List.of(AVAILABLE, PENDING)); List cabinetIds = buildingCabinets.stream() .filter(cabinet -> cabinet.isStatus(PENDING)) .map(Cabinet::getCabinetId).collect(Collectors.toList()); @@ -302,7 +300,7 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { cabinetOptionalFetcher.findAllFloorsByBuilding(building).stream() .collect(toMap(key -> key, value -> new ArrayList<>())); Map> lentHistoriesMap = - lentOptionalFetcher.findAllLentHistoriesByCabinetIds(cabinetIds) + lentOptionalFetcher.findAllByCabinetIdsAfterDate(yesterday, cabinetIds) .stream().collect(groupingBy(LentHistory::getCabinetId)); buildingCabinets.forEach(cabinet -> { Integer floor = cabinet.getCabinetPlace().getLocation().getFloor(); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index 8b3aac5e7..04682b81a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.lent.repository; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -164,7 +165,7 @@ public Optional findPreviousLentHistoryByCabinetId(Long cabinetId) return lentRepository.findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc(cabinetId); } - public List findAllLentHistoriesByCabinetIds(List cabinetIds) { - return lentRepository.findAllByCabinetIdIn(cabinetIds); + public List findAllByCabinetIdsAfterDate(LocalDate date, List cabinetIds) { + return lentRepository.findAllByCabinetIdsAfterDate(date, cabinetIds); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 66bde5129..bac79705f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.lent.repository; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -162,9 +163,15 @@ Page findPaginationByUserIdAndEndedAtNotNull( * 여러 사물함들의 대여 기록을 모두 가져옵니다. * * @param cabinetIds 조회하고자 하는 사물함의 Id {@link List} + * @param date 조회하고자 하는 날짜(시간 제외) * @return 조회하고자 하는 사물함들 {@link LentHistory}의 {@link List} */ - List findAllByCabinetIdIn(List cabinetIds); + @Query("SELECT lh " + + "FROM LentHistory lh " + + "WHERE lh.cabinetId IN :cabinetIds " + + "AND DATE(lh.endedAt) >= DATE(:date)") + List findAllByCabinetIdsAfterDate(@Param("date") LocalDate date, + @Param("cabinetIds") List cabinetIds); /** * 연체되어 있는 사물함을 모두 가져옵니다. From fbaacfe97af944fb762e93909e2e5411763a32ab Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 5 Dec 2023 15:41:25 +0900 Subject: [PATCH 0039/1029] =?UTF-8?q?[BE]=20no=20unique=20exception=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftclub/cabinet/lent/repository/LentOptionalFetcher.java | 2 +- .../org/ftclub/cabinet/lent/repository/LentRepository.java | 2 +- .../ftclub/cabinet/lent/service/LentFacadeServiceImpl.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index 04682b81a..1715a9006 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -160,7 +160,7 @@ public List findAllActiveLentHistoriesByUserId(Long userId) { return lentRepository.findAllActiveLentHistoriesByUserId(userId); } - public Optional findPreviousLentHistoryByCabinetId(Long cabinetId) { + public List findPreviousLentHistoryByCabinetId(Long cabinetId) { log.debug("Called findPreviousLentUserNameByCabinetId: {}", cabinetId); return lentRepository.findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc(cabinetId); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index bac79705f..151b27870 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -89,7 +89,7 @@ int countReturnByTimeDuration(@Param("startDate") LocalDateTime startDate, * @param cabinetId 찾으려는 cabinet id * @return 반납한 {@link LentHistory}의 {@link Optional} */ - Optional findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc( + List findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc( @Param("cabinetId") Long cabinetId); /** diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java index 342532941..37ff9ab53 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java @@ -149,10 +149,10 @@ public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { String previousUserName = lentRedis.getPreviousUserName( myCabinet.getCabinetId().toString()); if (previousUserName == null) { - Optional previousLentHistory = lentOptionalFetcher.findPreviousLentHistoryByCabinetId( + List previousLentHistory = lentOptionalFetcher.findPreviousLentHistoryByCabinetId( cabinetId); - if (previousLentHistory.isPresent()) { - previousUserName = previousLentHistory.get().getUser().getName(); + if (!previousLentHistory.isEmpty()) { + previousUserName = previousLentHistory.get(0).getUser().getName(); } } return cabinetMapper.toMyCabinetResponseDto(myCabinet, lentDtoList, From 36ac099beaffffbcf3449f6a9b57fed7a4b509a8 Mon Sep 17 00:00:00 2001 From: hyungseok Date: Tue, 5 Dec 2023 15:44:20 +0900 Subject: [PATCH 0040/1029] =?UTF-8?q?[FE]=20FEAT:=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=9C=20pendingCabinets=20api=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/ftclub/cabinet/firebase/FCMInitializer.java | 4 +--- frontend/src/api/axios/axios.custom.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java index f5aa923fb..7cf4733a6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java +++ b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java @@ -29,9 +29,7 @@ public class FCMInitializer { @PostConstruct public void initialize() throws IOException { - Path currentPath = Paths.get(new ClassPathResource("/").getFile().getPath()) - .getParent().getParent().getParent().getParent().getParent() - .toAbsolutePath().normalize(); + Path currentPath = Paths.get("").toAbsolutePath().normalize(); Resource resource = resourceLoader.getResource("file:" + currentPath + credentialsPath); try (InputStream inputStream = resource.getInputStream()) { FirebaseOptions options = FirebaseOptions.builder() diff --git a/frontend/src/api/axios/axios.custom.ts b/frontend/src/api/axios/axios.custom.ts index a01e0533b..a9ac008fd 100644 --- a/frontend/src/api/axios/axios.custom.ts +++ b/frontend/src/api/axios/axios.custom.ts @@ -520,7 +520,7 @@ export const axiosCancel = async (cabinetId: number | null): Promise => { } }; -const axiosGetPendingCabinetsURL = "/v4/cabinets/pending"; +const axiosGetPendingCabinetsURL = "/v4/cabinets/buildings/새롬관/pending"; export const axiosGetPendingCabinets = async (): Promise => { try { const response = await instance.get(axiosGetPendingCabinetsURL); From 108e95304238e6d59952c89b6123b27634e91ea5 Mon Sep 17 00:00:00 2001 From: space Date: Wed, 6 Dec 2023 16:45:40 +0900 Subject: [PATCH 0041/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=B0=8F=20config=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/config/HaneProperties.java | 10 ++++++++-- .../cabinet/occupiedtime/OccupiedTimeManager.java | 3 +-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java index ffc5bd3b8..c094cd841 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.config; + import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -7,6 +8,7 @@ @Component @Getter public class HaneProperties { + private static final int SIXTY = 60; @Value("${spring.hane.token}") private String jwtToken; @@ -14,6 +16,10 @@ public class HaneProperties { @Value("${spring.hane.url}") private String url; - @Value("${spring.hane.limit-time}") - private int limit_time; + @Value("${spring.hane.limit-hours}") + private int limitHours; + + public int getLimitTimeSeconds() { + return limitHours * SIXTY * SIXTY; + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java index 62a56343e..8ad1dc7b3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java @@ -12,7 +12,6 @@ import org.ftclub.cabinet.exception.UtilException; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -37,7 +36,7 @@ public List filterToMetUserMonthlyTime( List userMonthData = Arrays.stream(userMonthDataDtoList) .filter(dto -> allCabiUsers.stream() .anyMatch(user -> user.getName().equals(dto.getLogin()))) - .filter(dto -> dto.getMonthAccumationTime() > haneProperties.getLimit_time()) + .filter(dto -> dto.getMonthAccumationTime() > haneProperties.getLimitTimeSeconds()) .collect(Collectors.toList()); return userMonthData; } From 08597b1158a653b611bbd243ce83da72b5b1c303 Mon Sep 17 00:00:00 2001 From: space Date: Wed, 6 Dec 2023 16:46:36 +0900 Subject: [PATCH 0042/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=B0=8F=20config=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index e755c38d0..b20670717 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit e755c38d06b1e5ed2801b61f3bbd75ec39f633f7 +Subproject commit b20670717fddf78f23169f24f839750b51905088 From 2b5129633b2f4d1c469607a7c8e081d8e4b25f47 Mon Sep 17 00:00:00 2001 From: space Date: Wed, 6 Dec 2023 17:21:53 +0900 Subject: [PATCH 0043/1029] =?UTF-8?q?[BE]=20HOTFIX:=2080=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=9D=B4=EC=83=81=20120=EC=8B=9C=EA=B0=84=EB=AF=B8=EB=A7=8C=20?= =?UTF-8?q?=EC=A7=80=EA=B8=89=EB=A1=9C=EC=A7=81=20(=EC=9E=84=EC=8B=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../occupiedtime/OccupiedTimeController.java | 24 ++++++++++++++++++- .../occupiedtime/OccupiedTimeManager.java | 10 ++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java index 0315a7508..ce030b072 100644 --- a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java +++ b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java @@ -1,7 +1,11 @@ package org.ftclub.cabinet.occupiedtime; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.dto.UserMonthDataDto; +import org.ftclub.cabinet.user.service.LentExtensionService; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -13,9 +17,27 @@ @RequiredArgsConstructor @Log4j2 public class OccupiedTimeController { -/* + private final OccupiedTimeManager occupiedTimeManager; + private final LentExtensionService lentExtensionService; + + @GetMapping("/data") + public List test(){ + return occupiedTimeManager.filterCustomUserMonthlyTime(occupiedTimeManager.getUserLastMonthOccupiedTime()); + } + + @GetMapping("/ongoing") + public String bulk(){ + List userMonthDataDtos = occupiedTimeManager.filterCustomUserMonthlyTime( + occupiedTimeManager.getUserLastMonthOccupiedTime()); + userMonthDataDtos.forEach(dto -> { + lentExtensionService.assignLentExtension(dto.getLogin()); + }); + return "ok"; + + } + /* @GetMapping("/data") public List previousMeet() { diff --git a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java index 8ad1dc7b3..9220e42e2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java @@ -30,6 +30,16 @@ public class OccupiedTimeManager { private final HaneProperties haneProperties; private final UserOptionalFetcher userOptionalFetcher; + public List filterCustomUserMonthlyTime(UserMonthDataDto[] userMonthDataDtoList){ + List allCabiUsers = userOptionalFetcher.findAllActiveUsers(); + return Arrays.stream(userMonthDataDtoList) + .filter(dto -> allCabiUsers.stream() + .anyMatch(user -> user.getName().equals(dto.getLogin()))) + .filter(dto -> dto.getMonthAccumationTime() >= haneProperties.getLimitTimeSeconds() && + dto.getMonthAccumationTime() < 432000) + .collect(Collectors.toList()); + } + public List filterToMetUserMonthlyTime( UserMonthDataDto[] userMonthDataDtoList) { List allCabiUsers = userOptionalFetcher.findAllActiveUsers(); From 5367842b195b18a4ca50d99149608b752da2db48 Mon Sep 17 00:00:00 2001 From: space Date: Wed, 6 Dec 2023 17:26:45 +0900 Subject: [PATCH 0044/1029] [BE] HOTFIX: controller disabled --- .../cabinet/occupiedtime/OccupiedTimeController.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java index ce030b072..a1a077e62 100644 --- a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java +++ b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java @@ -12,6 +12,8 @@ /** * FOR TEST ONLY */ + + /* @RestController @RequestMapping("/v4/calc") @RequiredArgsConstructor @@ -37,7 +39,7 @@ public String bulk(){ } - /* + @GetMapping("/data") public List previousMeet() { @@ -97,4 +99,5 @@ public List fetchData(@PathVariable("year") int year, } */ -} +//} + From 59529a1fd1a1a6366c4962c79fdb090233f6a7db Mon Sep 17 00:00:00 2001 From: space Date: Wed, 6 Dec 2023 18:49:06 +0900 Subject: [PATCH 0045/1029] [BE] HOTFIX: git log fix --- backend/src/main/resources/log4j2-local.yml | 49 +++++++++++++++------ config | 2 +- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/backend/src/main/resources/log4j2-local.yml b/backend/src/main/resources/log4j2-local.yml index 7d6979406..4ab313ccf 100644 --- a/backend/src/main/resources/log4j2-local.yml +++ b/backend/src/main/resources/log4j2-local.yml @@ -12,6 +12,27 @@ Configuration: target: SYSTEM_OUT PatternLayout: pattern: ${pattern} +# RollingFile: +# name: rollingFile +# # 파일 저장 위치를 어디로? +# fileName: logs/42cabi.log +# filePattern: logs/42cabi-%d{yyyy-MM-dd}-%i.log.gz +# PatternLayout: +# pattern: ${pattern} +# Policies: +# # 1일 단위로 로그를 압축해서 저장 +# TimeBasedTriggeringPolicy: +# interval: 1 +# modulate: true +# DefaultRolloverStrategy: +# Delete: +# basePath: logs +# ifFileName: +# glob: "42cabi-*.log.gz" +# # 30일 이상된 로그는 삭제 +# ifLastModified: +# age: "30d" +# max: 30 Loggers: logger: @@ -22,27 +43,29 @@ Configuration: additivity: false AppenderRef: - ref: console +# - ref: rollingFile - name: org.ftclub.cabinet level: debug additivity: false AppenderRef: - ref: console +# - ref: rollingFile # SQL문 확인을 위한 설정 (시작) # sql을 봐야한다면 해당 부분 주석 해제하면 됨. # trace로 해놓는 이유는 JDBC 파라미터를 보기 위함 -# - -# name: org.hibernate.type -# level: trace -# additivity: false -# AppenderRef: -# ref: console -# -# - -# name: org.hibernate.SQL -# level: debug -# additivity: false -# AppenderRef: -# ref: console + # - + # name: org.hibernate.type + # level: trace + # additivity: false + # AppenderRef: + # ref: console + # + # - + # name: org.hibernate.SQL + # level: debug + # additivity: false + # AppenderRef: + # ref: console # SQL문 확인을 위한 설정 (끝) diff --git a/config b/config index b20670717..e9c2b9b78 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit b20670717fddf78f23169f24f839750b51905088 +Subproject commit e9c2b9b78e6ecb6ce542c23fbfdea24df1e3da17 From 1a54a621c1afd272ec34c965322696411d54d759 Mon Sep 17 00:00:00 2001 From: space Date: Wed, 6 Dec 2023 18:49:35 +0900 Subject: [PATCH 0046/1029] [BE] HOTFIX: Slf4j to Log4j2 --- .../main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java index 7b10a1371..3ba51d43d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import lombok.extern.log4j.Log4j2; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.ftclub.cabinet.config.JwtProperties; @@ -20,7 +20,7 @@ /** * {@link AuthGuard} 어노테이션이 붙은 메소드나 클래스에 대해 인증을 검사하는 클래스입니다. */ -@Slf4j +@Log4j2 @Aspect @Component @RequiredArgsConstructor From febe466e668f65319a2f95147f14c683281167aa Mon Sep 17 00:00:00 2001 From: space Date: Wed, 6 Dec 2023 19:35:49 +0900 Subject: [PATCH 0047/1029] [BE] fix: config --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index e9c2b9b78..3983ee97b 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit e9c2b9b78e6ecb6ce542c23fbfdea24df1e3da17 +Subproject commit 3983ee97b3603b24f62b0a41e8ff26e61aae3d03 From 1d4984a33e95ca91fc66562d72d74b4a5ba09ab6 Mon Sep 17 00:00:00 2001 From: space Date: Wed, 6 Dec 2023 19:36:30 +0900 Subject: [PATCH 0048/1029] [BE] FIX: config --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index e9c2b9b78..3983ee97b 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit e9c2b9b78e6ecb6ce542c23fbfdea24df1e3da17 +Subproject commit 3983ee97b3603b24f62b0a41e8ff26e61aae3d03 From cf5d51121a7a6007b4bf812b4593ebf195a133e0 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Wed, 6 Dec 2023 19:40:38 +0900 Subject: [PATCH 0049/1029] =?UTF-8?q?[BE]=20FIX:=20server=20config=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 3983ee97b..e5de95b8f 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 3983ee97b3603b24f62b0a41e8ff26e61aae3d03 +Subproject commit e5de95b8fae3a141198e8353039b9d38b1ee961b From 5fe54c3106834c9ad370964df15f238e19312fad Mon Sep 17 00:00:00 2001 From: space Date: Sat, 9 Dec 2023 12:49:37 +0900 Subject: [PATCH 0050/1029] =?UTF-8?q?[BE]=20FEAT:=20slack=20alarm=20?= =?UTF-8?q?=EC=B6=94=20&=20yml=20=EC=95=8C=EB=9E=8C=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/alarm/config/AlarmProperties.java | 58 +++++++++------ .../cabinet/alarm/config/GmailProperties.java | 14 ++-- .../ftclub/cabinet/alarm/dto/SlackDto.java | 10 +++ .../alarm/handler/PushAlarmSender.java | 3 +- .../alarm/handler/SlackAlarmSender.java | 72 +++++++++++++++++-- .../cabinet/alarm/slack/SlackApiManager.java | 6 +- .../alarm/slack/config/SlackApiConfig.java | 18 +++++ .../alarm/slack/config/SlackProperties.java | 2 +- 8 files changed, 145 insertions(+), 38 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/dto/SlackDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java index b46d6d810..216a5440f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -9,68 +9,86 @@ public class AlarmProperties { /*===================== lentSuccess =========================*/ - @Value("${spring.mail.lentSuccess.subject}") + @Value("${alarm.mail.lentSuccess.subject}") private String lentSuccessSubject; - @Value("${spring.mail.lentSuccess.template}") + @Value("${alarm.mail.lentSuccess.template}") private String lentSuccessMailTemplateUrl; - @Value("${spring.fcm.lentSuccess.template}") + @Value("${alarm.fcm.lentSuccess.template}") private String lentSuccessFcmTemplate; + @Value("${alarm.slack.lentSuccess.template}") + private String lentSuccessSlackTemplate; + /*======================= overdue ===========================*/ - @Value("${spring.mail.overdue.subject}") + @Value("${alarm.mail.overdue.subject}") private String overdueSubject; - @Value("${spring.mail.overdue.template}") + @Value("${alarm.mail.overdue.template}") private String overdueMailTemplateUrl; - @Value("${spring.fcm.overdue.template}") + @Value("${alarm.fcm.overdue.template}") private String overdueFcmTemplate; + @Value("${alarm.slack.overdue.template}") + private String overdueSlackTemplate; + /*===================== soonOverdue =========================*/ - @Value("${spring.mail.soonOverdue.term}") + @Value("${alarm.mail.soonOverdue.term}") private Long soonOverdueTerm; - @Value("${spring.mail.soonOverdue.subject}") + @Value("${alarm.mail.soonOverdue.subject}") private String soonOverdueSubject; - @Value("${spring.mail.soonOverdue.template}") + @Value("${alarm.mail.soonOverdue.template}") private String soonOverdueMailTemplateUrl; - @Value("${spring.fcm.soonOverdue.template}") + @Value("${alarm.fcm.soonOverdue.template}") private String soonOverdueFcmTemplate; + @Value("${alarm.slack.soonOverdue.template}") + private String soonOverdueSlackTemplate; + /*================== extensionIssuance ======================*/ - @Value("${spring.mail.extensionIssuance.subject}") + @Value("${alarm.mail.extensionIssuance.subject}") private String extensionIssuanceSubject; - @Value("${spring.mail.extensionIssuance.template}") + @Value("${alarm.mail.extensionIssuance.template}") private String extensionIssuanceMailTemplateUrl; - @Value("${spring.fcm.extensionIssuance.template}") + @Value("${alarm.fcm.extensionIssuance.template}") private String extensionIssuanceFcmTemplate; + @Value("${alarm.slack.extensionIssuance.template}") + private String extensionIssuanceSlackTemplate; + /*================= extensionExpiration =====================*/ - @Value("${spring.mail.extensionExpiration.term}") + @Value("${alarm.mail.extensionExpiration.term}") private Long extensionExpirationTerm; - @Value("${spring.mail.extensionExpiration.subject}") + @Value("${alarm.mail.extensionExpiration.subject}") private String extensionExpirationImminentSubject; - @Value("${spring.mail.extensionExpiration.template}") + @Value("${alarm.mail.extensionExpiration.template}") private String extensionExpirationImminentMailTemplateUrl; - @Value("${spring.fcm.extensionExpiration.template}") + @Value("${alarm.fcm.extensionExpiration.template}") private String extensionExpirationImminentFcmTemplate; + @Value("${alarm.slack.extensionExpiration.template}") + private String extensionExpirationImminentSlackTemplate; + /*==================== announcement =========================*/ - @Value("${spring.mail.announcement.subject}") + @Value("${alarm.mail.announcement.subject}") private String announcementSubject; - @Value("${spring.mail.announcement.template}") + @Value("${alarm.mail.announcement.template}") private String announcementMailTemplateUrl; - @Value("${spring.fcm.announcement.template}") + @Value("${alarm.fcm.announcement.template}") private String announcementFcmTemplate; + + @Value("${alarm.slack.announcement.template}") + private String announcementSlackTemplate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java index 09afbe0d6..bdb6e55da 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java @@ -11,24 +11,24 @@ public class GmailProperties { @Value("${spring.production}") private Boolean isProduction; - @Value("${spring.mail.host}") + @Value("${alarm.mail.host}") private String mailServerHost; - @Value("${spring.mail.port}") + @Value("${alarm.mail.port}") private int mailServerPort; - @Value("${spring.mail.display-sender-name}") + @Value("${alarm.mail.display-sender-name}") private String displaySenderName; - @Value("${spring.mail.username}") + @Value("${alarm.mail.username}") private String username; - @Value("${spring.mail.password}") + @Value("${alarm.mail.password}") private String password; - @Value("${spring.mail.properties.mail.smtp.auth}") + @Value("${alarm.mail.properties.mail.smtp.auth}") private Boolean useAuth; - @Value("${spring.mail.properties.mail.smtp.starttls.enable}") + @Value("${alarm.mail.properties.mail.smtp.starttls.enable}") private Boolean useStartTls; } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/SlackDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/SlackDto.java new file mode 100644 index 000000000..b30fe77a6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/SlackDto.java @@ -0,0 +1,10 @@ +package org.ftclub.cabinet.alarm.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class SlackDto { + private final String content; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java index b013b220d..014acbac0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.util.Optional; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.alarm.domain.Alarm; @@ -23,7 +24,7 @@ import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Component; -@Slf4j +@Log4j2 @Component @RequiredArgsConstructor public class PushAlarmSender { diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index 8e05a9c09..a8bb3fcc7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -1,7 +1,23 @@ package org.ftclub.cabinet.alarm.handler; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.Message; +import com.slack.api.Slack; +import java.time.LocalDateTime; +import java.util.Optional; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.config.AlarmProperties; +import org.ftclub.cabinet.alarm.domain.Alarm; import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; +import org.ftclub.cabinet.alarm.dto.FCMDto; +import org.ftclub.cabinet.alarm.dto.SlackDto; import org.ftclub.cabinet.alarm.slack.SlackApiManager; import org.ftclub.cabinet.alarm.slack.dto.SlackUserInfo; import org.ftclub.cabinet.exception.ExceptionStatus; @@ -10,24 +26,66 @@ import org.springframework.stereotype.Component; import org.thymeleaf.util.StringUtils; +@Log4j2 @Component @RequiredArgsConstructor public class SlackAlarmSender { private final SlackApiManager slackApiManager; + private final AlarmProperties alarmProperties; + + + public void send(User user, AlarmEvent alarmEvent) { + log.info("slack alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); - // 접근지정자가 없습니다. - void send(User user, AlarmEvent alarmEvent) { SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); String id = slackUserInfo.getId(); if (StringUtils.isEmpty(id)) { throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); } - // toString을 이용하는 것이 아니라 직접적으로 메시지(String)으로 변환하는 메서드를 두는 게 좋을 것 같습니다. - // 개인적인 느낌으로는 toString은 내부적으로 보는 내용이고, 메시지는 외부적으로 보는 내용 같은 느낌이라... - // 이후에 작성하는 부분이 생길 때에도 toString을 override 하게하는 것 보다 인터페이스에서 명시적으로 강제하는게 좋을 것 같습니다. - // 그리고 파라미터 뒤집혀있네요 id, alarm.toString()이어야 함 - slackApiManager.sendMessage(alarmEvent.getAlarm().toString(), id); + SlackDto slackDto = messageParse(alarmEvent.getAlarm()); + slackApiManager.sendMessage(id, slackDto.getContent()); } + + private SlackDto messageParse(Alarm alarm) { + if (alarm instanceof LentSuccessAlarm) { + String building = ((LentSuccessAlarm) alarm).getLocation().getBuilding(); + Integer floor = ((LentSuccessAlarm) alarm).getLocation().getFloor(); + Integer visibleNum = ((LentSuccessAlarm) alarm).getVisibleNum(); + String body = String.format(alarmProperties.getLentSuccessMailTemplateUrl(), + building + " " + floor + "층 " + visibleNum + "번"); + return new SlackDto(body); + } else if (alarm instanceof LentExpirationImminentAlarm) { + Long daysAfterFromExpireDate = ((LentExpirationImminentAlarm) alarm).getDaysAfterFromExpireDate(); + String body = String.format(alarmProperties.getSoonOverdueSlackTemplate(), + Math.abs(daysAfterFromExpireDate)); + return new SlackDto(body); + } else if (alarm instanceof LentExpirationAlarm) { + Long daysLeftFromExpireDate = ((LentExpirationAlarm) alarm).getDaysLeftFromExpireDate(); + String body = String.format(alarmProperties.getOverdueSlackTemplate(), + Math.abs(daysLeftFromExpireDate)); + return new SlackDto(body); + } else if (alarm instanceof ExtensionIssuanceAlarm) { + Integer daysToExtend = ((ExtensionIssuanceAlarm) alarm).getDaysToExtend(); + String extensionName = ((ExtensionIssuanceAlarm) alarm).getExtensionName(); + String body = String.format(alarmProperties.getExtensionIssuanceMailTemplateUrl(), + daysToExtend, extensionName); + return new SlackDto(body); + } else if (alarm instanceof ExtensionExpirationImminentAlarm) { + String extensionName = ((ExtensionExpirationImminentAlarm) alarm).getExtensionName(); + LocalDateTime extensionExpireDate = ((ExtensionExpirationImminentAlarm) alarm).getExtensionExpirationDate(); + String body = String.format( + alarmProperties.getExtensionExpirationImminentMailTemplateUrl(), + extensionName, extensionExpireDate); + return new SlackDto(body); + } else if (alarm instanceof AnnouncementAlarm) { + String body = alarmProperties.getAnnouncementMailTemplateUrl(); + return new SlackDto(body); + } else { + throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); + } + } + + } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java index e908a8357..7ee11817f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java @@ -7,6 +7,7 @@ import feign.FeignException.FeignClientException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.slack.config.SlackApiConfig; import org.ftclub.cabinet.alarm.slack.config.SlackProperties; import org.ftclub.cabinet.alarm.slack.dto.SlackResponse; import org.ftclub.cabinet.alarm.slack.dto.SlackUserInfo; @@ -23,6 +24,7 @@ public class SlackApiManager { private final SlackProperties slackProperties; private final SlackFeignClient slackFeignClient; + private final MethodsClient slackApi; public SlackUserInfo requestSlackUserInfo(String email) { @@ -52,13 +54,13 @@ public void sendMessage(String channelId, String message) { log.info("Called sendMessage channelId={}, message={}", channelId, message); try { // token으로 그때그때 instance를 get해오는 게 아니고, 빈으로 등록해서 사용하는게 낫지 않나요? - MethodsClient methods = Slack.getInstance().methods(slackProperties.getAppToken()); +// slackApi = Slack.getInstance().methods(slackProperties.getAppToken()); ChatPostMessageRequest request = ChatPostMessageRequest.builder() .channel(channelId) // DM & channel .text(message) .build(); - methods.chatPostMessage(request); + slackApi.chatPostMessage(request); } catch (SlackApiException | IOException e) { log.error("{}", e.getMessage()); throw new ServiceException(ExceptionStatus.SLACK_MESSAGE_SEND_BAD_GATEWAY); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java new file mode 100644 index 000000000..2a9561b8e --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java @@ -0,0 +1,18 @@ +package org.ftclub.cabinet.alarm.slack.config; + +import com.slack.api.Slack; +import com.slack.api.methods.MethodsClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SlackApiConfig { + @Value("${slack.token.app-token") + private String appToken; + + @Bean + public MethodsClient methodsClient() { + return Slack.getInstance().methods(appToken); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java index 72989d1ac..0db34aeff 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java @@ -12,7 +12,7 @@ public class SlackProperties { private final String bearer = "Bearer "; @Value("${slack.token.singing-secret}") - private String singingSecert; + private String singingSecret; @Value("${slack.token.bot-token") private String botToken; @Value("${slack.token.app-token") From afa37ae50e5653d4e26de1affd55ddff13280c3d Mon Sep 17 00:00:00 2001 From: space Date: Sat, 9 Dec 2023 12:49:57 +0900 Subject: [PATCH 0051/1029] [BE] FIX: xmls --- .../.gitignore" | 8 +++++ .../cabi.iml" | 9 ++++++ .../compiler.xml" | 30 +++++++++++++++++++ .../gradle.xml" | 16 ++++++++++ .../jarRepositories.xml" | 20 +++++++++++++ .../misc.xml" | 7 +++++ .../modules.xml" | 8 +++++ .../vcs.xml" | 7 +++++ .idea/misc.xml | 2 +- .idea/modules.xml | 6 +--- 10 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 ".idea \353\263\265\354\202\254\353\263\270/.gitignore" create mode 100644 ".idea \353\263\265\354\202\254\353\263\270/cabi.iml" create mode 100644 ".idea \353\263\265\354\202\254\353\263\270/compiler.xml" create mode 100644 ".idea \353\263\265\354\202\254\353\263\270/gradle.xml" create mode 100644 ".idea \353\263\265\354\202\254\353\263\270/jarRepositories.xml" create mode 100644 ".idea \353\263\265\354\202\254\353\263\270/misc.xml" create mode 100644 ".idea \353\263\265\354\202\254\353\263\270/modules.xml" create mode 100644 ".idea \353\263\265\354\202\254\353\263\270/vcs.xml" diff --git "a/.idea \353\263\265\354\202\254\353\263\270/.gitignore" "b/.idea \353\263\265\354\202\254\353\263\270/.gitignore" new file mode 100644 index 000000000..c3f502a19 --- /dev/null +++ "b/.idea \353\263\265\354\202\254\353\263\270/.gitignore" @@ -0,0 +1,8 @@ +# 디폴트 무시된 파일 +/shelf/ +/workspace.xml +# 에디터 기반 HTTP 클라이언트 요청 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git "a/.idea \353\263\265\354\202\254\353\263\270/cabi.iml" "b/.idea \353\263\265\354\202\254\353\263\270/cabi.iml" new file mode 100644 index 000000000..d6ebd4805 --- /dev/null +++ "b/.idea \353\263\265\354\202\254\353\263\270/cabi.iml" @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git "a/.idea \353\263\265\354\202\254\353\263\270/compiler.xml" "b/.idea \353\263\265\354\202\254\353\263\270/compiler.xml" new file mode 100644 index 000000000..36a525faa --- /dev/null +++ "b/.idea \353\263\265\354\202\254\353\263\270/compiler.xml" @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/.idea \353\263\265\354\202\254\353\263\270/gradle.xml" "b/.idea \353\263\265\354\202\254\353\263\270/gradle.xml" new file mode 100644 index 000000000..e3d5fba50 --- /dev/null +++ "b/.idea \353\263\265\354\202\254\353\263\270/gradle.xml" @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git "a/.idea \353\263\265\354\202\254\353\263\270/jarRepositories.xml" "b/.idea \353\263\265\354\202\254\353\263\270/jarRepositories.xml" new file mode 100644 index 000000000..fdc392fe8 --- /dev/null +++ "b/.idea \353\263\265\354\202\254\353\263\270/jarRepositories.xml" @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git "a/.idea \353\263\265\354\202\254\353\263\270/misc.xml" "b/.idea \353\263\265\354\202\254\353\263\270/misc.xml" new file mode 100644 index 000000000..ade1ecd10 --- /dev/null +++ "b/.idea \353\263\265\354\202\254\353\263\270/misc.xml" @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git "a/.idea \353\263\265\354\202\254\353\263\270/modules.xml" "b/.idea \353\263\265\354\202\254\353\263\270/modules.xml" new file mode 100644 index 000000000..54beef274 --- /dev/null +++ "b/.idea \353\263\265\354\202\254\353\263\270/modules.xml" @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git "a/.idea \353\263\265\354\202\254\353\263\270/vcs.xml" "b/.idea \353\263\265\354\202\254\353\263\270/vcs.xml" new file mode 100644 index 000000000..229def5dc --- /dev/null +++ "b/.idea \353\263\265\354\202\254\353\263\270/vcs.xml" @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 5e319ef27..e0797ed03 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index b1f67fbdc..54beef274 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,11 +2,7 @@ - - - - - + \ No newline at end of file From 30bb186fb666065117525fe09166205598be720d Mon Sep 17 00:00:00 2001 From: space Date: Sat, 9 Dec 2023 15:11:25 +0900 Subject: [PATCH 0052/1029] =?UTF-8?q?[BE]=20FIX:=20xml=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/modules.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/modules.xml b/.idea/modules.xml index 54beef274..c9893ddb8 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,7 @@ + \ No newline at end of file From b0aa10570360915f784cd5cb25a91eb6e38fbbc1 Mon Sep 17 00:00:00 2001 From: space Date: Sat, 9 Dec 2023 15:11:40 +0900 Subject: [PATCH 0053/1029] =?UTF-8?q?[BE]=20FEAT:=20slack=20alarm=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftclub/cabinet/alarm/domain/AlarmEvent.java | 2 ++ .../cabinet/alarm/handler/AlarmEventHandler.java | 13 ++++++++++--- .../cabinet/alarm/handler/SlackAlarmSender.java | 13 ++++++++----- .../cabinet/alarm/slack/SlackApiManager.java | 8 ++++---- .../alarm/slack/config/SlackApiConfig.java | 2 +- .../alarm/slack/config/SlackProperties.java | 10 +++++----- .../cabinet/alarm/slack/dto/SlackUserInfo.java | 15 +++++++-------- config | 2 +- 8 files changed, 38 insertions(+), 27 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java index 9b3994677..4e7d97587 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmEvent.java @@ -1,8 +1,10 @@ package org.ftclub.cabinet.alarm.domain; import lombok.Getter; +import lombok.ToString; @Getter +@ToString public class AlarmEvent { private final Long receiverId; private final Alarm alarm; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index 6d350955c..cd2d2607b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.alarm.handler; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.AlarmType; import org.ftclub.cabinet.exception.ServiceException; @@ -18,7 +19,9 @@ @Component @RequiredArgsConstructor +@Log4j2 public class AlarmEventHandler { + private final UserRepository userRepository; private final SlackAlarmSender slackAlarmSender; private final EmailAlarmSender emailAlarmSender; @@ -26,17 +29,21 @@ public class AlarmEventHandler { @TransactionalEventListener public void handleAlarmEvent(AlarmEvent alarmEvent) { + log.info("alarmEvent = {}", alarmEvent); User receiver = userRepository.findUserWithOptOutById(alarmEvent.getReceiverId()) .orElseThrow(() -> new ServiceException(NOT_FOUND_USER)); Set alarmOptOuts = receiver.getAlarmOptOuts() .stream().map(AlarmOptOut::getAlarmType).collect(Collectors.toSet()); // else-if가 아니어야 하는 것 아닌가? - if (alarmOptOuts.contains(SLACK)) + if (alarmOptOuts.contains(SLACK)) { slackAlarmSender.send(receiver, alarmEvent); - if (alarmOptOuts.contains(EMAIL)) + } + if (alarmOptOuts.contains(EMAIL)) { emailAlarmSender.send(receiver, alarmEvent); - if (alarmOptOuts.contains(PUSH)) + } + if (alarmOptOuts.contains(PUSH)) { pushAlarmSender.send(receiver, alarmEvent); + } } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index a8bb3fcc7..1858e0fb4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -36,10 +36,12 @@ public class SlackAlarmSender { public void send(User user, AlarmEvent alarmEvent) { - log.info("slack alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); + log.info("slack alarm Event : user = {}, alarmEvent = {}", user.getName(), alarmEvent); SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); + log.debug("slackUserInfo = {}", slackUserInfo); String id = slackUserInfo.getId(); + log.debug("slack id = {}", id); if (StringUtils.isEmpty(id)) { throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); } @@ -49,11 +51,12 @@ public void send(User user, AlarmEvent alarmEvent) { } private SlackDto messageParse(Alarm alarm) { + log.debug("alarm = {}", alarm); if (alarm instanceof LentSuccessAlarm) { String building = ((LentSuccessAlarm) alarm).getLocation().getBuilding(); Integer floor = ((LentSuccessAlarm) alarm).getLocation().getFloor(); Integer visibleNum = ((LentSuccessAlarm) alarm).getVisibleNum(); - String body = String.format(alarmProperties.getLentSuccessMailTemplateUrl(), + String body = String.format(alarmProperties.getLentSuccessSlackTemplate(), building + " " + floor + "층 " + visibleNum + "번"); return new SlackDto(body); } else if (alarm instanceof LentExpirationImminentAlarm) { @@ -69,18 +72,18 @@ private SlackDto messageParse(Alarm alarm) { } else if (alarm instanceof ExtensionIssuanceAlarm) { Integer daysToExtend = ((ExtensionIssuanceAlarm) alarm).getDaysToExtend(); String extensionName = ((ExtensionIssuanceAlarm) alarm).getExtensionName(); - String body = String.format(alarmProperties.getExtensionIssuanceMailTemplateUrl(), + String body = String.format(alarmProperties.getExtensionIssuanceSlackTemplate(), daysToExtend, extensionName); return new SlackDto(body); } else if (alarm instanceof ExtensionExpirationImminentAlarm) { String extensionName = ((ExtensionExpirationImminentAlarm) alarm).getExtensionName(); LocalDateTime extensionExpireDate = ((ExtensionExpirationImminentAlarm) alarm).getExtensionExpirationDate(); String body = String.format( - alarmProperties.getExtensionExpirationImminentMailTemplateUrl(), + alarmProperties.getExtensionExpirationImminentSlackTemplate(), extensionName, extensionExpireDate); return new SlackDto(body); } else if (alarm instanceof AnnouncementAlarm) { - String body = alarmProperties.getAnnouncementMailTemplateUrl(); + String body = alarmProperties.getAnnouncementSlackTemplate(); return new SlackDto(body); } else { throw new ServiceException(ExceptionStatus.NOT_FOUND_ALARM); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java index 7ee11817f..03184fce1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java @@ -27,9 +27,7 @@ public class SlackApiManager { private final MethodsClient slackApi; public SlackUserInfo requestSlackUserInfo(String email) { - log.info("Called requestSlackUserInfo email={}", email); - try { SlackResponse slackResponse = slackFeignClient.getUserInfoByEmail( slackProperties.getApplicationForm(), @@ -37,8 +35,10 @@ public SlackUserInfo requestSlackUserInfo(String email) { email); // getOK()인데 Error일 수 있는 부분이 의아합니다. - String RESPONSE_ERROR_MSG = "error"; - if (slackResponse.getOk().equals(RESPONSE_ERROR_MSG)) { + String RESPONSE_ERROR_MESSAGE = "error"; + String RESPONSE_NOT_FOUND = "false"; + String slackResponseCode = slackResponse.getOk(); + if (slackResponseCode.equals(RESPONSE_ERROR_MESSAGE) || slackResponseCode.equals(RESPONSE_NOT_FOUND)) { log.error("Slack Response ERROR Error {} ", slackResponse); throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java index 2a9561b8e..4dac79ee1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java @@ -8,7 +8,7 @@ @Configuration public class SlackApiConfig { - @Value("${slack.token.app-token") + @Value("${alarm.slack.token.app-token}") private String appToken; @Bean diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java index 0db34aeff..52d7709a9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java @@ -11,15 +11,15 @@ public class SlackProperties { private final String applicationForm = "application/x-www-form-urlencoded"; private final String bearer = "Bearer "; - @Value("${slack.token.singing-secret}") + @Value("${alarm.slack.token.singing-secret}") private String singingSecret; - @Value("${slack.token.bot-token") + @Value("${alarm.slack.token.bot-token}") private String botToken; - @Value("${slack.token.app-token") + @Value("${alarm.slack.token.app-token}") private String appToken; - @Value("${slack.channel.cabi") + @Value("${alarm.slack.channel.cabi}") private String cabiChannelId; - @Value("${slack.channel.ramdom") + @Value("${alarm.slack.channel.random}") private String randomChannelId; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java index 7da7fd2d2..15001be79 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackUserInfo.java @@ -5,21 +5,20 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.ToString; @Getter @NoArgsConstructor @AllArgsConstructor +@ToString @JsonIgnoreProperties(ignoreUnknown = true) public class SlackUserInfo { - - // 접근 지정자가 없습니다. - String id; - String name; - // 얘는 뭔가요? + private String id; + private String name; @JsonAlias("real_name") - String realName; + private String realName; @JsonAlias("team_id") - String teamId; - Boolean deleted; + private String teamId; + private Boolean deleted; } diff --git a/config b/config index e5de95b8f..2dda1f7bf 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit e5de95b8fae3a141198e8353039b9d38b1ee961b +Subproject commit 2dda1f7bf676175122c6b926197ffcf593dd8ecb From a6b903b287994e954f4232d94a9c987bd21e6316 Mon Sep 17 00:00:00 2001 From: space Date: Sat, 9 Dec 2023 15:24:13 +0900 Subject: [PATCH 0054/1029] =?UTF-8?q?[BE]=20FIX:=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EB=A1=9C=EA=B7=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index 1858e0fb4..e22d66153 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -39,9 +39,8 @@ public void send(User user, AlarmEvent alarmEvent) { log.info("slack alarm Event : user = {}, alarmEvent = {}", user.getName(), alarmEvent); SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); - log.debug("slackUserInfo = {}", slackUserInfo); String id = slackUserInfo.getId(); - log.debug("slack id = {}", id); + if (StringUtils.isEmpty(id)) { throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); } From 159014d4a928402b98f98250fdbba05e98cbbbee Mon Sep 17 00:00:00 2001 From: space Date: Sat, 9 Dec 2023 15:40:42 +0900 Subject: [PATCH 0055/1029] =?UTF-8?q?[BE]=20REFACTOR:=20slack=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=B2=B4=ED=81=AC=20=EB=B6=80=EB=B6=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=BD=94=EB=A9=98=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/alarm/slack/SlackApiManager.java | 13 +------ .../alarm/slack/dto/SlackResponse.java | 34 +++++++++++++++++-- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java index 03184fce1..4f1706a9c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java @@ -1,13 +1,11 @@ package org.ftclub.cabinet.alarm.slack; -import com.slack.api.Slack; import com.slack.api.methods.MethodsClient; import com.slack.api.methods.SlackApiException; import com.slack.api.methods.request.chat.ChatPostMessageRequest; import feign.FeignException.FeignClientException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.alarm.slack.config.SlackApiConfig; import org.ftclub.cabinet.alarm.slack.config.SlackProperties; import org.ftclub.cabinet.alarm.slack.dto.SlackResponse; import org.ftclub.cabinet.alarm.slack.dto.SlackUserInfo; @@ -33,16 +31,7 @@ public SlackUserInfo requestSlackUserInfo(String email) { slackProperties.getApplicationForm(), slackProperties.getBearer() + slackProperties.getAppToken(), email); - - // getOK()인데 Error일 수 있는 부분이 의아합니다. - String RESPONSE_ERROR_MESSAGE = "error"; - String RESPONSE_NOT_FOUND = "false"; - String slackResponseCode = slackResponse.getOk(); - if (slackResponseCode.equals(RESPONSE_ERROR_MESSAGE) || slackResponseCode.equals(RESPONSE_NOT_FOUND)) { - log.error("Slack Response ERROR Error {} ", slackResponse); - throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); - } - + slackResponse.responseCheck(); return slackResponse.getSlackUserInfo(); } catch (FeignClientException e) { log.error("{}", e.getMessage()); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java index 73a6c7230..8155ffaca 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java @@ -3,13 +3,41 @@ import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +@Log4j2 @Getter @AllArgsConstructor public class SlackResponse { - private final String ok; - @JsonAlias("user") - private final SlackUserInfo slackUserInfo; + private final String ok; + + @JsonAlias("user") + private final SlackUserInfo slackUserInfo; + + private static final String CONFIG_ERROR = "false"; + + private static final String AUTH_ERROR = "error"; + + /** + * config 에러 혹은 키 관련 에러일 경우에도 200 ok 를 줍니다. + * ok 의 값을 확인해야 에러인지 확인할 수 있습니다. + */ + public void responseCheck(){ + log.error("Slack Response ERROR Error {} ", this.toString()); + + if (ok.equals(CONFIG_ERROR)) { + throw new ServiceException(ExceptionStatus.SLACK_REQUEST_BAD_GATEWAY); + } + if (ok.equals(AUTH_ERROR)) { + throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); + } + } + + public boolean hasError() { + return ok.equals(CONFIG_ERROR) || ok.equals(AUTH_ERROR); + } } From 035b330d20895de380a2da2d178be8edfdc49673 Mon Sep 17 00:00:00 2001 From: space Date: Sat, 9 Dec 2023 15:42:45 +0900 Subject: [PATCH 0056/1029] =?UTF-8?q?[BE]=20REFACTOR:=20=EC=BD=94=EB=A9=98?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/alarm/slack/SlackApiManager.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java index 4f1706a9c..eda5ca10f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/SlackApiManager.java @@ -24,6 +24,11 @@ public class SlackApiManager { private final SlackFeignClient slackFeignClient; private final MethodsClient slackApi; + /** + * email 주소로, Slack 고유 ID를 가져옵니다. + * @param email + * @return + */ public SlackUserInfo requestSlackUserInfo(String email) { log.info("Called requestSlackUserInfo email={}", email); try { @@ -42,14 +47,12 @@ public SlackUserInfo requestSlackUserInfo(String email) { public void sendMessage(String channelId, String message) { log.info("Called sendMessage channelId={}, message={}", channelId, message); try { - // token으로 그때그때 instance를 get해오는 게 아니고, 빈으로 등록해서 사용하는게 낫지 않나요? -// slackApi = Slack.getInstance().methods(slackProperties.getAppToken()); - ChatPostMessageRequest request = ChatPostMessageRequest.builder() .channel(channelId) // DM & channel .text(message) .build(); slackApi.chatPostMessage(request); + } catch (SlackApiException | IOException e) { log.error("{}", e.getMessage()); throw new ServiceException(ExceptionStatus.SLACK_MESSAGE_SEND_BAD_GATEWAY); From 1bf2b994c5571d275d13e6397f36bec359c6c9a5 Mon Sep 17 00:00:00 2001 From: Jiwon Park <82518170+Z1Park@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:44:07 +0900 Subject: [PATCH 0057/1029] =?UTF-8?q?Revert=20"[FE]=20REFACTOR=20:=20pendi?= =?UTF-8?q?ngPage=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EC=82=AC=EB=AC=BC=ED=95=A8=20=ED=83=80=EC=9E=85=EB=B3=84=20?= =?UTF-8?q?=ED=86=A0=EA=B8=80=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/firebase/FCMInitializer.java | 4 +- frontend/src/api/axios/axios.custom.ts | 3 +- .../components/AdminInfo/Chart/LineChart.tsx | 2 +- .../CabinetListItem/CabinetListItem.tsx | 7 +- .../components/Common/MultiToggleSwitch.tsx | 82 -------------- frontend/src/pages/Layout.tsx | 4 +- .../src/pages/PendingPage/PendingPage.tsx | 100 +++--------------- .../PendingPage/components/FloorContainer.tsx | 9 +- .../components/PendingCountdown.tsx | 66 ------------ .../pages/PendingPage/components/Timer.tsx | 61 +++++++++++ frontend/src/types/dto/cabinet.dto.ts | 4 - frontend/src/types/enum/time.enum.ts | 7 -- 12 files changed, 97 insertions(+), 252 deletions(-) delete mode 100644 frontend/src/components/Common/MultiToggleSwitch.tsx delete mode 100644 frontend/src/pages/PendingPage/components/PendingCountdown.tsx create mode 100644 frontend/src/pages/PendingPage/components/Timer.tsx delete mode 100644 frontend/src/types/enum/time.enum.ts diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java index 7cf4733a6..f5aa923fb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java +++ b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java @@ -29,7 +29,9 @@ public class FCMInitializer { @PostConstruct public void initialize() throws IOException { - Path currentPath = Paths.get("").toAbsolutePath().normalize(); + Path currentPath = Paths.get(new ClassPathResource("/").getFile().getPath()) + .getParent().getParent().getParent().getParent().getParent() + .toAbsolutePath().normalize(); Resource resource = resourceLoader.getResource("file:" + currentPath + credentialsPath); try (InputStream inputStream = resource.getInputStream()) { FirebaseOptions options = FirebaseOptions.builder() diff --git a/frontend/src/api/axios/axios.custom.ts b/frontend/src/api/axios/axios.custom.ts index a9ac008fd..bfd6280ab 100644 --- a/frontend/src/api/axios/axios.custom.ts +++ b/frontend/src/api/axios/axios.custom.ts @@ -17,6 +17,7 @@ const axiosMyInfoURL = "/v4/users/me"; export const axiosMyInfo = async (): Promise => { try { const response = await instance.get(axiosMyInfoURL); + console.log(response); return response; } catch (error) { throw error; @@ -520,7 +521,7 @@ export const axiosCancel = async (cabinetId: number | null): Promise => { } }; -const axiosGetPendingCabinetsURL = "/v4/cabinets/buildings/새롬관/pending"; +const axiosGetPendingCabinetsURL = "/v4/cabinets/pending"; export const axiosGetPendingCabinets = async (): Promise => { try { const response = await instance.get(axiosGetPendingCabinetsURL); diff --git a/frontend/src/components/AdminInfo/Chart/LineChart.tsx b/frontend/src/components/AdminInfo/Chart/LineChart.tsx index f517bf3c9..f7b2fe829 100644 --- a/frontend/src/components/AdminInfo/Chart/LineChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/LineChart.tsx @@ -58,7 +58,7 @@ const LineChart = ({ data }: { data: IMonthlyData[] }) => ( reverse: false, }} curve={"linear"} - colors={["var(--main-color)", "red"]} + colors={["purple", "red"]} yFormat=" >0" axisRight={null} axisBottom={{ diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx index 2417b760e..44cdebbd8 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -62,9 +62,10 @@ const CabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { ) { if (props.lentType === "PRIVATE") cabinetLabelText = props.name; else if (props.lentType === "SHARE") { - cabinetLabelText = !!props.title - ? props.title - : `${props.userCount} / ${props.maxUser}`; + cabinetLabelText = + !!props.title + ? props.title + : `${props.userCount} / ${props.maxUser}`; } else if (props.lentType === "CLUB") cabinetLabelText = props.title ? props.title : "동아리"; } else { diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx deleted file mode 100644 index 3f48ce838..000000000 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useEffect } from "react"; -import styled from "styled-components"; - -interface toggleItem { - name: string; - key: string; -} - -interface MultiToggleSwitchProps { - initialState: T; // 초기값 - setState: React.Dispatch>; // 상태를 변경하는 dispatcher - toggleList: toggleItem[]; // 토글 리스트 -} - -const MultiToggleSwitch = ({ - initialState, - setState, - toggleList, -}: MultiToggleSwitchProps) => { - useEffect(() => { - const buttons = document.querySelectorAll("button"); - - buttons.forEach((button) => { - if (button.className === initialState) { - button.style.color = "white"; - button.style.backgroundColor = "var(--main-color)"; - } - }); - }, []); - - function switchToggle(e: any) { - const target = e.target; - - if (target === e.currentTarget) return; - - const buttons = document.querySelectorAll("button"); - - buttons.forEach((button) => { - button.style.color = "black"; - button.style.backgroundColor = "transparent"; - }); - - target.style.color = "white"; - target.style.backgroundColor = "var(--main-color)"; - - setState(target.className); - } - - return ( - - {toggleList.map((item) => ( - - ))} - - ); -}; - -const WrapperStyled = styled.div` - width: fit-content; - display: flex; - align-items: center; - background-color: var(--lightgray-color); - - border-radius: 10px; - button { - display: flex; - justify-content: center; - align-items: center; - width: fit-content; - min-width: 50px; - border-radius: 10px; - font-size: 0.9rem; - height: 30px; - font-weight: 500; - background-color: transparent; - color: black; - } -`; - -export default MultiToggleSwitch; diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index dbbf5955a..f19d4628c 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -47,8 +47,8 @@ const Layout = (): JSX.Element => { data: { date: serverTime }, } = await axiosMyInfo(); - const formattedServerTime = serverTime.split(" KST")[0]; // 서버 시간을 Date 객체로 변환하기 위해 KST 제거 - setServerTime(new Date(formattedServerTime)); // 접속 후(세션 초기화 후) 최초 서버 시간을 가져옴 + const formattedServerTime = serverTime.split(" KST")[0]; + setServerTime(new Date(formattedServerTime)); // 접속 후 최초 서버 시간을 가져옴 setMyInfoData(myInfo); setUser(myInfo); setIsValidToken(true); diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 1cafbc3f0..0a76aa82e 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -3,40 +3,16 @@ import { useRecoilState } from "recoil"; import styled from "styled-components"; import { isCurrentSectionRenderState } from "@/recoil/atoms"; import FloorContainer from "@/pages/PendingPage/components/FloorContainer"; -import PendingCountdown from "@/pages/PendingPage/components/PendingCountdown"; +import Timer from "@/pages/PendingPage/components/Timer"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import MultiToggleSwitch from "@/components/Common/MultiToggleSwitch"; -import { - CabinetPreviewInfo, - PendingCabinetsInfo, -} from "@/types/dto/cabinet.dto"; +import { CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; import { axiosGetPendingCabinets } from "@/api/axios/axios.custom"; import useDebounce from "@/hooks/useDebounce"; -enum PendingCabinetsType { - ALL = "ALL", - PRIVATE = "PRIVATE", - SHARE = "SHARE", -} - -const toggleList = [ - { name: "전체", key: PendingCabinetsType.ALL }, - { name: "개인", key: PendingCabinetsType.PRIVATE }, - { name: "공유", key: PendingCabinetsType.SHARE }, -]; - const PendingPage = () => { - const [toggleType, setToggleType] = useState( - PendingCabinetsType.ALL - ); - const [cabinets, setCabinets] = useState({}); - const [pendingCabinets, setPendingCabinets] = useState( - {} - ); - const [privateCabinets, setPrivateCabinets] = useState( - {} - ); - const [sharedCabinets, setSharedCabinets] = useState({}); + const [pendingCabinets, setPendingCabinets] = useState< + CabinetPreviewInfo[][] + >([[]]); const [isLoaded, setIsLoaded] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [isOpenTime, setIsOpenTime] = useState(false); @@ -45,33 +21,13 @@ const PendingPage = () => { ); const { debounce } = useDebounce(); + const isShowingLoadingAnimation = !isRefreshing && isLoaded; + const getPendingCabinets = async () => { try { const response = await axiosGetPendingCabinets(); const pendingCabinets = response.data.cabinetInfoResponseDtos; - - const filterCabinetsByType = (type: string) => - Object.fromEntries( - Object.entries(pendingCabinets).map(([key, cabinets]: any) => [ - key, - cabinets.filter( - (cabinet: CabinetPreviewInfo) => cabinet.lentType === type - ), - ]) - ); - - const privateCabinets = filterCabinetsByType(PendingCabinetsType.PRIVATE); - const sharedCabinets = filterCabinetsByType(PendingCabinetsType.SHARE); - - const updatedCabinets = - toggleType === PendingCabinetsType.ALL - ? pendingCabinets - : filterCabinetsByType(toggleType); - - setCabinets(updatedCabinets); setPendingCabinets(pendingCabinets); - setPrivateCabinets(privateCabinets); - setSharedCabinets(sharedCabinets); } catch (error) { throw error; } @@ -91,7 +47,7 @@ const PendingPage = () => { useEffect(() => { setTimeout(() => { - // 새로고침 광클 방지를 위한 초기 로딩 딜레이 + // 새로고침 광클 방지를 위한 딜레이 setIsLoaded(true); }, 500); }, []); @@ -109,39 +65,20 @@ const PendingPage = () => { } }, [isOpenTime]); - useEffect(() => { - if (toggleType === PendingCabinetsType.ALL) setCabinets(pendingCabinets); - else if (toggleType === PendingCabinetsType.PRIVATE) - setCabinets(privateCabinets); - else if (toggleType === PendingCabinetsType.SHARE) - setCabinets(sharedCabinets); - }, [toggleType]); - return ( - - - 사용 가능 사물함

매일 오후 1시 사용 가능한 사물함이 업데이트됩니다.

- {isRefreshing ? ( - - ) : ( - 새로고침 - )} + 새로고침
- setIsOpenTime(true)} /> - {isLoaded && cabinets ? ( - Object.entries(cabinets).map(([key, value]) => ( + setIsOpenTime(true)} /> + {isShowingLoadingAnimation && pendingCabinets ? ( + Object.entries(pendingCabinets).map(([key, value]) => ( ` font-size: 1.1rem; color: var(--black); padding-left: 5px; - padding-right: 5px; + border-bottom: 1.5px solid #d9d9d9; + div { + } button { z-index: 2; height: 30px; @@ -67,6 +69,7 @@ const FloorTitleStyled = styled.h2<{ isToggled: boolean }>` background: url(/src/assets/images/select.svg) no-repeat 100%; transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; + margin-right: 5px; } `; @@ -99,4 +102,8 @@ const NoPendingCabinetMessageStyled = styled.div<{ isToggled: boolean }>` } `; +const LoadingContainerStyled = styled.div` + margin-top: 30px; +`; + export default FloorContainer; diff --git a/frontend/src/pages/PendingPage/components/PendingCountdown.tsx b/frontend/src/pages/PendingPage/components/PendingCountdown.tsx deleted file mode 100644 index 51c2ed711..000000000 --- a/frontend/src/pages/PendingPage/components/PendingCountdown.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useEffect, useState } from "react"; -import { useRecoilState } from "recoil"; -import styled from "styled-components"; -import { serverTimeState } from "@/recoil/atoms"; -import Time from "@/types/enum/time.enum"; - -const openTime = new Date(); -openTime.setHours(13, 0, 0, 0); // 오픈 시간인 13:00:00(오후 1시)로 설정 - -const hours24 = 86400000; // 24시간을 밀리초로 표현 - -const PendingCountdown = ({ - observeOpenTime, -}: { - observeOpenTime: () => void; -}) => { - const [serverTime] = useRecoilState(serverTimeState); - const [remainingTime, setRemainingTime] = useState(hours24); // 기본 24시로 초기화(처음 함수 호출을 위한 값. 큰 의미 없음) - - const hours = Math.floor(remainingTime / 3600000); - const minutes = Math.floor((remainingTime % 3600000) / 60000); - const seconds = Math.floor((remainingTime % 60000) / 1000); - - useEffect(() => { - if (serverTime.toLocaleTimeString() === Time.PENDING_OPEN) - observeOpenTime(); // 오픈 시간이 되면 업데이트 된 사물함 정보를 가져옴 - if (remainingTime !== 0) setRemainingTime(getRemainingTime()); // 이미 오픈했으면 OPEN으로 표시 - }, [serverTime]); - - function getRemainingTime() { - let timeRemains; - - timeRemains = openTime.getTime() - serverTime.getTime(); - - if (openTime.getTime() < serverTime.getTime()) timeRemains += hours24; // 24시간을 더해줌 - - if (timeRemains < 0) return -timeRemains; - return timeRemains; - } - - return ( - <> - - - {remainingTime === 0 - ? "OPEN" - : `${hours}시간 ${minutes}분 ${seconds}초`} - - - ); -}; - -const PendingCountdownIconStyled = styled.img` - height: 25px; - width: 25px; - margin-top: 50px; -`; - -const PendingCountdownStyled = styled.div` - margin-top: 5px; - color: var(--main-color); - font-size: 1.8rem; - font-weight: 600; -`; - -export default PendingCountdown; diff --git a/frontend/src/pages/PendingPage/components/Timer.tsx b/frontend/src/pages/PendingPage/components/Timer.tsx new file mode 100644 index 000000000..3091f5f48 --- /dev/null +++ b/frontend/src/pages/PendingPage/components/Timer.tsx @@ -0,0 +1,61 @@ +import { useEffect, useState } from "react"; +import { useRecoilState } from "recoil"; +import styled from "styled-components"; +import { s } from "vitest/dist/env-afee91f0"; +import { serverTimeState } from "@/recoil/atoms"; + +const openTime = "오후 1:00:00"; + +const Timer = ({ observeOpenTime }: { observeOpenTime: () => void }) => { + const [serverTime] = useRecoilState(serverTimeState); + const [remainingTime, setRemainingTime] = useState(86400000); // 기본 24시로 초기화 + + useEffect(() => { + if (serverTime.toLocaleTimeString() === openTime) observeOpenTime(); + setRemainingTime(calculateRemainingTime()); + }, [serverTime]); + + function calculateRemainingTime() { + if (remainingTime === 0) return 0; // 이미 오픈했으면 OPEN으로 표시 + const openTime = new Date(); + openTime.setHours(13, 0, 0, 0); // 13:00:00로 설정 + + let timeDiff; + + timeDiff = openTime.getTime() - serverTime.getTime(); + if (openTime.getTime() < serverTime.getTime()) timeDiff += 86400000; // 24시간을 더해줌 + + if (timeDiff < 0) return -timeDiff; + return timeDiff; + } + + const hours = Math.floor(remainingTime / 3600000); + const minutes = Math.floor((remainingTime % 3600000) / 60000); + const seconds = Math.floor((remainingTime % 60000) / 1000); + + return ( + <> + + + {remainingTime === 0 + ? "OPEN" + : `${hours}시간 ${minutes}분 ${seconds}초`} + + + ); +}; + +const TimerIconStyled = styled.img` + height: 25px; + width: 25px; + margin-top: 50px; +`; + +const TimerStyled = styled.div` + margin-top: 5px; + color: var(--main-color); + font-size: 1.8rem; + font-weight: 600; +`; + +export default Timer; diff --git a/frontend/src/types/dto/cabinet.dto.ts b/frontend/src/types/dto/cabinet.dto.ts index 3979d45ea..e29e67987 100644 --- a/frontend/src/types/dto/cabinet.dto.ts +++ b/frontend/src/types/dto/cabinet.dto.ts @@ -58,7 +58,3 @@ export interface CabinetInfoByBuildingFloorDto { section: string; // swagger의 CabinetPerSectionDto에 맞추어 object -> string으로 수정했습니다. cabinets: CabinetPreviewInfo[]; } - -export interface PendingCabinetsInfo { - [key: string]: CabinetPreviewInfo[]; -} diff --git a/frontend/src/types/enum/time.enum.ts b/frontend/src/types/enum/time.enum.ts deleted file mode 100644 index 421de833e..000000000 --- a/frontend/src/types/enum/time.enum.ts +++ /dev/null @@ -1,7 +0,0 @@ -enum Time { - PENDING_OPEN = "오후 1:00:00", -} - -//Date 객체 .toLocaleTimeString() 반환값 기준 - -export default Time; From 90b3b9af21cb15c6091524634d696c0048b4069c Mon Sep 17 00:00:00 2001 From: hyungseok Date: Mon, 11 Dec 2023 19:47:23 +0900 Subject: [PATCH 0058/1029] [FE] before pull dev --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index e755c38d0..2dda1f7bf 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit e755c38d06b1e5ed2801b61f3bbd75ec39f633f7 +Subproject commit 2dda1f7bf676175122c6b926197ffcf593dd8ecb From fbdf66167447b4f5a5d5243275c8808b5e64240d Mon Sep 17 00:00:00 2001 From: jiwon Date: Mon, 11 Dec 2023 20:24:29 +0900 Subject: [PATCH 0059/1029] =?UTF-8?q?[BE]=20properties=20=EC=95=88?= =?UTF-8?q?=EB=A7=9E=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?-=20alarm.mail...,=20hane.limit-hours?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CabinetComplexRepositoryImpl.java | 2 +- .../java/org/ftclub/cabinet/config/HaneProperties.java | 2 +- .../ftclub/cabinet/config/MailOverdueProperties.java | 10 +++++----- .../ftclub/cabinet/utils/release/ReleaseManager.java | 3 +-- config | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepositoryImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepositoryImpl.java index 32f4f32d1..038c83c58 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepositoryImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepositoryImpl.java @@ -65,7 +65,7 @@ public List findCabinetsActiveLentHistoriesByBuilding @Override public List findAllCabinetsByCabinetStatusAndBeforeEndedAt(CabinetStatus cabinetStatus, LocalDateTime currentDate) { - //LentHistory 에서, 오늘날짜 이전의 endedAt이면서, 중복되지 않는 레코드 와 cabinet 을 join 해서 가져온다. + //LentHistory 에서, 오늘날짜 이전의 endedAt이면서, 중복되지 않는 레코드와 cabinet 을 join 해서 가져온다. //cabinetStatus 는 PENDING 이어야한다. return queryFactory.selectFrom(cabinet) .join(lentHistory) diff --git a/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java index ffc5bd3b8..3a29be9b2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java @@ -14,6 +14,6 @@ public class HaneProperties { @Value("${spring.hane.url}") private String url; - @Value("${spring.hane.limit-time}") + @Value("${spring.hane.limit-hours}") private int limit_time; } diff --git a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java index d5380d0af..7eeab81c1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java @@ -8,18 +8,18 @@ @Getter public class MailOverdueProperties { - @Value("${spring.mail.soonoverdue.term}") + @Value("${alarm.mail.soonoverdue.term}") private Long soonOverdueTerm; - @Value("${spring.mail.overdue.subject}") + @Value("${alarm.mail.overdue.subject}") private String overdueMailSubject; - @Value("${spring.mail.overdue.template}") + @Value("${alarm.mail.overdue.template}") private String overdueMailTemplateUrl; - @Value("${spring.mail.soonoverdue.subject}") + @Value("${alarm.mail.soonoverdue.subject}") private String soonOverdueMailSubject; - @Value("${spring.mail.soonoverdue.template}") + @Value("${alarm.mail.soonoverdue.template}") private String soonOverdueMailTemplateUrl; } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java index 9d3081d98..7d8c67329 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java @@ -21,8 +21,7 @@ public class ReleaseManager { private List getAllPendedYesterdayCabinet() { return cabinetOptionalFetcher.findAllPendingCabinetsByCabinetStatusAndBeforeEndedAt( - CabinetStatus.PENDING, LocalDateTime.from( - LocalDate.now().atStartOfDay())); + CabinetStatus.PENDING, LocalDateTime.from(LocalDate.now().atStartOfDay())); } private void releaseCabinets(List cabinets) { diff --git a/config b/config index 93733e27b..2dda1f7bf 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 93733e27b56328ac837f6fdbd63535a917fad3c1 +Subproject commit 2dda1f7bf676175122c6b926197ffcf593dd8ecb From 805936c93ff0fe589240ad718a3ea13857de0a11 Mon Sep 17 00:00:00 2001 From: jiwon Date: Mon, 11 Dec 2023 21:42:59 +0900 Subject: [PATCH 0060/1029] =?UTF-8?q?[BE]=20test=20=EA=B9=A8=EC=A7=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(FCM=20json=20=EC=A0=9C=EC=99=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/test/resources/application.yml | 75 +++++++++++----------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index b3eae72db..5491b1b07 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -83,30 +83,8 @@ spring: password: master_password domain: cabi.42seoul.io email: ${spring.oauth2.master.id}@${spring.oauth2.master.domain} - mail: display-sender-name: "Cabi" - lentSuccess: - template: "mail/lentsuccess" - subject: "Cabi 사물함 대여 성공 알림" - soonOverdue: - term: -1 - template: "mail/soonoverdue" - subject: "Cabi 사물함 연체 예정 알림" - overdue: - template: "mail/overdue" - subject: "Cabi 사물함 연체 알림" - extensionIssuance: - template: "mail/extensionIssuance" - subject: "Cabi 사물함 연장권 발급 알림" - extensionExpiration: - term: -1 - template: "mail/extensionExpiration" - subject: "Cabi 사물함 연장권 만료 알림" - announcement: - template: "mail/announcement" - subject: "Cabi 공지사항 안내" - host: smtp.gmail.com port: 587 username: example@gmail.com @@ -118,20 +96,6 @@ spring: starttls: enable: true - fcm: - lentSuccess: - template: "%s 자리의 사물함 대여에 성공했습니다." - soonOverdue: - template: "대여한 사물함이 %d일 후에 연체됩니다." - overdue: - template: "대여한 사물함이 %d일 연체되었습니다." - extensionIssuance: - template: "%d일 짜리 %s 연장권이 발급 되었습니다." - extensionExpiration: - template: "%s 연장권이 %t일에 만료됩니다." - announcement: - template: "새로운 공지사항이 있으니 확인해주세요." - cabinet: in-session: term: 10 @@ -163,7 +127,7 @@ spring: hane: url: https://api.24hoursarenotenough.42seoul.kr/ext/cabi42/permonth token: jwt_secret_key_must_be_long_jwt_secret_key_must_be_long_jwt_secret_key_must_be_long_jwt_secret_key_must_be_long - limit-time: 432000 + limit-hours: 432000 schedule: cron: leave-absence: 0 0 0 * * * # 매일 0시 0분 0초 @@ -174,6 +138,43 @@ spring: extension-delete-time: 30 59 23 L * ? # 매월 마지막날 23시 59분 30초 extension-issue-time: 0 0 0 2 * * # 매월 2일 0시 0분 0초 +alarm: + mail: + lentSuccess: + template: "mail/lentsuccess" + subject: "Cabi 사물함 대여 성공 알림" + soonOverdue: + term: -1 + template: "mail/soonoverdue" + subject: "Cabi 사물함 연체 예정 알림" + overdue: + template: "mail/overdue" + subject: "Cabi 사물함 연체 알림" + extensionIssuance: + template: "mail/extensionIssuance" + subject: "Cabi 사물함 연장권 발급 알림" + extensionExpiration: + term: -1 + template: "mail/extensionExpiration" + subject: "Cabi 사물함 연장권 만료 알림" + announcement: + template: "mail/announcement" + subject: "Cabi 공지사항 안내" + + fcm: + lentSuccess: + template: "%s 자리의 사물함 대여에 성공했습니다." + soonOverdue: + template: "대여한 사물함이 %d일 후에 연체됩니다." + overdue: + template: "대여한 사물함이 %d일 연체되었습니다." + extensionIssuance: + template: "%d일 짜리 %s 연장권이 발급 되었습니다." + extensionExpiration: + template: "%s 연장권이 %t일에 만료됩니다." + announcement: + template: "새로운 공지사항이 있으니 확인해주세요." + management: server: port: 2424 From a6d2feeb048ebb733cea6ec9f4ac0f1115098c20 Mon Sep 17 00:00:00 2001 From: jiwon Date: Mon, 11 Dec 2023 21:52:53 +0900 Subject: [PATCH 0061/1029] =?UTF-8?q?[BE]=20FCMInitializer=20=EB=A1=A4?= =?UTF-8?q?=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/ftclub/cabinet/firebase/FCMInitializer.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java index f5aa923fb..7cf4733a6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java +++ b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java @@ -29,9 +29,7 @@ public class FCMInitializer { @PostConstruct public void initialize() throws IOException { - Path currentPath = Paths.get(new ClassPathResource("/").getFile().getPath()) - .getParent().getParent().getParent().getParent().getParent() - .toAbsolutePath().normalize(); + Path currentPath = Paths.get("").toAbsolutePath().normalize(); Resource resource = resourceLoader.getResource("file:" + currentPath + credentialsPath); try (InputStream inputStream = resource.getInputStream()) { FirebaseOptions options = FirebaseOptions.builder() From 2cdd3ce3c6194527cdf289dbfe1616a4e4cb6750 Mon Sep 17 00:00:00 2001 From: jiwon Date: Mon, 11 Dec 2023 22:22:43 +0900 Subject: [PATCH 0062/1029] =?UTF-8?q?[BE]=20FCMInitializer=20test=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/firebase/FCMInitializer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java index 7cf4733a6..3710a1117 100644 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java +++ b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java @@ -29,7 +29,10 @@ public class FCMInitializer { @PostConstruct public void initialize() throws IOException { - Path currentPath = Paths.get("").toAbsolutePath().normalize(); + Path currentPath = Paths.get("").toAbsolutePath(); + if (currentPath.endsWith("backend")) { + currentPath = currentPath.getParent(); + } Resource resource = resourceLoader.getResource("file:" + currentPath + credentialsPath); try (InputStream inputStream = resource.getInputStream()) { FirebaseOptions options = FirebaseOptions.builder() From cce9eed0015ef7fc0e7e88500fdfc534b5d4a229 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Tue, 12 Dec 2023 10:09:31 +0900 Subject: [PATCH 0063/1029] =?UTF-8?q?fix=20:=20dev=20config=20=EC=84=9C?= =?UTF-8?q?=EB=B8=8C=EB=AA=A8=EB=93=88=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 2dda1f7bf..e755c38d0 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 2dda1f7bf676175122c6b926197ffcf593dd8ecb +Subproject commit e755c38d06b1e5ed2801b61f3bbd75ec39f633f7 From 144ae6deb75f31caa90d5d03533d642495fc32cb Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Tue, 12 Dec 2023 10:57:40 +0900 Subject: [PATCH 0064/1029] =?UTF-8?q?fix=20:=20typo=20=EC=88=98=EC=A0=95,?= =?UTF-8?q?=20FCMInitializer=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=8B=9C?= =?UTF-8?q?=EC=97=90=20=EC=A3=BC=EC=9E=85=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=A0=9C=EC=99=B8,=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20alarm=20profile?= =?UTF-8?q?=EC=9D=84=20=EB=A9=94=EC=9D=B8=20yml=EC=97=90=EC=84=9C=20import?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/config/MailOverdueProperties.java | 10 ++-- .../cabinet/firebase/FCMInitializer.java | 4 +- backend/src/main/resources/application.yml | 6 +-- backend/src/test/resources/application.yml | 47 +++++++++---------- config | 2 +- 5 files changed, 33 insertions(+), 36 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java index 7eeab81c1..7032a4d05 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java @@ -8,18 +8,18 @@ @Getter public class MailOverdueProperties { - @Value("${alarm.mail.soonoverdue.term}") + @Value("${spring.mail.soonOverdue.term}") private Long soonOverdueTerm; - @Value("${alarm.mail.overdue.subject}") + @Value("${spring.mail.overdue.subject}") private String overdueMailSubject; - @Value("${alarm.mail.overdue.template}") + @Value("${spring.mail.overdue.template}") private String overdueMailTemplateUrl; - @Value("${alarm.mail.soonoverdue.subject}") + @Value("${spring.mail.overdue.subject}") private String soonOverdueMailSubject; - @Value("${alarm.mail.soonoverdue.template}") + @Value("${spring.mail.overdue.template}") private String soonOverdueMailTemplateUrl; } diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java index 3710a1117..ddd585695 100644 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java +++ b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java @@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.ClassPathResource; +import org.springframework.context.annotation.Profile; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; @@ -20,6 +20,7 @@ @Slf4j @Component @RequiredArgsConstructor +@Profile("!test") public class FCMInitializer { private final ResourceLoader resourceLoader; @@ -28,7 +29,6 @@ public class FCMInitializer { @PostConstruct public void initialize() throws IOException { - Path currentPath = Paths.get("").toAbsolutePath(); if (currentPath.endsWith("backend")) { currentPath = currentPath.getParent(); diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index d3bf9b1da..74a268261 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -4,7 +4,7 @@ server: port: 4242 spring: config: - import: classpath:application-auth.yml, classpath:application-alarm.yml + import: classpath:application-auth.yml activate: on-profile: prod logging: @@ -16,7 +16,7 @@ server: port: 4242 spring: config: - import: classpath:application-auth.yml, classpath:application-alarm.yml + import: classpath:application-auth.yml activate: on-profile: dev logging: @@ -28,7 +28,7 @@ server: port: 2424 spring: config: - import: classpath:application-auth.yml, classpath:application-alarm.yml + import: classpath:application-auth.yml activate: on-profile: local logging: diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index 5491b1b07..2b265ab4d 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -95,6 +95,26 @@ spring: auth: true starttls: enable: true + lentSuccess: + template: "mail/lentsuccess" + subject: "Cabi 사물함 대여 성공 알림" + soonOverdue: + term: -1 + template: "mail/soonoverdue" + subject: "Cabi 사물함 연체 예정 알림" + overdue: + template: "mail/overdue" + subject: "Cabi 사물함 연체 알림" + extensionIssuance: + template: "mail/extensionIssuance" + subject: "Cabi 사물함 연장권 발급 알림" + extensionExpiration: + term: -1 + template: "mail/extensionExpiration" + subject: "Cabi 사물함 연장권 만료 알림" + announcement: + template: "mail/announcement" + subject: "Cabi 공지사항 안내" cabinet: in-session: @@ -138,29 +158,6 @@ spring: extension-delete-time: 30 59 23 L * ? # 매월 마지막날 23시 59분 30초 extension-issue-time: 0 0 0 2 * * # 매월 2일 0시 0분 0초 -alarm: - mail: - lentSuccess: - template: "mail/lentsuccess" - subject: "Cabi 사물함 대여 성공 알림" - soonOverdue: - term: -1 - template: "mail/soonoverdue" - subject: "Cabi 사물함 연체 예정 알림" - overdue: - template: "mail/overdue" - subject: "Cabi 사물함 연체 알림" - extensionIssuance: - template: "mail/extensionIssuance" - subject: "Cabi 사물함 연장권 발급 알림" - extensionExpiration: - term: -1 - template: "mail/extensionExpiration" - subject: "Cabi 사물함 연장권 만료 알림" - announcement: - template: "mail/announcement" - subject: "Cabi 공지사항 안내" - fcm: lentSuccess: template: "%s 자리의 사물함 대여에 성공했습니다." @@ -198,8 +195,8 @@ webhook: firebase: messaging: credentials: - path: "/config/backend/src/main/resources/local/firebase-example.json" - + path: "/config/backend/src/test/resources/firebase-example.json" + slack: token: singing_secret: signing_secret diff --git a/config b/config index e755c38d0..658dfe55d 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit e755c38d06b1e5ed2801b61f3bbd75ec39f633f7 +Subproject commit 658dfe55d85539f248ff982da20e9f44693866c6 From d880c3b9e57f00869e1d456aca329f579453cdb5 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Tue, 12 Dec 2023 11:01:01 +0900 Subject: [PATCH 0065/1029] =?UTF-8?q?hotfix=20:=20properties=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=9E=98=EB=AA=BB=20=EC=A3=BC=EC=9E=85=EB=90=98?= =?UTF-8?q?=EB=8A=94=20value=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftclub/cabinet/config/MailOverdueProperties.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java index 7032a4d05..7cdbfd018 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java @@ -11,15 +11,15 @@ public class MailOverdueProperties { @Value("${spring.mail.soonOverdue.term}") private Long soonOverdueTerm; - @Value("${spring.mail.overdue.subject}") - private String overdueMailSubject; + @Value("${spring.mail.soonOverdue.subject}") + private String soonOverdueMailSubject; - @Value("${spring.mail.overdue.template}") - private String overdueMailTemplateUrl; + @Value("${spring.mail.soonOverdue.template}") + private String soonOverdueMailTemplateUrl; @Value("${spring.mail.overdue.subject}") - private String soonOverdueMailSubject; + private String overdueMailSubject; @Value("${spring.mail.overdue.template}") - private String soonOverdueMailTemplateUrl; + private String overdueMailTemplateUrl; } From 9c979a417e3c4218d1d7cfd7f2a07d3c64c27d2a Mon Sep 17 00:00:00 2001 From: eunbi9n Date: Tue, 12 Dec 2023 14:30:17 +0900 Subject: [PATCH 0066/1029] =?UTF-8?q?[BE]=20FIX:=20config=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 658dfe55d..47056e2e5 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 658dfe55d85539f248ff982da20e9f44693866c6 +Subproject commit 47056e2e503d727d127d4757e150ca9887ae95e3 From ebe93ad25860d9678a945e6d8348ab41003d9a2d Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Tue, 12 Dec 2023 15:44:50 +0900 Subject: [PATCH 0067/1029] =?UTF-8?q?[BE]=20FIX:=20config=20=EC=A3=BC?= =?UTF-8?q?=EC=9E=85=20=EC=98=A4=EB=A5=98=20&=20spring.=20->=20cabinet.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/config/CabinetProperties.java | 18 +++++++------- .../cabinet/config/DomainProperties.java | 16 ++++++------- .../cabinet/config/FtApiProperties.java | 24 +++++++++---------- .../cabinet/config/GmailProperties.java | 4 ++-- .../cabinet/config/GoogleApiProperties.java | 22 ++++++++--------- .../ftclub/cabinet/config/HaneProperties.java | 6 ++--- .../ftclub/cabinet/config/JwtProperties.java | 12 +++++----- .../cabinet/config/MailOverdueProperties.java | 10 ++++---- .../cabinet/config/MasterProperties.java | 8 +++---- .../service/LentExtensionServiceImpl.java | 2 +- .../utils/scheduler/SystemScheduler.java | 10 ++++---- config | 2 +- 12 files changed, 67 insertions(+), 67 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java index 7489512a5..dfaf5db39 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java @@ -8,22 +8,22 @@ @Getter public class CabinetProperties { - @Value("${spring.cabinet.lent.term.private}") + @Value("${cabinet.policy.lent.term.private}") private Integer lentTermPrivate; - @Value("${spring.cabinet.lent.term.share}") + @Value("${cabinet.policy.lent.term.share}") private Integer lentTermShare; - @Value("${spring.cabinet.lent.term.share-basic}") + @Value("${cabinet.policy.lent.term.share-basic}") private Integer lentTermShareBasic; - @Value("${spring.cabinet.lent.term.extend}") + @Value("${cabinet.policy.lent.term.extend}") private Integer lentExtendTerm; - @Value("${spring.cabinet.penalty.day.share}") + @Value("${cabinet.policy.penalty.day.share}") private Integer penaltyDayShare; - @Value("${spring.cabinet.penalty.day.padding}") + @Value("${cabinet.policy.penalty.day.padding}") private Integer penaltyDayPadding; - @Value("${spring.cabinet.lent.limit.share.min-user-count}") + @Value("${cabinet.policy.lent.limit.share.min-user-count}") private Long shareMinUserCount; - @Value("${spring.cabinet.lent.limit.share.max-user-count}") + @Value("${cabinet.policy.lent.limit.share.max-user-count}") private Long shareMaxUserCount; - @Value("${spring.cabinet.in-session.term}") + @Value("${cabinet.policy.in-session.term}") private Integer inSessionTerm; } diff --git a/backend/src/main/java/org/ftclub/cabinet/config/DomainProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/DomainProperties.java index 70f8cb5b9..1f1cc2848 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/DomainProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/DomainProperties.java @@ -8,28 +8,28 @@ @Getter public class DomainProperties { - @Value("${spring.oauth2.domain-name.cookie-domain}") + @Value("${cabinet.domain-name.cookie-domain}") private String cookieDomain; - @Value("${spring.oauth2.domain-name.local}") + @Value("${cabinet.domain-name.local}") private String local; - @Value("${spring.oauth2.domain-name.dev}") + @Value("${cabinet.domain-name.dev}") private String dev; - @Value("${spring.oauth2.domain-name.main}") + @Value("${cabinet.domain-name.main}") private String main; - @Value("${spring.server.be-host}") + @Value("${cabinet.server.be-host}") private String beHost; - @Value("${spring.server.fe-host}") + @Value("${cabinet.server.fe-host}") private String feHost; - @Value("${spring.oauth2.domain-name.admin-email}") + @Value("${cabinet.domain-name.admin-email}") private String adminEmailDomain; - @Value("${spring.oauth2.domain-name.user-email}") + @Value("${cabinet.domain-name.user-email}") private String userEmailDomain; } diff --git a/backend/src/main/java/org/ftclub/cabinet/config/FtApiProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/FtApiProperties.java index 0688fbf1f..783d50d15 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/FtApiProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/FtApiProperties.java @@ -8,39 +8,39 @@ @Getter public class FtApiProperties implements ApiProperties { - @Value("${spring.oauth2.client.registration.ft.name}") + @Value("${cabinet.oauth2.client.registration.ft.name}") private String providerName; - @Value("${spring.auth.ft.client-id}") + @Value("${cabinet.oauth2.auth.ft.client-id}") private String clientId; - @Value("${spring.auth.ft.client-secret}") + @Value("${cabinet.oauth2.auth.ft.client-secret}") private String clientSecret; - @Value("${spring.urls.user-login-callback}") + @Value("${cabinet.oauth2.urls.user-login-callback}") private String redirectUri; - @Value("${spring.oauth2.client.registration.ft.grant-type}") + @Value("${cabinet.oauth2.client.registration.ft.grant-type}") private String grantType; - @Value("${spring.oauth2.client.registration.ft.access-token-name}") + @Value("${cabinet.oauth2.client.registration.ft.access-token-name}") private String accessTokenName; - @Value("${spring.oauth2.client.registration.ft.token-grant-type}") + @Value("${cabinet.oauth2.client.registration.ft.token-grant-type}") private String tokenGrantType; - @Value("${spring.oauth2.client.registration.provider.ft.token-uri}") + @Value("${cabinet.oauth2.client.registration.provider.ft.token-uri}") private String tokenUri; - @Value("${spring.oauth2.client.registration.provider.ft.authorization-uri}") + @Value("${cabinet.oauth2.client.registration.provider.ft.authorization-uri}") private String authUri; - @Value("${spring.oauth2.client.registration.provider.ft.user-info-uri}") + @Value("${cabinet.oauth2.client.registration.provider.ft.user-info-uri}") private String userInfoUri; - @Value("${spring.oauth2.client.registration.provider.ft.users-info-uri}") + @Value("${cabinet.oauth2.client.registration.provider.ft.users-info-uri}") private String usersInfoUri; - @Value("${spring.oauth2.client.registration.ft.scope}") + @Value("${cabinet.oauth2.client.registration.ft.scope}") private String scope; } diff --git a/backend/src/main/java/org/ftclub/cabinet/config/GmailProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/GmailProperties.java index 51b22f28d..634b2689f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/GmailProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/GmailProperties.java @@ -8,7 +8,7 @@ @Getter public class GmailProperties { - @Value("${spring.production}") + @Value("${cabinet.production}") private Boolean isProduction; @Value("${spring.mail.host}") @@ -17,7 +17,7 @@ public class GmailProperties { @Value("${spring.mail.port}") private int mailServerPort; - @Value("${spring.mail.display-sender-name}") + @Value("${cabinet.alarm.mail.display-sender-name}") private String displaySenderName; @Value("${spring.mail.username}") diff --git a/backend/src/main/java/org/ftclub/cabinet/config/GoogleApiProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/GoogleApiProperties.java index bd16d31be..dea822b72 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/GoogleApiProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/GoogleApiProperties.java @@ -8,36 +8,36 @@ @Getter public class GoogleApiProperties implements ApiProperties { - @Value("${spring.oauth2.client.registration.google.name}") + @Value("${cabinet.oauth2.client.registration.google.name}") private String providerName; - @Value("${spring.auth.google.client-id}") + @Value("${cabinet.oauth2.auth.google.client-id}") private String clientId; - @Value("${spring.auth.google.client-secret}") + @Value("${cabinet.oauth2.auth.google.client-secret}") private String clientSecret; - @Value("${spring.urls.admin-login-callback}") + @Value("${cabinet.oauth2.urls.admin-login-callback}") private String redirectUri; - @Value("${spring.oauth2.client.registration.google.grant-type}") + @Value("${cabinet.oauth2.client.registration.google.grant-type}") private String grantType; - @Value("${spring.oauth2.client.registration.google.access-token-name}") + @Value("${cabinet.oauth2.client.registration.google.access-token-name}") private String accessTokenName; - @Value("${spring.oauth2.client.registration.google.token-grant-type}") + @Value("${cabinet.oauth2.client.registration.google.token-grant-type}") private String tokenGrantType; - @Value("${spring.oauth2.client.registration.provider.google.token-uri}") + @Value("${cabinet.oauth2.client.registration.provider.google.token-uri}") private String tokenUri; - @Value("${spring.oauth2.client.registration.provider.google.authorization-uri}") + @Value("${cabinet.oauth2.client.registration.provider.google.authorization-uri}") private String authUri; - @Value("${spring.oauth2.client.registration.provider.google.user-info-uri}") + @Value("${cabinet.oauth2.client.registration.provider.google.user-info-uri}") private String userInfoUri; - @Value("${spring.oauth2.client.registration.google.scope}") + @Value("${cabinet.oauth2.client.registration.google.scope}") private String scope; } diff --git a/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java index 3f3d93612..165236379 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/HaneProperties.java @@ -9,13 +9,13 @@ public class HaneProperties { private static final int SIXTY = 60; - @Value("${spring.hane.token}") + @Value("${cabinet.hane.token}") private String jwtToken; - @Value("${spring.hane.url}") + @Value("${cabinet.hane.url}") private String url; - @Value("${spring.hane.limit-hours}") + @Value("${cabinet.hane.limit-hours}") private int limitHours; public int getLimitTimeSeconds() { diff --git a/backend/src/main/java/org/ftclub/cabinet/config/JwtProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/JwtProperties.java index 8e07d1ac3..c13b85d43 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/JwtProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/JwtProperties.java @@ -13,22 +13,22 @@ @Getter public class JwtProperties { - @Value("${spring.auth.jwt-secret-key}") + @Value("${cabinet.jwt.jwt-secret-key}") private String secret; - @Value("${spring.oauth2.jwt.token.main-token-name}") + @Value("${cabinet.jwt.token.main-token-name}") private String mainTokenName; - @Value("${spring.oauth2.jwt.token.main-provider}") + @Value("${cabinet.jwt.token.main-provider}") private String mainProviderName; - @Value("${spring.oauth2.jwt.token.admin-token-name}") + @Value("${cabinet.jwt.token.admin-token-name}") private String adminTokenName; - @Value("${spring.oauth2.jwt.token.admin-provider}") + @Value("${cabinet.jwt.token.admin-provider}") private String adminProviderName; - @Value("${spring.oauth2.jwt.token.expiry}") + @Value("${cabinet.jwt.token.expiry}") private Integer expiryDays; public Key getSigningKey() { diff --git a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java index 7eeab81c1..6821c835e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/MailOverdueProperties.java @@ -8,18 +8,18 @@ @Getter public class MailOverdueProperties { - @Value("${alarm.mail.soonoverdue.term}") + @Value("${cabinet.alarm.mail.soonoverdue.term}") private Long soonOverdueTerm; - @Value("${alarm.mail.overdue.subject}") + @Value("${cabinet.alarm.mail.overdue.subject}") private String overdueMailSubject; - @Value("${alarm.mail.overdue.template}") + @Value("${cabinet.alarm.mail.overdue.template}") private String overdueMailTemplateUrl; - @Value("${alarm.mail.soonoverdue.subject}") + @Value("${cabinet.alarm.mail.soonoverdue.subject}") private String soonOverdueMailSubject; - @Value("${alarm.mail.soonoverdue.template}") + @Value("${cabinet.alarm.mail.soonoverdue.template}") private String soonOverdueMailTemplateUrl; } diff --git a/backend/src/main/java/org/ftclub/cabinet/config/MasterProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/MasterProperties.java index 3e0817619..68e1febfa 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/MasterProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/MasterProperties.java @@ -8,16 +8,16 @@ @Getter public class MasterProperties { - @Value("${spring.oauth2.master.id}") + @Value("${cabinet.master.id}") private String id; - @Value("${spring.oauth2.master.password}") + @Value("${cabinet.master.password}") private String password; - @Value("${spring.oauth2.master.domain}") + @Value("${cabinet.master.domain}") private String domain; - @Value("${spring.oauth2.master.email}") + @Value("${cabinet.master.email}") private String email; } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java index 8d5231477..60df9114e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java @@ -46,7 +46,7 @@ public class LentExtensionServiceImpl implements LentExtensionService { private final LentExtensionPolicy lentExtensionPolicy; @Override - @Scheduled(cron = "${spring.schedule.cron.extension-issue-time}") + @Scheduled(cron = "${cabinet.schedule.cron.extension-issue-time}") public void issueLentExtension() { log.debug("Called issueLentExtension"); List userMonthDataDtos = occupiedTimeManager.filterToMetUserMonthlyTime( diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 75e225cd2..5450037a0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -38,7 +38,7 @@ public class SystemScheduler { /** * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 */ - @Scheduled(cron = "${spring.schedule.cron.leave-absence}") + @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") public void checkAllLents() { log.info("called checkAllLents"); List activeLents = lentService.getAllActiveLentHistories(); @@ -58,7 +58,7 @@ public void checkAllLents() { /** * 매주 월요일 자정 42분에 블랙홀에 빠진 유저 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 */ - @Scheduled(cron = "${spring.schedule.cron.risk-of-blackhole}") + @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") public void checkRiskOfBlackhole() { log.info("called checkRiskOfBlackhole"); List blackholeInfos = userService.getAllRiskOfBlackholeInfo(); @@ -75,7 +75,7 @@ public void checkRiskOfBlackhole() { /** * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 */ - @Scheduled(cron = "${spring.schedule.cron.no-risk-of-blackhole}") + @Scheduled(cron = "${cabinet.schedule.cron.no-risk-of-blackhole}") public void checkNoRiskOfBlackhole() { log.info("called checkNoRiskOfBlackhole"); List blackholeInfos = userService.getAllNoRiskOfBlackholeInfo(); @@ -93,13 +93,13 @@ public void checkNoRiskOfBlackhole() { * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 */ //현재 5분마다 도는 로직 0 */5 * * * * - @Scheduled(cron = "${spring.schedule.cron.cabinet-release-time}") + @Scheduled(cron = "${cabinet.schedule.cron.cabinet-release-time}") public void releasePendingCabinet() { log.info("releasePendingCabinet {}", LocalDateTime.now()); releaseManager.releasingCabinets(); } -// @Scheduled(cron = "${spring.schedule.cron.extensible-user-check}") +// @Scheduled(cron = "${cabinet.schedule.cron.extensible-user-check}") // public void checkUserQualifyForExtensible(){ // log.info("called checkUserQualifyForExtensible"); // List userMonthDataDtos = occupiedTimeManager.metLimitTimeUser(occupiedTimeManager.getUserLastMonthOccupiedTime()); diff --git a/config b/config index e755c38d0..3502b853c 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit e755c38d06b1e5ed2801b61f3bbd75ec39f633f7 +Subproject commit 3502b853cfa8b9f51c7164578611dd0714881d85 From 56cfdc7ed45bbb88237623c704b8083eb169c607 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Tue, 12 Dec 2023 15:58:56 +0900 Subject: [PATCH 0068/1029] =?UTF-8?q?[BE]=20FIX:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20config=20=EC=A0=9C=EA=B1=B0=20&=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20config=20=EC=9E=AC=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/firebase/FCMInitializer.java | 4 +- backend/src/main/resources/application.yml | 38 --- backend/src/test/resources/application.yml | 260 +++++++++--------- 3 files changed, 135 insertions(+), 167 deletions(-) delete mode 100644 backend/src/main/resources/application.yml diff --git a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java index 3710a1117..ddd585695 100644 --- a/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java +++ b/backend/src/main/java/org/ftclub/cabinet/firebase/FCMInitializer.java @@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.ClassPathResource; +import org.springframework.context.annotation.Profile; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; @@ -20,6 +20,7 @@ @Slf4j @Component @RequiredArgsConstructor +@Profile("!test") public class FCMInitializer { private final ResourceLoader resourceLoader; @@ -28,7 +29,6 @@ public class FCMInitializer { @PostConstruct public void initialize() throws IOException { - Path currentPath = Paths.get("").toAbsolutePath(); if (currentPath.endsWith("backend")) { currentPath = currentPath.getParent(); diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml deleted file mode 100644 index d3bf9b1da..000000000 --- a/backend/src/main/resources/application.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -#프로덕션 환경 -server: - port: 4242 -spring: - config: - import: classpath:application-auth.yml, classpath:application-alarm.yml - activate: - on-profile: prod -logging: - config: classpath:log4j2-prod.yml - ---- -#개발서버 환경 -server: - port: 4242 -spring: - config: - import: classpath:application-auth.yml, classpath:application-alarm.yml - activate: - on-profile: dev -logging: - config: classpath:log4j2-dev.yml - ---- -#로컬 환경 -server: - port: 2424 -spring: - config: - import: classpath:application-auth.yml, classpath:application-alarm.yml - activate: - on-profile: local -logging: - config: classpath:log4j2-local.yml - - ---- diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index 5491b1b07..0118ec20c 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -1,37 +1,35 @@ server: port: 2424 -spring: +#------------------------------CABINET------------------------------ +cabinet: production: false # 배포 환경에서는 true server: fe-host: http://localhost be-host: http://localhost - - redis: - host: localhost - port: 6379 - - datasource: - driver-class-name: org.h2.Driver - url: jdbc:h2:mem:;MODE=MySQL - username: root - password: test_password - - sql: - init: - mode: embedded - - jpa: - hibernate: - ddl-auto: create-drop - database-platform: org.hibernate.dialect.MySQL5InnoDBDialect - properties: - hibernate: - globally_quoted_identifiers: true - dialect: org.hibernate.dialect.MySQL5InnoDBDialect - format_sql: true - defer-datasource-initialization: true - + #------------------------------JWT, TOKEN------------------------------ + jwt: + jwt-secret-key: JWT_SECRET_KEY_JWT_SECRET_KEY_JWT_SECRET_KEY_JWT_SECRET_KEY_JWT_SECRET_KEY + token: + main-token-name: access_token + admin-token-name: admin_access_token + expiry: 28 #days + main-provider: ft + admin-provider: google + #------------------------------DOMAIN, URL------------------------------ + domain-name: + cookie-domain: cabi.42seoul.io + local: localhost + dev: dev.cabi.42seoul.io + main: cabi.42seoul.io + admin-email: gmail.com + user-email: student.42seoul.kr + #------------------------------MASTER------------------------------ + master: + id: master_id + password: master_password + domain: cabi.42seoul.io + email: ${cabinet.master.id}@${cabinet.master.domain} #------------------------------OAUTH 2.0------------------------------ oauth2: client: @@ -58,47 +56,49 @@ spring: token-uri: https://api.intra.42.fr/oauth/token user-info-uri: https://api.intra.42.fr/v2/me users-info-uri: https://api.intra.42.fr/v2/users - - #------------------------------JWT, TOKEN------------------------------ - jwt: - token: - main-token-name: access_token - admin-token-name: admin_access_token - expiry: 28 #days - main-provider: ft - admin-provider: google - - #------------------------------DOMAIN, URL------------------------------ - domain-name: - cookie-domain: cabi.42seoul.io - local: localhost - dev: dev.cabi.42seoul.io - main: cabi.42seoul.io - admin-email: gmail.com - user-email: student.42seoul.kr - - #------------------------------MASTER------------------------------ - master: - id: master_id - password: master_password - domain: cabi.42seoul.io - email: ${spring.oauth2.master.id}@${spring.oauth2.master.domain} - mail: - display-sender-name: "Cabi" - host: smtp.gmail.com - port: 587 - username: example@gmail.com - password: example_password - properties: - mail: - smtp: - auth: true - starttls: - enable: true - - cabinet: + urls: + admin-login-callback: ${cabinet.server.be-host}/v4/admin/auth/login/callback + user-login-callback: ${cabinet.server.be-host}/v4/auth/login/callback + auth: + ft: + client-id: ft-client-id + client-secret: ft-client-secret + google: + client-id: google-client-id + client-secret: google-client-secret + #------------------------------HANE------------------------------ + hane: + url: https://hane-api/blabla + token: hane-token-hane-token-hane-token-hane-token-hane-token-hane-token + limit-hours: 432000 + #------------------------------FCM Message------------------------------ + fcm: + lentSuccess: + template: "%s 자리의 사물함 대여에 성공했습니다." + soonOverdue: + template: "대여한 사물함이 %d일 후에 연체됩니다." + overdue: + template: "대여한 사물함이 %d일 연체되었습니다." + extensionIssuance: + template: "%d일 짜리 %s 연장권이 발급 되었습니다." + extensionExpiration: + template: "%s 연장권이 %t일에 만료됩니다." + announcement: + template: "새로운 공지사항이 있으니 확인해주세요." + #------------------------------SCHEDULE------------------------------ + schedule: + cron: + leave-absence: 0 0 0 * * * # 매일 0시 0분 0초 + risk-of-blackhole: 0 42 0 * * MON # 매주 월요일 0시 42분 0초 + no-risk-of-blackhole: 0 42 1 1 * * # 매월 1일 1시 42분 0초 + extensible-user-check: 0 0 0 2 * * # 매월 2일 0시 0분 0초 + cabinet-release-time: 0 */5 * * * * # 5분마다 + extension-delete-time: 0 */5 * * * * # 5분마다 + extension-issue-time: 0 0 0 2 * * # 매월 2일 0시 0분 0초 + #------------------------------CABINET POLICY------------------------------ + policy: in-session: - term: 10 + term: 1 lent: term: private: 31 @@ -113,68 +113,71 @@ spring: day: share: 3 padding: 2 - auth: - ft: - client-id: cliend_id - client-secret: client_secret - google: - client-id: client_id - client-secret: client_secret - jwt-secret-key: jwt_secret_key_must_be_long_jwt_secret_key_must_be_long_jwt_secret_key_must_be_long_jwt_secret_key_must_be_long - urls: - admin-login-callback: ${spring.server.fe-host}/v4/admin/auth/login/callback - user-login-callback: ${spring.server.fe-host}/v4/auth/login/callback - hane: - url: https://api.24hoursarenotenough.42seoul.kr/ext/cabi42/permonth - token: jwt_secret_key_must_be_long_jwt_secret_key_must_be_long_jwt_secret_key_must_be_long_jwt_secret_key_must_be_long - limit-hours: 432000 - schedule: - cron: - leave-absence: 0 0 0 * * * # 매일 0시 0분 0초 - risk-of-blackhole: 0 42 0 * * MON # 매주 월요일 0시 42분 0초 - no-risk-of-blackhole: 0 42 1 1 * * # 매월 1일 1시 42분 0초 - extensible-user-check: 0 0 0 2 * * # 매월 2일 0시 0분 0초 - cabinet-release-time: 0 */5 * * * * # 매일 0시 0분 0초 - extension-delete-time: 30 59 23 L * ? # 매월 마지막날 23시 59분 30초 - extension-issue-time: 0 0 0 2 * * # 매월 2일 0시 0분 0초 + #------------------------------ALARM------------------------------ + alarm: + mail: + display-sender-name: "Cabi" + lentSuccess: + template: "mail/lentsuccess" + subject: "Cabi 사물함 대여 성공 알림" + soonOverdue: + term: -1 + template: "mail/soonoverdue" + subject: "Cabi 사물함 연체 예정 알림" + overdue: + template: "mail/overdue" + subject: "Cabi 사물함 연체 알림" + extensionIssuance: + template: "mail/extensionIssuance" + subject: "Cabi 사물함 연장권 발급 알림" + extensionExpiration: + term: -1 + template: "mail/extensionExpiration" + subject: "Cabi 사물함 연장권 만료 알림" + announcement: + template: "mail/announcement" + subject: "Cabi 공지사항 안내" + +#------------------------------SPRING------------------------------ +spring: + redis: + host: localhost + port: 6379 -alarm: - mail: - lentSuccess: - template: "mail/lentsuccess" - subject: "Cabi 사물함 대여 성공 알림" - soonOverdue: - term: -1 - template: "mail/soonoverdue" - subject: "Cabi 사물함 연체 예정 알림" - overdue: - template: "mail/overdue" - subject: "Cabi 사물함 연체 알림" - extensionIssuance: - template: "mail/extensionIssuance" - subject: "Cabi 사물함 연장권 발급 알림" - extensionExpiration: - term: -1 - template: "mail/extensionExpiration" - subject: "Cabi 사물함 연장권 만료 알림" - announcement: - template: "mail/announcement" - subject: "Cabi 공지사항 안내" + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:;MODE=MySQL + username: root + password: test_password - fcm: - lentSuccess: - template: "%s 자리의 사물함 대여에 성공했습니다." - soonOverdue: - template: "대여한 사물함이 %d일 후에 연체됩니다." - overdue: - template: "대여한 사물함이 %d일 연체되었습니다." - extensionIssuance: - template: "%d일 짜리 %s 연장권이 발급 되었습니다." - extensionExpiration: - template: "%s 연장권이 %t일에 만료됩니다." - announcement: - template: "새로운 공지사항이 있으니 확인해주세요." + sql: + init: + mode: embedded + + jpa: + hibernate: + ddl-auto: create-drop + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + properties: + hibernate: + globally_quoted_identifiers: true + dialect: org.hibernate.dialect.MySQL5InnoDBDialect + format_sql: true + defer-datasource-initialization: true + + mail: + host: smtp.gmail.com + port: 587 + username: example@gmail.com + password: example_password + properties: + mail: + smtp: + auth: true + starttls: + enable: true +#------------------------------ACTUATOR------------------------------ management: server: port: 2424 @@ -192,14 +195,17 @@ management: health: show-details: always +#------------------------------WEBHOOK------------------------------ webhook: discord-admin: https://discord.com/api/webhooks/for-test +#------------------------------FIREBASE------------------------------ firebase: messaging: credentials: - path: "/config/backend/src/main/resources/local/firebase-example.json" + path: "/config/backend/src/test/resources/firebase-example.json" +#------------------------------SLACK------------------------------ slack: token: singing_secret: signing_secret From 96d8feabfab2e898349a901ffb60494e9c74f619 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Tue, 12 Dec 2023 15:59:11 +0900 Subject: [PATCH 0069/1029] =?UTF-8?q?[BE]=20FIX:=20server=20config=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 3502b853c..c35892e1f 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 3502b853cfa8b9f51c7164578611dd0714881d85 +Subproject commit c35892e1f38120365370bdb419f73869962c0444 From 854472e6eddf462d7b2253a66404745f46c9364d Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Tue, 12 Dec 2023 17:15:46 +0900 Subject: [PATCH 0070/1029] =?UTF-8?q?hotfix=20:=20getCabinetsPerSection=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=EB=90=9C=20transactional=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CabinetFacadeServiceImpl.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index 9c58c3fbd..d31c5d8e7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -1,7 +1,5 @@ package org.ftclub.cabinet.cabinet.service; -import java.time.LocalDate; -import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -23,12 +21,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.mapping; -import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.*; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; @@ -108,8 +106,7 @@ private String checkCabinetTitle(Cabinet cabinet, List lentHistorie if (cabinet.getTitle() != null && !cabinet.getTitle().isEmpty()) { // 12/2 패치 : 개인 사물함도 타이틀이 있을 경우 타이틀을 노출합니다. return cabinet.getTitle(); - } - else if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { + } else if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { return lentHistories.get(0).getUser().getName(); } @@ -120,6 +117,7 @@ else if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { * {@inheritDoc} */ @Override + @Transactional(readOnly = true) public List getCabinetsPerSection(String building, Integer floor) { log.debug("getCabinetsPerSection"); List currentLentCabinets = cabinetOptionalFetcher @@ -141,7 +139,7 @@ public List getCabinetsPerSection(String building String title = checkCabinetTitle(cabinet, lentHistories); cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()) .add(cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), title)); - }); + }); return cabinetPreviewsBySection.entrySet().stream() .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); @@ -301,7 +299,7 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { .collect(toMap(key -> key, value -> new ArrayList<>())); Map> lentHistoriesMap = lentOptionalFetcher.findAllByCabinetIdsAfterDate(yesterday, cabinetIds) - .stream().collect(groupingBy(LentHistory::getCabinetId)); + .stream().collect(groupingBy(LentHistory::getCabinetId)); buildingCabinets.forEach(cabinet -> { Integer floor = cabinet.getCabinetPlace().getLocation().getFloor(); if (cabinet.isStatus(AVAILABLE)) { From 74845476b76fa37af4365c397f14a7c844ef3170 Mon Sep 17 00:00:00 2001 From: hyungseok Date: Tue, 12 Dec 2023 17:27:27 +0900 Subject: [PATCH 0071/1029] =?UTF-8?q?[FE]=20HOTFIX=20:=20pendingPage=20?= =?UTF-8?q?=EA=B8=B4=EA=B8=89=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/axios/axios.custom.ts | 2 +- .../components/Common/MultiToggleSwitch.tsx | 82 ++++++++++++++ .../src/pages/PendingPage/PendingPage.tsx | 100 +++++++++++++++--- .../PendingPage/components/FloorContainer.tsx | 9 +- .../components/PendingCountdown.tsx | 66 ++++++++++++ .../pages/PendingPage/components/Timer.tsx | 61 ----------- frontend/src/types/enum/time.enum.ts | 5 + 7 files changed, 239 insertions(+), 86 deletions(-) create mode 100644 frontend/src/components/Common/MultiToggleSwitch.tsx create mode 100644 frontend/src/pages/PendingPage/components/PendingCountdown.tsx delete mode 100644 frontend/src/pages/PendingPage/components/Timer.tsx create mode 100644 frontend/src/types/enum/time.enum.ts diff --git a/frontend/src/api/axios/axios.custom.ts b/frontend/src/api/axios/axios.custom.ts index bfd6280ab..f836160ef 100644 --- a/frontend/src/api/axios/axios.custom.ts +++ b/frontend/src/api/axios/axios.custom.ts @@ -521,7 +521,7 @@ export const axiosCancel = async (cabinetId: number | null): Promise => { } }; -const axiosGetPendingCabinetsURL = "/v4/cabinets/pending"; +const axiosGetPendingCabinetsURL = "/v4/cabinets/buildings/새롬관/pending"; export const axiosGetPendingCabinets = async (): Promise => { try { const response = await instance.get(axiosGetPendingCabinetsURL); diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx new file mode 100644 index 000000000..3f48ce838 --- /dev/null +++ b/frontend/src/components/Common/MultiToggleSwitch.tsx @@ -0,0 +1,82 @@ +import React, { useEffect } from "react"; +import styled from "styled-components"; + +interface toggleItem { + name: string; + key: string; +} + +interface MultiToggleSwitchProps { + initialState: T; // 초기값 + setState: React.Dispatch>; // 상태를 변경하는 dispatcher + toggleList: toggleItem[]; // 토글 리스트 +} + +const MultiToggleSwitch = ({ + initialState, + setState, + toggleList, +}: MultiToggleSwitchProps) => { + useEffect(() => { + const buttons = document.querySelectorAll("button"); + + buttons.forEach((button) => { + if (button.className === initialState) { + button.style.color = "white"; + button.style.backgroundColor = "var(--main-color)"; + } + }); + }, []); + + function switchToggle(e: any) { + const target = e.target; + + if (target === e.currentTarget) return; + + const buttons = document.querySelectorAll("button"); + + buttons.forEach((button) => { + button.style.color = "black"; + button.style.backgroundColor = "transparent"; + }); + + target.style.color = "white"; + target.style.backgroundColor = "var(--main-color)"; + + setState(target.className); + } + + return ( + + {toggleList.map((item) => ( + + ))} + + ); +}; + +const WrapperStyled = styled.div` + width: fit-content; + display: flex; + align-items: center; + background-color: var(--lightgray-color); + + border-radius: 10px; + button { + display: flex; + justify-content: center; + align-items: center; + width: fit-content; + min-width: 50px; + border-radius: 10px; + font-size: 0.9rem; + height: 30px; + font-weight: 500; + background-color: transparent; + color: black; + } +`; + +export default MultiToggleSwitch; diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 0a76aa82e..1cafbc3f0 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -3,16 +3,40 @@ import { useRecoilState } from "recoil"; import styled from "styled-components"; import { isCurrentSectionRenderState } from "@/recoil/atoms"; import FloorContainer from "@/pages/PendingPage/components/FloorContainer"; -import Timer from "@/pages/PendingPage/components/Timer"; +import PendingCountdown from "@/pages/PendingPage/components/PendingCountdown"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; +import MultiToggleSwitch from "@/components/Common/MultiToggleSwitch"; +import { + CabinetPreviewInfo, + PendingCabinetsInfo, +} from "@/types/dto/cabinet.dto"; import { axiosGetPendingCabinets } from "@/api/axios/axios.custom"; import useDebounce from "@/hooks/useDebounce"; +enum PendingCabinetsType { + ALL = "ALL", + PRIVATE = "PRIVATE", + SHARE = "SHARE", +} + +const toggleList = [ + { name: "전체", key: PendingCabinetsType.ALL }, + { name: "개인", key: PendingCabinetsType.PRIVATE }, + { name: "공유", key: PendingCabinetsType.SHARE }, +]; + const PendingPage = () => { - const [pendingCabinets, setPendingCabinets] = useState< - CabinetPreviewInfo[][] - >([[]]); + const [toggleType, setToggleType] = useState( + PendingCabinetsType.ALL + ); + const [cabinets, setCabinets] = useState({}); + const [pendingCabinets, setPendingCabinets] = useState( + {} + ); + const [privateCabinets, setPrivateCabinets] = useState( + {} + ); + const [sharedCabinets, setSharedCabinets] = useState({}); const [isLoaded, setIsLoaded] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [isOpenTime, setIsOpenTime] = useState(false); @@ -21,13 +45,33 @@ const PendingPage = () => { ); const { debounce } = useDebounce(); - const isShowingLoadingAnimation = !isRefreshing && isLoaded; - const getPendingCabinets = async () => { try { const response = await axiosGetPendingCabinets(); const pendingCabinets = response.data.cabinetInfoResponseDtos; + + const filterCabinetsByType = (type: string) => + Object.fromEntries( + Object.entries(pendingCabinets).map(([key, cabinets]: any) => [ + key, + cabinets.filter( + (cabinet: CabinetPreviewInfo) => cabinet.lentType === type + ), + ]) + ); + + const privateCabinets = filterCabinetsByType(PendingCabinetsType.PRIVATE); + const sharedCabinets = filterCabinetsByType(PendingCabinetsType.SHARE); + + const updatedCabinets = + toggleType === PendingCabinetsType.ALL + ? pendingCabinets + : filterCabinetsByType(toggleType); + + setCabinets(updatedCabinets); setPendingCabinets(pendingCabinets); + setPrivateCabinets(privateCabinets); + setSharedCabinets(sharedCabinets); } catch (error) { throw error; } @@ -47,7 +91,7 @@ const PendingPage = () => { useEffect(() => { setTimeout(() => { - // 새로고침 광클 방지를 위한 딜레이 + // 새로고침 광클 방지를 위한 초기 로딩 딜레이 setIsLoaded(true); }, 500); }, []); @@ -65,20 +109,39 @@ const PendingPage = () => { } }, [isOpenTime]); + useEffect(() => { + if (toggleType === PendingCabinetsType.ALL) setCabinets(pendingCabinets); + else if (toggleType === PendingCabinetsType.PRIVATE) + setCabinets(privateCabinets); + else if (toggleType === PendingCabinetsType.SHARE) + setCabinets(sharedCabinets); + }, [toggleType]); + return ( + + + 사용 가능 사물함

매일 오후 1시 사용 가능한 사물함이 업데이트됩니다.

- 새로고침 + {isRefreshing ? ( + + ) : ( + 새로고침 + )}
- setIsOpenTime(true)} /> - {isShowingLoadingAnimation && pendingCabinets ? ( - Object.entries(pendingCabinets).map(([key, value]) => ( + setIsOpenTime(true)} /> + {isLoaded && cabinets ? ( + Object.entries(cabinets).map(([key, value]) => ( ` font-size: 1.1rem; color: var(--black); padding-left: 5px; - + padding-right: 5px; border-bottom: 1.5px solid #d9d9d9; - div { - } button { z-index: 2; height: 30px; @@ -69,7 +67,6 @@ const FloorTitleStyled = styled.h2<{ isToggled: boolean }>` background: url(/src/assets/images/select.svg) no-repeat 100%; transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; - margin-right: 5px; } `; @@ -102,8 +99,4 @@ const NoPendingCabinetMessageStyled = styled.div<{ isToggled: boolean }>` } `; -const LoadingContainerStyled = styled.div` - margin-top: 30px; -`; - export default FloorContainer; diff --git a/frontend/src/pages/PendingPage/components/PendingCountdown.tsx b/frontend/src/pages/PendingPage/components/PendingCountdown.tsx new file mode 100644 index 000000000..51c2ed711 --- /dev/null +++ b/frontend/src/pages/PendingPage/components/PendingCountdown.tsx @@ -0,0 +1,66 @@ +import { useEffect, useState } from "react"; +import { useRecoilState } from "recoil"; +import styled from "styled-components"; +import { serverTimeState } from "@/recoil/atoms"; +import Time from "@/types/enum/time.enum"; + +const openTime = new Date(); +openTime.setHours(13, 0, 0, 0); // 오픈 시간인 13:00:00(오후 1시)로 설정 + +const hours24 = 86400000; // 24시간을 밀리초로 표현 + +const PendingCountdown = ({ + observeOpenTime, +}: { + observeOpenTime: () => void; +}) => { + const [serverTime] = useRecoilState(serverTimeState); + const [remainingTime, setRemainingTime] = useState(hours24); // 기본 24시로 초기화(처음 함수 호출을 위한 값. 큰 의미 없음) + + const hours = Math.floor(remainingTime / 3600000); + const minutes = Math.floor((remainingTime % 3600000) / 60000); + const seconds = Math.floor((remainingTime % 60000) / 1000); + + useEffect(() => { + if (serverTime.toLocaleTimeString() === Time.PENDING_OPEN) + observeOpenTime(); // 오픈 시간이 되면 업데이트 된 사물함 정보를 가져옴 + if (remainingTime !== 0) setRemainingTime(getRemainingTime()); // 이미 오픈했으면 OPEN으로 표시 + }, [serverTime]); + + function getRemainingTime() { + let timeRemains; + + timeRemains = openTime.getTime() - serverTime.getTime(); + + if (openTime.getTime() < serverTime.getTime()) timeRemains += hours24; // 24시간을 더해줌 + + if (timeRemains < 0) return -timeRemains; + return timeRemains; + } + + return ( + <> + + + {remainingTime === 0 + ? "OPEN" + : `${hours}시간 ${minutes}분 ${seconds}초`} + + + ); +}; + +const PendingCountdownIconStyled = styled.img` + height: 25px; + width: 25px; + margin-top: 50px; +`; + +const PendingCountdownStyled = styled.div` + margin-top: 5px; + color: var(--main-color); + font-size: 1.8rem; + font-weight: 600; +`; + +export default PendingCountdown; diff --git a/frontend/src/pages/PendingPage/components/Timer.tsx b/frontend/src/pages/PendingPage/components/Timer.tsx deleted file mode 100644 index 3091f5f48..000000000 --- a/frontend/src/pages/PendingPage/components/Timer.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useEffect, useState } from "react"; -import { useRecoilState } from "recoil"; -import styled from "styled-components"; -import { s } from "vitest/dist/env-afee91f0"; -import { serverTimeState } from "@/recoil/atoms"; - -const openTime = "오후 1:00:00"; - -const Timer = ({ observeOpenTime }: { observeOpenTime: () => void }) => { - const [serverTime] = useRecoilState(serverTimeState); - const [remainingTime, setRemainingTime] = useState(86400000); // 기본 24시로 초기화 - - useEffect(() => { - if (serverTime.toLocaleTimeString() === openTime) observeOpenTime(); - setRemainingTime(calculateRemainingTime()); - }, [serverTime]); - - function calculateRemainingTime() { - if (remainingTime === 0) return 0; // 이미 오픈했으면 OPEN으로 표시 - const openTime = new Date(); - openTime.setHours(13, 0, 0, 0); // 13:00:00로 설정 - - let timeDiff; - - timeDiff = openTime.getTime() - serverTime.getTime(); - if (openTime.getTime() < serverTime.getTime()) timeDiff += 86400000; // 24시간을 더해줌 - - if (timeDiff < 0) return -timeDiff; - return timeDiff; - } - - const hours = Math.floor(remainingTime / 3600000); - const minutes = Math.floor((remainingTime % 3600000) / 60000); - const seconds = Math.floor((remainingTime % 60000) / 1000); - - return ( - <> - - - {remainingTime === 0 - ? "OPEN" - : `${hours}시간 ${minutes}분 ${seconds}초`} - - - ); -}; - -const TimerIconStyled = styled.img` - height: 25px; - width: 25px; - margin-top: 50px; -`; - -const TimerStyled = styled.div` - margin-top: 5px; - color: var(--main-color); - font-size: 1.8rem; - font-weight: 600; -`; - -export default Timer; diff --git a/frontend/src/types/enum/time.enum.ts b/frontend/src/types/enum/time.enum.ts new file mode 100644 index 000000000..55dc92405 --- /dev/null +++ b/frontend/src/types/enum/time.enum.ts @@ -0,0 +1,5 @@ +enum Time { + PENDING_OPEN = "오후 1:00:00", +} + +export default Time; From e93b2dae093c316b4fa27e9660f6c15f367c0ae2 Mon Sep 17 00:00:00 2001 From: hyungseok Date: Tue, 12 Dec 2023 17:31:13 +0900 Subject: [PATCH 0072/1029] =?UTF-8?q?[FE]=20HOTFIX=20:=20pendingPage=20?= =?UTF-8?q?=EA=B8=B4=EA=B8=89=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/types/dto/cabinet.dto.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/types/dto/cabinet.dto.ts b/frontend/src/types/dto/cabinet.dto.ts index e29e67987..3979d45ea 100644 --- a/frontend/src/types/dto/cabinet.dto.ts +++ b/frontend/src/types/dto/cabinet.dto.ts @@ -58,3 +58,7 @@ export interface CabinetInfoByBuildingFloorDto { section: string; // swagger의 CabinetPerSectionDto에 맞추어 object -> string으로 수정했습니다. cabinets: CabinetPreviewInfo[]; } + +export interface PendingCabinetsInfo { + [key: string]: CabinetPreviewInfo[]; +} From 329dd30c69ca83cf508a403c2b1e5fb173f79f4b Mon Sep 17 00:00:00 2001 From: moonseonghui Date: Wed, 13 Dec 2023 16:21:03 +0900 Subject: [PATCH 0073/1029] =?UTF-8?q?[FE]=20FEAT:=20=EA=B0=9C=EC=9D=B8=20?= =?UTF-8?q?=EC=82=AC=EB=AC=BC=ED=95=A8=20titie=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=20=EC=B6=94=EA=B0=80#1428?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CabinetListItem/CabinetListItem.tsx | 12 ++++++------ .../src/components/Modals/MemoModal/MemoModal.tsx | 15 +++++++-------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx index 44cdebbd8..8044f42c9 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -60,12 +60,12 @@ const CabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { props.status != "IN_SESSION" && props.status != "PENDING" ) { - if (props.lentType === "PRIVATE") cabinetLabelText = props.name; - else if (props.lentType === "SHARE") { - cabinetLabelText = - !!props.title - ? props.title - : `${props.userCount} / ${props.maxUser}`; + if (props.lentType === "PRIVATE") { + cabinetLabelText = !!props.title ? props.title : props.name; + } else if (props.lentType === "SHARE") { + cabinetLabelText = !!props.title + ? props.title + : `${props.userCount} / ${props.maxUser}`; } else if (props.lentType === "CLUB") cabinetLabelText = props.title ? props.title : "동아리"; } else { diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/components/Modals/MemoModal/MemoModal.tsx index 9a7b0eb51..4221996ee 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/components/Modals/MemoModal/MemoModal.tsx @@ -29,9 +29,7 @@ const MemoModal = ({ const newMemo = useRef(null); const handleClickWriteMode = (e: any) => { setMode("write"); - if (cabinetType === "PRIVATE" && newMemo.current) { - newMemo.current.select(); - } else if (newTitle.current) { + if (newTitle.current) { newTitle.current.select(); } }; @@ -39,13 +37,14 @@ const MemoModal = ({ //사물함 제목, 사물함 비밀메모 update api 호출 // onClose(e); document.getElementById("unselect-input")?.focus(); - if (cabinetType === "SHARE" && newTitle.current!.value) { + if (newTitle.current!.value) { onSave(newTitle.current!.value, newMemo.current!.value); } else { onSave(null, newMemo.current!.value); } setMode("read"); }; + return ( @@ -56,7 +55,7 @@ const MemoModal = ({ 메모 관리 - + 사물함 이름 { @@ -72,7 +71,7 @@ const MemoModal = ({ maxLength={MAX_INPUT_LENGTH} /> - + 비밀 메모 { @@ -157,8 +156,8 @@ const ContentItemSectionStyled = styled.div` width: 100%; `; -const ContentItemWrapperStyled = styled.div<{ isVisible: boolean }>` - display: ${({ isVisible }) => (isVisible ? "flex" : "none")}; +const ContentItemWrapperStyled = styled.div` + display: flex; flex-direction: column; align-items: flex-start; margin-bottom: 25px; From b23e8bbae1785c92e1e525f1263b2ed5fd5abae4 Mon Sep 17 00:00:00 2001 From: moonseonghui Date: Wed, 13 Dec 2023 16:40:06 +0900 Subject: [PATCH 0074/1029] =?UTF-8?q?[FE]=20BUG=20:=20top=EC=9D=98=20?= =?UTF-8?q?=EB=82=B4=20=EC=82=AC=EB=AC=BC=ED=95=A8=20=EB=88=8C=EB=A0=80?= =?UTF-8?q?=EC=9D=84=20=EB=96=84=20memo=EA=B0=80=20=EC=82=AC=EB=9D=BC?= =?UTF-8?q?=EC=A7=80=EB=8A=94=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TopNavButtonGroup/TopNavButtonGroup.tsx | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx b/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx index 922dffd49..4fad185e0 100644 --- a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx +++ b/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx @@ -44,35 +44,16 @@ const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => { const [myCabinetInfo, setMyCabinetInfo] = useRecoilState(myCabinetInfoState); const { pathname } = useLocation(); const navigator = useNavigate(); - const defaultCabinetInfo = getDefaultCabinetInfo(); - const resetCabinetInfo = () => { - setMyCabinetInfo({ - ...defaultCabinetInfo, - memo: "", - shareCode: 0, - previousUserName: "", - }); - setTargetCabinetInfo(defaultCabinetInfo); - setCurrentCabinetId(0); - }; + async function setTargetCabinetInfoToMyCabinet() { - if (myInfo.cabinetId === null && !myCabinetInfo?.cabinetId) { - resetCabinetInfo(); - } else setCurrentCabinetId(myInfo.cabinetId); + setCurrentCabinetId(myInfo.cabinetId); setMyInfo((prev) => ({ ...prev, cabinetId: null })); try { if (!myCabinetInfo?.cabinetId) return; const { data } = await axiosCabinetById(myCabinetInfo.cabinetId); if (data.lents.length === 0 && myInfo.cabinetId !== null) { - resetCabinetInfo(); setMyInfo((prev) => ({ ...prev, cabinetId: null })); } else { - setMyCabinetInfo((prev) => ({ - ...data, - memo: "", - shareCode: prev.shareCode, - previousUserName: prev.previousUserName, - })); const doesNameExist = data.lents.some( (lent: LentDto) => lent.name === myInfo.name ); @@ -80,7 +61,7 @@ const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => { setTargetCabinetInfo(data); setCurrentCabinetId(data.cabinetId); setMyInfo((prev) => ({ ...prev, cabinetId: data.cabinetId })); - } else resetCabinetInfo(); + } } } catch (error) { console.log(error); From c43b77337fbc529ee7385ef9d486cf8922c87665 Mon Sep 17 00:00:00 2001 From: hyungseok Date: Thu, 14 Dec 2023 18:14:17 +0900 Subject: [PATCH 0075/1029] =?UTF-8?q?[FE]=20HOTFIX:=20pendingPage=20refres?= =?UTF-8?q?hButton=20=EC=95=88=20=EB=B3=B4=EC=9D=B4=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=EC=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/PendingPage/PendingPage.tsx | 22 ++++++++----------- .../PendingPage/components/FloorContainer.tsx | 17 ++++++++++---- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 1cafbc3f0..86285d14a 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -131,14 +131,14 @@ const PendingPage = () => {

매일 오후 1시 사용 가능한 사물함이 업데이트됩니다.

- - {isRefreshing ? ( - - ) : ( - 새로고침 - )} - + + {isRefreshing ? ( + + ) : ( + 새로고침 + )} + setIsOpenTime(true)} /> {isLoaded && cabinets ? ( Object.entries(cabinets).map(([key, value]) => ( @@ -194,14 +194,10 @@ const SubHeaderStyled = styled.div` `; const RefreshButtonStyled = styled.button` - margin-top: 40px; background-color: transparent; + margin-top: 40px; width: 35px; - height: 35px; - img { - width: 35px; - height: 35px; - } + min-height: 35px; &:hover { opacity: 0.7; } diff --git a/frontend/src/pages/PendingPage/components/FloorContainer.tsx b/frontend/src/pages/PendingPage/components/FloorContainer.tsx index 03fb64015..afec815d1 100644 --- a/frontend/src/pages/PendingPage/components/FloorContainer.tsx +++ b/frontend/src/pages/PendingPage/components/FloorContainer.tsx @@ -25,7 +25,9 @@ const FloorContainer = ({
{floorNumber}층
- +
{pendingCabinetsList.length !== 0 ? ( @@ -61,10 +63,17 @@ const FloorTitleStyled = styled.h2<{ isToggled: boolean }>` padding-right: 5px; border-bottom: 1.5px solid #d9d9d9; button { + display: flex; + justify-content: center; + align-items: center; z-index: 2; - height: 30px; - width: 10px; - background: url(/src/assets/images/select.svg) no-repeat 100%; + height: 25px; + width: 25px; + img { + width: 15px; + height: 10px; + } + background-color: transparent; transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; } From e03123962aae1f4102555839ffa0319b3243e72b Mon Sep 17 00:00:00 2001 From: hyungseok Date: Thu, 14 Dec 2023 18:15:52 +0900 Subject: [PATCH 0076/1029] =?UTF-8?q?[FE]=20HOTFIX:=20pendingPage=20refres?= =?UTF-8?q?hButton=20=EC=95=88=20=EB=B3=B4=EC=9D=B4=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=EC=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PendingPage/PendingPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 86285d14a..c24bb65c1 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -194,7 +194,7 @@ const SubHeaderStyled = styled.div` `; const RefreshButtonStyled = styled.button` - background-color: transparent; + background-color: inherit; margin-top: 40px; width: 35px; min-height: 35px; From 64dc174103f7fe3f7961c2d3c97df2c1a867903b Mon Sep 17 00:00:00 2001 From: hyungseok Date: Thu, 14 Dec 2023 18:18:03 +0900 Subject: [PATCH 0077/1029] =?UTF-8?q?[FE]=20HOTFIX:=20pendingPage=20refres?= =?UTF-8?q?hButton=20=EC=95=88=20=EB=B3=B4=EC=9D=B4=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=EC=83=81=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/PendingPage/PendingPage.tsx | 22 +++++++++++-------- .../PendingPage/components/FloorContainer.tsx | 17 ++++---------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index c24bb65c1..1cafbc3f0 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -131,14 +131,14 @@ const PendingPage = () => {

매일 오후 1시 사용 가능한 사물함이 업데이트됩니다.

+ + {isRefreshing ? ( + + ) : ( + 새로고침 + )} + - - {isRefreshing ? ( - - ) : ( - 새로고침 - )} - setIsOpenTime(true)} /> {isLoaded && cabinets ? ( Object.entries(cabinets).map(([key, value]) => ( @@ -194,10 +194,14 @@ const SubHeaderStyled = styled.div` `; const RefreshButtonStyled = styled.button` - background-color: inherit; margin-top: 40px; + background-color: transparent; width: 35px; - min-height: 35px; + height: 35px; + img { + width: 35px; + height: 35px; + } &:hover { opacity: 0.7; } diff --git a/frontend/src/pages/PendingPage/components/FloorContainer.tsx b/frontend/src/pages/PendingPage/components/FloorContainer.tsx index afec815d1..03fb64015 100644 --- a/frontend/src/pages/PendingPage/components/FloorContainer.tsx +++ b/frontend/src/pages/PendingPage/components/FloorContainer.tsx @@ -25,9 +25,7 @@ const FloorContainer = ({
{floorNumber}층
- +
{pendingCabinetsList.length !== 0 ? ( @@ -63,17 +61,10 @@ const FloorTitleStyled = styled.h2<{ isToggled: boolean }>` padding-right: 5px; border-bottom: 1.5px solid #d9d9d9; button { - display: flex; - justify-content: center; - align-items: center; z-index: 2; - height: 25px; - width: 25px; - img { - width: 15px; - height: 10px; - } - background-color: transparent; + height: 30px; + width: 10px; + background: url(/src/assets/images/select.svg) no-repeat 100%; transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; } From fd6333c85143ad911a27811977b89a9e0557a4ae Mon Sep 17 00:00:00 2001 From: hyungseok Date: Thu, 14 Dec 2023 18:21:22 +0900 Subject: [PATCH 0078/1029] =?UTF-8?q?[FE]=20HOTFIX:=20pendingPage=20refres?= =?UTF-8?q?hButton=20=EC=95=88=20=EB=B3=B4=EC=9D=B4=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=EC=83=81=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/images/refresh.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/assets/images/refresh.svg b/frontend/src/assets/images/refresh.svg index a7a6bc801..c854d6d75 100644 --- a/frontend/src/assets/images/refresh.svg +++ b/frontend/src/assets/images/refresh.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 1a2e8096bf32898f5454f56a0ae5cd1897c3eb2a Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Tue, 19 Dec 2023 15:59:15 +0900 Subject: [PATCH 0079/1029] [BE] FIX: jpa local option change --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index c35892e1f..8b8015cf0 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit c35892e1f38120365370bdb419f73869962c0444 +Subproject commit 8b8015cf0c51f8740f16b2fe4f90cfaf8cd8b4e9 From cad0f8defb48f7a6fc4cbc0d166a515fd75eaf76 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Tue, 19 Dec 2023 16:56:37 +0900 Subject: [PATCH 0080/1029] [BE] FIX: slack, mail, fcm alarm message fix --- .../cabinet/alarm/config/AlarmProperties.java | 52 +++++++++---------- .../cabinet/alarm/config/GmailProperties.java | 2 +- .../alarm/slack/config/SlackApiConfig.java | 2 +- .../alarm/slack/config/SlackProperties.java | 10 ++-- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java index 216a5440f..c4ca699c7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -9,86 +9,86 @@ public class AlarmProperties { /*===================== lentSuccess =========================*/ - @Value("${alarm.mail.lentSuccess.subject}") + @Value("${cabinet.alarm.mail.lentSuccess.subject}") private String lentSuccessSubject; - @Value("${alarm.mail.lentSuccess.template}") + @Value("${cabinet.alarm.mail.lentSuccess.template}") private String lentSuccessMailTemplateUrl; - @Value("${alarm.fcm.lentSuccess.template}") + @Value("${cabinet.alarm.fcm.lentSuccess.template}") private String lentSuccessFcmTemplate; - @Value("${alarm.slack.lentSuccess.template}") + @Value("${cabinet.alarm.slack.lentSuccess.template}") private String lentSuccessSlackTemplate; /*======================= overdue ===========================*/ - @Value("${alarm.mail.overdue.subject}") + @Value("${cabinet.alarm.mail.overdue.subject}") private String overdueSubject; - @Value("${alarm.mail.overdue.template}") + @Value("${cabinet.alarm.mail.overdue.template}") private String overdueMailTemplateUrl; - @Value("${alarm.fcm.overdue.template}") + @Value("${cabinet.alarm.fcm.overdue.template}") private String overdueFcmTemplate; - @Value("${alarm.slack.overdue.template}") + @Value("${cabinet.alarm.slack.overdue.template}") private String overdueSlackTemplate; /*===================== soonOverdue =========================*/ - @Value("${alarm.mail.soonOverdue.term}") + @Value("${cabinet.alarm.mail.soonOverdue.term}") private Long soonOverdueTerm; - @Value("${alarm.mail.soonOverdue.subject}") + @Value("${cabinet.alarm.mail.soonOverdue.subject}") private String soonOverdueSubject; - @Value("${alarm.mail.soonOverdue.template}") + @Value("${cabinet.alarm.mail.soonOverdue.template}") private String soonOverdueMailTemplateUrl; - @Value("${alarm.fcm.soonOverdue.template}") + @Value("${cabinet.alarm.fcm.soonOverdue.template}") private String soonOverdueFcmTemplate; - @Value("${alarm.slack.soonOverdue.template}") + @Value("${cabinet.alarm.slack.soonOverdue.template}") private String soonOverdueSlackTemplate; /*================== extensionIssuance ======================*/ - @Value("${alarm.mail.extensionIssuance.subject}") + @Value("${cabinet.alarm.mail.extensionIssuance.subject}") private String extensionIssuanceSubject; - @Value("${alarm.mail.extensionIssuance.template}") + @Value("${cabinet.alarm.mail.extensionIssuance.template}") private String extensionIssuanceMailTemplateUrl; - @Value("${alarm.fcm.extensionIssuance.template}") + @Value("${cabinet.alarm.fcm.extensionIssuance.template}") private String extensionIssuanceFcmTemplate; - @Value("${alarm.slack.extensionIssuance.template}") + @Value("${cabinet.alarm.slack.extensionIssuance.template}") private String extensionIssuanceSlackTemplate; /*================= extensionExpiration =====================*/ - @Value("${alarm.mail.extensionExpiration.term}") + @Value("${cabinet.alarm.mail.extensionExpiration.term}") private Long extensionExpirationTerm; - @Value("${alarm.mail.extensionExpiration.subject}") + @Value("${cabinet.alarm.mail.extensionExpiration.subject}") private String extensionExpirationImminentSubject; - @Value("${alarm.mail.extensionExpiration.template}") + @Value("${cabinet.alarm.mail.extensionExpiration.template}") private String extensionExpirationImminentMailTemplateUrl; - @Value("${alarm.fcm.extensionExpiration.template}") + @Value("${cabinet.alarm.fcm.extensionExpiration.template}") private String extensionExpirationImminentFcmTemplate; - @Value("${alarm.slack.extensionExpiration.template}") + @Value("${cabinet.alarm.slack.extensionExpiration.template}") private String extensionExpirationImminentSlackTemplate; /*==================== announcement =========================*/ - @Value("${alarm.mail.announcement.subject}") + @Value("${cabinet.alarm.mail.announcement.subject}") private String announcementSubject; - @Value("${alarm.mail.announcement.template}") + @Value("${cabinet.alarm.mail.announcement.template}") private String announcementMailTemplateUrl; - @Value("${alarm.fcm.announcement.template}") + @Value("${cabinet.alarm.fcm.announcement.template}") private String announcementFcmTemplate; - @Value("${alarm.slack.announcement.template}") + @Value("${cabinet.alarm.slack.announcement.template}") private String announcementSlackTemplate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java index 634b2689f..3fc3211a2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/GmailProperties.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.config; +package org.ftclub.cabinet.alarm.config; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java index 4dac79ee1..b9776b1d6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackApiConfig.java @@ -8,7 +8,7 @@ @Configuration public class SlackApiConfig { - @Value("${alarm.slack.token.app-token}") + @Value("${slack.token.app-token}") private String appToken; @Bean diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java index 52d7709a9..4a5e92adc 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/config/SlackProperties.java @@ -11,15 +11,15 @@ public class SlackProperties { private final String applicationForm = "application/x-www-form-urlencoded"; private final String bearer = "Bearer "; - @Value("${alarm.slack.token.singing-secret}") + @Value("${slack.token.singing-secret}") private String singingSecret; - @Value("${alarm.slack.token.bot-token}") + @Value("${slack.token.bot-token}") private String botToken; - @Value("${alarm.slack.token.app-token}") + @Value("${slack.token.app-token}") private String appToken; - @Value("${alarm.slack.channel.cabi}") + @Value("${slack.channel.cabi}") private String cabiChannelId; - @Value("${alarm.slack.channel.random}") + @Value("${slack.channel.random}") private String randomChannelId; } From 4c3a38f8575f960cfc6959a4015036b4c50e1ea1 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Tue, 19 Dec 2023 17:00:56 +0900 Subject: [PATCH 0081/1029] [BE] FIX: config add --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 8b8015cf0..868b4f34e 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 8b8015cf0c51f8740f16b2fe4f90cfaf8cd8b4e9 +Subproject commit 868b4f34e936b36d739a6d68d280c3e3a148c118 From 329bc6d6bf8a8fd9048ec7252697038336ccff15 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Tue, 19 Dec 2023 19:42:55 +0900 Subject: [PATCH 0082/1029] =?UTF-8?q?[BE]=20FIX:=20logger=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=A3=BC=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/log4j2-local.yml | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/src/main/resources/log4j2-local.yml b/backend/src/main/resources/log4j2-local.yml index 4ab313ccf..07cb20654 100644 --- a/backend/src/main/resources/log4j2-local.yml +++ b/backend/src/main/resources/log4j2-local.yml @@ -55,17 +55,17 @@ Configuration: # SQL문 확인을 위한 설정 (시작) # sql을 봐야한다면 해당 부분 주석 해제하면 됨. # trace로 해놓는 이유는 JDBC 파라미터를 보기 위함 - # - - # name: org.hibernate.type - # level: trace - # additivity: false - # AppenderRef: - # ref: console - # - # - - # name: org.hibernate.SQL - # level: debug - # additivity: false - # AppenderRef: - # ref: console +# - +# name: org.hibernate.type +# level: debug +# additivity: false +# AppenderRef: +# - ref: console +# +# - +# name: org.hibernate.SQL +# level: debug +# additivity: false +# AppenderRef: +# - ref: console # SQL문 확인을 위한 설정 (끝) From 450b5dcb4870e67673727df60b2759968a854557 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Tue, 19 Dec 2023 19:45:11 +0900 Subject: [PATCH 0083/1029] =?UTF-8?q?[BE]=20FIX:=20Slack=20alarm,=20mail?= =?UTF-8?q?=20Async=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/ftclub/cabinet/CabinetApplication.java | 2 ++ .../ftclub/cabinet/alarm/handler/AlarmEventHandler.java | 1 - .../org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java | 2 ++ .../org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java | 2 ++ .../org/ftclub/cabinet/lent/service/LentServiceImpl.java | 7 ------- .../java/org/ftclub/cabinet/user/domain/AlarmOptOut.java | 2 +- .../src/main/java/org/ftclub/cabinet/user/domain/User.java | 5 +++-- config | 2 +- 8 files changed, 11 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java b/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java index 79d4b3e2c..cde8506ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java +++ b/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java @@ -6,7 +6,9 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Import; +import org.springframework.scheduling.annotation.EnableAsync; +@EnableAsync @EnableFeignClients @SpringBootApplication(exclude = SecurityAutoConfiguration.class) @Import({CorsConfig.class}) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index cd2d2607b..c65e3238a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -35,7 +35,6 @@ public void handleAlarmEvent(AlarmEvent alarmEvent) { Set alarmOptOuts = receiver.getAlarmOptOuts() .stream().map(AlarmOptOut::getAlarmType).collect(Collectors.toSet()); - // else-if가 아니어야 하는 것 아닌가? if (alarmOptOuts.contains(SLACK)) { slackAlarmSender.send(receiver, alarmEvent); } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java index ab305a453..b3cbfbe69 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java @@ -11,6 +11,7 @@ import org.ftclub.cabinet.user.domain.User; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.thymeleaf.ITemplateEngine; import org.thymeleaf.context.Context; @@ -28,6 +29,7 @@ public class EmailAlarmSender { private final GmailProperties gmailProperties; private final AlarmProperties alarmProperties; + @Async public void send(User user, AlarmEvent alarmEvent) { log.info("Email Alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); if (gmailProperties.getIsProduction() == false) { diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index e22d66153..f22f6c4e7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -23,6 +23,7 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.thymeleaf.util.StringUtils; @@ -35,6 +36,7 @@ public class SlackAlarmSender { private final AlarmProperties alarmProperties; + @Async public void send(User user, AlarmEvent alarmEvent) { log.info("slack alarm Event : user = {}, alarmEvent = {}", user.getName(), alarmEvent); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java index d03eeafb7..032782328 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java @@ -1,6 +1,5 @@ package org.ftclub.cabinet.lent.service; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -34,12 +33,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor @Transactional diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java index 690993871..473b76878 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java @@ -18,7 +18,7 @@ @Table(name = "ALARM_OPT_OUT") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -@ToString +@ToString(exclude = {"user"}) public class AlarmOptOut { @Id diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index ead1861bb..527d673a4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.user.domain; import java.time.LocalDateTime; +import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; @@ -29,7 +30,7 @@ @Table(name = "USER") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -@ToString +@ToString (exclude = {"alarmOptOuts"}) @Log4j2 public class User { @@ -57,7 +58,7 @@ public class User { private UserRole role; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private Set alarmOptOuts; + private Set alarmOptOuts = new HashSet<>(); protected User(String name, String email, LocalDateTime blackholedAt, UserRole userRole) { this.name = name; diff --git a/config b/config index 868b4f34e..290416e13 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 868b4f34e936b36d739a6d68d280c3e3a148c118 +Subproject commit 290416e13fff18216998886ac1a2c5f6845c9253 From 8940019bbd93cc0ef05f3baf32d6d014cbee3928 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Tue, 19 Dec 2023 20:22:59 +0900 Subject: [PATCH 0084/1029] =?UTF-8?q?[BE]=20FEAT:=20AlarmTypes=20/me=20myp?= =?UTF-8?q?rofile=20=EC=A0=95=EB=B3=B4=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftclub/cabinet/CabinetApplication.java | 6 +-- .../repository/AlarmOptOutRepository.java | 13 +++++ .../cabinet/dto/MyProfileResponseDto.java | 4 +- .../org/ftclub/cabinet/mapper/UserMapper.java | 4 +- .../cabinet/user/domain/AlarmOptOut.java | 12 ++++- .../user/service/UserFacadeServiceImpl.java | 50 ++++++++++++++----- .../user/service/UserFacadeServiceTest.java | 8 +-- 7 files changed, 73 insertions(+), 24 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptOutRepository.java diff --git a/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java b/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java index cde8506ce..1197b65d8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java +++ b/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java @@ -15,8 +15,8 @@ // @CrossOrigin(origins = {"*"}, allowedHeaders = {"*"}, originPatterns = {"*"}) public class CabinetApplication { - public static void main(String[] args) { - SpringApplication.run(CabinetApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(CabinetApplication.class, args); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptOutRepository.java b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptOutRepository.java new file mode 100644 index 000000000..9b392240c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptOutRepository.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.alarm.repository; + +import java.util.List; +import org.ftclub.cabinet.user.domain.AlarmOptOut; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface AlarmOptOutRepository extends JpaRepository { + + @Query("SELECT ao FROM AlarmOptOut ao WHERE ao.user.userId = :userId") + List findAllByUserId(Long userId); + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java index 08b8bb8b1..395bd2498 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java @@ -2,10 +2,11 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.Locale; import lombok.AllArgsConstructor; import lombok.Getter; -import org.ftclub.cabinet.user.domain.LentExtension; +import org.ftclub.cabinet.alarm.domain.AlarmType; /** * 내 프로필 정보와 대여 중인 사물함의 ID를 반환하는 DTO입니다. @@ -22,4 +23,5 @@ public class MyProfileResponseDto { DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'KST'", Locale.US) ); private final LentExtensionResponseDto lentExtensionResponseDto; + private final List alarmTypes; } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java index da11e80e6..f6cf36553 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java @@ -3,6 +3,7 @@ import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; import java.util.List; +import org.ftclub.cabinet.alarm.domain.AlarmType; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.ClubUserListDto; @@ -39,7 +40,8 @@ public interface UserMapper { @Mapping(target = "name", source = "user.name") @Mapping(target = "cabinetId", source = "cabinet.cabinetId") MyProfileResponseDto toMyProfileResponseDto(UserSessionDto user, Cabinet cabinet, - BanHistory banHistory, LentExtensionResponseDto lentExtensionResponseDto); + BanHistory banHistory, LentExtensionResponseDto lentExtensionResponseDto, + List alarmTypes); BlockedUserPaginationDto toBlockedUserPaginationDto(List result, Long totalLength); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java index 473b76878..d6bd40e24 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java @@ -1,5 +1,15 @@ package org.ftclub.cabinet.user.domain; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -9,8 +19,6 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.utils.ExceptionUtil; -import javax.persistence.*; - /** * 유저의 알람 수신 거부 정보 엔티티입니다. */ diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java index 50f9442f0..7b7d08a34 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java @@ -1,15 +1,40 @@ package org.ftclub.cabinet.user.service; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.alarm.repository.AlarmOptOutRepository; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.BlockedUserPaginationDto; +import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.ClubUserListDto; +import org.ftclub.cabinet.dto.LentExtensionPaginationDto; +import org.ftclub.cabinet.dto.LentExtensionResponseDto; +import org.ftclub.cabinet.dto.MyProfileResponseDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserBlockedInfoDto; +import org.ftclub.cabinet.dto.UserCabinetDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserProfileDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.user.domain.*; +import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.user.domain.AlarmOptOut; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.LentExtension; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.user.repository.LentExtensionOptionalFetcher; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; import org.ftclub.cabinet.utils.DateUtil; @@ -18,12 +43,6 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor @Log4j2 @@ -37,6 +56,7 @@ public class UserFacadeServiceImpl implements UserFacadeService { private final CabinetMapper cabinetMapper; private final LentExtensionService lentExtensionService; private final LentExtensionOptionalFetcher lentExtensionOptionalFetcher; + private final AlarmOptOutRepository alarmOptOutRepository; @Override public MyProfileResponseDto getMyProfile(UserSessionDto user) { @@ -49,8 +69,12 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { LentExtensionResponseDto activeLentExtension = lentExtensionService.getActiveLentExtension( user); + List alarmOptOuts = alarmOptOutRepository.findAllByUserId(user.getUserId()); + List alarmTypes = alarmOptOuts.stream().map(AlarmOptOut::getAlarmType) + .collect(Collectors.toList()); + return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, - activeLentExtension); + activeLentExtension, alarmTypes); } @Override @@ -72,7 +96,7 @@ public BlockedUserPaginationDto getAllBanUsers(Integer page, Integer size, Local @Override public UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, - Integer size) { + Integer size) { log.debug("Called getUserProfileListByPartialName: {}", name); // todo - size가 0일 때 모든 데이터를 가져오기 if (size <= 0) { @@ -88,7 +112,7 @@ public UserProfilePaginationDto getUserProfileListByPartialName(String name, Int @Override public UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, - Integer size) { + Integer size) { log.debug("Called findUserCabinetListByPartialName: {}", name); // todo - size가 0일 때 모든 데이터를 가져오기 if (size <= 0) { @@ -178,8 +202,8 @@ public void updateUserBlackholedAt(Long userId, LocalDateTime newBlackholedAt) { @Override public void banUser(Long userId, LentType lentType, LocalDateTime startedAt, - LocalDateTime endedAt, - LocalDateTime expiredAt) { + LocalDateTime endedAt, + LocalDateTime expiredAt) { log.debug("Called banUser: {}", userId); userService.banUser(userId, lentType, startedAt, endedAt, expiredAt); } diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java index 7cc67adee..d3219a02a 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java @@ -105,8 +105,8 @@ public class UserFacadeServiceTest { userSessionDto.getUserId()).get(0); MyProfileResponseDto myProfileResponseDto = new MyProfileResponseDto( userSessionDto.getUserId(), userSessionDto.getName(), - cabinet1.getCabinetId(), null, null); - given(userMapper.toMyProfileResponseDto(userSessionDto, cabinet1, null, null)) + cabinet1.getCabinetId(), null, null, null); + given(userMapper.toMyProfileResponseDto(userSessionDto, cabinet1, null, null, null)) .willReturn(myProfileResponseDto); // when @@ -137,8 +137,8 @@ public class UserFacadeServiceTest { LentExtension lentExtension = lentExtensionOptionalFetcher.findAllByUserId( userSessionDto.getUserId()).get(0); - given(userMapper.toMyProfileResponseDto(userSessionDto, null, banHistory1, null)).willReturn( - new MyProfileResponseDto(2L, "testUser2", null, testDate.plusDays(1), null)); +// given(userMapper.toMyProfileResponseDto(userSessionDto, null, banHistory1, null)).willReturn( +// new MyProfileResponseDto(2L, "testUser2", null, testDate.plusDays(1), null)); // when MyProfileResponseDto myProfile = userFacadeService.getMyProfile(userSessionDto); From fde35dfad9f79289693b64305bdbcaa827596436 Mon Sep 17 00:00:00 2001 From: eunbi9n Date: Tue, 19 Dec 2023 20:32:54 +0900 Subject: [PATCH 0085/1029] =?UTF-8?q?[BE]=20DOCS:=20log4j2=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/log4j2-local.yml | 82 ++++++++++----------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/backend/src/main/resources/log4j2-local.yml b/backend/src/main/resources/log4j2-local.yml index 4ab313ccf..36fba56ff 100644 --- a/backend/src/main/resources/log4j2-local.yml +++ b/backend/src/main/resources/log4j2-local.yml @@ -1,5 +1,5 @@ Configuration: - status: debug + status: INFO Properties: property: @@ -12,27 +12,27 @@ Configuration: target: SYSTEM_OUT PatternLayout: pattern: ${pattern} -# RollingFile: -# name: rollingFile -# # 파일 저장 위치를 어디로? -# fileName: logs/42cabi.log -# filePattern: logs/42cabi-%d{yyyy-MM-dd}-%i.log.gz -# PatternLayout: -# pattern: ${pattern} -# Policies: -# # 1일 단위로 로그를 압축해서 저장 -# TimeBasedTriggeringPolicy: -# interval: 1 -# modulate: true -# DefaultRolloverStrategy: -# Delete: -# basePath: logs -# ifFileName: -# glob: "42cabi-*.log.gz" -# # 30일 이상된 로그는 삭제 -# ifLastModified: -# age: "30d" -# max: 30 + # RollingFile: + # name: rollingFile + # # 파일 저장 위치를 어디로? + # fileName: logs/42cabi.log + # filePattern: logs/42cabi-%d{yyyy-MM-dd}-%i.log.gz + # PatternLayout: + # pattern: ${pattern} + # Policies: + # # 1일 단위로 로그를 압축해서 저장 + # TimeBasedTriggeringPolicy: + # interval: 1 + # modulate: true + # DefaultRolloverStrategy: + # Delete: + # basePath: logs + # ifFileName: + # glob: "42cabi-*.log.gz" + # # 30일 이상된 로그는 삭제 + # ifLastModified: + # age: "30d" + # max: 30 Loggers: logger: @@ -43,29 +43,29 @@ Configuration: additivity: false AppenderRef: - ref: console -# - ref: rollingFile + # - ref: rollingFile - name: org.ftclub.cabinet level: debug additivity: false AppenderRef: - ref: console -# - ref: rollingFile + # - ref: rollingFile - # SQL문 확인을 위한 설정 (시작) - # sql을 봐야한다면 해당 부분 주석 해제하면 됨. - # trace로 해놓는 이유는 JDBC 파라미터를 보기 위함 - # - - # name: org.hibernate.type - # level: trace - # additivity: false - # AppenderRef: - # ref: console - # - # - - # name: org.hibernate.SQL - # level: debug - # additivity: false - # AppenderRef: - # ref: console - # SQL문 확인을 위한 설정 (끝) + # SQL문 확인을 위한 설정 (시작) + # sql을 봐야한다면 해당 부분 주석 해제하면 됨. + # trace로 해놓는 이유는 JDBC 파라미터를 보기 위함 + - + name: org.hibernate.type + level: debug + additivity: false + AppenderRef: + ref: console + + - + name: org.hibernate.SQL + level: debug + additivity: false + AppenderRef: + ref: console + # SQL문 확인을 위한 설정 (끝) \ No newline at end of file From d1f79df073ded42c9905872e8fd861b4e81e9a6a Mon Sep 17 00:00:00 2001 From: jiwon Date: Wed, 20 Dec 2023 14:41:59 +0900 Subject: [PATCH 0086/1029] create branch From f30474174715372d53b02429ed0b8152d616cfe1 Mon Sep 17 00:00:00 2001 From: ldw Date: Wed, 20 Dec 2023 16:05:30 +0900 Subject: [PATCH 0087/1029] [BE] REFACTOR : first commit for refactor --- .../newService/CabinetCommandService.java | 10 +++ .../newService/CabinetFacadeService.java | 28 ++++++++ .../newService/CabinetQueryService.java | 28 ++++++++ .../lent/newService/LentCommandService.java | 14 ++++ .../lent/newService/LentFacadeService.java | 66 +++++++++++++++++++ .../lent/newService/LentQueryService.java | 38 +++++++++++ .../search/controller/SearchController.java | 2 - .../newService/BanHistoryCommandService.java | 12 ++++ .../newService/BanHistoryQueryService.java | 26 ++++++++ .../user/newService/UserCommandService.java | 21 ++++++ .../user/newService/UserFacadeService.java | 23 +++++++ .../user/newService/UserQueryService.java | 31 +++++++++ .../user/repository/UserOptionalFetcher.java | 4 +- .../user/repository/UserRepository.java | 2 +- .../user/repository/UserRepositoryTest.java | 2 +- config | 2 +- 16 files changed, 302 insertions(+), 7 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java new file mode 100644 index 000000000..28ea6135b --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java @@ -0,0 +1,10 @@ +package org.ftclub.cabinet.cabinet.newService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CabinetCommandService { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java new file mode 100644 index 000000000..a8104f359 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -0,0 +1,28 @@ +package org.ftclub.cabinet.cabinet.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.dto.BuildingFloorsDto; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class CabinetFacadeService { + private final CabinetQueryService cabinetQueryService; + private final CabinetCommandService cabinetCommandService; + + /** + * {@inheritDoc} + *

+ * 존재하는 모든 건물들을 가져오고, 각 건물별 층 정보들을 가져옵니다. + */ + @Transactional(readOnly = true) + public List getBuildingFloorsResponse() { + log.debug("getBuildingFloorsResponse"); + + } + + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java new file mode 100644 index 000000000..687335a9b --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -0,0 +1,28 @@ +package org.ftclub.cabinet.cabinet.newService; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.repository.CabinetRepository; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class CabinetQueryService { + + private final CabinetRepository cabinetRepository; + + public Cabinet getCabinet(Long cabinetId) { + Optional cabinet = cabinetRepository.findById(cabinetId); + return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); + } + + public List findAllBuildings() { + log.debug("Called findAllBuildings"); + + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java new file mode 100644 index 000000000..7a406109a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java @@ -0,0 +1,14 @@ +package org.ftclub.cabinet.lent.newService; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.lent.repository.LentRedis; +import org.ftclub.cabinet.lent.repository.LentRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LentCommandService { + + private final LentRepository lentRepository; + private final LentRedis lentRedis; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java new file mode 100644 index 000000000..7e2b8c2e6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java @@ -0,0 +1,66 @@ +package org.ftclub.cabinet.lent.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentHistoryDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.mapper.LentMapper; +import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LentFacadeService { + + private final LentQueryService lentQueryService; + private final LentCommandService lentCommandService; + private final UserQueryService userQueryService; + private final CabinetQueryService cabinetQueryService; + + private final LentMapper lentMapper; + + + public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size) { + log.debug("Called getAllUserLentHistories: {}", userId); + userQueryService.getUser(userId); + + if (size <= 0) { + size = Integer.MAX_VALUE; + } + PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); + Page lentHistories = lentQueryService.findUserLentHistories(userId, pageable); + List result = lentHistories.stream() + .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) + .collect(Collectors.toList()); + return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); + } + + public List getLentDtoList(Long cabinetId) { + log.debug("Called getLentDtoList: {}", cabinetId); + cabinetQueryService.getCabinet(cabinetId); + +// cabinetOptionalFetcher.getCabinet(cabinetId); +// List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( +// cabinetId); +// return lentHistories.stream().map( +// e -> lentMapper.toLentDto(e.getUser(), e)).collect(Collectors.toList()); +// return lentHistories.stream() +// .map(e -> new LentDto( +// e.getUserId(), +// e.getUser().getName(), +// e.getLentHistoryId(), +// e.getStartedAt(), +// e.getExpiredAt())) +// .collect(Collectors.toList()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java new file mode 100644 index 000000000..13db26ab6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java @@ -0,0 +1,38 @@ +package org.ftclub.cabinet.lent.newService; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.repository.LentRedis; +import org.ftclub.cabinet.lent.repository.LentRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class LentQueryService { + + private final LentRepository lentRepository; + private final LentRedis lentRedis; + + public Page findUserLentHistories(Long userId, PageRequest pageable) { + return lentRepository.findPaginationByUserId(userId, pageable); + } + + public LentHistory findCabinetActiveLentHistory(Long cabinetId) { + List lentHistories = + lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); + if (lentHistories.size() >= 2) { + throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); + } + if (lentHistories.isEmpty()) { + return null; + } + return lentHistories.get(0); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java b/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java index 0e25e5c7b..e081fd786 100644 --- a/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java @@ -64,6 +64,4 @@ public UserCabinetPaginationDto getCabinetsLentInfo( log.info("Called getCabinetsLentInfo {}", name); return userFacadeService.findUserCabinetListByPartialName(name, page, size); } - - } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java new file mode 100644 index 000000000..71218a924 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class BanHistoryCommandService { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java new file mode 100644 index 000000000..d5599237c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -0,0 +1,26 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.repository.BanHistoryRepository; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class BanHistoryQueryService { + + private final BanHistoryRepository banHistoryRepository; + + public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { + log.debug("Called findRecentActiveBanHistory: {}", userId); + + List banHistories = banHistoryRepository.findAll(); + + } + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java new file mode 100644 index 000000000..06aace4e8 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class UserCommandService { + + + + + + + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java new file mode 100644 index 000000000..4b58d6676 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -0,0 +1,23 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.dto.MyProfileResponseDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class UserFacadeService { + + public MyProfileResponseDto getMyProfile(UserSessionDto user) { + log.debug("Called getMyProfile: {}", user.getName()); + + // Cabinet cabinet = lentService.findActiveLentCabinetByUserId(); + + + } + +} + diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java new file mode 100644 index 000000000..1d30c10ef --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -0,0 +1,31 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.repository.BanHistoryRepository; +import org.ftclub.cabinet.user.repository.UserRepository; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class UserQueryService { + + private final UserRepository userRepository; + + public User getUser(Long userId) { + Optional user = userRepository.findById(userId); + return user.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } + + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index d1cd4ba63..ca96376fe 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -53,7 +53,7 @@ public List findAllActiveUsers() { */ public User findUser(Long userId) { log.debug("Called findUser: {}", userId); - return userRepository.findUser(userId).orElse(null); + return userRepository.findById(userId).orElse(null); } /** @@ -172,7 +172,7 @@ public Page findClubUsers(Pageable pageable) { */ public User getUser(Long userId) { log.debug("Called getUser: {}", userId); - return userRepository.findUser(userId) + return userRepository.findById(userId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index 78b74e0f5..fb6a85f5b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -21,7 +21,7 @@ public interface UserRepository extends JpaRepository { * @return {@link User} */ @Query("SELECT u FROM User u WHERE u.userId = :userId AND u.deletedAt IS NULL") - Optional findUser(@Param("userId") Long userId); + Optional findById(@Param("userId") Long userId); /** * 유저 이름으로 유저를 찾습니다. diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java index a1690f077..2cf6d441c 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java @@ -37,7 +37,7 @@ public class UserRepositoryTest { @Test public void testGetUser() { Long userId = 9L; - Optional user = userRepository.findUser(userId); + Optional user = userRepository.findById(userId); assertTrue(user.isPresent()); assertEquals("user1", user.get().getName()); diff --git a/config b/config index c35892e1f..868b4f34e 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit c35892e1f38120365370bdb419f73869962c0444 +Subproject commit 868b4f34e936b36d739a6d68d280c3e3a148c118 From 6d27f6216c3c67125cbb2d88ebd04c9b4112a7a5 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Wed, 20 Dec 2023 18:31:44 +0900 Subject: [PATCH 0088/1029] =?UTF-8?q?[BE]=20FEAT:=20Transactional=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=99=80=20=EC=9D=BC=EB=B0=98=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/domain/AnnouncementAlarm.java | 2 +- .../ExtensionExpirationImminentAlarm.java | 5 ++- .../alarm/domain/ExtensionIssuanceAlarm.java | 5 ++- .../alarm/domain/LentExpirationAlarm.java | 2 +- .../domain/LentExpirationImminentAlarm.java | 2 +- .../alarm/domain/LentSuccessAlarm.java | 2 +- .../alarm/domain/TransactionalAlarmEvent.java | 5 +++ .../alarm/handler/AlarmEventHandler.java | 31 ++++++++++++++----- .../alarm/slack/dto/SlackResponse.java | 30 +++++++++--------- 9 files changed, 53 insertions(+), 31 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/domain/TransactionalAlarmEvent.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java index 884c2a5d9..2fb611bfc 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java @@ -8,7 +8,7 @@ */ @Getter @AllArgsConstructor -public class AnnouncementAlarm { +public class AnnouncementAlarm implements Alarm, TransactionalAlarmEvent { private final String announcementContent; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java index c6916aad5..de42d5357 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java @@ -1,16 +1,15 @@ package org.ftclub.cabinet.alarm.domain; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Getter; -import java.time.LocalDateTime; - /** * 연장 만료 임박 알람 */ @AllArgsConstructor @Getter -public class ExtensionExpirationImminentAlarm implements Alarm { +public class ExtensionExpirationImminentAlarm implements Alarm, TransactionalAlarmEvent { private final String extensionName; private final LocalDateTime extensionExpirationDate; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java index 208088919..39fde0bf3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java @@ -1,8 +1,7 @@ package org.ftclub.cabinet.alarm.domain; -import lombok.AllArgsConstructor; - import java.time.LocalDateTime; +import lombok.AllArgsConstructor; import lombok.Getter; /** @@ -10,7 +9,7 @@ */ @AllArgsConstructor @Getter -public class ExtensionIssuanceAlarm implements Alarm { +public class ExtensionIssuanceAlarm implements Alarm, TransactionalAlarmEvent { private final String extensionName; private final LocalDateTime extensionExpirationDate; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java index 0b08d56c0..6c1822e2e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java @@ -8,7 +8,7 @@ */ @Getter @AllArgsConstructor -public class LentExpirationAlarm implements Alarm { +public class LentExpirationAlarm implements Alarm, TransactionalAlarmEvent { private final Long daysLeftFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index 3edc3f95b..cd3d006a4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -8,7 +8,7 @@ */ @Getter @AllArgsConstructor -public class LentExpirationImminentAlarm implements Alarm { +public class LentExpirationImminentAlarm implements Alarm, TransactionalAlarmEvent { private final Long daysAfterFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java index 94b706679..de0fae956 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java @@ -10,7 +10,7 @@ */ @Getter @AllArgsConstructor -public class LentSuccessAlarm implements Alarm { +public class LentSuccessAlarm implements Alarm, TransactionalAlarmEvent { private final Location location; private final Integer visibleNum; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/TransactionalAlarmEvent.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/TransactionalAlarmEvent.java new file mode 100644 index 000000000..fe2185222 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/TransactionalAlarmEvent.java @@ -0,0 +1,5 @@ +package org.ftclub.cabinet.alarm.domain; + +public interface TransactionalAlarmEvent { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index c65e3238a..1b65b6cfe 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -1,22 +1,25 @@ package org.ftclub.cabinet.alarm.handler; +import static org.ftclub.cabinet.alarm.domain.AlarmType.EMAIL; +import static org.ftclub.cabinet.alarm.domain.AlarmType.PUSH; +import static org.ftclub.cabinet.alarm.domain.AlarmType.SLACK; +import static org.ftclub.cabinet.exception.ExceptionStatus.NOT_FOUND_USER; + +import java.util.Set; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.alarm.domain.TransactionalAlarmEvent; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.AlarmOptOut; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.repository.UserRepository; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; -import java.util.Set; -import java.util.stream.Collectors; - -import static org.ftclub.cabinet.alarm.domain.AlarmType.*; -import static org.ftclub.cabinet.exception.ExceptionStatus.NOT_FOUND_USER; - @Component @RequiredArgsConstructor @Log4j2 @@ -28,8 +31,22 @@ public class AlarmEventHandler { private final PushAlarmSender pushAlarmSender; @TransactionalEventListener + public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactionalAlarmEvent) { + log.info("handleAlarmEventWithTransactional = {}", transactionalAlarmEvent); + if (!(transactionalAlarmEvent instanceof TransactionalAlarmEvent)) { + return; + } + AlarmEvent alarmEvent = (AlarmEvent) transactionalAlarmEvent; + eventProceed(alarmEvent); + } + + @EventListener public void handleAlarmEvent(AlarmEvent alarmEvent) { - log.info("alarmEvent = {}", alarmEvent); + log.info("handleAlarmEvent = {}", alarmEvent); + eventProceed(alarmEvent); + } + + private void eventProceed(AlarmEvent alarmEvent) { User receiver = userRepository.findUserWithOptOutById(alarmEvent.getReceiverId()) .orElseThrow(() -> new ServiceException(NOT_FOUND_USER)); Set alarmOptOuts = receiver.getAlarmOptOuts() diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java index 8155ffaca..6a53a8753 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/slack/dto/SlackResponse.java @@ -3,36 +3,38 @@ import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; @Log4j2 @Getter +@ToString @AllArgsConstructor public class SlackResponse { + private static final String CONFIG_ERROR = "false"; + private static final String AUTH_ERROR = "error"; private final String ok; - @JsonAlias("user") private final SlackUserInfo slackUserInfo; - private static final String CONFIG_ERROR = "false"; - - private static final String AUTH_ERROR = "error"; - /** - * config 에러 혹은 키 관련 에러일 경우에도 200 ok 를 줍니다. - * ok 의 값을 확인해야 에러인지 확인할 수 있습니다. + * config 에러 혹은 키 관련 에러일 경우에도 200 ok 를 줍니다. ok 의 값을 확인해야 에러인지 확인할 수 있습니다. */ - public void responseCheck(){ - log.error("Slack Response ERROR Error {} ", this.toString()); - - if (ok.equals(CONFIG_ERROR)) { - throw new ServiceException(ExceptionStatus.SLACK_REQUEST_BAD_GATEWAY); + public void responseCheck() { + if (!hasError()) { + return; } - if (ok.equals(AUTH_ERROR)) { - throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); + + switch (ok) { + case CONFIG_ERROR: + log.error("Slack Response ERROR Error {} ", this.toString()); + throw new ServiceException(ExceptionStatus.SLACK_REQUEST_BAD_GATEWAY); + case AUTH_ERROR: + log.error("Slack Response ERROR Error {} ", this.toString()); + throw new ServiceException(ExceptionStatus.SLACK_ID_NOT_FOUND); } } From b527fce5cf3ebc96dc377c6700e799866ecdbd2f Mon Sep 17 00:00:00 2001 From: ldw Date: Wed, 20 Dec 2023 18:32:17 +0900 Subject: [PATCH 0089/1029] [BE] REFACTOR : findRecentActiveBanHistory --- .../cabinet/newService/CabinetQueryService.java | 1 - .../cabinet/lent/newService/LentFacadeService.java | 2 +- .../user/newService/BanHistoryQueryService.java | 10 ++++++++-- .../cabinet/user/repository/BanHistoryRepository.java | 6 ++++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 687335a9b..8281ec59b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -3,7 +3,6 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; -import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.springframework.stereotype.Service; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java index 7e2b8c2e6..d3905a175 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java @@ -48,7 +48,7 @@ public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer pag public List getLentDtoList(Long cabinetId) { log.debug("Called getLentDtoList: {}", cabinetId); cabinetQueryService.getCabinet(cabinetId); - + lentQueryService.findCabinetActiveLentHistory(cabinetId); // cabinetOptionalFetcher.getCabinet(cabinetId); // List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( // cabinetId); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index d5599237c..6a7d75e65 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.Comparator; import java.util.List; @Service @@ -19,8 +20,13 @@ public class BanHistoryQueryService { public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { log.debug("Called findRecentActiveBanHistory: {}", userId); - List banHistories = banHistoryRepository.findAll(); - + List banHistories = banHistoryRepository.findByUserId(userId); + return banHistories.stream() + .filter(history -> history.getUnbannedAt().isAfter(now)) + .sorted(Comparator.comparing(BanHistory::getUnbannedAt, Comparator.reverseOrder())) + .findFirst() + .orElse(null); } + } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java index b1494b03d..5504282be 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java @@ -32,8 +32,10 @@ List findUserActiveBanList( * @param userId 유저 고유 아이디 * @return {@link BanHistory} 리스트 */ - @Query("SELECT b FROM BanHistory b WHERE b.user.userId = :userId") - List findBanHistoriesByUserId(@Param("userId") Long userId); + @Query("SELECT bh" + + " FROM BanHistory bh" + + " WHERE bh.user.userId = :userId") + List findByUserId(@Param("userId") Long userId); /** * 현재 날짜 기준 active한 ban history를 가져옵니다. From a923f688b57b27c4cfdfadf5b9450c7ac59ee9cc Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Wed, 20 Dec 2023 18:32:40 +0900 Subject: [PATCH 0090/1029] [BE] FEAT: alarm toggle endpoint WIP --- .../user/controller/UserController.java | 111 ++++++++++-------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java index ea9eecc90..a7f3aa601 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java @@ -22,59 +22,68 @@ @Log4j2 public class UserController { - private final UserFacadeService userFacadeService; + private final UserFacadeService userFacadeService; - /** - * 현재 로그인한 유저의 프로필을 반환합니다. 전체 사물함 뷰에서 본인의 사물함을 표시하기 위해 사용됩니다. - * - * @param userSessionDto 현재 로그인한 유저의 세션 정보 - * @return {@link MyProfileResponseDto} 현재 로그인한 유저의 프로필 - */ - @GetMapping("/me") - @AuthGuard(level = AuthLevel.USER_ONLY) - public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSessionDto) { - log.info("Called getMyProfile: {}", userSessionDto.getName()); - return userFacadeService.getMyProfile(userSessionDto); - } + /** + * 현재 로그인한 유저의 프로필을 반환합니다. 전체 사물함 뷰에서 본인의 사물함을 표시하기 위해 사용됩니다. + * + * @param userSessionDto 현재 로그인한 유저의 세션 정보 + * @return {@link MyProfileResponseDto} 현재 로그인한 유저의 프로필 + */ + @GetMapping("/me") + @AuthGuard(level = AuthLevel.USER_ONLY) + public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSessionDto) { + log.info("Called getMyProfile: {}", userSessionDto.getName()); + return userFacadeService.getMyProfile(userSessionDto); + } - /** - * 현재 로그인한 유저의 모든 연장권 정보를 리턴합니다. - * - * @param userSessionDto 현재 로그인한 유저의 세션 정보 - * @return {@link LentExtensionPaginationDto} 현재 로그인한 유저의 연장권 정보 - */ - @GetMapping("/me/lent-extensions") - @AuthGuard(level = AuthLevel.USER_ONLY) - public LentExtensionPaginationDto getMyLentExtension( - @UserSession UserSessionDto userSessionDto) { - log.info("Called getMyLentExtension: {}", userSessionDto.getName()); - return userFacadeService.getMyLentExtension(userSessionDto); - } + /** + * 현재 로그인한 유저의 모든 연장권 정보를 리턴합니다. + * + * @param userSessionDto 현재 로그인한 유저의 세션 정보 + * @return {@link LentExtensionPaginationDto} 현재 로그인한 유저의 연장권 정보 + */ + @GetMapping("/me/lent-extensions") + @AuthGuard(level = AuthLevel.USER_ONLY) + public LentExtensionPaginationDto getMyLentExtension( + @UserSession UserSessionDto userSessionDto) { + log.info("Called getMyLentExtension: {}", userSessionDto.getName()); + return userFacadeService.getMyLentExtension(userSessionDto); + } - /** - * 현재 로그인한 유저의 사용가능한 연장권 정보를 리턴합니다. - * - * @param userSessionDto 현재 로그인한 유저의 세션 정보 - * @return {@link LentExtensionPaginationDto} 현재 로그인한 유저의 활성화중인 연장권 정보 - */ - @GetMapping("/me/lent-extensions/active") - @AuthGuard(level = AuthLevel.USER_ONLY) - public LentExtensionPaginationDto getMyActiveLentExtension( - @UserSession UserSessionDto userSessionDto) { - log.info("Called getMyActiveLentExtension: {}", userSessionDto.getName()); - return userFacadeService.getMyActiveLentExtensionPage(userSessionDto); - } + /** + * 현재 로그인한 유저의 사용가능한 연장권 정보를 리턴합니다. + * + * @param userSessionDto 현재 로그인한 유저의 세션 정보 + * @return {@link LentExtensionPaginationDto} 현재 로그인한 유저의 활성화중인 연장권 정보 + */ + @GetMapping("/me/lent-extensions/active") + @AuthGuard(level = AuthLevel.USER_ONLY) + public LentExtensionPaginationDto getMyActiveLentExtension( + @UserSession UserSessionDto userSessionDto) { + log.info("Called getMyActiveLentExtension: {}", userSessionDto.getName()); + return userFacadeService.getMyActiveLentExtensionPage(userSessionDto); + } - /** - * 현재 로그인한 유저의 연장권을 사용합니다. - * - * @param userSessionDto 현재 로그인한 유저의 세션 정보 - */ - @GetMapping("/me/lent-extensions/use") - @AuthGuard(level = AuthLevel.USER_ONLY) - public void useLentExtension( - @UserSession UserSessionDto userSessionDto) { - log.info("Called useLentExtension"); - userFacadeService.useLentExtension(userSessionDto); - } + /** + * 현재 로그인한 유저의 연장권을 사용합니다. + * + * @param userSessionDto 현재 로그인한 유저의 세션 정보 + */ + @GetMapping("/me/lent-extensions/use") + @AuthGuard(level = AuthLevel.USER_ONLY) + public void useLentExtension( + @UserSession UserSessionDto userSessionDto) { + log.info("Called useLentExtension"); + userFacadeService.useLentExtension(userSessionDto); + } + +// @PutMapping("/me/alarms") +// @AuthGuard(level = AuthLevel.USER_ONLY) +// public void updateMyProfile( +// @UserSession UserSessionDto userSessionDto, +// @RequestBody UpdateAlarmRequestDto updateAlarmRequestDto) { +// log.info("Called updateMyProfile"); +// userFacadeService.updateMyProfile(userSessionDto); +// } } From d7760cffdd721eea4810600a6458a3950cdb473a Mon Sep 17 00:00:00 2001 From: ldw Date: Wed, 20 Dec 2023 20:40:45 +0900 Subject: [PATCH 0091/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20getActiveLentEx?= =?UTF-8?q?tension=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/dto/LentExtensionResponseDto.java | 2 ++ .../LentExtensionCommandService.java | 11 +++++++ .../newService/LentExtensionQueryService.java | 29 +++++++++++++++++++ .../user/newService/UserFacadeService.java | 15 ++++++++-- 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java index 8368f1d15..d46d452e0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.dto; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.ToString; import org.ftclub.cabinet.user.domain.LentExtensionType; @@ -8,6 +9,7 @@ @Getter @AllArgsConstructor @ToString +@Builder public class LentExtensionResponseDto { private long lentExtensionId; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java new file mode 100644 index 000000000..fb19dc66b --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java @@ -0,0 +1,11 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class LentExtensionCommandService { +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java new file mode 100644 index 000000000..190ca852c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java @@ -0,0 +1,29 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.dto.LentExtensionResponseDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.user.domain.LentExtension; +import org.ftclub.cabinet.user.domain.LentExtensions; +import org.ftclub.cabinet.user.repository.LentExtensionRepository; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class LentExtensionQueryService { + + private final LentExtensionRepository lentExtensionRepository; + public LentExtension getActiveLentExtension(UserSessionDto userSessionDto) { + log.debug("Called getActiveLentExtension: {}", userSessionDto.getName()); + + List lentExtensions = lentExtensionRepository.findAllByUserId(userSessionDto.getUserId()); + return LentExtensions.builder() + .lentExtensions(lentExtensions) + .build() + .findImminentActiveLentExtension(); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 4b58d6676..7ba29564b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -2,22 +2,33 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.dto.LentExtensionResponseDto; import org.ftclub.cabinet.dto.MyProfileResponseDto; import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.mapper.UserMapper; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.LentExtension; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor @Log4j2 public class UserFacadeService { + private final BanHistoryQueryService banHistoryQueryService; + private final LentExtensionQueryService lentExtensionQueryService; + private final UserMapper userMapper; + public MyProfileResponseDto getMyProfile(UserSessionDto user) { log.debug("Called getMyProfile: {}", user.getName()); // Cabinet cabinet = lentService.findActiveLentCabinetByUserId(); + BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), LocalDateTime.now()); + LentExtension lentExtension = lentExtensionQueryService.getActiveLentExtension(user); - + return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, LentExtensionResponseDto.builder().); } - } From 4c073d9c830d537bb692df2ae5739612fbf96bf4 Mon Sep 17 00:00:00 2001 From: ldw Date: Wed, 20 Dec 2023 21:04:46 +0900 Subject: [PATCH 0092/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20getMyProfile?= =?UTF-8?q?=EC=9D=84=20UserFacadeService=EB=A1=9C=20=EC=9D=B4=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftclub/cabinet/dto/LentExtensionResponseDto.java | 1 + .../cabinet/user/newService/UserFacadeService.java | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java index d46d452e0..1896b95ec 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java @@ -15,6 +15,7 @@ public class LentExtensionResponseDto { private long lentExtensionId; private String name; private int extensionPeriod; + // 추후에 프론트랑 의논 후 expiredAt의 타입을 다시 LocalDateTime으로 변경해야 함 private String expiredAt; private LentExtensionType lentExtensionType; } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 7ba29564b..35bf7e086 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -24,11 +24,17 @@ public class UserFacadeService { public MyProfileResponseDto getMyProfile(UserSessionDto user) { log.debug("Called getMyProfile: {}", user.getName()); - // Cabinet cabinet = lentService.findActiveLentCabinetByUserId(); +// Cabinet cabinet = cabinetQueryService.findActiveLentCabinetByUserId(); BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), LocalDateTime.now()); LentExtension lentExtension = lentExtensionQueryService.getActiveLentExtension(user); - return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, LentExtensionResponseDto.builder().); + return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, + LentExtensionResponseDto.builder() + .lentExtensionId(lentExtension.getLentExtensionId()) + .name(lentExtension.getName()) + .extensionPeriod(lentExtension.getExtensionPeriod()) + .expiredAt(lentExtension.getExpiredAt().toString()) + .lentExtensionType(lentExtension.getLentExtensionType())); } } From 24ed31bdc0fa553132af5b11cadd6b3d49caa91a Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Wed, 20 Dec 2023 21:29:10 +0900 Subject: [PATCH 0093/1029] =?UTF-8?q?fix:=20challenge=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 28 +++---------- .../auth/service/AuthFacadeService.java | 16 ++++--- .../auth/service/AuthFacadeServiceImpl.java | 42 ++++--------------- 3 files changed, 22 insertions(+), 64 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java index ba4ece08e..ca51d7fe1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java @@ -1,9 +1,5 @@ package org.ftclub.cabinet.auth.controller; -import java.io.IOException; -import java.time.LocalDateTime; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.CookieManager; import org.ftclub.cabinet.auth.domain.TokenProvider; @@ -17,6 +13,11 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; + @RestController @RequestMapping("/v4/auth") @RequiredArgsConstructor @@ -60,7 +61,7 @@ public void login(HttpServletResponse response) throws IOException { */ @GetMapping("/login/callback") public void loginCallback(@RequestParam String code, HttpServletRequest req, - HttpServletResponse res) throws IOException { + HttpServletResponse res) throws IOException { authFacadeService.handleLogin(code, req, res, ftApiProperties, LocalDateTime.now()); res.sendRedirect(DomainProperties.getFeHost() + "/home"); } @@ -74,21 +75,4 @@ public void loginCallback(@RequestParam String code, HttpServletRequest req, public void logout(HttpServletResponse res) { authFacadeService.logout(res, ftApiProperties); } - - @GetMapping("/challenge") - public void challengeLogin(HttpServletRequest req, HttpServletResponse res) throws IOException { - // 유저 랜덤생성 && 유저 역할은 일반 유저 - // 토큰 생성 및 response에 쿠키만들어서 심어주기 - authFacadeService.handleTestLogin(req, ftApiProperties, res, LocalDateTime.now()); - System.out.println("??????????????here??????????????????/"); - res.sendRedirect(DomainProperties.getFeHost() + "/home"); -// Map claims = tokenProvider -// String accessToken = tokenProvider.createTokenForTestUser(testUser, LocalDateTime.now()); -// Cookie cookie = cookieManager.cookieOf("access_token", -// accessToken); -// cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); -// System.out.println("?????????????????????? cookie domain = "); -// res.sendRedirect(DomainProperties.getLocal() + "/main"); -// res.sendRedirect(DomainProperties.getFeHost() + "/home"); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index f9670edfc..7026403f9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -1,24 +1,22 @@ package org.ftclub.cabinet.auth.service; -import java.io.IOException; -import java.time.LocalDateTime; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.ftclub.cabinet.config.ApiProperties; import org.ftclub.cabinet.dto.MasterLoginDto; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; + public interface AuthFacadeService { void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) throws IOException; void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, - ApiProperties apiProperties, LocalDateTime now); + ApiProperties apiProperties, LocalDateTime now); void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now); + HttpServletResponse res, LocalDateTime now); void logout(HttpServletResponse res, ApiProperties apiProperties); - - void handleTestLogin(HttpServletRequest req, ApiProperties apiProperties, - HttpServletResponse res, LocalDateTime now); } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java index cd2147986..8aea67960 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java @@ -1,13 +1,6 @@ package org.ftclub.cabinet.auth.service; import com.fasterxml.jackson.databind.JsonNode; -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.CookieManager; import org.ftclub.cabinet.auth.domain.TokenProvider; @@ -16,11 +9,15 @@ import org.ftclub.cabinet.dto.MasterLoginDto; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.user.repository.UserOptionalFetcher; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Map; + @Service @RequiredArgsConstructor public class AuthFacadeServiceImpl implements AuthFacadeService { @@ -31,9 +28,6 @@ public class AuthFacadeServiceImpl implements AuthFacadeService { private final AuthService authService; private final OauthService oauthService; - private final UserOptionalFetcher userOptionalFetcher; - - @Override public void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) throws IOException { @@ -42,7 +36,7 @@ public void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperti @Override public void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, - ApiProperties apiProperties, LocalDateTime now) { + ApiProperties apiProperties, LocalDateTime now) { String apiToken = oauthService.getTokenByCodeRequest(code, apiProperties); JsonNode profile = oauthService.getProfileJsonByToken(apiToken, apiProperties); Map claims = tokenProvider.makeClaimsByProviderProfile( @@ -54,27 +48,9 @@ public void handleLogin(String code, HttpServletRequest req, HttpServletResponse cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); } - @Override - public void handleTestLogin(HttpServletRequest req, ApiProperties apiProperties, - HttpServletResponse res, LocalDateTime now) { - Map claims = new HashMap<>(); - claims.put("name", - "Test:" + userOptionalFetcher.findUsersByPartialName("Test:", Pageable.unpaged()) - .getTotalElements()); - claims.put("email", userOptionalFetcher.findUsersByPartialName("Test:", Pageable.unpaged()) - .getTotalElements() + "Test@student.42seoul.kr"); - claims.put("blackholedAt", null); - claims.put("role", UserRole.USER); - authService.addUserIfNotExistsByClaims(claims); - String accessToken = tokenProvider.createToken(claims, now); - Cookie cookie = cookieManager.cookieOf( - tokenProvider.getTokenNameByProvider(apiProperties.getProviderName()), accessToken); - cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); - } - @Override public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now) { + HttpServletResponse res, LocalDateTime now) { if (!authService.validateMasterLogin(masterLoginDto)) { throw new ControllerException(ExceptionStatus.UNAUTHORIZED); } From 78a6f3f1e39c0928d4050d5cb9a2852da199de92 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Wed, 20 Dec 2023 21:53:23 +0900 Subject: [PATCH 0094/1029] =?UTF-8?q?[BE]=20FIX:=20Alarm=20dto=20=EB=A6=AC?= =?UTF-8?q?=ED=84=B4=ED=83=80=EC=9E=85=20boolean=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/modules.xml | 1 + .../cabinet/alarm/domain/AlarmType.java | 3 +- .../alarm/dto/AlarmTypeResponseDto.java | 33 +++++++++++++++++++ .../cabinet/dto/MyProfileResponseDto.java | 5 ++- .../org/ftclub/cabinet/mapper/UserMapper.java | 4 +-- .../user/service/UserFacadeServiceImpl.java | 5 ++- 6 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java diff --git a/.idea/modules.xml b/.idea/modules.xml index c9893ddb8..3ba732021 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -4,6 +4,7 @@ + \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmType.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmType.java index 183dd9f51..5550ccaed 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmType.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AlarmType.java @@ -3,6 +3,5 @@ public enum AlarmType { SLACK, EMAIL, - PUSH, - ; + PUSH } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java new file mode 100644 index 000000000..5ca1ce085 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java @@ -0,0 +1,33 @@ +package org.ftclub.cabinet.alarm.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import org.ftclub.cabinet.alarm.domain.AlarmType; + +@AllArgsConstructor +@Getter +public class AlarmTypeResponseDto { + + private boolean slack; + private boolean email; + private boolean push; + + @Builder + public AlarmTypeResponseDto(List alarmTypes) { + alarmTypes.forEach(alarmType -> { + switch (alarmType) { + case SLACK: + this.slack = true; + break; + case EMAIL: + this.email = true; + break; + case PUSH: + this.push = true; + break; + } + }); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java index 395bd2498..08d5f9713 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/MyProfileResponseDto.java @@ -2,11 +2,10 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.List; import java.util.Locale; import lombok.AllArgsConstructor; import lombok.Getter; -import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; /** * 내 프로필 정보와 대여 중인 사물함의 ID를 반환하는 DTO입니다. @@ -23,5 +22,5 @@ public class MyProfileResponseDto { DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'KST'", Locale.US) ); private final LentExtensionResponseDto lentExtensionResponseDto; - private final List alarmTypes; + private final AlarmTypeResponseDto alarmTypes; } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java index f6cf36553..c92f763ab 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java @@ -3,7 +3,7 @@ import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; import java.util.List; -import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.ClubUserListDto; @@ -41,7 +41,7 @@ public interface UserMapper { @Mapping(target = "cabinetId", source = "cabinet.cabinetId") MyProfileResponseDto toMyProfileResponseDto(UserSessionDto user, Cabinet cabinet, BanHistory banHistory, LentExtensionResponseDto lentExtensionResponseDto, - List alarmTypes); + AlarmTypeResponseDto alarmTypes); BlockedUserPaginationDto toBlockedUserPaginationDto(List result, Long totalLength); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java index 7b7d08a34..42fe5a60c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; import org.ftclub.cabinet.alarm.repository.AlarmOptOutRepository; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.LentType; @@ -72,9 +73,11 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { List alarmOptOuts = alarmOptOutRepository.findAllByUserId(user.getUserId()); List alarmTypes = alarmOptOuts.stream().map(AlarmOptOut::getAlarmType) .collect(Collectors.toList()); + AlarmTypeResponseDto alarmTypeResponseDto = AlarmTypeResponseDto.builder() + .alarmTypes(alarmTypes).build(); return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, - activeLentExtension, alarmTypes); + activeLentExtension, alarmTypeResponseDto); } @Override From f5ff97d2da2ea6325e18a26d59317d70f7aee769 Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 21 Dec 2023 00:25:50 +0900 Subject: [PATCH 0095/1029] =?UTF-8?q?[FE]=20FIX:=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20console.log=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/axios/axios.custom.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/api/axios/axios.custom.ts b/frontend/src/api/axios/axios.custom.ts index f836160ef..a9ac008fd 100644 --- a/frontend/src/api/axios/axios.custom.ts +++ b/frontend/src/api/axios/axios.custom.ts @@ -17,7 +17,6 @@ const axiosMyInfoURL = "/v4/users/me"; export const axiosMyInfo = async (): Promise => { try { const response = await instance.get(axiosMyInfoURL); - console.log(response); return response; } catch (error) { throw error; From faf3fcc754a26a10ab211432ea3c28f55de7e26b Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 21 Dec 2023 00:26:19 +0900 Subject: [PATCH 0096/1029] =?UTF-8?q?[FE]=20FIX:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20=EC=95=8C?= =?UTF-8?q?=EB=9E=8C=20=EC=B9=B4=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ProfilePage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index a23a0c407..5014887aa 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -57,6 +57,7 @@ const ProfilePage = () => { unbannedAt={myInfo.unbannedAt} /> + )} From 813cb4f5743dfe0b89fde878d7c091fa9839646e Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 21 Dec 2023 03:32:41 +0900 Subject: [PATCH 0097/1029] =?UTF-8?q?[FE]=20FEAT:=20Alarm=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20AlarmInfo=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20UserDto=20=EC=97=90=20AlarmInfo=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/recoil/atoms.ts | 1 + frontend/src/types/dto/alarm.dto.ts | 12 ++++++++++++ frontend/src/types/dto/user.dto.ts | 12 ++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 frontend/src/types/dto/alarm.dto.ts diff --git a/frontend/src/recoil/atoms.ts b/frontend/src/recoil/atoms.ts index c4a390537..4f87bc581 100644 --- a/frontend/src/recoil/atoms.ts +++ b/frontend/src/recoil/atoms.ts @@ -23,6 +23,7 @@ export const userState = atom({ name: "default", lentExtensionResponseDto: null, unbannedAt: null, + alarmTypes: null, }, }); diff --git a/frontend/src/types/dto/alarm.dto.ts b/frontend/src/types/dto/alarm.dto.ts new file mode 100644 index 000000000..26b4a17d3 --- /dev/null +++ b/frontend/src/types/dto/alarm.dto.ts @@ -0,0 +1,12 @@ +/** + * @description 유저 알림 정보 + * @interface + * @property {boolean} email : 이메일 알림 여부 + * @property {boolean} push : 웹 (브라우저) 푸시 알림 여부 + * @property {boolean} slack : 슬랙 알림 여부 + */ +export interface AlarmInfo { + email: boolean; + push: boolean; + slack: boolean; +} diff --git a/frontend/src/types/dto/user.dto.ts b/frontend/src/types/dto/user.dto.ts index 64afd5f54..e9c9fd478 100644 --- a/frontend/src/types/dto/user.dto.ts +++ b/frontend/src/types/dto/user.dto.ts @@ -1,6 +1,17 @@ +import { AlarmInfo } from "@/types/dto/alarm.dto"; import { CabinetInfo } from "@/types/dto/cabinet.dto"; import { LentExtensionDto } from "@/types/dto/lent.dto"; +/** + * @description 유저 정보 + * @interface + * @property {number} userId : 42 고유 ID + * @property {string} name : 42 로그인 ID (인트라 아이디) + * @property {number} cabinetId : 캐비닛 고유 ID + * @property {LentExtensionDto} lentExtensionResponseDto : 연장권 정보 + * @property {Date} unbannedAt : 벤 해제 시간 + * @property {AlarmInfo} alarm : 알림 정보 + */ export interface UserDto { userId: number | null; // 42 고유 ID name: string | null; // 42 로그인 ID @@ -9,6 +20,7 @@ export interface UserDto { cabinetId: number | null; // 캐비닛 고유 ID lentExtensionResponseDto: LentExtensionDto | null; unbannedAt?: Date | null; + alarmTypes: AlarmInfo | null; } export interface UserInfo { From e3e448cacdbf1412e6c49d4838caa88fbe7c23a4 Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 21 Dec 2023 03:33:19 +0900 Subject: [PATCH 0098/1029] =?UTF-8?q?[FE]=20FEAT:=20=EC=83=88=20=EC=95=8C?= =?UTF-8?q?=EB=9E=8C=20=EC=84=A4=EC=A0=95=EC=9D=84=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=ED=95=98=EB=8A=94=20axiosUpdateAlarm=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/axios/axios.custom.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frontend/src/api/axios/axios.custom.ts b/frontend/src/api/axios/axios.custom.ts index a9ac008fd..211b62df2 100644 --- a/frontend/src/api/axios/axios.custom.ts +++ b/frontend/src/api/axios/axios.custom.ts @@ -1,3 +1,4 @@ +import { AlarmInfo } from "@/types/dto/alarm.dto"; import { ClubUserDto } from "@/types/dto/lent.dto"; import CabinetStatus from "@/types/enum/cabinet.status.enum"; import CabinetType from "@/types/enum/cabinet.type.enum"; @@ -58,6 +59,18 @@ export const axiosUseExtension = async (): Promise => { } }; +const axiosUpdateAlarmURL = "/v4/users/me/alarms"; +export const axiosUpdateAlarm = async (alarm: AlarmInfo): Promise => { + try { + const response = await instance.put(axiosUpdateAlarmURL, { + alarm, + }); + return response; + } catch (error) { + throw error; + } +}; + // V3 API const axiosBuildingFloorURL = "/v4/cabinets/buildings/floors"; export const axiosBuildingFloor = async (): Promise => { From c8f6d813c0d6f1d6343b3ba67d52a8fd2cac91af Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 21 Dec 2023 03:36:24 +0900 Subject: [PATCH 0099/1029] =?UTF-8?q?[FE]=20FIX:=20=EC=95=8C=EB=9E=8C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EB=8C=80=EB=A1=9C=20switch=20toggle=20?= =?UTF-8?q?=EC=9D=B4=20=EC=84=A4=EC=A0=95=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95;=20=EA=B8=B0=EB=B3=B8=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20/=20=EC=97=90=EB=9F=AC=20/=20=EC=B7=A8=EC=86=8C(=EC=9B=90?= =?UTF-8?q?=EB=B3=B5)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationCard.container.tsx | 105 +++++++++++++++++- .../NotificationCard/NotificationCard.tsx | 39 ++++++- frontend/src/pages/ProfilePage.tsx | 2 +- 3 files changed, 138 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx index b807e5066..7dc12d8ed 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx @@ -1,7 +1,108 @@ +import { useEffect, useState } from "react"; +import { set } from "react-ga"; import NotificationCard from "@/components/Card/NotificationCard/NotificationCard"; +import ModalPortal from "@/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/components/Modals/ResponseModal/ResponseModal"; +import { AlarmInfo } from "@/types/dto/alarm.dto"; +import { axiosUpdateAlarm } from "@/api/axios/axios.custom"; -const NotificationCardContainer = () => { - return ; +const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { + const [showResponseModal, setShowResponseModal] = useState(false); + const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false); + const [modalTitle, setModalTitle] = useState(""); + const [currentAlarms, setCurrentAlarms] = useState(alarm); + const [originalAlarms, setOriginalAlarms] = useState(alarm); + const [isModified, setIsModified] = useState(false); + const [forceRender, setForceRender] = useState(0); + + useEffect(() => { + setCurrentAlarms(alarm); + setOriginalAlarms(alarm); + }, [alarm]); + + const handleToggleChange = (newSetting: AlarmInfo) => { + setCurrentAlarms(newSetting); + if (JSON.stringify(newSetting) === JSON.stringify(originalAlarms)) { + setIsModified(false); + } else { + setIsModified(true); + } + }; + + const handleSave = async () => { + try { + await axiosUpdateAlarm({ + email: currentAlarms?.email ?? false, + push: currentAlarms?.push ?? false, + slack: currentAlarms?.slack ?? false, + }); + setOriginalAlarms(currentAlarms); + setModalTitle("설정이 저장되었습니다"); + } catch (error: any) { + setCurrentAlarms(originalAlarms); + setForceRender((prev) => prev + 1); + setHasErrorOnResponse(true); + setModalTitle(error.response.data.message); + } finally { + setShowResponseModal(true); + setIsModified(false); + } + }; + + const handleCancel = () => { + setCurrentAlarms(originalAlarms); + setIsModified(false); + setForceRender((prev) => prev + 1); + }; + + const handleCloseModal = () => { + setShowResponseModal(false); + }; + + return ( + <> + + + {showResponseModal && + (hasErrorOnResponse ? ( + + ) : ( + + ))} + + + ); }; export default NotificationCardContainer; diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.tsx b/frontend/src/components/Card/NotificationCard/NotificationCard.tsx index 1b3205ae6..c97789ddc 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.tsx +++ b/frontend/src/components/Card/NotificationCard/NotificationCard.tsx @@ -1,31 +1,60 @@ -import Card from "@/components/Card/Card"; +import Card, { IButtonProps } from "@/components/Card/Card"; import { CardContentStyled, CardContentWrapper, ContentInfoStyled, } from "@/components/Card/CardStyles"; import ToggleSwitch from "@/components/Common/ToggleSwitch"; +import { AlarmInfo } from "@/types/dto/alarm.dto"; -const NotificationCard = () => { +interface NotificationCardProps { + alarm: AlarmInfo | null; + buttons: IButtonProps[]; + onToggleChange: (newAlarms: AlarmInfo) => void; +} + +const NotificationCard = ({ + alarm, + buttons, + onToggleChange, +}: NotificationCardProps) => { + const handleToggle = (type: keyof AlarmInfo, checked: boolean) => { + if (!alarm) return; + const newAlarms = { ...alarm, [type]: checked }; + onToggleChange(newAlarms); + }; return ( 메일 - + handleToggle("email", value)} + /> 슬랙 - + handleToggle("slack", value)} + /> 브라우저 - + handleToggle("push", value)} + /> diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index 5014887aa..e72322ea6 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -57,7 +57,7 @@ const ProfilePage = () => { unbannedAt={myInfo.unbannedAt} /> - + )} From a475c4f554089ba434bf58a96f10fa0750792aa7 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 13:23:08 +0900 Subject: [PATCH 0100/1029] =?UTF-8?q?[BE]=20lent=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EB=8F=99=EC=9E=91=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminCabinetController.java | 2 +- .../cabinet/cabinet/domain/Cabinet.java | 21 - .../newService/CabinetCommandService.java | 37 ++ .../newService/CabinetFacadeService.java | 10 +- .../newService/CabinetQueryService.java | 42 +- .../repository/CabinetOptionalFetcher.java | 10 +- .../cabinet/repository/CabinetRepository.java | 33 +- .../service/CabinetFacadeServiceImpl.java | 68 +++- .../cabinet/dto/UserVerifyRequestDto.java | 21 + .../cabinet/exception/ExceptionStatus.java | 2 +- .../lent/controller/AdminLentController.java | 15 +- .../lent/controller/LentController.java | 41 +- .../cabinet/lent/domain/LentPolicy.java | 92 ----- .../cabinet/lent/domain/LentPolicyImpl.java | 256 ------------ .../lent/newService/LentCommandService.java | 30 +- .../lent/newService/LentFacadeService.java | 382 +++++++++++++++--- .../lent/newService/LentPolicyService.java | 186 +++++++++ .../lent/newService/LentQueryService.java | 50 +-- .../lent/newService/LentRedisService.java | 119 ++++++ .../lent/repository/LentOptionalFetcher.java | 9 +- .../cabinet/lent/repository/LentRedis.java | 198 +++++---- .../lent/repository/LentRepository.java | 17 +- .../lent/service/LentFacadeService.java | 156 ------- .../lent/service/LentFacadeServiceImpl.java | 261 ------------ .../cabinet/lent/service/LentService.java | 93 ----- .../cabinet/lent/service/LentServiceImpl.java | 284 ------------- .../cabinet/redis/ExpirationListener.java | 13 +- .../user/controller/AdminUserController.java | 201 +++++---- .../newService/BanHistoryCommandService.java | 11 + .../newService/BanHistoryQueryService.java | 22 +- .../user/newService/BanPolicyService.java | 29 ++ .../user/newService/UserFacadeService.java | 16 +- .../user/newService/UserQueryService.java | 22 +- .../user/repository/BanHistoryRepository.java | 2 +- .../user/repository/UserRepository.java | 14 +- .../cabinet/user/service/UserServiceImpl.java | 2 +- .../org/ftclub/cabinet/utils/DateUtil.java | 4 + .../blackhole/manager/BlackholeManager.java | 22 +- .../leave/absence/LeaveAbsenceManager.java | 8 +- .../utils/scheduler/SystemScheduler.java | 6 +- .../cabinet/redis/RedisRepositoryTest.java | 4 +- .../repository/BanHistoryRepositoryTest.java | 2 +- .../user/service/UserServiceUnitTest.java | 25 +- 43 files changed, 1220 insertions(+), 1618 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicy.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyImpl.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/LentService.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java index 6bcc99d9a..5adb31b22 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java @@ -17,7 +17,7 @@ import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Cabinet.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Cabinet.java index a979beb2f..1da699ad5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Cabinet.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Cabinet.java @@ -212,25 +212,4 @@ public boolean equals(final Object other) { public int hashCode() { return Objects.hash(this.cabinetId); } - - /** - * 대여 시작/종료에 따른 사용자의 수와 현재 상태에 따라 상태를 변경합니다. - * - * @param userCount 현재 사용자 수 - */ - public void specifyStatusByUserCount(Integer userCount) { - log.info("specifyStatusByUserCount : {}", userCount); - if (this.status.equals(CabinetStatus.BROKEN)) { - throw new DomainException(INVALID_STATUS); - } - if (userCount.equals(0)) { - this.status = CabinetStatus.PENDING; -// this.status = CabinetStatus.AVAILABLE; - return; - } - if (userCount.equals(this.maxUser)) { - this.status = CabinetStatus.FULL; - return; - } - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java index 28ea6135b..88fa7b181 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java @@ -1,10 +1,47 @@ package org.ftclub.cabinet.cabinet.newService; +import static org.ftclub.cabinet.exception.ExceptionStatus.INVALID_STATUS; + import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.repository.CabinetRepository; +import org.ftclub.cabinet.exception.DomainException; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class CabinetCommandService { + private final CabinetRepository cabinetRepository; + + public void changeStatus(Cabinet cabinet, CabinetStatus cabinetStatus) { + cabinet.specifyStatus(cabinetStatus); + cabinetRepository.save(cabinet); + } + + public void changeUserCount(Cabinet cabinet, int userCount) { + if (cabinet.isStatus(CabinetStatus.BROKEN)) { + throw new DomainException(INVALID_STATUS); + } + if (userCount == 0) { + cabinet.specifyStatus(CabinetStatus.PENDING); + cabinet.writeMemo(""); + cabinet.writeTitle(""); + } + if (userCount == cabinet.getMaxUser()) { + cabinet.specifyStatus(CabinetStatus.FULL); + } + cabinetRepository.save(cabinet); + } + + public void updateTitle(Cabinet cabinet, String title) { + cabinet.writeTitle(title); + cabinetRepository.save(cabinet); + } + + public void updateMemo(Cabinet cabinet, String memo) { + cabinet.writeMemo(memo); + cabinetRepository.save(cabinet); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index a8104f359..872b505fa 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -18,11 +18,11 @@ public class CabinetFacadeService { *

* 존재하는 모든 건물들을 가져오고, 각 건물별 층 정보들을 가져옵니다. */ - @Transactional(readOnly = true) - public List getBuildingFloorsResponse() { - log.debug("getBuildingFloorsResponse"); - - } +// @Transactional(readOnly = true) +// public List getBuildingFloorsResponse() { +// log.debug("getBuildingFloorsResponse"); +// +// } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 687335a9b..9c41def37 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -1,28 +1,48 @@ package org.ftclub.cabinet.cabinet.newService; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; -import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.springframework.stereotype.Service; -import java.util.Optional; - @Service @RequiredArgsConstructor public class CabinetQueryService { - private final CabinetRepository cabinetRepository; + private final CabinetRepository cabinetRepository; + + public Cabinet getCabinet(Long cabinetId) { + Optional cabinet = cabinetRepository.findById(cabinetId); + return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); + } + + public Cabinet getCabinetWithLock(Long cabinetId) { + Optional cabinet = cabinetRepository.findByIdWithLock(cabinetId); + return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); + } + + public List getCabinetWithLock(List cabinetIds) { + return cabinetRepository.findAllByIdsWithLock(cabinetIds); + } - public Cabinet getCabinet(Long cabinetId) { - Optional cabinet = cabinetRepository.findById(cabinetId); - return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); - } + public Cabinet getUserActiveCabinetWithLock(Long userId) { + Optional cabinet = + cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNullWithLock(userId); + return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); + } - public List findAllBuildings() { - log.debug("Called findAllBuildings"); + public Cabinet findUserActiveCabinet(Long userId) { + Optional cabinet = + cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId); + return cabinet.orElse(null); + } - } +// public List findAllBuildings() { +// log.debug("Called findAllBuildings"); +// +// } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java index 98834b716..4966939d4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java @@ -49,7 +49,7 @@ public List findCabinetsActiveLentHistoriesByBuilding */ public Cabinet findLentCabinetByUserId(Long userId) { log.debug("Called findLentCabinetByUserId: {}", userId); - return cabinetRepository.findByUserIdAndEndedAtIsNull(userId).orElse(null); + return cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId).orElse(null); } public List findAllBuildings() { @@ -103,7 +103,7 @@ public List findAllCabinetsByBuildingAndFloor(String building, Integer */ public Cabinet getCabinetForUpdate(Long cabinetId) { log.debug("Called getCabinetForUpdate: {}", cabinetId); - return cabinetRepository.findByCabinetIdForUpdate(cabinetId) + return cabinetRepository.findByIdWithLock(cabinetId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } @@ -129,7 +129,7 @@ public Cabinet getCabinet(Long cabinetId) { */ public Cabinet getLentCabinetByUserId(Long userId) { log.debug("Called getLentCabinetByUserId: {}", userId); - return cabinetRepository.findByUserIdAndEndedAtIsNull(userId) + return cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } @@ -153,8 +153,8 @@ public Cabinet getClubCabinet(Long cabinetId) { /** * building과 status에 맞고 lentType이 아닌 사물함을 찾습니다. * - * @param building 건물 이름 - * @param lentType 사물함 타입 + * @param building 건물 이름 + * @param lentType 사물함 타입 * @param cabinetStatuses 사물함 상태 {@link List} * @return {@link Cabinet} {@link List} */ diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 9daf9672a..bbc6b8859 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -47,7 +47,32 @@ public interface CabinetRepository extends JpaRepository, Cabinet @Query("SELECT c " + "FROM Cabinet c " + "WHERE c.cabinetId = :cabinetId") - Optional findByCabinetIdForUpdate(@Param("cabinetId") Long cabinetId); + Optional findByIdWithLock(@Param("cabinetId") Long cabinetId); + + /** + * cabinetId 리스트로 사물함을 조회한다.(조회 이후 업데이트를 위해 X Lock을 건다.) + * + * @param cabinetIds 사물함 ID 리스트 + * @return 사물함 {@link List} + */ + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("SELECT c " + + "FROM Cabinet c " + + "WHERE c.cabinetId IN (:cabinetIds)") + List findAllByIdsWithLock(List cabinetIds); + + /** + * userId로 현재 대여 중인 사물함을 조회한다. + * + * @param userId 사용자 ID + * @return 사물함 {@link Optional} + */ + @Query("SELECT c " + + "FROM Cabinet c " + + "LEFT JOIN LentHistory lh ON c.cabinetId = lh.cabinetId " + + "LEFT JOIN User u ON u.userId = lh.userId " + + "WHERE u.userId = :userId AND lh.endedAt IS NULL") + Optional findByUserIdAndLentHistoryEndedAtIsNull(@Param("userId") Long userId); /** * userId로 현재 대여 중인 사물함을 조회한다. @@ -55,18 +80,20 @@ public interface CabinetRepository extends JpaRepository, Cabinet * @param userId 사용자 ID * @return 사물함 {@link Optional} */ + @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("SELECT c " + "FROM Cabinet c " + "LEFT JOIN LentHistory lh ON c.cabinetId = lh.cabinetId " + "LEFT JOIN User u ON u.userId = lh.userId " + "WHERE u.userId = :userId AND lh.endedAt IS NULL") - Optional findByUserIdAndEndedAtIsNull(@Param("userId") Long userId); + Optional findByUserIdAndLentHistoryEndedAtIsNullWithLock(@Param("userId") Long userId); Page findPaginationByLentType(@Param("lentType") LentType lentType, Pageable pageable); Page findPaginationByStatus(@Param("status") CabinetStatus status, Pageable pageable); - Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, Pageable pageable); + Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, + Pageable pageable); @Query("SELECT c " + "FROM Cabinet c " diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index d31c5d8e7..05363483c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -1,5 +1,20 @@ package org.ftclub.cabinet.cabinet.service; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toMap; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -7,7 +22,22 @@ import org.ftclub.cabinet.cabinet.domain.Grid; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; +import org.ftclub.cabinet.dto.BuildingFloorsDto; +import org.ftclub.cabinet.dto.CabinetClubStatusRequestDto; +import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetPaginationDto; +import org.ftclub.cabinet.dto.CabinetPendingResponseDto; +import org.ftclub.cabinet.dto.CabinetPreviewDto; +import org.ftclub.cabinet.dto.CabinetSimpleDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.CabinetStatusRequestDto; +import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentHistoryDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; import org.ftclub.cabinet.lent.repository.LentRedis; @@ -21,15 +51,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.*; -import java.util.stream.Collectors; - -import static java.util.stream.Collectors.*; -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; - @Service @RequiredArgsConstructor @Log4j2 @@ -70,9 +91,10 @@ public List getBuildingFloorsResponse() { public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { log.debug("getCabinetInfo"); List lentDtos = new ArrayList<>(); - List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId(cabinetId); + List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( + cabinetId); if (lentHistories.isEmpty()) { - ArrayList users = lentRedis.getUserIdsByCabinetIdInRedis(cabinetId.toString()); + List users = lentRedis.getAllUserInCabinet(cabinetId.toString()); for (String user : users) { String userName = userOptionalFetcher.findUser(Long.valueOf(user)).getName(); lentDtos.add(new LentDto(Long.valueOf(user), userName, null, null, null)); @@ -83,7 +105,7 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { lentDtos.add(lentMapper.toLentDto(findUser, lentHistory)); } return cabinetMapper.toCabinetInfoResponseDto(cabinetOptionalFetcher.findCabinet(cabinetId), - lentDtos, lentRedis.getSessionExpiredAtInRedis(cabinetId)); + lentDtos, lentRedis.getCabinetExpiredAt(cabinetId.toString())); } /** @@ -118,7 +140,8 @@ private String checkCabinetTitle(Cabinet cabinet, List lentHistorie */ @Override @Transactional(readOnly = true) - public List getCabinetsPerSection(String building, Integer floor) { + public List getCabinetsPerSection(String building, + Integer floor) { log.debug("getCabinetsPerSection"); List currentLentCabinets = cabinetOptionalFetcher .findCabinetsActiveLentHistoriesByBuildingAndFloor(building, floor); @@ -138,10 +161,12 @@ public List getCabinetsPerSection(String building cabinetLentHistories.getOrDefault(cabinet, Collections.emptyList()); String title = checkCabinetTitle(cabinet, lentHistories); cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()) - .add(cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), title)); + .add(cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), + title)); }); return cabinetPreviewsBySection.entrySet().stream() - .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), entry.getValue())) + .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), + entry.getValue())) .collect(Collectors.toList()); } @@ -151,7 +176,7 @@ public List getCabinetsPerSection(String building @Override @Transactional(readOnly = true) public CabinetPaginationDto getCabinetPaginationByLentType(LentType lentType, Integer page, - Integer size) { + Integer size) { log.debug("getCabinetPaginationByLentType"); if (size <= 0) { size = Integer.MAX_VALUE; @@ -172,7 +197,7 @@ public CabinetPaginationDto getCabinetPaginationByLentType(LentType lentType, In @Override @Transactional(readOnly = true) public CabinetPaginationDto getCabinetPaginationByStatus(CabinetStatus status, Integer page, - Integer size) { + Integer size) { log.debug("getCabinetPaginationByStatus"); if (size <= 0) { size = Integer.MAX_VALUE; @@ -192,7 +217,7 @@ public CabinetPaginationDto getCabinetPaginationByStatus(CabinetStatus status, I @Override @Transactional(readOnly = true) public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, Integer page, - Integer size) { + Integer size) { log.debug("getCabinetPaginationByVisibleNum"); if (size <= 0) { size = Integer.MAX_VALUE; @@ -212,7 +237,7 @@ public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, @Override @Transactional(readOnly = true) public LentHistoryPaginationDto getCabinetLentHistoriesPagination(Long cabinetId, Integer page, - Integer size) { + Integer size) { log.debug("getCabinetLentHistoriesPagination"); if (size <= 0) { size = Integer.MAX_VALUE; @@ -310,7 +335,8 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { .map(LentHistory::getEndedAt) .max(LocalDateTime::compareTo).orElse(null); if (latestEndedAt != null && latestEndedAt.toLocalDate().isEqual(yesterday)) { - cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); + cabinetFloorMap.get(floor) + .add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); } } }); diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java new file mode 100644 index 000000000..36e50e8f3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.UserRole; + +@Getter +@AllArgsConstructor +public class UserVerifyRequestDto { + + private UserRole userRole; + private LocalDateTime blackholedAt; + private int lentCount; + private Long cabinetId; + private CabinetStatus cabinetStatus; + private List banHistories; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index ce602bb12..1e8362db8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -34,7 +34,6 @@ public enum ExceptionStatus { INVALID_EXPIRED_AT(HttpStatus.BAD_REQUEST, "잘못된 만료일 입니다"), INCORRECT_ARGUMENT(HttpStatus.BAD_REQUEST, "잘못된 입력입니다"), ALL_BANNED_USER(HttpStatus.BAD_REQUEST, "ALL 밴 상태의 유저입니다."), - SHARE_BANNED_USER(HttpStatus.BAD_REQUEST, "초대코드를 3회 이상 틀린 유저입니다."), LENT_PENDING(HttpStatus.BAD_REQUEST, "오픈 예정인 사물함입니다."), WRONG_SHARE_CODE(HttpStatus.BAD_REQUEST, "초대코드가 유효하지 않습니다."), NOT_FOUND_BAN_HISTORY(HttpStatus.NOT_FOUND, "현재 정지 상태인 유저가 아닙니다."), @@ -49,6 +48,7 @@ public enum ExceptionStatus { EXTENSION_NOT_FOUND(HttpStatus.BAD_REQUEST, "연장권이 존재하지 않습니다."), EXTENSION_SOLO_IN_SHARE_NOT_ALLOWED(HttpStatus.UNAUTHORIZED, "연장권은 1명일 때 사용할 수 없습니다."), EXTENSION_LENT_DELAYED(HttpStatus.UNAUTHORIZED, "연장권은 연체된 사물함에 사용할 수 없습니다."), + INVALID_LENT_TYPE(HttpStatus.BAD_REQUEST, "사물함의 대여 타입이 유효하지 않습니다."), ; final private int statusCode; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java index cfc88625d..a3389c891 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java @@ -7,10 +7,9 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; 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; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -29,21 +28,13 @@ public void terminateLentCabinets( @Valid @RequestBody ReturnCabinetsRequestDto returnCabinetsRequestDto) { log.info("Called terminateLentCabinets returnCabinetsRequestDto={}", returnCabinetsRequestDto); - lentFacadeService.terminateLentCabinets(returnCabinetsRequestDto); + lentFacadeService.endCabinetLent(returnCabinetsRequestDto.getCabinetIds()); } @PatchMapping("/return-users/{userId}") @AuthGuard(level = ADMIN_ONLY) public void terminateLentUser(@PathVariable("userId") Long userId) { log.info("Called terminateLentUser userId={}", userId); - lentFacadeService.terminateLentCabinet(userId); - } - - @PostMapping("lent/users/{userId}/cabinets/{cabinetId}") - @AuthGuard(level = ADMIN_ONLY) - public void assignLent(@PathVariable("userId") Long userId, - @PathVariable("cabinetId") Long cabinetId) { - log.info("Called assignLent userId={} cabinetId={}", userId, cabinetId); - lentFacadeService.assignLent(userId, cabinetId); + lentFacadeService.endUserLent(userId); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java index b63617c32..eab419be2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java @@ -8,11 +8,10 @@ import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.dto.MyCabinetResponseDto; import org.ftclub.cabinet.dto.ShareCodeDto; -import org.ftclub.cabinet.dto.UpdateCabinetMemoDto; -import org.ftclub.cabinet.dto.UpdateCabinetTitleDto; import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.user.domain.UserSession; +import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -21,7 +20,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -39,6 +37,7 @@ public void startLentCabinet( log.info("Called startLentCabinet user: {}, cabinetId: {}", user, cabinetId); lentFacadeService.startLentCabinet(user.getUserId(), cabinetId); } + @PostMapping("/cabinets/share/{cabinetId}") public void startLentShareCabinet( @UserSession UserSessionDto user, @@ -54,14 +53,14 @@ public void cancelLentShareCabinet( @UserSession UserSessionDto user, @PathVariable Long cabinetId) { log.info("Called cancelLentShareCabinet user: {}, cabinetId: {}", user, cabinetId); - lentFacadeService.cancelLentShareCabinet(user.getUserId(), cabinetId); + lentFacadeService.cancelShareCabinetLent(user.getUserId(), cabinetId); } @PatchMapping("/return") public void endLent( @UserSession UserSessionDto userSessionDto) { log.info("Called endLent user: {}", userSessionDto); - lentFacadeService.endLentCabinet(userSessionDto); + lentFacadeService.endUserLent(userSessionDto.getUserId()); } @PatchMapping("/return-memo") @@ -70,25 +69,7 @@ public void endLentWithMemo( @Valid @RequestBody LentEndMemoDto lentEndMemoDto) { log.info("Called endLentWithMemo user: {}, lentEndMemoDto: {}", userSessionDto, lentEndMemoDto); - lentFacadeService.endLentCabinetWithMemo(userSessionDto, lentEndMemoDto); - } - - @PatchMapping("/me/memo") - public void updateCabinetMemo( - @UserSession UserSessionDto user, - @Valid @RequestBody UpdateCabinetMemoDto updateCabinetMemoDto) { - log.info("Called updateCabinetMemo user: {}, updateCabinetMemoDto: {}", user, - updateCabinetMemoDto); - lentFacadeService.updateCabinetMemo(user, updateCabinetMemoDto); - } - - @PatchMapping("/me/cabinet-title") - public void updateCabinetTitle( - @UserSession UserSessionDto user, - @Valid @RequestBody UpdateCabinetTitleDto updateCabinetTitleDto) { - log.info("Called updateCabinetTitle user: {}, updateCabinetTitleDto: {}", user, - updateCabinetTitleDto); - lentFacadeService.updateCabinetTitle(user, updateCabinetTitleDto); + lentFacadeService.endUserLent(userSessionDto.getUserId(), lentEndMemoDto.getCabinetMemo()); } @PatchMapping("/me/cabinet") @@ -97,7 +78,8 @@ public void updateCabinetInfo( @RequestBody CabinetInfoRequestDto cabinetInfoRequestDto) { log.info("Called updateCabinetInfo user: {}, cabinetInfoRequestDto: {}", user, cabinetInfoRequestDto); - lentFacadeService.updateCabinetInfo(user, cabinetInfoRequestDto); + lentFacadeService.updateLentCabinetInfo(user.getUserId(), + cabinetInfoRequestDto.getTitle(), cabinetInfoRequestDto.getMemo()); } @GetMapping("/me") @@ -114,9 +96,8 @@ public ResponseEntity getMyLentInfo( @GetMapping("/me/histories") public LentHistoryPaginationDto getMyLentLog( @UserSession UserSessionDto user, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getMyLentLog user: {}, page: {}, size: {}", user, page, size); - return lentFacadeService.getMyLentLog(user, page, size); + @RequestBody @Valid PageRequest pageRequest) { + log.info("Called getMyLentLog user: {}, pageable: {}", user, pageRequest); + return lentFacadeService.getMyLentLog(user, pageRequest); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicy.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicy.java deleted file mode 100644 index 851324d30..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicy.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.ftclub.cabinet.lent.domain; - -import java.time.LocalDateTime; -import java.util.List; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.User; - -/** - * lent정책과 관련된 class - */ -public interface LentPolicy { - - /** - * @param now : 현재 시각 - * @param totalUserCount : 공유사물함에 성공적으로 등록된 유저 수 - * @return - */ - LocalDateTime generateSharedCabinetExpirationDate(LocalDateTime now, - Integer totalUserCount); - - /** - * 적절한 만료일을 만들어냅니다 - * - * @param now 현재 시각 - * @param cabinet 대여하려는 사물함 - * @return cabinet을 빌릴 때 들어가야 하는 만료일 - */ - LocalDateTime generateExpirationDate(LocalDateTime now, Cabinet cabinet); - - /** - * @param now - * @return - */ - LocalDateTime generateExtendedExpirationDate(LocalDateTime now); - - /** - * 만료일을 @{@link LentHistory}에 적용시킵니다. 현재와 과거의 기록들에 적용합니다. - * - * @param curHistory 현재 대여 기록 - * @param expiredAt 적용하려는 만료일 - */ - void applyExpirationDate(LentHistory curHistory, LocalDateTime expiredAt); - - /** - * 대여할 수 있는 유저인지 확인합니다. - * - * @param user 대여를 하려는 유저 - * @param cabinet 대여를 하려는 사물함 - * @param userActiveLentCount 유저가 빌리고 있는 사물함 개수 - * @param userActiveBanList 유저의 벤 당하고 있는 리스트 없다면 빈 리스트 - * @return {@link LentPolicyStatus} 현재 대여의 상태 - */ - LentPolicyStatus verifyUserForLent(User user, Cabinet cabinet, int userActiveLentCount, - List userActiveBanList); - - LentPolicyStatus verifyUserForLentShare(User user, Cabinet cabinet, int userActiveLentCount, - List userActiveBanList); - - /** - * 대여할 수 있는 사물함인지 확인합니다. - * - * @param cabinet 대여하려는 사물함 - * @return {@link LentPolicyStatus} - */ - LentPolicyStatus verifyCabinetForLent(Cabinet cabinet); - - /** - * @return 개인 사물함을 대여 할 수 있는 날 - */ - Integer getDaysForLentTermPrivate(); - - /** - * @return 공유 사물함을 대여 할 수 있는 날 - */ - Integer getDaysForLentTermShare(Integer totalUserCount); - - /** - * @return 만료가 임박하여 공유 사물함을 빌릴 수 없는 날 - */ - Integer getDaysForNearExpiration(); - - /** - * 정책에 대한 결과 상태({@link LentPolicyStatus})에 맞는 적절한 {@link ServiceException}을 throw합니다. - * - * @param status 정책에 대한 결과 상태 - * @param banHistory 유저의 ban history - */ - void handlePolicyStatus(LentPolicyStatus status, List banHistory); - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyImpl.java deleted file mode 100644 index 940687816..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyImpl.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.ftclub.cabinet.lent.domain; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.config.CabinetProperties; -import org.ftclub.cabinet.dto.UserBlackholeInfoDto; -import org.ftclub.cabinet.exception.CustomExceptionStatus; -import org.ftclub.cabinet.exception.CustomServiceException; -import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.lent.repository.LentRedis; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.utils.DateUtil; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -@Log4j2 -public class LentPolicyImpl implements LentPolicy { - - private final CabinetProperties cabinetProperties; - private final ApplicationEventPublisher publisher; - private final LentRedis lentRedis; - - @Override - public LocalDateTime generateSharedCabinetExpirationDate(LocalDateTime now, - Integer totalUserCount) { - log.info("Called generateSharedCabinetExpirationDate now: {}, totalUserCount: {}", now, - totalUserCount); - return now.plusDays(getDaysForLentTermShare(totalUserCount)) - .withHour(23) - .withMinute(59) - .withSecond(0); - } - - @Override - public LocalDateTime generateExpirationDate(LocalDateTime now, Cabinet cabinet) { - log.info("Called generateExpirationDate now: {}, cabinet: {}", now, cabinet); - - if (!DateUtil.isSameDay(now)) { - throw new IllegalArgumentException("현재 시각이 아닙니다."); - } - - LentType lentType = cabinet.getLentType(); - switch (lentType) { - case PRIVATE: - return now.plusDays(getDaysForLentTermPrivate()) - .withHour(23) - .withMinute(59) - .withSecond(59); - case CLUB: - return DateUtil.getInfinityDate(); - } - throw new IllegalArgumentException("대여 상태가 잘못되었습니다."); - } - - @Override - public LocalDateTime generateExtendedExpirationDate(LocalDateTime now) { - log.info("Called generateExtendedExpirationDate now: {}, cabinet: {}", now); - if (DateUtil.isPast(now)) { - throw new DomainException(ExceptionStatus.LENT_EXPIRED); - } - return now.plusDays(getDaysForLentTermPrivate()) - .withHour(23) - .withMinute(59) - .withSecond(0); - } - - @Override - public void applyExpirationDate(LentHistory curHistory, LocalDateTime expiredAt) { - log.info( - "Called applyExpirationDate curHistory: {}, expiredAt: {}", curHistory, expiredAt); - if (expiredAt == null) { - throw new DomainException(ExceptionStatus.INVALID_ARGUMENT); - } - if (DateUtil.isPast(expiredAt)) { - throw new DomainException(ExceptionStatus.INVALID_EXPIRED_AT); - } - curHistory.setExpiredAt(expiredAt); - } - - @Override - public LentPolicyStatus verifyUserForLent(User user, Cabinet cabinet, int userActiveLentCount, - List userActiveBanList) { - log.debug("Called verifyUserForLent"); - if (!user.isUserRole(UserRole.USER)) { - return LentPolicyStatus.NOT_USER; - } - if (userActiveLentCount != 0) { - return LentPolicyStatus.ALREADY_LENT_USER; - } - if (user.getBlackholedAt() != null && user.getBlackholedAt() - .isBefore(LocalDateTime.now())) { - publisher.publishEvent(UserBlackholeInfoDto.of(user)); - if (user.getBlackholedAt() != null && user.getBlackholedAt() - .isBefore(LocalDateTime.now())) { - return LentPolicyStatus.BLACKHOLED_USER; - } - } - - // 유저가 페널티 2 종류 이상 받을 수 있나? <- 실제로 그럴리 없지만 lentPolicy 객체는 그런 사실을 모르고, 유연하게 구현? - if (userActiveBanList == null || userActiveBanList.isEmpty()) { - return LentPolicyStatus.FINE; - } - LentPolicyStatus ret = LentPolicyStatus.FINE; - for (BanHistory banHistory : userActiveBanList) { - switch (banHistory.getBanType()) { - case ALL: - return LentPolicyStatus.ALL_BANNED_USER; - case SHARE: - if (cabinet.isLentType(LentType.SHARE)) { - ret = LentPolicyStatus.SHARE_BANNED_USER; - } - break; - default: - break; - } - } - return ret; - } - - @Override - public LentPolicyStatus verifyUserForLentShare(User user, Cabinet cabinet, - int userActiveLentCount, - List userActiveBanList) { - - LentPolicyStatus ret = verifyUserForLent(user, cabinet, userActiveLentCount, - userActiveBanList); - - // 유저가 패스워드를 3번 이상 틀린 경우 - Long cabinetId = cabinet.getCabinetId(); - Long userId = user.getUserId(); - // 사물함을 빌릴 수 있는 유저라면 공유 사물함 비밀번호 입력 횟수를 확인 - if (ret == LentPolicyStatus.FINE && lentRedis.isShadowKey( - cabinet.getCabinetId())) { - String passwordCount = lentRedis.getPwTrialCountInRedis( - cabinetId.toString(), - userId.toString()); - // 사물함을 빌릴 수 있는 유저면서, 해당 공유사물함에 처음 접근하는 유저인 경우 - if (passwordCount != null && Integer.parseInt(passwordCount) >= 3) { - ret = LentPolicyStatus.SHARE_BANNED_USER; - } - } - return ret; - } - - @Override - public LentPolicyStatus verifyCabinetForLent(Cabinet cabinet) { - log.info("Called verifyCabinetForLent cabinet: {}", cabinet); - // 빌릴 수 있는지 검증. 빌릴 수 없으면 return lentPolicyDto; - switch (cabinet.getStatus()) { - case FULL: - return LentPolicyStatus.FULL_CABINET; - case BROKEN: - return LentPolicyStatus.BROKEN_CABINET; - case OVERDUE: - return LentPolicyStatus.OVERDUE_CABINET; - case PENDING: - return LentPolicyStatus.PENDING_CABINET; - } - if (cabinet.isLentType(LentType.CLUB)) { - return LentPolicyStatus.LENT_CLUB; - } - // 기존의 공유사물함 정책에서 검사해야 되는 부분 -> 현재 필요 x -// if (cabinet.isLentType(LentType.SHARE)) { -// if (cabinetLentHistories == null || cabinetLentHistories.isEmpty()) { -// return LentPolicyStatus.INTERNAL_ERROR; -// } -// Long diffDays = DateUtil.calculateTwoDateDiffAbs( -// cabinetLentHistories.get(0).getExpiredAt(), now); -// if (diffDays <= getDaysForNearExpiration()) { // -// return LentPolicyStatus.IMMINENT_EXPIRATION; -// } -// } - return LentPolicyStatus.FINE; - } - - @Override - public Integer getDaysForLentTermPrivate() { - log.debug("Called getDaysForLentTermPrivate"); - return cabinetProperties.getLentTermPrivate(); - } - - @Override - public Integer getDaysForLentTermShare(Integer totalUserCount) { - log.debug("Called getDaysForLentTermShare"); - return cabinetProperties.getLentTermShareBasic() - + cabinetProperties.getLentTermShare() * totalUserCount; - } - - @Override - public Integer getDaysForNearExpiration() { - log.debug("Called getDaysForNearExpiration"); - return cabinetProperties.getPenaltyDayShare() + cabinetProperties.getPenaltyDayPadding(); - } - - @Override - public void handlePolicyStatus(LentPolicyStatus status, List banHistory) - throws ServiceException { - log.info("Called handlePolicyStatus status: {}", status); - switch (status) { - case FINE: - break; - case BROKEN_CABINET: - throw new ServiceException(ExceptionStatus.LENT_BROKEN); - case FULL_CABINET: - throw new ServiceException(ExceptionStatus.LENT_FULL); - case OVERDUE_CABINET: - throw new ServiceException(ExceptionStatus.LENT_EXPIRED); - case LENT_CLUB: - throw new ServiceException(ExceptionStatus.LENT_CLUB); - case IMMINENT_EXPIRATION: - throw new ServiceException(ExceptionStatus.LENT_EXPIRE_IMMINENT); - case ALREADY_LENT_USER: - throw new ServiceException(ExceptionStatus.LENT_ALREADY_EXISTED); - case ALL_BANNED_USER: - handleBannedUserResponse(status, banHistory.get(0)); - case SHARE_BANNED_USER: - throw new ServiceException(ExceptionStatus.SHARE_CODE_TRIAL_EXCEEDED); - case BLACKHOLED_USER: - throw new ServiceException(ExceptionStatus.BLACKHOLED_USER); - case PENDING_CABINET: - throw new ServiceException(ExceptionStatus.LENT_PENDING); - case NOT_USER: - case INTERNAL_ERROR: - default: - throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); - } - } - - public void handleBannedUserResponse(LentPolicyStatus status, BanHistory banHistory) { - log.info("Called handleBannedUserResponse: {}", status); - - LocalDateTime unbannedAt = banHistory.getUnbannedAt(); - String unbannedTimeString = unbannedAt.format( - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); - - if (status.equals(LentPolicyStatus.ALL_BANNED_USER)) { - throw new CustomServiceException( - new CustomExceptionStatus(ExceptionStatus.ALL_BANNED_USER, unbannedTimeString)); - } else if (status.equals(LentPolicyStatus.SHARE_BANNED_USER)) { - throw new CustomServiceException( - new CustomExceptionStatus(ExceptionStatus.SHARE_BANNED_USER, - unbannedTimeString)); - } - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java index 7a406109a..cb4729576 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java @@ -1,7 +1,9 @@ package org.ftclub.cabinet.lent.newService; +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.lent.repository.LentRedis; +import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentRepository; import org.springframework.stereotype.Service; @@ -9,6 +11,28 @@ @RequiredArgsConstructor public class LentCommandService { - private final LentRepository lentRepository; - private final LentRedis lentRedis; + private final LentRepository lentRepository; + + public void startLent(Long userId, Long cabinetId, LocalDateTime now, + LocalDateTime expiredAt) { + LentHistory lentHistory = LentHistory.of(now, expiredAt, userId, cabinetId); + lentRepository.save(lentHistory); + } + + public void startLent(List userIds, Long cabinetId, LocalDateTime now, + LocalDateTime expiredAt) { + userIds.forEach(userId -> { + LentHistory lentHistory = LentHistory.of(now, expiredAt, userId, cabinetId); + lentRepository.save(lentHistory); + }); + } + + public void endLent(LentHistory lentHistory, LocalDateTime now) { + lentHistory.endLent(now); + lentRepository.save(lentHistory); + } + + public void setExpiredAt(LentHistory lentHistory, LocalDateTime expiredAt) { + lentHistory.setExpiredAt(expiredAt); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java index 7e2b8c2e6..306a5dbc8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java @@ -1,66 +1,358 @@ package org.ftclub.cabinet.lent.newService; +import static org.ftclub.cabinet.cabinet.domain.LentType.PRIVATE; +import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.domain.LentType; +import org.ftclub.cabinet.cabinet.newService.CabinetCommandService; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; +import org.ftclub.cabinet.dto.MyCabinetResponseDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.dto.UserVerifyRequestDto; import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.LentMapper; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserSession; +import org.ftclub.cabinet.user.newService.BanHistoryCommandService; +import org.ftclub.cabinet.user.newService.BanHistoryQueryService; +import org.ftclub.cabinet.user.newService.BanPolicyService; import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; +import org.springframework.transaction.annotation.Transactional; @Slf4j @Service @RequiredArgsConstructor public class LentFacadeService { - private final LentQueryService lentQueryService; - private final LentCommandService lentCommandService; - private final UserQueryService userQueryService; - private final CabinetQueryService cabinetQueryService; - - private final LentMapper lentMapper; - - - public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size) { - log.debug("Called getAllUserLentHistories: {}", userId); - userQueryService.getUser(userId); - - if (size <= 0) { - size = Integer.MAX_VALUE; - } - PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); - Page lentHistories = lentQueryService.findUserLentHistories(userId, pageable); - List result = lentHistories.stream() - .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) - .collect(Collectors.toList()); - return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); - } - - public List getLentDtoList(Long cabinetId) { - log.debug("Called getLentDtoList: {}", cabinetId); - cabinetQueryService.getCabinet(cabinetId); - -// cabinetOptionalFetcher.getCabinet(cabinetId); -// List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( -// cabinetId); -// return lentHistories.stream().map( -// e -> lentMapper.toLentDto(e.getUser(), e)).collect(Collectors.toList()); -// return lentHistories.stream() -// .map(e -> new LentDto( -// e.getUserId(), -// e.getUser().getName(), -// e.getLentHistoryId(), -// e.getStartedAt(), -// e.getExpiredAt())) -// .collect(Collectors.toList()); - } + private final LentRedisService lentRedisService; + private final LentQueryService lentQueryService; + private final LentCommandService lentCommandService; + private final UserQueryService userQueryService; + private final CabinetQueryService cabinetQueryService; + private final CabinetCommandService cabinetCommandService; + private final BanHistoryQueryService banHistoryQueryService; + private final BanHistoryCommandService banHistoryCommandService; + + private final LentPolicyService lentPolicyService; + private final BanPolicyService banPolicyService; + + private final LentMapper lentMapper; + private final CabinetMapper cabinetMapper; + + + @Transactional(readOnly = true) + public LentHistoryPaginationDto getUserLentHistories(Long userId, PageRequest pageable) { + log.debug("Called getAllUserLentHistories: {}", userId); + + userQueryService.getUser(userId); + Page lentHistories = + lentQueryService.findUserLentHistories(userId, pageable); + List result = lentHistories.stream() + .sorted(Comparator.comparing(LentHistory::getStartedAt)) + .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) + .collect(Collectors.toList()); + return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); + } + + @Transactional(readOnly = true) + public List getCabinetLentHistories(Long cabinetId) { + log.debug("Called getCabinetLentHistories: {}", cabinetId); + + cabinetQueryService.getCabinet(cabinetId); + List lentHistories = lentQueryService.findCabinetActiveLentHistory(cabinetId); + return lentHistories.stream() + .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public List getCabinetSessionLentHistories(Long cabinetId) { + log.debug("Called getLentDtoListFromRedis: {}", cabinetId); + + List userIdsInCabinet = lentRedisService.findUsersInCabinet(cabinetId); + List userList = userQueryService.getUsers(userIdsInCabinet); + return userList.stream().map(user -> lentMapper.toLentDto(user, null)) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, PageRequest pageable) { + log.debug("Called getMyLentLog: {}", user.getName()); + + Page lentHistories = + lentQueryService.findUserLentHistories(user.getUserId(), pageable); + List result = lentHistories.stream() + .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) + .map(lentHistory -> lentMapper.toLentHistoryDto( + lentHistory, + lentHistory.getUser(), + lentHistory.getCabinet())) + .collect(Collectors.toList()); + return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); + } + + @Transactional(readOnly = true) + public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { + log.debug("Called getMyLentInfo: {}", user.getName()); + + Cabinet userActiveCabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); + Long cabinetId; + List lentDtoList; + if (Objects.isNull(userActiveCabinet)) { + cabinetId = lentRedisService.findCabinetJoinedUser(user.getUserId()); + if (Objects.isNull(cabinetId)) { + return null; + } + List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); + List userList = userQueryService.getUsers(usersInCabinet); + userActiveCabinet = cabinetQueryService.getCabinet(cabinetId); + lentDtoList = userList.stream() + .map(u -> lentMapper.toLentDto(u, null)).collect(Collectors.toList()); + } else { + cabinetId = userActiveCabinet.getCabinetId(); + List lentHistories = + lentQueryService.findCabinetActiveLentHistory(cabinetId); + lentDtoList = lentHistories.stream() + .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(Collectors.toList()); + } + // TODO : shareCode, sessionExpiredAt, previousUserName이 상황에 맞춰 null이 되는지 확인 + String shareCode = lentRedisService.getShareCode(cabinetId); + LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); + String previousUserName = lentRedisService.getPreviousUserName(cabinetId); + return cabinetMapper.toMyCabinetResponseDto(userActiveCabinet, lentDtoList, + shareCode, sessionExpiredAt, previousUserName); + } + + @Transactional(readOnly = true) + public List getAllActiveLentHistories() { + log.debug("Called getAllActiveLentHistories"); + + LocalDateTime now = LocalDateTime.now(); + List lentHistories = lentQueryService.findAllActiveLentHistories(); + return lentHistories.stream() + .map(lh -> lentMapper.toActiveLentHistoryDto(lh, + lh.getUser(), + lh.getCabinet(), + lh.isExpired(now), + lh.getDaysUntilExpiration(now))) + .collect(Collectors.toList()); + } + + /*------------------------------------------ CUD -------------------------------------------*/ + + @Transactional + public void startLentCabinet(Long userId, Long cabinetId) { + log.debug("Called startLentCabinet: {}, {}", userId, cabinetId); + + LocalDateTime now = LocalDateTime.now(); + User user = userQueryService.getUser(userId); + Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + int lentCount = lentQueryService.countUserActiveLent(userId); + List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); + int userCount = lentQueryService.countCabinetUser(cabinetId); + + lentPolicyService.verifyCabinetLentCount( + cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetType(cabinet.getLentType(), PRIVATE); + lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), + user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); + + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, PRIVATE, 1); + lentCommandService.startLent(user.getUserId(), cabinet.getCabinetId(), now, expiredAt); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); + cabinetCommandService.changeUserCount(cabinet, lentCount + 1); + } + + @Transactional + public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) { + log.info("Called startLentShareCabinet: {}, {}, {}", userId, cabinetId, shareCode); + + LocalDateTime now = LocalDateTime.now(); + User user = userQueryService.getUser(userId); + Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + int lentCount = lentQueryService.countUserActiveLent(userId); + List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); + int userCount = lentQueryService.countCabinetUser(cabinetId); + + lentPolicyService.verifyCabinetLentCount( + cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetType(cabinet.getLentType(), SHARE); + lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), + user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + boolean isExist = lentRedisService.isInCabinetSession(cabinetId); + if (!isExist) { + lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); + shareCode = lentRedisService.createCabinetSession(cabinetId); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.IN_SESSION); + } + lentRedisService.joinCabinetSession(cabinetId, userId, shareCode); + if (lentRedisService.isCabinetSessionFull(cabinetId)) { + List userIdsInCabinet = lentRedisService.getUsersInCabinetSession(cabinetId); + LocalDateTime expiredAt = + lentPolicyService.generateExpirationDate(now, SHARE, userIdsInCabinet.size()); + lentCommandService.startLent(userIdsInCabinet, cabinet.getCabinetId(), now, expiredAt); + lentRedisService.clearCabinetSession(cabinetId); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); + cabinetCommandService.changeUserCount(cabinet, userIdsInCabinet.size()); + } + } + + @Transactional + public void startLentClubCabinet(Long userId, Long cabinetId) { + log.debug("Called startLentClubCabinet: {}, {}", userId, cabinetId); + + LocalDateTime now = LocalDateTime.now(); + // TODO : ClubUser 추가 이후 userId로 ClubUser 검증 로직 필요(Policy) + Cabinet cabinet = cabinetQueryService.getCabinet(cabinetId); + int userCount = lentQueryService.countCabinetUser(cabinetId); + + lentPolicyService.verifyCabinetLentCount( + cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.CLUB); + lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); + LocalDateTime expiredAt = + lentPolicyService.generateExpirationDate(now, cabinet.getLentType(), 1); + lentCommandService.startLent(userId, cabinetId, now, expiredAt); + cabinetCommandService.changeUserCount(cabinet, userCount + 1); + } + + @Transactional + public void endUserLent(Long userId) { + log.debug("Called endLentCabinet: {}", userId); + + LocalDateTime now = LocalDateTime.now(); + LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithLock(userId); + List cabinetLentHistories = + lentQueryService.findCabinetActiveLentHistory(userLentHistory.getCabinetId()); + Cabinet cabinet = + cabinetQueryService.getCabinetWithLock(userLentHistory.getCabinetId()); + + int userRemainCount = cabinetLentHistories.size() - 1; + cabinetCommandService.changeUserCount(cabinet, userRemainCount); + lentCommandService.endLent(userLentHistory, now); + lentRedisService.setPreviousUserName( + cabinet.getCabinetId(), userLentHistory.getUser().getName()); + + LocalDateTime endedAt = userLentHistory.getEndedAt(); + BanType banType = banPolicyService.verifyBan(endedAt, userLentHistory.getExpiredAt()); + if (!banType.equals(BanType.NONE)) { + LocalDateTime unbannedAt = banPolicyService.getUnBannedAt( + endedAt, userLentHistory.getExpiredAt()); + banHistoryCommandService.banUser(userId, endedAt, unbannedAt, banType); + } + if (cabinet.isLentType(SHARE)) { + LocalDateTime expiredAt = lentPolicyService.adjustSharCabinetExpirationDate( + userRemainCount, now, userLentHistory); + cabinetLentHistories.stream().filter(lh -> !lh.equals(userLentHistory)) + .forEach(lh -> lentCommandService.setExpiredAt(lh, expiredAt)); + } + } + + @Transactional + public void endUserLent(Long userId, String memo) { + log.debug("Called endLentCabinet: {}", userId); + + LocalDateTime now = LocalDateTime.now(); + LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithLock(userId); + List cabinetLentHistories = + lentQueryService.findCabinetActiveLentHistory(userLentHistory.getCabinetId()); + Cabinet cabinet = + cabinetQueryService.getCabinetWithLock(userLentHistory.getCabinetId()); + + int userRemainCount = cabinetLentHistories.size() - 1; + cabinetCommandService.changeUserCount(cabinet, userRemainCount); + cabinetCommandService.updateMemo(cabinet, memo); + lentCommandService.endLent(userLentHistory, now); + lentRedisService.setPreviousUserName( + cabinet.getCabinetId(), userLentHistory.getUser().getName()); + + LocalDateTime endedAt = userLentHistory.getEndedAt(); + BanType banType = banPolicyService.verifyBan(endedAt, userLentHistory.getExpiredAt()); + if (!banType.equals(BanType.NONE)) { + LocalDateTime unbannedAt = banPolicyService.getUnBannedAt( + endedAt, userLentHistory.getExpiredAt()); + banHistoryCommandService.banUser(userId, endedAt, unbannedAt, banType); + } + if (cabinet.isLentType(SHARE)) { + LocalDateTime expiredAt = lentPolicyService.adjustSharCabinetExpirationDate( + userRemainCount, now, userLentHistory); + cabinetLentHistories.stream().filter(lh -> !lh.equals(userLentHistory)) + .forEach(lh -> lentCommandService.setExpiredAt(lh, expiredAt)); + } + } + + @Transactional + public void endCabinetLent(List cabinetIds) { + log.debug("Called endCabinetsLent: {}", cabinetIds); + + LocalDateTime now = LocalDateTime.now(); + List cabinets = cabinetQueryService.getCabinetWithLock(cabinetIds); + cabinets.forEach(cabinet -> { + List cabinetLentHistories = + lentQueryService.findCabinetActiveLentHistory(cabinet.getCabinetId()); + cabinetLentHistories.forEach(lh -> lentCommandService.endLent(lh, now)); + cabinetCommandService.changeUserCount(cabinet, 0); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); + lentRedisService.setPreviousUserName( + cabinet.getCabinetId(), cabinetLentHistories.get(0).getUser().getName()); + }); + } + + @Transactional + public void updateLentCabinetInfo(Long userId, String title, String memo) { + log.debug("Called updateLentCabinetInfo: {}, {}, {}", userId, title, memo); + + Cabinet cabinet = cabinetQueryService.getUserActiveCabinetWithLock(userId); + cabinetCommandService.updateTitle(cabinet, title); + cabinetCommandService.updateMemo(cabinet, memo); + } + + @Transactional + public void cancelShareCabinetLent(Long userId, Long cabinetId) { + log.debug("Called cancelShareCabinetLent: {}, {}", userId, cabinetId); + + lentRedisService.deleteUserInCabinetSession(cabinetId, userId); + if (lentRedisService.isCabinetSessionEmpty(cabinetId)) { + Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); + } + } + + @Transactional + public void shareCabinetSessionExpired(Long cabinetId) { + log.debug("Called shareCabinetSessionExpired: {}", cabinetId); + + Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + List usersInCabinetSession = lentRedisService.getUsersInCabinetSession(cabinetId); + if (lentPolicyService.verifyUserCountOnShareCabinet(usersInCabinetSession.size())) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate( + now, SHARE, usersInCabinetSession.size()); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); + lentCommandService.startLent(usersInCabinetSession, cabinetId, now, expiredAt); + } else { + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); + } + lentRedisService.confirmCabinetSession(cabinetId, usersInCabinetSession); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java new file mode 100644 index 000000000..f381d88e2 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java @@ -0,0 +1,186 @@ +package org.ftclub.cabinet.lent.newService; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.domain.LentType; +import org.ftclub.cabinet.config.CabinetProperties; +import org.ftclub.cabinet.dto.UserVerifyRequestDto; +import org.ftclub.cabinet.exception.CustomExceptionStatus; +import org.ftclub.cabinet.exception.CustomServiceException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.domain.LentPolicyStatus; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.user.domain.UserRole; +import org.ftclub.cabinet.utils.DateUtil; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LentPolicyService { + + private final CabinetProperties cabinetProperties; + + + private void handlePolicyStatus(LentPolicyStatus status, LocalDateTime unbannedAt) { + String unbannedAtString = null; + switch (status) { + case FINE: + break; + case BROKEN_CABINET: + throw new ServiceException(ExceptionStatus.LENT_BROKEN); + case FULL_CABINET: + throw new ServiceException(ExceptionStatus.LENT_FULL); + case OVERDUE_CABINET: + throw new ServiceException(ExceptionStatus.LENT_EXPIRED); + case LENT_CLUB: + throw new ServiceException(ExceptionStatus.LENT_CLUB); + case IMMINENT_EXPIRATION: + throw new ServiceException(ExceptionStatus.LENT_EXPIRE_IMMINENT); + case ALREADY_LENT_USER: + throw new ServiceException(ExceptionStatus.LENT_ALREADY_EXISTED); + case ALL_BANNED_USER: + unbannedAtString = unbannedAt.format( + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + throw new CustomServiceException( + new CustomExceptionStatus(ExceptionStatus.ALL_BANNED_USER, + unbannedAtString)); + case SHARE_BANNED_USER: + unbannedAtString = unbannedAt.format( + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + throw new CustomServiceException( + new CustomExceptionStatus(ExceptionStatus.SHARE_CODE_TRIAL_EXCEEDED, + unbannedAtString)); + case BLACKHOLED_USER: + throw new ServiceException(ExceptionStatus.BLACKHOLED_USER); + case PENDING_CABINET: + throw new ServiceException(ExceptionStatus.LENT_PENDING); + case NOT_USER: + case INTERNAL_ERROR: + default: + throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); + } + } + + public void verifyUserForLent(UserVerifyRequestDto requestDto) { + log.debug("Called verifyUser"); + + LocalDateTime now = LocalDateTime.now(); + LentPolicyStatus status = LentPolicyStatus.FINE; + if (!requestDto.getUserRole().equals(UserRole.USER)) { + status = LentPolicyStatus.NOT_USER; + } + if (requestDto.getLentCount() != 0) { + status = LentPolicyStatus.ALREADY_LENT_USER; + } + if (requestDto.getBlackholedAt() != null && requestDto.getBlackholedAt().isBefore(now)) { + status = LentPolicyStatus.BLACKHOLED_USER; + } + if (requestDto.getBanHistories() != null && !requestDto.getBanHistories().isEmpty()) { + for (BanHistory banHistory : requestDto.getBanHistories()) { + if (banHistory.getBanType().equals(BanType.ALL)) { + status = LentPolicyStatus.ALL_BANNED_USER; + break; + } + if (banHistory.getBanType().equals(BanType.SHARE)) { + status = LentPolicyStatus.SHARE_BANNED_USER; + } + } + } + LocalDateTime unbannedAt = null; + if (requestDto.getBanHistories() != null) { + unbannedAt = requestDto.getBanHistories().stream() + .map(BanHistory::getUnbannedAt) + .max(LocalDateTime::compareTo).orElse(null); + } + this.handlePolicyStatus(status, unbannedAt); + } + + public void verifyCabinetForLent(CabinetStatus cabinetStatus, LentType lentType) { + log.debug("Called verifyCabinet"); + + LentPolicyStatus status = LentPolicyStatus.FINE; + if (lentType.equals(LentType.CLUB)) { + status = LentPolicyStatus.LENT_CLUB; + } + switch (cabinetStatus) { + case FULL: + status = LentPolicyStatus.FULL_CABINET; + case BROKEN: + status = LentPolicyStatus.BROKEN_CABINET; + case OVERDUE: + status = LentPolicyStatus.OVERDUE_CABINET; + case PENDING: + status = LentPolicyStatus.PENDING_CABINET; + } + handlePolicyStatus(status, null); + } + + public void verifyCabinetType(LentType cabinetLentType, LentType lentType) { + log.debug("Called verifyCabinetType"); + + if (!cabinetLentType.equals(lentType)) { + throw new ServiceException(ExceptionStatus.INVALID_LENT_TYPE); + } + } + + public void verifyCabinetLentCount(LentType lentType, int maxUserCount, int lentCount) { + log.debug("Called verifyCabinetLentCount"); + + int maxLentCount = 1; + if (lentType.equals(LentType.SHARE)) { + maxLentCount = cabinetProperties.getShareMaxUserCount().intValue(); + } + if (maxUserCount != maxLentCount) { + throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); + } + if (lentCount >= maxLentCount) { + throw new ServiceException(ExceptionStatus.LENT_FULL); + } + } + + public LocalDateTime generateExpirationDate(LocalDateTime now, LentType lentType, + int lentUserCount) { + log.debug("Called generateExpirationDate"); + + if (!DateUtil.isSameDay(now)) { + throw new ServiceException(ExceptionStatus.INVALID_ARGUMENT); + } + int lentTerm = 0; + if (lentType.equals(LentType.PRIVATE)) { + lentTerm = cabinetProperties.getLentTermPrivate(); + } else if (lentType.equals(LentType.SHARE)) { + lentTerm = cabinetProperties.getLentTermShareBasic() + + cabinetProperties.getLentTermShare() * lentUserCount; + } + LocalDateTime expiredAt = DateUtil.setLastTime(now.plusDays(lentTerm)); + if (DateUtil.isPast(expiredAt)) { + throw new ServiceException(ExceptionStatus.INVALID_EXPIRED_AT); + } + return expiredAt; + } + + public LocalDateTime adjustSharCabinetExpirationDate(int userCount, LocalDateTime now, + LentHistory lentHistory) { + log.debug("Called adjustExpriationDate"); + + double daysUntilExpiration = lentHistory.getDaysUntilExpiration(now) * -1; + double secondsUntilExpiration = daysUntilExpiration * 24 * 60 * 60; + long secondsRemaining = Math.round(secondsUntilExpiration * userCount / (userCount + 1)); + return DateUtil.setLastTime(now.plusSeconds(secondsRemaining)); + } + + public boolean verifyUserCountOnShareCabinet(int userCount) { + log.debug("Called verifyUserCountOnShareCabinet"); + + long minUserCount = cabinetProperties.getShareMinUserCount(); + long maxUserCount = cabinetProperties.getShareMaxUserCount(); + return minUserCount <= userCount && userCount <= maxUserCount; + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java index 13db26ab6..175064fd1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java @@ -1,38 +1,40 @@ package org.ftclub.cabinet.lent.newService; +import java.util.List; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.repository.LentRedis; import org.ftclub.cabinet.lent.repository.LentRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; -import java.util.List; - @Service @RequiredArgsConstructor public class LentQueryService { - private final LentRepository lentRepository; - private final LentRedis lentRedis; - - public Page findUserLentHistories(Long userId, PageRequest pageable) { - return lentRepository.findPaginationByUserId(userId, pageable); - } - - public LentHistory findCabinetActiveLentHistory(Long cabinetId) { - List lentHistories = - lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); - if (lentHistories.size() >= 2) { - throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); - } - if (lentHistories.isEmpty()) { - return null; - } - return lentHistories.get(0); - } + private final LentRepository lentRepository; + + public Page findUserLentHistories(Long userId, PageRequest pageable) { + return lentRepository.findPaginationByUserId(userId, pageable); + } + + public List findCabinetActiveLentHistory(Long cabinetId) { + return lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); + } + + public int countUserActiveLent(Long userId) { + return lentRepository.countByUserIdAndEndedAtIsNull(userId); + } + + public int countCabinetUser(Long cabinetId) { + return lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId); + } + + public LentHistory findUserActiveLentHistoryWithLock(Long userId) { + return lentRepository.findByUserIdAndEndedAtIsNullForUpdate(userId).orElse(null); + } + + public List findAllActiveLentHistories() { + return lentRepository.findAllByEndedAtIsNull(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java new file mode 100644 index 000000000..213063724 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java @@ -0,0 +1,119 @@ +package org.ftclub.cabinet.lent.newService; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.config.CabinetProperties; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.repository.LentRedis; +import org.ftclub.cabinet.lent.repository.LentRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LentRedisService { + + private final LentRedis lentRedis; + private final LentRepository lentRepository; + private final CabinetProperties cabinetProperties; + + public List findUsersInCabinet(Long cabinetId) { + List userIdList = lentRedis.getAllUserInCabinet( + cabinetId.toString()); + return userIdList.stream().map(Long::valueOf).collect(Collectors.toList()); + } + + public Long findCabinetJoinedUser(Long userId) { + String cabinetId = lentRedis.findCabinetByUser(userId.toString()); + if (Objects.isNull(cabinetId)) { + return null; + } + return Long.valueOf(cabinetId); + } + + public String getShareCode(Long cabinetId) { + return lentRedis.getShareCode(cabinetId.toString()); + } + + public LocalDateTime getSessionExpired(Long cabinetId) { + return lentRedis.getCabinetExpiredAt(cabinetId.toString()); + } + + public String createCabinetSession(Long cabinetId) { + return lentRedis.setShadowKey(cabinetId.toString()); + } + + public boolean isInCabinetSession(Long cabinetId) { + return lentRedis.isExistShadowKey(cabinetId.toString()); + } + + public void joinCabinetSession(Long cabinetId, Long userId, String shareCode) { + lentRedis.attemptJoinCabinet(cabinetId.toString(), userId.toString(), shareCode); + } + + public boolean isCabinetSessionEmpty(Long cabinetId) { + return lentRedis.countUserInCabinet(cabinetId.toString()) == 0; + } + + public boolean isCabinetSessionFull(Long cabinetId) { + Long userCount = lentRedis.countUserInCabinet(cabinetId.toString()); + return Objects.equals(userCount, cabinetProperties.getShareMaxUserCount()); + } + + public List getUsersInCabinetSession(Long cabinetId) { + List userIdList = lentRedis.getAllUserInCabinet(cabinetId.toString()); + return userIdList.stream().map(Long::valueOf).collect(Collectors.toList()); + } + + public void deleteUserInCabinetSession(Long cabinetId, Long userId) { + String cabinetIdString = cabinetId.toString(); + String userIdString = userId.toString(); + if (!lentRedis.isUserInCabinet(cabinetIdString, userIdString)) { + throw new DomainException(ExceptionStatus.NOT_FOUND_USER); + } + lentRedis.deleteUserInCabinet(cabinetIdString, userIdString); + if (lentRedis.countUserInCabinet(cabinetIdString) == 0) { + lentRedis.deleteShadowKey(cabinetIdString); + } + } + + public void confirmCabinetSession(Long cabinetId, List userIdList) { + String cabinetIdString = cabinetId.toString(); + userIdList.stream().map(Object::toString) + .forEach(userId -> lentRedis.deleteUserInCabinet(cabinetIdString, userId)); + lentRedis.deleteCabinet(cabinetIdString); + } + + public void clearCabinetSession(Long cabinetId) { + String cabinetIdString = cabinetId.toString(); + List userList = lentRedis.getAllUserInCabinet(cabinetIdString); + lentRedis.deleteShadowKey(cabinetIdString); + userList.forEach(userId -> lentRedis.deleteUserInCabinet(cabinetIdString, userId)); + lentRedis.deleteCabinet(cabinetIdString); + } + + public String getPreviousUserName(Long cabinetId) { + String previousUserName = lentRedis.getPreviousUserName(cabinetId.toString()); + if (Objects.isNull(previousUserName)) { + List cabinetLentHistories = + lentRepository.findByCabinetIdAndEndedAtIsNotNull(cabinetId); + previousUserName = cabinetLentHistories.stream() + .sorted(Comparator.comparing(LentHistory::getEndedAt).reversed()) + .limit(1L).map(lh -> lh.getUser().getName()) + .findFirst().orElse(null); + if (Objects.nonNull(previousUserName)) { + lentRedis.setPreviousUserName(cabinetId.toString(), previousUserName); + } + } + return previousUserName; + } + + public void setPreviousUserName(Long cabinetId, String userName) { + lentRedis.setPreviousUserName(cabinetId.toString(), userName); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index 1715a9006..b7b6fecb0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -3,7 +3,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -118,7 +117,7 @@ public List findAllActiveLentHistories() { */ public Cabinet findActiveLentCabinetByUserId(Long userId) { log.debug("Called findActiveLentCabinetByUserId: {}", userId); - return cabinetRepository.findByUserIdAndEndedAtIsNull(userId).orElse(null); + return cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId).orElse(null); } /** @@ -129,7 +128,7 @@ public Cabinet findActiveLentCabinetByUserId(Long userId) { */ public Long findCabinetIdByUserIdFromRedis(Long userId) { log.debug("Called findActiveLentCabinetByUserIdFromRedis: {}", userId); - return lentRedis.findCabinetIdByUserIdInRedis(userId); + return Long.valueOf(lentRedis.findCabinetByUser(userId.toString())); } /** @@ -140,7 +139,7 @@ public Long findCabinetIdByUserIdFromRedis(Long userId) { */ public List findUserIdsByCabinetIdFromRedis(Long cabinetId) { log.debug("Called findActiveLentUserIdsByCabinetId: {}", cabinetId); - return lentRedis.getUserIdsByCabinetIdInRedis(cabinetId.toString()); + return lentRedis.getAllUserInCabinet(cabinetId.toString()); } public List findAllOverdueLent(LocalDateTime date, Pageable pageable) { @@ -162,7 +161,7 @@ public List findAllActiveLentHistoriesByUserId(Long userId) { public List findPreviousLentHistoryByCabinetId(Long cabinetId) { log.debug("Called findPreviousLentUserNameByCabinetId: {}", cabinetId); - return lentRepository.findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc(cabinetId); + return lentRepository.findByCabinetIdAndEndedAtIsNotNull(cabinetId); } public List findAllByCabinetIdsAfterDate(LocalDate date, List cabinetIds) { diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java index 63bec0678..1d8302818 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java @@ -1,23 +1,23 @@ package org.ftclub.cabinet.lent.repository; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.config.CabinetProperties; +import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Map; -import java.util.Objects; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - @Component @Log4j2 public class LentRedis { @@ -28,158 +28,150 @@ public class LentRedis { private static final String VALUE_KEY_SUFFIX = ":user"; private static final String PREVIOUS_USER_SUFFIX = ":previousUser"; - private final HashOperations valueHashOperations; - private final ValueOperations valueOperations; - private final RedisTemplate shadowKeyRedisTemplate; - private final ValueOperations previousUserRedisTemplate; + private final HashOperations shareCabinetTemplate; + private final ValueOperations userCabinetTemplate; + private final RedisTemplate shadowKeyTemplate; + private final ValueOperations previousUserTemplate; private final CabinetProperties cabinetProperties; @Autowired public LentRedis(RedisTemplate valueHashRedisTemplate, RedisTemplate valueRedisTemplate, - RedisTemplate shadowKeyRedisTemplate, - RedisTemplate previousUserRedisTemplate, + RedisTemplate shadowKeyTemplate, + RedisTemplate previousUserTemplate, CabinetProperties cabinetProperties) { - this.valueOperations = valueRedisTemplate.opsForValue(); - this.valueHashOperations = valueHashRedisTemplate.opsForHash(); - this.shadowKeyRedisTemplate = shadowKeyRedisTemplate; - this.previousUserRedisTemplate = previousUserRedisTemplate.opsForValue(); + this.userCabinetTemplate = valueRedisTemplate.opsForValue(); + this.shareCabinetTemplate = valueHashRedisTemplate.opsForHash(); + this.shadowKeyTemplate = shadowKeyTemplate; + this.previousUserTemplate = previousUserTemplate.opsForValue(); this.cabinetProperties = cabinetProperties; } /** - * @param cabinetId : cabinetId - * @param userId : userId - * @param shareCode : 초대코드 - * @param hasShadowKey : 최초 대여인지 아닌지 여부 + * @param cabinetId : cabinetId + * @param userId : userId + * @param shareCode : 초대코드 */ - public void saveUserInRedis(String cabinetId, String userId, String shareCode, - boolean hasShadowKey) { - log.debug("called saveUserInRedis: {}, {}, {}, {}", cabinetId, userId, shareCode, - hasShadowKey); - if (!hasShadowKey || isValidShareCode(Long.valueOf(cabinetId), - shareCode)) { // 방장이거나 초대코드를 맞게 입력한 경우 - valueHashOperations.put(cabinetId, userId, - USER_ENTERED); // userId를 hashKey로 하여 -1을 value로 저장 // TODO: -1 대신 새로운 플래그값 넣어도 될듯? - valueOperations.set(userId + VALUE_KEY_SUFFIX, - cabinetId); // userId를 key로 하여 cabinetId를 value로 저장 - } else { // 초대코드가 틀린 경우 - if (valueHashOperations.hasKey(cabinetId, userId)) { // 이미 존재하는 유저인 경우 - valueHashOperations.increment(cabinetId, userId, 1L); // trialCount를 1 증가시켜서 저장 - } else { // 존재하지 않는 유저인 경우 - valueHashOperations.put(cabinetId, userId, "1"); // trialCount를 1로 저장 + public void attemptJoinCabinet(String cabinetId, String userId, String shareCode) { + log.debug("called saveUserInRedis: {}, {}, {}", cabinetId, userId, shareCode); + + String savedCode = shadowKeyTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX); + if (Objects.equals(savedCode, shareCode)) { + shareCabinetTemplate.put(cabinetId, userId, USER_ENTERED); + userCabinetTemplate.set(userId + VALUE_KEY_SUFFIX, cabinetId); + } else { + if (shareCabinetTemplate.hasKey(cabinetId, userId)) { + shareCabinetTemplate.increment(cabinetId, userId, 1L); + } else { + shareCabinetTemplate.put(cabinetId, userId, "1"); } - throw new ServiceException(ExceptionStatus.WRONG_SHARE_CODE); + throw new DomainException(ExceptionStatus.WRONG_SHARE_CODE); } } - public boolean isValidShareCode(Long cabinetId, String shareCode) { - log.debug("called isValidShareCode: {}, {}", cabinetId, shareCode); - return Objects.equals( - shadowKeyRedisTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX), - shareCode); - } - - public boolean checkPwTrialCount(String cabinetId, String userId) { - log.debug("called checkPwTrialCount: {}, {}", cabinetId, userId); - return Objects.equals(valueHashOperations.get(cabinetId, userId), MAX_SHARE_CODE_TRY); - } - - public Boolean isUserInRedis(String cabinetId, String userId) { + public boolean isUserInCabinet(String cabinetId, String userId) { log.debug("called isUserInRedis: {}, {}", cabinetId, userId); - return valueHashOperations.hasKey(cabinetId, userId); + + return shareCabinetTemplate.hasKey(cabinetId, userId); } - public Long getSizeOfUsersInSession(String cabinetId) { + public Long countUserInCabinet(String cabinetId) { log.debug("called getSizeOfUsersInSession: {}", cabinetId); - Map entries = valueHashOperations.entries(cabinetId); - return entries.values().stream().filter(Objects::nonNull) - .filter(value -> value.equals(USER_ENTERED)) - .count(); + + Collection joinUsers = shareCabinetTemplate.entries(cabinetId).values(); + return joinUsers.parallelStream() + .filter(value -> Objects.nonNull(value) && value.equals(USER_ENTERED)).count(); } - public String getPwTrialCountInRedis(String cabinetId, String userId) { + public String getAttemptCountInCabinet(String cabinetId, String userId) { log.debug("called getPwTrialCountInRedis: {}, {}", cabinetId, userId); - return valueHashOperations.get(cabinetId, userId); + + return shareCabinetTemplate.get(cabinetId, userId); + } + + public boolean isExistShadowKey(String cabinetId) { + log.debug("called isShadowKey: {}", cabinetId); + + Boolean isExist = shadowKeyTemplate.hasKey(cabinetId + SHADOW_KEY_SUFFIX); + return Objects.nonNull(isExist) && isExist; } - public String getShareCode(Long cabinetId) { + public String getShareCode(String cabinetId) { log.debug("called getShareCode: {}", cabinetId); - return shadowKeyRedisTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX); + + return shadowKeyTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX); } - public void setShadowKey(Long cabinetId) { + public String setShadowKey(String cabinetId) { + log.debug("called setShadowKey: {}", cabinetId); + Random rand = new Random(); - Integer shareCode = 1000 + rand.nextInt(9000); + String shareCode = Integer.toString(1000 + rand.nextInt(9000)); String shadowKey = cabinetId + SHADOW_KEY_SUFFIX; - shadowKeyRedisTemplate.opsForValue().set(shadowKey, shareCode.toString()); - // 해당 키가 처음 생성된 것이라면 timeToLive 설정 - log.debug("called setShadowKey: {}, shareCode: {}", shadowKey, shareCode); - shadowKeyRedisTemplate.expire(shadowKey, cabinetProperties.getInSessionTerm(), TimeUnit.MINUTES); -// shadowKeyRedisTemplate.expire(shadowKey, 30, TimeUnit.SECONDS); - } - - public Boolean isShadowKey(Long cabinetId) { - log.debug("called isShadowKey: {}", cabinetId); - // 해당 키가 존재하는지 확인 - return shadowKeyRedisTemplate.hasKey(cabinetId + SHADOW_KEY_SUFFIX); + shadowKeyTemplate.opsForValue().set(shadowKey, shareCode); + shadowKeyTemplate.expire(shadowKey, cabinetProperties.getInSessionTerm(), TimeUnit.MINUTES); + return shareCode; } - public void deleteShadowKey(Long cabinetId) { + public void deleteShadowKey(String cabinetId) { log.debug("called deleteShadowKey: {}", cabinetId); - shadowKeyRedisTemplate.delete(cabinetId + SHADOW_KEY_SUFFIX); + + shadowKeyTemplate.delete(cabinetId + SHADOW_KEY_SUFFIX); } - public void deleteUserInRedis(String cabinetId, String userId) { // user를 지우는 delete + public void deleteUserInCabinet(String cabinetId, String userId) { log.debug("called deleteUserInRedis: {}, {}", cabinetId, userId); - valueHashOperations.delete(cabinetId, userId); - valueOperations.getOperations().delete(userId + VALUE_KEY_SUFFIX); + + shareCabinetTemplate.delete(cabinetId, userId); + userCabinetTemplate.getOperations().delete(userId + VALUE_KEY_SUFFIX); } - public void deleteCabinetIdInRedis(String cabinetId) { + public void deleteCabinet(String cabinetId) { log.debug("called deleteCabinetIdInRedis: {}", cabinetId); - valueHashOperations.getOperations().delete(cabinetId); + + shareCabinetTemplate.getOperations().delete(cabinetId); } - public void deleteUserIdInRedis(Long userId) { + public void deleteUser(Long userId) { log.debug("called deleteUserIdInRedis: {}", userId); - valueOperations.getOperations().delete(userId + VALUE_KEY_SUFFIX); + + userCabinetTemplate.getOperations().delete(userId + VALUE_KEY_SUFFIX); } - public Long findCabinetIdByUserIdInRedis(Long userId) { + public String findCabinetByUser(String userId) { log.debug("Called findCabinetIdByUserIdInRedis: {}", userId); - String cabinetId = valueOperations.get(userId + VALUE_KEY_SUFFIX); - if (cabinetId == null) { - log.info("cabinetId is null"); - return null; - } - return Long.valueOf(cabinetId); + + return userCabinetTemplate.get(userId + VALUE_KEY_SUFFIX); } - public ArrayList getUserIdsByCabinetIdInRedis(String cabinetId) { + public List getAllUserInCabinet(String cabinetId) { log.debug("Called getUserIdsByCabinetIdInRedis: {}", cabinetId); - Map entries = valueHashOperations.entries(cabinetId); - return entries.entrySet().stream().filter(entry -> entry.getValue().equals(USER_ENTERED)) - .map(Map.Entry::getKey).collect(Collectors.toCollection(ArrayList::new)); + + Map entries = shareCabinetTemplate.entries(cabinetId); + return entries.entrySet().stream() + .filter(entry -> entry.getValue().equals(USER_ENTERED)) + .map(Map.Entry::getKey).collect(Collectors.toList()); } - public LocalDateTime getSessionExpiredAtInRedis(Long cabinetId) { + public LocalDateTime getCabinetExpiredAt(String cabinetId) { log.debug("Called getSessionExpiredAtInRedis: {}", cabinetId); - if (isShadowKey(cabinetId)) { - return LocalDateTime.now().plusSeconds( - shadowKeyRedisTemplate.getExpire(cabinetId + SHADOW_KEY_SUFFIX, - TimeUnit.SECONDS).longValue()); + if (this.isExistShadowKey(cabinetId)) { + String shadowKey = cabinetId + SHADOW_KEY_SUFFIX; + long expire = shadowKeyTemplate.getExpire(shadowKey, TimeUnit.SECONDS).longValue(); + return LocalDateTime.now().plusSeconds(expire); } return null; } - public void setPreviousUser(String cabinetId, String userName) { + /*---------------------------------------- Caching -----------------------------------------*/ + + public void setPreviousUserName(String cabinetId, String userName) { log.debug("Called setPreviousUser: {}, {}", cabinetId, userName); - previousUserRedisTemplate.set(cabinetId + PREVIOUS_USER_SUFFIX, userName); + previousUserTemplate.set(cabinetId + PREVIOUS_USER_SUFFIX, userName); } public String getPreviousUserName(String cabinetId) { log.debug("Called getPreviousUser: {}", cabinetId); - return previousUserRedisTemplate.get(cabinetId + PREVIOUS_USER_SUFFIX); + return previousUserTemplate.get(cabinetId + PREVIOUS_USER_SUFFIX); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 151b27870..c8a0132ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -89,8 +89,7 @@ int countReturnByTimeDuration(@Param("startDate") LocalDateTime startDate, * @param cabinetId 찾으려는 cabinet id * @return 반납한 {@link LentHistory}의 {@link Optional} */ - List findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc( - @Param("cabinetId") Long cabinetId); + List findByCabinetIdAndEndedAtIsNotNull(@Param("cabinetId") Long cabinetId); /** * 유저를 기준으로 아직 반납하지 않은 {@link LentHistory}중 하나를 가져옵니다. @@ -113,19 +112,17 @@ List findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc( Optional findByUserIdAndEndedAtIsNullForUpdate(@Param("userId") Long userId); /** - * 사물함의 대여기록 {@link LentHistory}들을 모두 가져옵니다. - * {@link Pageable}이 적용되었습니다. + * 사물함의 대여기록 {@link LentHistory}들을 모두 가져옵니다. {@link Pageable}이 적용되었습니다. * - * @param cabinetId 찾으려는 cabinet id - * @param pageable pagination 정보 + * @param cabinetId 찾으려는 cabinet id + * @param pageable pagination 정보 * @return {@link LentHistory}의 {@link Page} */ Page findPaginationByCabinetId( @Param("cabinetId") Long cabinetId, Pageable pageable); /** - * 유저가 지금까지 빌렸던 {@link LentHistory}들을 모두 가져옵니다. - * {@link Pageable}이 적용되었습니다. + * 유저가 지금까지 빌렸던 {@link LentHistory}들을 모두 가져옵니다. {@link Pageable}이 적용되었습니다. * * @param userId 찾으려는 user id * @param pageable pagination 정보 @@ -134,8 +131,8 @@ Page findPaginationByCabinetId( Page findPaginationByUserId(@Param("userId") Long userId, Pageable pageable); /** - * 유저가 지금까지 빌렸던 {@link LentHistory}들을 가져옵니다.(현재 빌리고 반납하지 않은 기록은 표시하지 않습니다.) - * {@link Pageable}이 적용되었습니다. + * 유저가 지금까지 빌렸던 {@link LentHistory}들을 가져옵니다.(현재 빌리고 반납하지 않은 기록은 표시하지 않습니다.) {@link Pageable}이 + * 적용되었습니다. * * @param userId 찾으려는 user id * @param pageable pagination 정보 diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java deleted file mode 100644 index ae9ab6466..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ /dev/null @@ -1,156 +0,0 @@ -package org.ftclub.cabinet.lent.service; - -import java.util.List; -import org.ftclub.cabinet.dto.CabinetInfoRequestDto; -import org.ftclub.cabinet.dto.LentDto; -import org.ftclub.cabinet.dto.LentEndMemoDto; -import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.dto.MyCabinetResponseDto; -import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.ftclub.cabinet.dto.UpdateCabinetMemoDto; -import org.ftclub.cabinet.dto.UpdateCabinetTitleDto; -import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.user.domain.UserSession; - -/** - * controller에서 사용하는 파사드 서비스 - */ -public interface LentFacadeService { - - /** - * 유저의 대여대기 정보를 가져옵니다. - * - * @param user - * @return - */ - MyCabinetResponseDto getMyLentInfoFromRedis(@UserSession UserSessionDto user); - - /** - * 사물함 대여를 합니다. - * - * @param userId 대여하려는 일반 user id - * @param cabinetId 대여하려는 cabinet id - */ - void startLentCabinet(Long userId, Long cabinetId); - - /*** - * 공유사물함 대여를 합니다. - * - * @param userId 대여하려는 일반 user id - * @param cabinetId 대여하려는 cabinet id - * @param shareCode 10분 간 유지되는 공유사물함 초대 코드 - */ - void startLentShareCabinet(Long userId, Long cabinetId, String shareCode); - - /** - * 동아리 사물함 대여를 합니다. - * - * @param userId 대여하려는 동아리 user id - * @param cabinetId 대여하려는 동아리 cabinet id - */ - void startLentClubCabinet(Long userId, Long cabinetId); - - /** - * 사물함을 반납 합니다. 유저가 정책에 따라 벤이 될 수 있습니다. - * - * @param userSessionDto 요청한 유저 dto - */ - void endLentCabinet(UserSessionDto userSessionDto); - - /** - * 사물함을 반납될 비밀번화와 함께 반납 합니다. - * - * @param userSessionDto 요청한 유저 dto - * @param lentEndMemoDto - */ - void endLentCabinetWithMemo(UserSessionDto userSessionDto, LentEndMemoDto lentEndMemoDto); - - /** - * 공유사물함 대여 대기열을 취소합니다. - * - * @param userId 대여하려는 일반 user id - */ - void cancelLentShareCabinet(Long userId, Long cabinetId); - - - /** - * 사물함을 강제 반납 합니다. 유저가 벤이 되진 않습니다 - * - * @param userId 반납하려는 user id - */ - void terminateLentCabinet(Long userId); - - /** - * 사물함들을 강제 반납 합니다. 유저가 벤이 되진 않습니다 - * - * @param returnCabinetsRequestDto 반납하려는 cabinet id list - */ - void terminateLentCabinets(ReturnCabinetsRequestDto returnCabinetsRequestDto); - - /** - * 페이지네이션을 적용해서 유저가 지금까지 대여했던 모든 기록을 가져옵니다. - * - * @param userId 찾으려는 user id - * @param page 페이지네이션 offset - * @param size 페이지네이션 size - * @return {@link LentHistoryPaginationDto} - */ - LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size); - - MyCabinetResponseDto getMyLentInfo(UserSessionDto user); - - /** - * 아직 반납하지 않은 사물함의 대여기록을 가져옵니다. - * - * @param cabinetId 찾으려는 cabinet id - * @return {@link LentDto}의 {@link List} - */ - List getLentDtoList(Long cabinetId); - - List getLentDtoListFromRedis(Long cabinetId); - - /** - * 내가 대여한 기록들을 페이지네이션 기준으로 가져옵니다. - * - * @param user 유저 정보 - * @param page 페이지 - * @param size 사이즈 - * @return 대여 기록 - */ - LentHistoryPaginationDto getMyLentLog(UserSessionDto user, - Integer page, Integer size); - - /** - * 자신이 대여한 사물함의 메모를 바꿉니다. - * - * @param userSessionDto 요청한 유저 dto - * @param updateCabinetMemoDto 변경할 사물함 제목 - */ - void updateCabinetMemo(UserSessionDto userSessionDto, - UpdateCabinetMemoDto updateCabinetMemoDto); - - /** - * 자신이 대여한 사물함의 제목을 바꿉니다. - * - * @param userSessionDto 요청한 유저 dto - * @param updateCabinetTitleDto 변경할 사물함 제목 - */ - void updateCabinetTitle(UserSessionDto userSessionDto, - UpdateCabinetTitleDto updateCabinetTitleDto); - - /** - * @param userSessionDto 요청한 유저 dto - * @param cabinetInfoRequestDto 변경할 사물함 정보 ( 제목, 메모 ) - */ - void updateCabinetInfo(UserSessionDto userSessionDto, - CabinetInfoRequestDto cabinetInfoRequestDto); - - /** - * 어드민으로 유저를 지정하여 캐비넷을 대여 시킵니다. - * - * @param userId 대여시킬 유저 Id - * @param cabinetId 대여시킬 캐비넷 Id - */ - void assignLent(Long userId, Long cabinetId); - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java deleted file mode 100644 index 37ff9ab53..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java +++ /dev/null @@ -1,261 +0,0 @@ -package org.ftclub.cabinet.lent.service; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import javax.transaction.Transactional; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.cabinet.service.CabinetService; -import org.ftclub.cabinet.config.CabinetProperties; -import org.ftclub.cabinet.dto.CabinetInfoRequestDto; -import org.ftclub.cabinet.dto.LentDto; -import org.ftclub.cabinet.dto.LentEndMemoDto; -import org.ftclub.cabinet.dto.LentHistoryDto; -import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.dto.MyCabinetResponseDto; -import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.ftclub.cabinet.dto.UpdateCabinetMemoDto; -import org.ftclub.cabinet.dto.UpdateCabinetTitleDto; -import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; -import org.ftclub.cabinet.lent.repository.LentRedis; -import org.ftclub.cabinet.mapper.CabinetMapper; -import org.ftclub.cabinet.mapper.LentMapper; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserSession; -import org.ftclub.cabinet.user.repository.UserOptionalFetcher; -import org.ftclub.cabinet.user.service.UserService; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; - -@Service -@AllArgsConstructor -@Transactional -@Log4j2 -public class LentFacadeServiceImpl implements LentFacadeService { - - private final UserOptionalFetcher userOptionalFetcher; - private final CabinetOptionalFetcher cabinetOptionalFetcher; - private final LentOptionalFetcher lentOptionalFetcher; - private final LentService lentService; - private final LentMapper lentMapper; - private final CabinetService cabinetService; - private final CabinetMapper cabinetMapper; - private final LentRedis lentRedis; - private final CabinetProperties cabinetProperties; - private final UserService userService; - - /*-------------------------------------------READ-------------------------------------------*/ - - @Override - public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, - Integer size) { - log.debug("Called getAllUserLentHistories: {}", userId); - userOptionalFetcher.findUser(userId); - //todo: 예쁘게 수정 - if (size <= 0) { - size = Integer.MAX_VALUE; - } - PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); - Page lentHistories = lentOptionalFetcher.findPaginationByUserId(userId, - pageable); - return generateLentHistoryPaginationDto(lentHistories.toList(), - lentHistories.getTotalElements()); - } - - @Override - public List getLentDtoList(Long cabinetId) { - log.debug("Called getLentDtoList: {}", cabinetId); - cabinetOptionalFetcher.getCabinet(cabinetId); - List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( - cabinetId); - return lentHistories.stream().map( - e -> lentMapper.toLentDto(e.getUser(), e)).collect(Collectors.toList()); -// return lentHistories.stream() -// .map(e -> new LentDto( -// e.getUserId(), -// e.getUser().getName(), -// e.getLentHistoryId(), -// e.getStartedAt(), -// e.getExpiredAt())) -// .collect(Collectors.toList()); - } - - @Override - public List getLentDtoListFromRedis(Long cabinetId) { - log.debug("Called getLentDtoListFromRedis: {}", cabinetId); - - List userIds = lentOptionalFetcher.findUserIdsByCabinetIdFromRedis(cabinetId); - return userIds.stream().map( - userId -> { - User user = userOptionalFetcher.findUser(Long.valueOf(userId)); - return new LentDto( - user.getUserId(), - user.getName(), - null, - null, - null); - }).collect(Collectors.toList()); - } - - /** - * {@InheritDocs} - * - * @param user 유저 정보 - * @param page 페이지 - * @param size 사이즈 - * @return LentHistoryPaginationDto 본인의 lent log - */ - @Override - public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, - Integer page, Integer size) { - log.debug("Called getMyLentLog: {}", user.getName()); - PageRequest pageable = PageRequest.of(page, size, - Sort.by(Sort.Direction.DESC, "startedAt")); - List myLentHistories = lentOptionalFetcher.findAllByUserIdAndEndedAtNotNull( - user.getUserId(), pageable); - List result = myLentHistories.stream() - .map(lentHistory -> lentMapper.toLentHistoryDto( - lentHistory, - lentHistory.getUser(), - lentHistory.getCabinet())) - .collect(Collectors.toList()); - return lentMapper.toLentHistoryPaginationDto(result, Long.valueOf(result.size())); - } - - private LentHistoryPaginationDto generateLentHistoryPaginationDto( - List lentHistories, Long totalLength) { - List lentHistoryDto = lentHistories.stream() - .map(e -> lentMapper.toLentHistoryDto(e, e.getUser(), e.getCabinet())) - .collect(Collectors.toList()); - return new LentHistoryPaginationDto(lentHistoryDto, totalLength); - } - - @Override - public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { - log.debug("Called getMyLentInfo: {}", user.getName()); - Cabinet myCabinet = lentOptionalFetcher.findActiveLentCabinetByUserId(user.getUserId()); - if (myCabinet == null) { // 대여 기록이 없거나 대여 대기 중인 경우 - return getMyLentInfoFromRedis(user); - } - Long cabinetId = myCabinet.getCabinetId(); - List lentDtoList = getLentDtoList(cabinetId); - String previousUserName = lentRedis.getPreviousUserName( - myCabinet.getCabinetId().toString()); - if (previousUserName == null) { - List previousLentHistory = lentOptionalFetcher.findPreviousLentHistoryByCabinetId( - cabinetId); - if (!previousLentHistory.isEmpty()) { - previousUserName = previousLentHistory.get(0).getUser().getName(); - } - } - return cabinetMapper.toMyCabinetResponseDto(myCabinet, lentDtoList, - null, null, previousUserName); - } - - @Override - public MyCabinetResponseDto getMyLentInfoFromRedis(@UserSession UserSessionDto user) { - log.debug("Called getMyLentInfoFromRedis: {}", user.getName()); - Long userId = user.getUserId(); - Long cabinetId = lentOptionalFetcher.findCabinetIdByUserIdFromRedis(userId); - log.debug("cabinetId: {}", cabinetId); - if (cabinetId == null) { - log.info("cabinetId is null"); - return null; - } - Cabinet cabinet = cabinetOptionalFetcher.getCabinet(cabinetId); - cabinet.specifyMaxUser(Math.toIntExact(cabinetProperties.getShareMaxUserCount())); - List lentDtoList = getLentDtoListFromRedis(cabinetId); - return cabinetMapper.toMyCabinetResponseDto(cabinet, lentDtoList, - lentRedis.getShareCode(cabinetId), - lentRedis.getSessionExpiredAtInRedis(cabinetId), null); - } - - - /*--------------------------------------------CUD--------------------------------------------*/ - - @Override - public void startLentCabinet(Long userId, Long cabinetId) { - lentService.startLentCabinet(userId, cabinetId); - } - - @Override - public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) { - lentService.startLentShareCabinet(userId, cabinetId, shareCode); - } - - @Override - public void startLentClubCabinet(Long userId, Long cabinetId) { - lentService.startLentClubCabinet(userId, cabinetId); - } - - @Override - public void endLentCabinet(UserSessionDto user) { - lentService.endLentCabinet(user.getUserId()); - } - - @Override - public void endLentCabinetWithMemo(UserSessionDto user, LentEndMemoDto lentEndMemoDto) { - log.debug("Called endLentCabinetWithMemo: {}", user.getName()); - Cabinet cabinet = cabinetService.getLentCabinetByUserId(user.getUserId()); - lentService.endLentCabinet(user.getUserId()); - cabinetService.updateMemo(cabinet.getCabinetId(), lentEndMemoDto.getCabinetMemo()); - } - - @Override - public void cancelLentShareCabinet(Long userId, Long cabinetId) { - lentService.cancelLentShareCabinet(userId, cabinetId); - } - - @Override - public void terminateLentCabinet(Long userId) { - log.debug("Called terminateLentCabinet {}", userId); - lentService.terminateLentCabinet(userId); - } - - @Override - public void terminateLentCabinets(ReturnCabinetsRequestDto returnCabinetsRequestDto) { - log.debug("Called terminateLentCabinets"); - returnCabinetsRequestDto.getCabinetIds().stream() - .forEach(lentService::terminateLentByCabinetId); - } - - @Override - public void updateCabinetMemo(UserSessionDto user, UpdateCabinetMemoDto updateCabinetMemoDto) { - log.debug("Called updateCabinetMemo: {}", user.getName()); - Cabinet myCabinet = cabinetService.getLentCabinetByUserId((user.getUserId())); - cabinetService.updateMemo(myCabinet.getCabinetId(), updateCabinetMemoDto.getMemo()); - } - - @Override - public void updateCabinetTitle(UserSessionDto user, - UpdateCabinetTitleDto updateCabinetTitleDto) { - log.debug("Called updateCabinetTitle: {}", user.getName()); - Cabinet myCabinet = cabinetService.getLentCabinetByUserId(user.getUserId()); - cabinetService.updateTitle(myCabinet.getCabinetId(), - updateCabinetTitleDto.getCabinetTitle()); - } - - @Override - public void updateCabinetInfo(UserSessionDto user, - CabinetInfoRequestDto cabinetInfoRequestDto) { - log.debug("Called updateCabinetInfo: {}", user.getName()); - - Cabinet myCabinet = cabinetService.getLentCabinetByUserId(user.getUserId()); - - cabinetService.updateTitleAndMemo(myCabinet.getCabinetId(), - cabinetInfoRequestDto.getTitle(), - cabinetInfoRequestDto.getMemo()); - } - - @Override - public void assignLent(Long userId, Long cabinetId) { - lentService.assignLent(userId, cabinetId); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentService.java deleted file mode 100644 index 3910e10b6..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentService.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.ftclub.cabinet.lent.service; - -import java.util.List; -import org.ftclub.cabinet.dto.ActiveLentHistoryDto; - -/** - * 대여 관련된 서비스 - */ -public interface LentService { - - /** - * 사물함 대여를 합니다. - * - * @param userId 대여하려는 일반 user id - * @param cabinetId 대여하려는 cabinet id - */ - void startLentCabinet(Long userId, Long cabinetId); - - /** - * 공유사물함 대여를 합니다. - * - * @param userId 대여하려는 일반 user id - * @param cabinetId 대여하려는 cabinet id - */ - void startLentShareCabinet(Long userId, Long cabinetId, String shareCode); - - /** - * 동아리 사물함 대여를 합니다. - * - * @param userId 대여하려는 동아리 user id - * @param cabinetId 대여하려는 동아리 cabinet id - */ - void startLentClubCabinet(Long userId, Long cabinetId); - - /** - * 사물함을 반납 합니다. 유저가 정책에 따라 벤이 될 수 있습니다. - * - * @param userId 반납하려는 user id - */ - void endLentCabinet(Long userId); - - /** - * 공유사물함 대기열에서 취소합니다. - * - * @param userId - 취소하려는 user id - */ - void cancelLentShareCabinet(Long userId, Long cabinetId); - - /** - * 사물함을 강제 반납 합니다. 유저가 벤이 되진 않습니다 - * - * @param userId 반납하려는 user id - */ - void terminateLentCabinet(Long userId); - - /** - * cabinet id로 사물함을 강제 반납 합니다. 유저가 벤이 되진 않습니다 - * - * @param cabinetId 반납하려는 cabinet id - */ - void terminateLentByCabinetId(Long cabinetId); - - /** - * 어드민으로 유저에게 사물함을 대여 시킵니다. - * - * @param userId 대여시킬 유저 Id - * @param cabinetId 대여시킬 캐비넷 Id - */ - void assignLent(Long userId, Long cabinetId); - - /** - * Redis에 저장된 대여 정보를 DB에 저장하고 Redis에서 삭제합니다. - * - * @param cabinetIdString Redis에 저장된 대여 정보의 키 - */ - void handleLentFromRedisExpired(String cabinetIdString); - - /** - * 현재 대여중인 모든 사물함의 대여기록을 가져옵니다. - * - * @return {@link ActiveLentHistoryDto}의 {@link List} - */ - List getAllActiveLentHistories(); - - /** - * 현재 대여중인 사물함의 모든 대여기록을 가져온 후, expiredAt을 갱신시키고, user 의 is_extensible 을 false 한다 userId로, - * cabinet 을 조회하고, cabinetId로 LentHistory를 조회한다. LentHistory의 expiredAt을 user의 isExtensible로 - * 갱신한다. - * - * @param userId - */ - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java deleted file mode 100644 index 17ad486a5..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java +++ /dev/null @@ -1,284 +0,0 @@ -package org.ftclub.cabinet.lent.service; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.config.CabinetProperties; -import org.ftclub.cabinet.dto.ActiveLentHistoryDto; -import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.domain.LentPolicy; -import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; -import org.ftclub.cabinet.lent.repository.LentRedis; -import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.mapper.LentMapper; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.repository.BanHistoryRepository; -import org.ftclub.cabinet.user.repository.UserOptionalFetcher; -import org.ftclub.cabinet.user.service.UserService; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional -@Log4j2 -public class LentServiceImpl implements LentService { - - private final LentRepository lentRepository; - private final LentPolicy lentPolicy; - private final LentOptionalFetcher lentOptionalFetcher; - private final CabinetOptionalFetcher cabinetOptionalFetcher; - private final UserOptionalFetcher userOptionalFetcher; - private final UserService userService; - private final BanHistoryRepository banHistoryRepository; - private final LentMapper lentMapper; - private final LentRedis lentRedis; - private final CabinetProperties cabinetProperties; - - - @Override - public void startLentCabinet(Long userId, Long cabinetId) { - log.info("Called startLentCabinet: {}, {}", userId, cabinetId); - LocalDateTime now = LocalDateTime.now(); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - User user = userOptionalFetcher.getUser(userId); - int userActiveLentCount = lentRepository.countByUserIdAndEndedAtIsNull(userId); - List userActiveBanList = banHistoryRepository.findUserActiveBanList(userId, - now); - // 대여 가능한 유저인지 확인 - lentPolicy.handlePolicyStatus( - lentPolicy.verifyUserForLent(user, cabinet, userActiveLentCount, userActiveBanList), - userActiveBanList); - // 대여 가능한 캐비넷인지 확인 - lentPolicy.handlePolicyStatus(lentPolicy.verifyCabinetForLent(cabinet), userActiveBanList); - // 캐비넷 상태 변경 - cabinet.specifyStatus(CabinetStatus.FULL); - // 만료 시간 적용 - LocalDateTime expiredAt = lentPolicy.generateExpirationDate(now, cabinet); - LentHistory lentHistory = LentHistory.of(now, expiredAt, userId, cabinetId); - lentPolicy.applyExpirationDate(lentHistory, expiredAt); - lentRepository.save(lentHistory); - } - - @Override - public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) { - log.info("Called startLentShareCabinet: {}, {}, {}", userId, cabinetId, shareCode); - LocalDateTime now = LocalDateTime.now(); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - User user = userOptionalFetcher.getUser(userId); - int userActiveLentCount = lentRepository.countByUserIdAndEndedAtIsNull(userId); - List userActiveBanList = banHistoryRepository.findUserActiveBanList(userId, - now); - // 대여 가능한 유저인지 확인 - lentPolicy.handlePolicyStatus( - lentPolicy.verifyUserForLentShare(user, cabinet, userActiveLentCount, - userActiveBanList), userActiveBanList); - boolean hasShadowKey = lentRedis.isShadowKey(cabinetId); - if (!hasShadowKey) {// 최초 대여인 경우 - lentPolicy.handlePolicyStatus(lentPolicy.verifyCabinetForLent(cabinet), - userActiveBanList); - cabinet.specifyStatus(CabinetStatus.IN_SESSION); - lentRedis.setShadowKey(cabinetId); - } - lentRedis.saveUserInRedis(cabinetId.toString(), userId.toString(), shareCode, - hasShadowKey); - // 4번째 (마지막) 대여자인 경우 - if (Objects.equals(lentRedis.getSizeOfUsersInSession(cabinetId.toString()), - cabinetProperties.getShareMaxUserCount())) { - cabinet.specifyStatus(CabinetStatus.FULL); - saveLentHistories(now, cabinetId); - // cabinetId에 대한 shadowKey, valueKey 삭제 - lentRedis.deleteShadowKey(cabinetId); - ArrayList userIds = lentRedis.getUserIdsByCabinetIdInRedis( - cabinetId.toString()); - for (String id : userIds) { - lentRedis.deleteUserIdInRedis(Long.valueOf(id)); - } - lentRedis.deleteCabinetIdInRedis(cabinetId.toString()); - } - } - - @Override - public void startLentClubCabinet(Long userId, Long cabinetId) { - log.debug("Called startLentClubCabinet: {}, {}", userId, cabinetId); - Cabinet cabinet = cabinetOptionalFetcher.getClubCabinet(cabinetId); - lentOptionalFetcher.checkExistedSpace(cabinetId); - LocalDateTime expirationDate = lentPolicy.generateExpirationDate(LocalDateTime.now(), - cabinet); - LentHistory result = - LentHistory.of(LocalDateTime.now(), expirationDate, userId, cabinetId); - lentRepository.save(result); - cabinet.specifyStatusByUserCount(1); // todo : policy에서 관리 - } - - @Override - public void endLentCabinet(Long userId) { - log.debug("Called endLentCabinet: {}", userId); - List allActiveLentHistoriesByUserId = - lentOptionalFetcher.findAllActiveLentHistoriesByUserId(userId); - LentHistory lentHistory = returnCabinetByUserId(userId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(lentHistory.getCabinetId()); - // cabinetType도 인자로 전달하면 좋을 거 같습니다 (공유사물함 3일이내 반납 페널티) - userService.banUser(userId, cabinet.getLentType(), lentHistory.getStartedAt(), - lentHistory.getEndedAt(), lentHistory.getExpiredAt()); - // 공유 사물함 반납 시 남은 대여일 수 차감 (원래 남은 대여일 수 * (남은 인원 / 원래 있던 인원)) - if (cabinet.getLentType().equals(LentType.SHARE)) { - double usersInShareCabinet = lentRepository.countByCabinetIdAndEndedAtIsNull( - cabinet.getCabinetId()); - double daysUntilExpiration = - lentHistory.getDaysUntilExpiration(LocalDateTime.now()).doubleValue() * -1.0; - double secondsRemaining = - daysUntilExpiration * (usersInShareCabinet / (usersInShareCabinet + 1.0) * 24.0 - * 60.0 * 60.0); - LocalDateTime expiredAt = LocalDateTime.now().plusSeconds(Math.round(secondsRemaining)) - .withHour(23) - .withMinute(59) - .withSecond(0); // 23:59:59 - allActiveLentHistoriesByUserId.forEach(e -> { - e.setExpiredAt(expiredAt); - }); - } - lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), lentHistory.getUser().getName()); - } - - @Override - public void terminateLentCabinet(Long userId) { - log.debug("Called terminateLentCabinet: {}", userId); - returnCabinetByUserId(userId); - } - - @Override - public void terminateLentByCabinetId(Long cabinetId) { - log.debug("Called terminateLentByCabinetId: {}", cabinetId); - returnCabinetByCabinetId(cabinetId); - } - - @Override - public void cancelLentShareCabinet(Long userId, Long cabinetId) { - log.debug("Called cancelLentShareCabinet: {}, {}", userId, cabinetId); - if (!lentRedis.isUserInRedis(cabinetId.toString(), userId.toString())) { - throw new DomainException(ExceptionStatus.NOT_FOUND_USER); - } - lentRedis.deleteUserInRedis(cabinetId.toString(), userId.toString()); - // 유저가 나갔을 때, 해당 키에 다른 유저가 없다면 전체 키 삭제 - if (lentRedis.getSizeOfUsersInSession(cabinetId.toString()) == 0) { - lentRedis.deleteShadowKey(cabinetId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - cabinet.specifyStatus(CabinetStatus.AVAILABLE); - } - } - - // cabinetId로 return하는 경우에서, 공유 사물함과 개인 사물함의 경우에 대한 분기가 되어 있지 않음. - // 또한 어드민의 경우에서 사용하는 returnByCabinetId와 유저가 사용하는 returnByCabinetId가 다른 상황이므로 - // (어드민의 경우에는 뭐든지 전체 반납, 유저가 사용하는 경우에는 본인이 사용하는 사물함에 대한 반납) - // 유저가 사용하는 경우에 대해서는 userId로만 쓰게하든, 한 방식으로만 사용하게끔 해야함 - 함수를 쪼갤 가능성도 있음. - // 우선 현재 관리자만 쓰고 있고, 한 군데에서만 사용되므로 List로 전체 반납을 하도록 구현, 이에 대한 논의는 TO-DO - private List returnCabinetByCabinetId(Long cabinetId) { - log.debug("Called returnCabinetByCabinetId: {}", cabinetId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( - cabinetId); // todo : 현재 returnCabinetByCabinetId는 개인사물함 반납에 대해서만 사용되고 있기 때문에 lentHistory에 대한 list로 받을 필요가 없음 - 추후 추가 확인 후 로직 수정 필요 - lentHistories.forEach(lentHistory -> lentHistory.endLent(LocalDateTime.now())); - cabinet.specifyStatusByUserCount(0); // policy로 빼는게..? -// log.info("cabinet status {}",cabinet.getStatus()); - cabinet.writeMemo(""); - cabinet.writeTitle(""); - lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), - lentHistories.get(0).getUser().getName()); - return lentHistories; - } - - private LentHistory returnCabinetByUserId(Long userId) { - log.debug("Called returnCabinet: {}", userId); - LentHistory lentHistory = lentOptionalFetcher.getActiveLentHistoryWithUserIdForUpdate( - userId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(lentHistory.getCabinetId()); - int activeLentCount = lentRepository.countByCabinetIdAndEndedAtIsNull(lentHistory.getCabinetId()); - lentHistory.endLent(LocalDateTime.now()); - cabinet.specifyStatusByUserCount(activeLentCount - 1); // policy로 빠질만한 부분인듯? - if (activeLentCount - 1 == 0) { - cabinet.writeMemo(""); - cabinet.writeTitle(""); - } - lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), - lentHistory.getUser().getName()); - return lentHistory; - } - - @Override - public void assignLent(Long userId, Long cabinetId) { - log.debug("Called assignLent: {}, {}", userId, cabinetId); - userOptionalFetcher.getUser(userId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - lentOptionalFetcher.checkExistedSpace(cabinetId); - LocalDateTime expirationDate = lentPolicy.generateExpirationDate(LocalDateTime.now(), - cabinet); - LentHistory result = LentHistory.of(LocalDateTime.now(), expirationDate, userId, cabinetId); - cabinet.specifyStatusByUserCount(1); - lentRepository.save(result); - } - - @Override - public void handleLentFromRedisExpired(String cabinetIdString) { - Long cabinetId = Long.parseLong(cabinetIdString); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - Long userCount = lentRedis.getSizeOfUsersInSession(cabinetId.toString()); - if (cabinetProperties.getShareMinUserCount() <= userCount - && userCount <= cabinetProperties.getShareMaxUserCount()) { // 2명 이상 4명 이하: 대여 성공 - LocalDateTime now = LocalDateTime.now(); - cabinet.specifyStatus(CabinetStatus.FULL); - saveLentHistories(now, cabinetId); - } else { - cabinet.specifyStatus(CabinetStatus.AVAILABLE); - } - ArrayList userIds = lentRedis.getUserIdsByCabinetIdInRedis( - cabinetId.toString()); - for (String userId : userIds) { - lentRedis.deleteUserIdInRedis(Long.valueOf(userId)); - } - lentRedis.deleteCabinetIdInRedis(cabinetId.toString()); - } - - @Override - public List getAllActiveLentHistories() { - log.debug("Called getAllActiveLentHistories"); - List lentHistories = lentOptionalFetcher.findAllActiveLentHistories(); - LocalDateTime now = LocalDateTime.now(); - return lentHistories.stream() - .map(e -> lentMapper.toActiveLentHistoryDto(e, - e.getUser(), - e.getCabinet(), - e.isExpired(now), - e.getDaysUntilExpiration(now) - )) - .collect(Collectors.toList()); - } - - public void saveLentHistories(LocalDateTime now, Long cabinetId) { - ArrayList userIdList = lentRedis.getUserIdsByCabinetIdInRedis( - cabinetId.toString()); - LocalDateTime expiredAt = lentPolicy.generateSharedCabinetExpirationDate(now, - userIdList.size()); - // userId 반복문 돌면서 수행 - userIdList.stream() - .map(userId -> LentHistory.of(now, expiredAt, Long.parseLong(userId), cabinetId)) - .forEach(lentHistory -> { - lentPolicy.applyExpirationDate(lentHistory, expiredAt); - lentRepository.save(lentHistory); - }); - } -} - diff --git a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java b/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java index 77ba3f2a1..2f6a32000 100644 --- a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java +++ b/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.redis; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.lent.service.LentServiceImpl; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; @@ -14,20 +14,20 @@ @Log4j2 public class ExpirationListener extends KeyExpirationEventMessageListener { - private final LentServiceImpl lentServiceImpl; + private final LentFacadeService lentFacadeService; /** * Creates new {@link MessageListener} for {@code __keyevent@*__:expired} messages. * * @param listenerContainer must not be {@literal null}. - * @param lentServiceImpl must not be {@literal null}. + * @param lentFacadeService must not be {@literal null}. */ public ExpirationListener( @Qualifier("redisMessageListenerContainer") RedisMessageListenerContainer listenerContainer, - LentServiceImpl lentServiceImpl) { + LentFacadeService lentFacadeService) { super(listenerContainer); - this.lentServiceImpl = lentServiceImpl; + this.lentFacadeService = lentFacadeService; } /** @@ -38,7 +38,6 @@ public ExpirationListener( public void onMessage(Message message, byte[] pattern) { log.debug("Called onMessage: {}, {}", message.toString(), pattern); String cabinetIdString = message.toString().split(":")[0]; - log.debug("cabinetIdWithSuffix: {}", cabinetIdString); - lentServiceImpl.handleLentFromRedisExpired(cabinetIdString); + lentFacadeService.shareCabinetSessionExpired(Long.valueOf(cabinetIdString)); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java index 98c10f7ef..572230da7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java @@ -9,9 +9,10 @@ import org.ftclub.cabinet.dto.ClubUserListDto; import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.user.service.LentExtensionService; import org.ftclub.cabinet.user.service.UserFacadeService; +import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -31,115 +32,113 @@ @Log4j2 public class AdminUserController { - private final UserFacadeService userFacadeService; - private final LentFacadeService lentFacadeService; - private final LentExtensionService lentExtensionService; + private final UserFacadeService userFacadeService; + private final LentFacadeService lentFacadeService; + private final LentExtensionService lentExtensionService; - /** - * 현재 유저가 차단된 상태일 때, 차단을 해제합니다. - * - * @param userId 유저 고유 아이디 - */ - @DeleteMapping("/{userId}/ban-history") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { - log.info("Called deleteBanHistoryByUserId: {}", userId); - userFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); - } + /** + * 현재 유저가 차단된 상태일 때, 차단을 해제합니다. + * + * @param userId 유저 고유 아이디 + */ + @DeleteMapping("/{userId}/ban-history") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { + log.info("Called deleteBanHistoryByUserId: {}", userId); + userFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); + } - /** - * 유저의 대여 기록을 반환합니다. - * - * @param userId 유저 고유 아이디 - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return {@link LentHistoryPaginationDto} 유저의 대여 기록 - */ - @GetMapping("/{userId}/lent-histories") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentHistoryPaginationDto getLentHistoriesByUserId(@PathVariable("userId") Long userId, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getLentHistoriesByUserId: {}", userId); - return lentFacadeService.getAllUserLentHistories(userId, page, size); - } + /** + * 유저의 대여 기록을 반환합니다. + * + * @param userId 유저 고유 아이디 + * @param pageRequest 페이지네이션 정보 + * @return {@link LentHistoryPaginationDto} 유저의 대여 기록 + */ + @GetMapping("/{userId}/lent-histories") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public LentHistoryPaginationDto getLentHistoriesByUserId(@PathVariable("userId") Long userId, + @RequestBody PageRequest pageRequest) { + log.info("Called getLentHistoriesByUserId: {}", userId); + return lentFacadeService.getUserLentHistories(userId, pageRequest); + } - /** - * 유저를 어드민으로 승격시킵니다. - * - * @param email 유저 이메일 - * @return redirect:cabi.42seoul.io/admin/login - */ - @GetMapping("/admins/promote") - @AuthGuard(level = AuthLevel.MASTER_ONLY) - public void promoteUserToAdmin(@RequestParam("email") String email) { - log.info("Called promoteUserToAdmin: {}", email); - userFacadeService.promoteUserToAdmin(email); - } + /** + * 유저를 어드민으로 승격시킵니다. + * + * @param email 유저 이메일 + * @return redirect:cabi.42seoul.io/admin/login + */ + @GetMapping("/admins/promote") + @AuthGuard(level = AuthLevel.MASTER_ONLY) + public void promoteUserToAdmin(@RequestParam("email") String email) { + log.info("Called promoteUserToAdmin: {}", email); + userFacadeService.promoteUserToAdmin(email); + } - /** - * 동아리 유저를 생성합니다. - * - * @param body - */ - @PostMapping("/club") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void createClubUser(@RequestBody HashMap body) { - log.info("Called createClub"); - String clubName = body.get("clubName"); - userFacadeService.createClubUser(clubName); - } + /** + * 동아리 유저를 생성합니다. + * + * @param body + */ + @PostMapping("/club") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void createClubUser(@RequestBody HashMap body) { + log.info("Called createClub"); + String clubName = body.get("clubName"); + userFacadeService.createClubUser(clubName); + } - /** - * 동아리 유저를 삭제합니다. - * - * @param clubId 동아리 고유 아이디 - */ - @DeleteMapping("/club/{clubId}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void deleteClubUser(@PathVariable("clubId") Long clubId) { - log.info("Called deleteClub"); - userFacadeService.deleteClubUser(clubId); - } + /** + * 동아리 유저를 삭제합니다. + * + * @param clubId 동아리 고유 아이디 + */ + @DeleteMapping("/club/{clubId}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void deleteClubUser(@PathVariable("clubId") Long clubId) { + log.info("Called deleteClub"); + userFacadeService.deleteClubUser(clubId); + } - @GetMapping("/clubs") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public ClubUserListDto findClubs(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getClubs"); - return userFacadeService.findAllClubUser(page, size); - } + @GetMapping("/clubs") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public ClubUserListDto findClubs(@RequestParam("page") Integer page, + @RequestParam("size") Integer size) { + log.info("Called getClubs"); + return userFacadeService.findAllClubUser(page, size); + } - @PatchMapping("/club/{clubId}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void updateClubUser(@PathVariable("clubId") Long clubId, - @RequestBody HashMap body) { - log.info("Called updateClub"); - String clubName = body.get("clubName"); - userFacadeService.updateClubUser(clubId, clubName); - } + @PatchMapping("/club/{clubId}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void updateClubUser(@PathVariable("clubId") Long clubId, + @RequestBody HashMap body) { + log.info("Called updateClub"); + String clubName = body.get("clubName"); + userFacadeService.updateClubUser(clubId, clubName); + } - @GetMapping("/lent-extensions") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentExtensionPaginationDto getAllLentExtension(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getAllLentExtension"); - return userFacadeService.getAllLentExtension(page, size); - } + @GetMapping("/lent-extensions") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public LentExtensionPaginationDto getAllLentExtension(@RequestParam("page") Integer page, + @RequestParam("size") Integer size) { + log.info("Called getAllLentExtension"); + return userFacadeService.getAllLentExtension(page, size); + } - @GetMapping("/lent-extensions/active") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getAllActiveLentExtension"); - return userFacadeService.getAllActiveLentExtension(page, size); - } + @GetMapping("/lent-extensions/active") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page") Integer page, + @RequestParam("size") Integer size) { + log.info("Called getAllActiveLentExtension"); + return userFacadeService.getAllActiveLentExtension(page, size); + } - @PostMapping("/lent-extensions/{user}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void issueLentExtension(@PathVariable("user") String username) { - log.info("Called issueLentExtension"); - lentExtensionService.assignLentExtension(username); + @PostMapping("/lent-extensions/{user}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void issueLentExtension(@PathVariable("user") String username) { + log.info("Called issueLentExtension"); + lentExtensionService.assignLentExtension(username); - } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java index 71218a924..e360c4447 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java @@ -1,7 +1,11 @@ package org.ftclub.cabinet.user.newService; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.springframework.stereotype.Service; @Service @@ -9,4 +13,11 @@ @Log4j2 public class BanHistoryCommandService { + private final BanHistoryRepository banHistoryRepository; + + public void banUser(Long userId, LocalDateTime endedAt, + LocalDateTime unBannedAt, BanType banType) { + BanHistory banHistory = BanHistory.of(endedAt, unBannedAt, banType, userId); + banHistoryRepository.save(banHistory); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index d5599237c..f85b0b3e9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -1,26 +1,30 @@ package org.ftclub.cabinet.user.newService; +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; - @Service @RequiredArgsConstructor @Log4j2 public class BanHistoryQueryService { - private final BanHistoryRepository banHistoryRepository; - - public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { - log.debug("Called findRecentActiveBanHistory: {}", userId); + private final BanHistoryRepository banHistoryRepository; - List banHistories = banHistoryRepository.findAll(); +// public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { +// log.debug("Called findRecentActiveBanHistory: {}", userId); +// +// List banHistories = banHistoryRepository.findAll(); +// +// } - } + public List findActiveBanHistories(Long userId, LocalDateTime date) { + log.debug("Called findActiveBanHistories: {}", userId); + return banHistoryRepository.findByUserIdAndUnbannedAt(userId, date); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java new file mode 100644 index 000000000..30c520b02 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java @@ -0,0 +1,29 @@ +package org.ftclub.cabinet.user.newService; + +import java.time.LocalDateTime; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.utils.DateUtil; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class BanPolicyService { + + public BanType verifyBan(LocalDateTime endedAt, LocalDateTime expiredAt) { + log.debug("Called verifyBan"); + + if (expiredAt.isBefore(endedAt)) { + return BanType.ALL; + } + return BanType.NONE; + } + + public LocalDateTime getUnBannedAt(LocalDateTime endedAt, LocalDateTime expiredAt) { + log.debug("Called getBanDate"); + + long recentBanDays = DateUtil.calculateTwoDateDiffCeil(expiredAt, endedAt); + double squaredBanDays = Math.pow(recentBanDays, 2); + return endedAt.plusDays((long) squaredBanDays); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 4b58d6676..71559d2a2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -2,8 +2,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.dto.MyProfileResponseDto; -import org.ftclub.cabinet.dto.UserSessionDto; import org.springframework.stereotype.Service; @Service @@ -11,13 +9,13 @@ @Log4j2 public class UserFacadeService { - public MyProfileResponseDto getMyProfile(UserSessionDto user) { - log.debug("Called getMyProfile: {}", user.getName()); - - // Cabinet cabinet = lentService.findActiveLentCabinetByUserId(); - - - } +// public MyProfileResponseDto getMyProfile(UserSessionDto user) { +// log.debug("Called getMyProfile: {}", user.getName()); +// +// // Cabinet cabinet = lentService.findActiveLentCabinetByUserId(); +// +// +// } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java index 1d30c10ef..5867d647b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -1,31 +1,29 @@ package org.ftclub.cabinet.user.newService; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - @Service @RequiredArgsConstructor @Log4j2 public class UserQueryService { - private final UserRepository userRepository; + private final UserRepository userRepository; - public User getUser(Long userId) { - Optional user = userRepository.findById(userId); - return user.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); - } + public User getUser(Long userId) { + Optional user = userRepository.findById(userId); + return user.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } + public List getUsers(List userIdsInCabinet) { + return userRepository.findAllByIds(userIdsInCabinet); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java index b1494b03d..6b362fa84 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java @@ -22,7 +22,7 @@ public interface BanHistoryRepository extends JpaRepository { * @return active {@link BanHistory} 리스트 */ @Query("SELECT b FROM BanHistory b WHERE b.user.userId = :userId AND b.unbannedAt > :today") - List findUserActiveBanList( + List findByUserIdAndUnbannedAt( @Param("userId") Long userId, @Param("today") LocalDateTime today); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index fb6a85f5b..2a0105088 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -51,19 +51,29 @@ public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.name LIKE %:name%") Page findByPartialName(@Param("name") String name, Pageable pageable); + /** + * 유저의 Id List로 유저들을 찾습니다. + * + * @param userIds 유저 Id {@link List} + * @return {@link User} 리스트 + */ + @Query("SELECT u FROM User u " + + "WHERE u.userId IN :userIds AND u.deletedAt IS NULL") + List findAllByIds(List userIds); + /** * */ Page findAllByRoleAndDeletedAtIsNull(@Param("role") UserRole role, Pageable pageable); - /** + /** * 블랙홀에 빠질 위험이 있는 유저들의 정보를 조회합니다. blackholedAt이 현재 시간보다 과거인 유저들을 블랙홀에 빠질 위험이 있는 유저로 판단합니다. * * @return {@link User} 리스트 */ @Query("SELECT u FROM User u WHERE u.blackholedAt IS NOT NULL OR u.blackholedAt <= CURRENT_TIMESTAMP") List findByRiskOfFallingIntoBlackholeUsers(); - + /** * 블랙홀에 빠질 위험이 없는 유저들의 정보를 조회합니다. blackholedAt이 null이거나 현재 시간보다 미래인 유저들을 블랙홀에 빠질 위험이 없는 유저로 * 판단합니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java index c62978d1d..c8051cb7f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java @@ -178,7 +178,7 @@ public void deleteRecentBanHistory(Long userId, LocalDateTime today) { @Override public boolean checkUserIsBanned(Long userId, LocalDateTime today) { log.debug("Called checkUserIsBanned: {}", userId); - List banHistory = banHistoryRepository.findUserActiveBanList(userId, + List banHistory = banHistoryRepository.findByUserIdAndUnbannedAt(userId, today); return (banHistory.size() != 0); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java b/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java index be8605e38..e37af5005 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java @@ -138,4 +138,8 @@ public static Long calculateTwoDateDiffCeil(LocalDateTime day1, LocalDateTime da System.out.println("diffInMillis = " + diffInMillis); return (long) Math.ceil(diffInMillis / 1000.0 / 60 / 60 / 24); } + + public static LocalDateTime setLastTime(LocalDateTime date) { + return date.withHour(23).withMinute(59).withSecond(59); + } } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java index 92fcb5379..d48051c83 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java @@ -9,7 +9,7 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.exception.UtilException; -import org.ftclub.cabinet.lent.service.LentService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.user.service.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -21,7 +21,7 @@ public class BlackholeManager { private final FtApiManager ftApiManager; - private final LentService lentService; + private final LentFacadeService lentFacadeService; private final UserService userService; /** @@ -61,11 +61,7 @@ private LocalDateTime parseBlackholedAt(JsonNode jsonUserInfo) { private boolean isBlackholed(LocalDateTime blackholedAtDate) { log.info("isBlackholed {} {}", blackholedAtDate); LocalDateTime now = LocalDateTime.now(); - if (blackholedAtDate == null || blackholedAtDate.isAfter(now)) { - return false; - } else { - return true; - } + return blackholedAtDate != null && !blackholedAtDate.isAfter(now); } /** @@ -76,7 +72,7 @@ private boolean isBlackholed(LocalDateTime blackholedAtDate) { */ private void handleNotCadet(UserBlackholeInfoDto userBlackholeInfoDto, LocalDateTime now) { log.warn("{}는 카뎃이 아닙니다.", userBlackholeInfoDto); - lentService.terminateLentCabinet(userBlackholeInfoDto.getUserId()); + lentFacadeService.endUserLent(userBlackholeInfoDto.getUserId()); userService.deleteUser(userBlackholeInfoDto.getUserId(), now); } @@ -88,7 +84,7 @@ private void handleNotCadet(UserBlackholeInfoDto userBlackholeInfoDto, LocalDate private void handleBlackholed(UserBlackholeInfoDto userBlackholeInfoDto) { log.info("{}는 블랙홀에 빠졌습니다.", userBlackholeInfoDto); LocalDateTime now = LocalDateTime.now(); - lentService.terminateLentCabinet(userBlackholeInfoDto.getUserId()); + lentFacadeService.endUserLent(userBlackholeInfoDto.getUserId()); userService.deleteUser(userBlackholeInfoDto.getUserId(), now); } @@ -116,7 +112,7 @@ private void handleHttpClientError(UserBlackholeInfoDto userBlackholeInfoDto, Lo log.error("handleBlackhole HttpClientErrorException {}", e.getStatusCode()); if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { log.warn("{}는 42에서 찾을 수 없습니다.", userBlackholeInfoDto); - lentService.terminateLentCabinet(userBlackholeInfoDto.getUserId()); + lentFacadeService.endUserLent(userBlackholeInfoDto.getUserId()); userService.deleteUser(userBlackholeInfoDto.getUserId(), now); } } @@ -192,10 +188,10 @@ public void handleBlackhole(UserBlackholeInfoDto userInfoDto) { } catch (ServiceException e) { if (e.getStatus().equals(ExceptionStatus.NO_LENT_CABINET)) { userService.deleteUser(userInfoDto.getUserId(), now); - } - else if (e.getStatus().equals(ExceptionStatus.OAUTH_BAD_GATEWAY)) + } else if (e.getStatus().equals(ExceptionStatus.OAUTH_BAD_GATEWAY)) { log.info("handleBlackhole ServiceException {}", e.getStatus()); - throw new UtilException(e.getStatus()); + } + throw new UtilException(e.getStatus()); } catch (Exception e) { log.error("handleBlackhole Exception: {}", userInfoDto, e); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java index b08c14e29..3a0ea8f35 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java @@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.service.FtApiManager; -import org.ftclub.cabinet.lent.service.LentService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.user.service.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -17,7 +17,7 @@ public class LeaveAbsenceManager { private final FtApiManager ftAPIManager; - private final LentService lentService; + private final LentFacadeService lentFacadeService; private final UserService userService; private Boolean isLeaveAbsence(JsonNode jsonUserInfo) { @@ -29,12 +29,12 @@ public void handleLeaveAbsence(Long userId, String name) { try { JsonNode jsonUserInfo = ftAPIManager.getFtUsersInfoByName(name); if (isLeaveAbsence(jsonUserInfo)) { - lentService.terminateLentCabinet(userId); + lentFacadeService.endUserLent(userId); } } catch (HttpClientErrorException e) { log.error("handleLeaveAbsence HttpClientErrorException {}", e.getStatusCode()); if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { - lentService.terminateLentCabinet(userId); + lentFacadeService.endUserLent(userId); userService.deleteUser(userId, LocalDateTime.now()); } } catch (Exception e) { diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 5450037a0..35f660a25 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -6,7 +6,7 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.UserBlackholeInfoDto; -import org.ftclub.cabinet.lent.service.LentService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; import org.ftclub.cabinet.user.service.UserService; import org.ftclub.cabinet.utils.blackhole.manager.BlackholeManager; @@ -28,7 +28,7 @@ public class SystemScheduler { private final LeaveAbsenceManager leaveAbsenceManager; private final OverdueManager overdueManager; - private final LentService lentService; + private final LentFacadeService lentFacadeService; private final UserService userService; private final BlackholeManager blackholeManager; private final ReleaseManager releaseManager; @@ -41,7 +41,7 @@ public class SystemScheduler { @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") public void checkAllLents() { log.info("called checkAllLents"); - List activeLents = lentService.getAllActiveLentHistories(); + List activeLents = lentFacadeService.getAllActiveLentHistories(); for (ActiveLentHistoryDto activeLent : activeLents) { overdueManager.handleOverdue(activeLent); /* diff --git a/backend/src/test/java/org/ftclub/cabinet/redis/RedisRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/redis/RedisRepositoryTest.java index 4d83d5b5a..3fe7991ff 100644 --- a/backend/src/test/java/org/ftclub/cabinet/redis/RedisRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/redis/RedisRepositoryTest.java @@ -24,8 +24,8 @@ void test() { Long cabinetId = 16L; lentRedis.setShadowKey(cabinetId); - lentRedis.saveUserInRedis("16L", "1234L", "1000", false); - lentRedis.saveUserInRedis("16L", "5678L", "1000", true); + lentRedis.attemptJoinCabinet("16L", "1234L", "1000", false); + lentRedis.attemptJoinCabinet("16L", "5678L", "1000", true); try { Thread.sleep(10000); diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/BanHistoryRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/BanHistoryRepositoryTest.java index 3214033ea..2333f2e58 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/BanHistoryRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/BanHistoryRepositoryTest.java @@ -31,7 +31,7 @@ public class BanHistoryRepositoryTest { // ban history 한 개 존재 Long userId = 1L; - List activeBanList = banHistoryRepository.findUserActiveBanList(userId, + List activeBanList = banHistoryRepository.findByUserIdAndUnbannedAt(userId, testDate); assertNotNull(activeBanList); diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java index 32b50bf6a..efec2404a 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java @@ -1,7 +1,10 @@ package org.ftclub.cabinet.user.service; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -67,7 +70,7 @@ class UserServiceUnitTest { boolean result = userService.checkUserExists("user1@student.42seoul.kr"); - assertEquals(true, result); + assertTrue(result); then(userOptionalFetcher).should(times(1)).findUserByEmail("user1@student.42seoul.kr"); } @@ -79,7 +82,7 @@ class UserServiceUnitTest { boolean result = userService.checkUserExists("notUser@student.42seoul.kr"); - assertEquals(false, result); + assertFalse(result); then(userOptionalFetcher).should(times(1)).findUserByEmail("notUser@student.42seoul.kr"); } @@ -103,7 +106,7 @@ class UserServiceUnitTest { boolean result = userService.checkAdminUserExists("admin@admin.com"); - assertEquals(true, result); + assertTrue(result); then(userOptionalFetcher).should(times(1)).findAdminUserByEmail("admin@admin.com"); } @@ -115,7 +118,7 @@ class UserServiceUnitTest { boolean result = userService.checkAdminUserExists("notAdmin@admin.com"); - assertEquals(false, result); + assertFalse(result); then(userOptionalFetcher).should(times(1)).findAdminUserByEmail("notAdmin@admin.com"); } @@ -531,12 +534,12 @@ void createAdminUser() { BanHistory banHistory = mock(BanHistory.class); list.add(banHistory); - given(banHistoryRepository.findUserActiveBanList(userId, now)).willReturn(list); + given(banHistoryRepository.findByUserIdAndUnbannedAt(userId, now)).willReturn(list); boolean result = userService.checkUserIsBanned(userId, now); - then(banHistoryRepository).should(times(1)).findUserActiveBanList(userId, now); - assertEquals(result, true); + then(banHistoryRepository).should(times(1)).findByUserIdAndUnbannedAt(userId, now); + assertTrue(result); } @Test @@ -546,12 +549,12 @@ void createAdminUser() { Long userId = 2L; LocalDateTime now = LocalDateTime.now(); - given(banHistoryRepository.findUserActiveBanList(userId, now)).willReturn(list); + given(banHistoryRepository.findByUserIdAndUnbannedAt(userId, now)).willReturn(list); boolean result = userService.checkUserIsBanned(userId, now); - then(banHistoryRepository).should(times(1)).findUserActiveBanList(userId, now); - assertEquals(result, false); + then(banHistoryRepository).should(times(1)).findByUserIdAndUnbannedAt(userId, now); + assertFalse(result); } @Test @@ -578,6 +581,6 @@ void createAdminUser() { AdminRole result = userService.getAdminUserRole(email); then(userOptionalFetcher).should(times(1)).findAdminUserRoleByEmail(email); - assertEquals(result, null); + assertNull(result); } } \ No newline at end of file From 4144bab6974b80f95b0f1e0d22acb3e27865f2a6 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 13:29:05 +0900 Subject: [PATCH 0101/1029] =?UTF-8?q?[BE]=20admin=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20java=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminUserController.java | 2 +- .../controller/SearchController.java | 2 +- .../controller/StatisticsController.java | 4 +- .../{user => admin}/domain/AdminRole.java | 2 +- .../cabinet/admin/domain/AdminUser.java | 90 ++++ .../repository/AdminUserRepository.java | 7 +- .../repository/StatisticsOptionalFetcher.java | 2 +- .../repository/StatisticsRepository.java | 2 +- .../service/StatisticsFacadeService.java | 7 +- .../service/StatisticsFacadeServiceImpl.java | 4 +- .../cabinet/auth/domain/TokenProvider.java | 2 +- .../cabinet/auth/domain/TokenValidator.java | 4 +- .../ftclub/cabinet/user/domain/AdminUser.java | 93 ---- .../user/repository/UserOptionalFetcher.java | 419 +++++++++--------- .../user/service/UserFacadeService.java | 364 +++++++-------- .../user/service/UserFacadeServiceImpl.java | 9 +- .../cabinet/user/service/UserService.java | 3 +- .../cabinet/user/service/UserServiceImpl.java | 6 +- .../auth/domain/TokenValidatorUnitTest.java | 38 +- .../controller/StatisticsControllerTest.java | 5 +- .../StatisticsFacadeServiceUnitTest.java | 3 +- .../cabinet/user/domain/AdminUserTest.java | 2 + .../repository/AdminUserRepositoryTest.java | 14 +- .../user/service/UserFacadeServiceTest.java | 4 +- .../cabinet/user/service/UserServiceTest.java | 6 +- .../user/service/UserServiceUnitTest.java | 6 +- .../java/org/ftclub/testutils/TestUtils.java | 13 +- 27 files changed, 569 insertions(+), 544 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/{user => admin}/controller/AdminUserController.java (99%) rename backend/src/main/java/org/ftclub/cabinet/{search => admin}/controller/SearchController.java (98%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/controller/StatisticsController.java (96%) rename backend/src/main/java/org/ftclub/cabinet/{user => admin}/domain/AdminRole.java (89%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminUser.java rename backend/src/main/java/org/ftclub/cabinet/{user => admin}/repository/AdminUserRepository.java (88%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/repository/StatisticsOptionalFetcher.java (80%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/repository/StatisticsRepository.java (94%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/service/StatisticsFacadeService.java (62%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/service/StatisticsFacadeServiceImpl.java (96%) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/user/domain/AdminUser.java diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java index 572230da7..b657d87db 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.controller; +package org.ftclub.cabinet.admin.controller; import java.time.LocalDateTime; import java.util.HashMap; diff --git a/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java index e081fd786..9e8acb1a1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.search.controller; +package org.ftclub.cabinet.admin.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/controller/StatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java similarity index 96% rename from backend/src/main/java/org/ftclub/cabinet/statistics/controller/StatisticsController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java index 2c54b0313..d03a68d9a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/controller/StatisticsController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.controller; +package org.ftclub.cabinet.admin.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; @@ -11,7 +11,7 @@ import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; -import org.ftclub.cabinet.statistics.service.StatisticsFacadeService; +import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.ftclub.cabinet.user.service.UserFacadeService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminRole.java b/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminRole.java similarity index 89% rename from backend/src/main/java/org/ftclub/cabinet/user/domain/AdminRole.java rename to backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminRole.java index c9941c4ff..ea9facd3e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminRole.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminRole.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.domain; +package org.ftclub.cabinet.admin.domain; /** * 어드민의 권한을 나타내는 열거형 클래스입니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminUser.java b/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminUser.java new file mode 100644 index 000000000..7105ebdda --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminUser.java @@ -0,0 +1,90 @@ +package org.ftclub.cabinet.admin.domain; + +import java.util.Objects; +import java.util.regex.Pattern; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.utils.ExceptionUtil; +import lombok.extern.log4j.Log4j2; + +/** + * 관리자 엔티티 클래스입니다. + */ +@Entity +@Table(name = "ADMIN_USER") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Log4j2 +public class AdminUser { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ADMIN_USER_ID") + private Long adminUserId; + + /** + * OAuth 방식을 사용하기 때문에 비밀번호 없이 이메일만 저장합니다. + */ + @NotNull + @Email + @Column(name = "EMAIL", length = 128, unique = true, nullable = false) + private String email; + + /** + * {@link AdminRole} + */ + @Enumerated(value = EnumType.STRING) + @Column(name = "ROLE", length = 16, nullable = false) + private AdminRole role = AdminRole.NONE; + + protected AdminUser(String email, AdminRole role) { + this.email = email; + this.role = role; + } + + public static AdminUser of(String email, AdminRole role) { + AdminUser adminUser = new AdminUser(email, role); + ExceptionUtil.throwIfFalse(adminUser.isValid(), + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + return adminUser; + } + + private boolean isValid() { + return role != null && role.isValid() && email != null && + Pattern.matches( + "^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\\.[A-Za-z0-9\\-]+\\.*[A-Za-z0-9\\-]*", + email); + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof AdminUser)) { + return false; + } + AdminUser adminUser = (AdminUser) other; + return Objects.equals(adminUserId, adminUser.adminUserId); + } + + public void changeAdminRole(AdminRole role) { + log.info("Called changedAdminRole - role from {} to {}", this.role, role); + this.role = role; + ExceptionUtil.throwIfFalse(this.isValid(), + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/AdminUserRepository.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/AdminUserRepository.java similarity index 88% rename from backend/src/main/java/org/ftclub/cabinet/user/repository/AdminUserRepository.java rename to backend/src/main/java/org/ftclub/cabinet/admin/repository/AdminUserRepository.java index ede9a0c67..9574770cd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/AdminUserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/repository/AdminUserRepository.java @@ -1,8 +1,8 @@ -package org.ftclub.cabinet.user.repository; +package org.ftclub.cabinet.admin.repository; import java.util.Optional; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -29,6 +29,7 @@ public interface AdminUserRepository extends JpaRepository { /** * 유저의 이메일로 어드민 유저를 찾고 어드민 유저의 권한을 반환합니다. + * * @param email * @return {@link AdminRole} */ diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java similarity index 80% rename from backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsOptionalFetcher.java rename to backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java index 0071f78cb..5576f1450 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.repository; +package org.ftclub.cabinet.admin.repository; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsRepository.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java similarity index 94% rename from backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsRepository.java rename to backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java index c541e7b92..9e2d7f807 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.repository; +package org.ftclub.cabinet.admin.repository; import java.util.List; import javax.transaction.Transactional; diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java similarity index 62% rename from backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java index 5352673ee..fedaa0bdf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.service; +package org.ftclub.cabinet.admin.service; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; @@ -11,7 +11,8 @@ public interface StatisticsFacadeService { - List getCabinetsCountOnAllFloors(); + List getCabinetsCountOnAllFloors(); - LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDate, LocalDateTime endDate); + LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDate, + LocalDateTime endDate); } diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java similarity index 96% rename from backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java index db9f7d312..f076e9f74 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.service; +package org.ftclub.cabinet.admin.service; import static org.ftclub.cabinet.utils.ExceptionUtil.throwIfFalse; @@ -14,7 +14,7 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.statistics.repository.StatisticsRepository; +import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.springframework.stereotype.Service; @Service diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java index 6dba0652c..04f0d8f4e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java @@ -16,7 +16,7 @@ import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.UserRole; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java index 5c44c7d4b..7471a6e2b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java @@ -1,6 +1,6 @@ package org.ftclub.cabinet.auth.domain; -import static org.ftclub.cabinet.user.domain.AdminRole.MASTER; +import static org.ftclub.cabinet.admin.domain.AdminRole.MASTER; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -19,7 +19,7 @@ import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.service.UserService; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminUser.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminUser.java deleted file mode 100644 index 090a457f5..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminUser.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.ftclub.cabinet.user.domain; - -import java.util.Objects; -import java.util.regex.Pattern; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.validation.Valid; -import javax.validation.constraints.Email; -import javax.validation.constraints.Max; -import javax.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.apache.el.util.ExceptionUtils; -import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.utils.ExceptionUtil; -import lombok.extern.log4j.Log4j2; - -/** - * 관리자 엔티티 클래스입니다. - */ -@Entity -@Table(name = "ADMIN_USER") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@Log4j2 -public class AdminUser { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "ADMIN_USER_ID") - private Long adminUserId; - - /** - * OAuth 방식을 사용하기 때문에 비밀번호 없이 이메일만 저장합니다. - */ - @NotNull - @Email - @Column(name = "EMAIL", length = 128, unique = true, nullable = false) - private String email; - - /** - * {@link AdminRole} - */ - @Enumerated(value = EnumType.STRING) - @Column(name = "ROLE", length = 16, nullable = false) - private AdminRole role = AdminRole.NONE; - - private boolean isValid() { - return role != null && role.isValid() && email != null && - Pattern.matches( - "^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\\.[A-Za-z0-9\\-]+\\.*[A-Za-z0-9\\-]*", - email); - } - - protected AdminUser(String email, AdminRole role) { - this.email = email; - this.role = role; - } - - public static AdminUser of(String email, AdminRole role) { - AdminUser adminUser = new AdminUser(email, role); - ExceptionUtil.throwIfFalse(adminUser.isValid(), - new DomainException(ExceptionStatus.INVALID_ARGUMENT)); - return adminUser; - } - - @Override - public boolean equals(final Object other) { - if (other == this) { - return true; - } - if (!(other instanceof AdminUser)) { - return false; - } - AdminUser adminUser = (AdminUser) other; - return Objects.equals(adminUserId, adminUser.adminUserId); - } - - public void changeAdminRole(AdminRole role) { - log.info("Called changedAdminRole - role from {} to {}", this.role, role); - this.role = role; - ExceptionUtil.throwIfFalse(this.isValid(), - new DomainException(ExceptionStatus.INVALID_ARGUMENT)); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index ca96376fe..c5a3d0d71 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -5,11 +5,12 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; @@ -23,234 +24,234 @@ @Log4j2 public class UserOptionalFetcher { - private final UserRepository userRepository; - private final AdminUserRepository adminUserRepository; - private final BanHistoryRepository banHistoryRepository; + private final UserRepository userRepository; + private final AdminUserRepository adminUserRepository; + private final BanHistoryRepository banHistoryRepository; - /*-------------------------------------------FIND-------------------------------------------*/ + /*-------------------------------------------FIND-------------------------------------------*/ - /** - * 유저 전체 목록을 가져옵니다. - * - * @return {@link List} of {@link User} - */ - public List findAllUsers() { - log.debug("Called findAllUsers"); - return userRepository.findAll(); - } + /** + * 유저 전체 목록을 가져옵니다. + * + * @return {@link List} of {@link User} + */ + public List findAllUsers() { + log.debug("Called findAllUsers"); + return userRepository.findAll(); + } - public List findAllActiveUsers() { + public List findAllActiveUsers() { - log.debug("Called findAllActiveUsers"); - return userRepository.findAllByDeletedAtIsNull(); - } + log.debug("Called findAllActiveUsers"); + return userRepository.findAllByDeletedAtIsNull(); + } - /** - * 유저가 존재하는지 확인하고 존재하지 않으면 null을 반환합니다. - * - * @param userId 유저의 고유 ID - * @return {@link User} - */ - public User findUser(Long userId) { - log.debug("Called findUser: {}", userId); - return userRepository.findById(userId).orElse(null); - } + /** + * 유저가 존재하는지 확인하고 존재하지 않으면 null을 반환합니다. + * + * @param userId 유저의 고유 ID + * @return {@link User} + */ + public User findUser(Long userId) { + log.debug("Called findUser: {}", userId); + return userRepository.findById(userId).orElse(null); + } - /** - * 유저가 존재하는지 확인하고 유저의 고유 ID를 반환합니다. 존재하지 않으면 null을 반환합니다. - * - * @param name 찾을 유저의 이름 - * @return 찾은 유저의 고유 id - */ - public User findUserByName(String name) { - log.debug("Called findUserByName: {}", name); - return userRepository.findByName(name).orElse(null); - } + /** + * 유저가 존재하는지 확인하고 유저의 고유 ID를 반환합니다. 존재하지 않으면 null을 반환합니다. + * + * @param name 찾을 유저의 이름 + * @return 찾은 유저의 고유 id + */ + public User findUserByName(String name) { + log.debug("Called findUserByName: {}", name); + return userRepository.findByName(name).orElse(null); + } - /** - * 유저의 이메일로 유저를 검색합니다. - * - * @param email 유저의 이메일 - */ - public User findUserByEmail(String email) { - log.debug("Called findUserByEmail: {}", email); - return userRepository.findByEmail(email).orElse(null); - } + /** + * 유저의 이메일로 유저를 검색합니다. + * + * @param email 유저의 이메일 + */ + public User findUserByEmail(String email) { + log.debug("Called findUserByEmail: {}", email); + return userRepository.findByEmail(email).orElse(null); + } - /** - * 유저의 이름 일부분으로 유저를 검색합니다. - * - * @param name 유저의 이름 일부분 - * @param pageable 페이지 정보 - * @return {@link Page} of {@link User} - */ - public Page findUsersByPartialName(String name, Pageable pageable) { - log.debug("Called findUsersByPartialName: {}", name); - return userRepository.findByPartialName(name, pageable); - } + /** + * 유저의 이름 일부분으로 유저를 검색합니다. + * + * @param name 유저의 이름 일부분 + * @param pageable 페이지 정보 + * @return {@link Page} of {@link User} + */ + public Page findUsersByPartialName(String name, Pageable pageable) { + log.debug("Called findUsersByPartialName: {}", name); + return userRepository.findByPartialName(name, pageable); + } - /** - * 어드민 유저 아이디로 어드민 유저를 찾습니다. - * - * @param adminUserId 어드민 유저 아이디 - * @return {@link AdminUser} - */ - public AdminUser findAdminUser(Long adminUserId) { - log.debug("Called findAdminUser: {}", adminUserId); - return adminUserRepository.findAdminUser(adminUserId).orElse(null); - } + /** + * 어드민 유저 아이디로 어드민 유저를 찾습니다. + * + * @param adminUserId 어드민 유저 아이디 + * @return {@link AdminUser} + */ + public AdminUser findAdminUser(Long adminUserId) { + log.debug("Called findAdminUser: {}", adminUserId); + return adminUserRepository.findAdminUser(adminUserId).orElse(null); + } - /** - * 어드민 유저의 이메일로 어드민 유저를 찾습니다. - * - * @param email 어드민 유저의 이메일 - * @return {@link AdminUser} - */ - public AdminUser findAdminUserByEmail(String email) { - log.debug("Called findAdminUserByEmail: {}", email); - return adminUserRepository.findAdminUserByEmail(email).orElse(null); - } + /** + * 어드민 유저의 이메일로 어드민 유저를 찾습니다. + * + * @param email 어드민 유저의 이메일 + * @return {@link AdminUser} + */ + public AdminUser findAdminUserByEmail(String email) { + log.debug("Called findAdminUserByEmail: {}", email); + return adminUserRepository.findAdminUserByEmail(email).orElse(null); + } - /** - * - */ - public AdminRole findAdminUserRoleByEmail(String email) { - log.debug("Called findAdminUserRoleByEmail: {}", email); - return adminUserRepository.findAdminUserRoleByEmail(email) - .orElse(null); - } + /** + * + */ + public AdminRole findAdminUserRoleByEmail(String email) { + log.debug("Called findAdminUserRoleByEmail: {}", email); + return adminUserRepository.findAdminUserRoleByEmail(email) + .orElse(null); + } - /** - * 유저의 BanHistory 목록을 가져옵니다. - * - * @param pageable 페이지 정보 - * @param now 현재 시간 - * @return {@link Page} of {@link BanHistory} - */ - public Page findPaginationActiveBanHistories(Pageable pageable, LocalDateTime now) { - log.debug("Called findPaginationActiveBanHistories"); - return banHistoryRepository.findPaginationActiveBanHistories(pageable, now); - } + /** + * 유저의 BanHistory 목록을 가져옵니다. + * + * @param pageable 페이지 정보 + * @param now 현재 시간 + * @return {@link Page} of {@link BanHistory} + */ + public Page findPaginationActiveBanHistories(Pageable pageable, LocalDateTime now) { + log.debug("Called findPaginationActiveBanHistories"); + return banHistoryRepository.findPaginationActiveBanHistories(pageable, now); + } - /** - * 유저의 가장 최근 BanHistory를 가져옵니다. 없으면 null을 반환합니다. - * - * @param userId 유저의 고유 ID - * @param now 현재 시간 - * @return {@link BanHistory} - */ - //TO-DO : isEmpty, List에 대한 0 인덱스 가져오기 등 리팩터링 매우 필요.. - public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { - log.debug("Called findRecentActiveBanHistory: {}", userId); - List banHistories = banHistoryRepository.findRecentBanHistoryByUserId(userId, - now, - PageRequest.of(0, 1)); - if (banHistories.isEmpty()) { - return null; - } - return banHistories.get(0); - } + /** + * 유저의 가장 최근 BanHistory를 가져옵니다. 없으면 null을 반환합니다. + * + * @param userId 유저의 고유 ID + * @param now 현재 시간 + * @return {@link BanHistory} + */ + //TO-DO : isEmpty, List에 대한 0 인덱스 가져오기 등 리팩터링 매우 필요.. + public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { + log.debug("Called findRecentActiveBanHistory: {}", userId); + List banHistories = banHistoryRepository.findRecentBanHistoryByUserId(userId, + now, + PageRequest.of(0, 1)); + if (banHistories.isEmpty()) { + return null; + } + return banHistories.get(0); + } - /** - * ROLE 이 동아리(CLUB)인 유저를 가져옵니다 - * - * @param pageable - * @return {@link Page} - */ - public Page findClubUsers(Pageable pageable) { - log.debug("Called findClubUsers"); - return userRepository.findAllByRoleAndDeletedAtIsNull(UserRole.CLUB, pageable); - } + /** + * ROLE 이 동아리(CLUB)인 유저를 가져옵니다 + * + * @param pageable + * @return {@link Page} + */ + public Page findClubUsers(Pageable pageable) { + log.debug("Called findClubUsers"); + return userRepository.findAllByRoleAndDeletedAtIsNull(UserRole.CLUB, pageable); + } - /*-------------------------------------------GET--------------------------------------------*/ + /*-------------------------------------------GET--------------------------------------------*/ - /** - * 유저가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. - * - * @param userId 유저의 고유 ID - * @return {@link User} - */ - public User getUser(Long userId) { - log.debug("Called getUser: {}", userId); - return userRepository.findById(userId) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); - } + /** + * 유저가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. + * + * @param userId 유저의 고유 ID + * @return {@link User} + */ + public User getUser(Long userId) { + log.debug("Called getUser: {}", userId); + return userRepository.findById(userId) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } - /** - * 유저가 존재하는지 확인하고 유저의 고유 ID를 반환합니다. 존재하지 않으면 예외를 발생시킵니다. - * - * @param name 찾을 유저의 이름 - * @return 찾은 유저의 고유 id - */ - public User getUserByName(String name) { - log.debug("Called getUserByName: {}", name); - return userRepository.findByName(name) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); - } + /** + * 유저가 존재하는지 확인하고 유저의 고유 ID를 반환합니다. 존재하지 않으면 예외를 발생시킵니다. + * + * @param name 찾을 유저의 이름 + * @return 찾은 유저의 고유 id + */ + public User getUserByName(String name) { + log.debug("Called getUserByName: {}", name); + return userRepository.findByName(name) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } - /** - * 동아리가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. - * - * @param userId 동아리의 고유 ID - * @return {@link User} - */ - public User getClubUser(Long userId) { - log.debug("Called getClubUser: {}", userId); - User user = getUser(userId); - if (!user.isUserRole(UserRole.CLUB)) { - throw new ServiceException(ExceptionStatus.NOT_FOUND_USER); - } - return user; - } + /** + * 동아리가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. + * + * @param userId 동아리의 고유 ID + * @return {@link User} + */ + public User getClubUser(Long userId) { + log.debug("Called getClubUser: {}", userId); + User user = getUser(userId); + if (!user.isUserRole(UserRole.CLUB)) { + throw new ServiceException(ExceptionStatus.NOT_FOUND_USER); + } + return user; + } - /** - * 관리자가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. - * - * @param adminUserId 관리자의 고유 ID - * @return {@link AdminUser} - */ - public AdminUser getAdminUser(Long adminUserId) { - log.debug("Called getAdminUser: {}", adminUserId); - return adminUserRepository.findAdminUser(adminUserId) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); - } + /** + * 관리자가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. + * + * @param adminUserId 관리자의 고유 ID + * @return {@link AdminUser} + */ + public AdminUser getAdminUser(Long adminUserId) { + log.debug("Called getAdminUser: {}", adminUserId); + return adminUserRepository.findAdminUser(adminUserId) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); + } - /** - * 이메일을 통해 어드민 유저가 존재하는지 확인하고 유저를 반환합니다. 존재하지 않으면 예외를 발생시킵니다. - * - * @param adminUserEmail 찾을 어드민 유저의 이메일 주소 - * @return {@link User} - */ - public AdminUser getAdminUserByEmail(String adminUserEmail) { - log.debug("Called getAdminUserByEmail: {}", adminUserEmail); - return adminUserRepository.findAdminUserByEmail(adminUserEmail) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); - } + /** + * 이메일을 통해 어드민 유저가 존재하는지 확인하고 유저를 반환합니다. 존재하지 않으면 예외를 발생시킵니다. + * + * @param adminUserEmail 찾을 어드민 유저의 이메일 주소 + * @return {@link User} + */ + public AdminUser getAdminUserByEmail(String adminUserEmail) { + log.debug("Called getAdminUserByEmail: {}", adminUserEmail); + return adminUserRepository.findAdminUserByEmail(adminUserEmail) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); + } - /** - * 최근 BanHistory를 가져옵니다. 없으면 예외를 발생시킵니다. - * - * @param userId 유저의 고유 ID - * @return {@link BanHistory} - */ - //to-do : if null exception - public BanHistory getRecentBanHistory(Long userId) { - log.debug("Called getRecentBanHistory: {}", userId); - List banHistory = banHistoryRepository.findRecentBanHistoryByUserId(userId, - LocalDateTime.now(), - PageRequest.of(0, 1)); - if (banHistory.isEmpty()) { - throw new DomainException(ExceptionStatus.NOT_FOUND_BAN_HISTORY); - } - return banHistory.get(0); - } + /** + * 최근 BanHistory를 가져옵니다. 없으면 예외를 발생시킵니다. + * + * @param userId 유저의 고유 ID + * @return {@link BanHistory} + */ + //to-do : if null exception + public BanHistory getRecentBanHistory(Long userId) { + log.debug("Called getRecentBanHistory: {}", userId); + List banHistory = banHistoryRepository.findRecentBanHistoryByUserId(userId, + LocalDateTime.now(), + PageRequest.of(0, 1)); + if (banHistory.isEmpty()) { + throw new DomainException(ExceptionStatus.NOT_FOUND_BAN_HISTORY); + } + return banHistory.get(0); + } - public BanHistory getActiveBanHistory(Long userId) { - log.debug("Called getActiveBanHistory: {}", userId); - Optional banHistory = banHistoryRepository.findRecentActiveBanHistoryByUserId( - userId, - LocalDateTime.now()); - return banHistory.get(); - } + public BanHistory getActiveBanHistory(Long userId) { + log.debug("Called getActiveBanHistory: {}", userId); + Optional banHistory = banHistoryRepository.findRecentActiveBanHistoryByUserId( + userId, + LocalDateTime.now()); + return banHistory.get(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 55bad8521..6a85d9538 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -11,191 +11,191 @@ import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; public interface UserFacadeService { - /** - * 현재 로그인한 유저의 프로필을 반환합니다. 대여한 사물함 아이디 정보가 포합됩니다. - * - * @param user 로그인한 유저의 정보 - * @return {@link MyProfileResponseDto} 현재 로그인한 유저의 정보 - */ - MyProfileResponseDto getMyProfile(UserSessionDto user); - - /** - * 모든 정지 유저를 반환합니다. - * - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @param now 현재 시간 - * @return {@link BlockedUserPaginationDto} 모든 정지 유저 - */ - /* 기존 searchByBanUser와 동일한 역할을 합니다. */ - BlockedUserPaginationDto getAllBanUsers(Integer page, Integer size, LocalDateTime now); - - /** - * 유저 이름의 일부를 입력받아 해당하는 유저들의 프로필을 받아옵니다. - * - * @param name 유저 이름의 일부 - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return {@link UserProfilePaginationDto} 해당하는 유저들의 프로필 - */ - /*기존 searchByIntraId 메서드와 동일한 역할을 합니다.*/ - UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, - Integer size); - - /** - * 유저 이름의 일부를 입력받아 해당 유저들의 캐비넷 정보를 반환합니다. - * - * @param name 유저 이름의 일부 - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return {@link UserCabinetPaginationDto} 해당하는 유저들의 캐비넷 정보 - */ - UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, - Integer size); - - /** - * 모든 유저의 정보를 가져옵니다. - * - * @return 모든 유저의 정보를 가져옵니다. - */ - List getAllUsers(); - - /** - * 유저가 존재하는지 확인합니다. - * - * @param name 유저 이름 - * @return 유저가 존재하면 true, 아니면 false - */ - boolean checkUserExists(String name); - - /** - * 유저를 생성합니다. - * - * @param name 유저 이름 - * @param email 유저 이메일 - * @param blackholedAt 유저 블랙홀 날짜 - * @param role 유저 역할 - */ - void createUser(String name, String email, LocalDateTime blackholedAt, UserRole role); - - /** - * @param clubName 동아리 유저 이름 - */ - void createClubUser(String clubName); - - /** - * 관리자가 존재하는지 확인합니다. - * - * @param email 관리자 이메일 - * @return 관리자가 존재하면 true, 아니면 false - */ - boolean checkAdminUserExists(String email); - - /** - * 관리자를 생성합니다. - * - * @param email 관리자 이메일 - */ - void createAdminUser(String email); - - /** - * 유저를 삭제합니다. - * - * @param userId 유저 고유 아이디 - * @param deletedAt 유저 삭제 날짜 - */ - void deleteUser(Long userId, LocalDateTime deletedAt); - - /** - * 관리자를 삭제합니다. - * - * @param adminUserId 관리자 고유 아이디 - */ - void deleteAdminUser(Long adminUserId); - - /** - * 유저의 권한을 변경합니다. - * - * @param adminUserId 관리자 고유 아이디 - * @param role 관리자 권한 - */ - void updateAdminUserRole(Long adminUserId, AdminRole role); - - /** - * 유저를 어드민으로 승격시킵니다. - * - * @param email 유저 이메일 - */ - void promoteUserToAdmin(String email); - - /** - * 유저의 블랙홀 시간을 변경합니다. - * - * @param userId 유저 고유 아이디 - * @param newBlackholedAt 새로운 유저 블랙홀 시간 - */ - void updateUserBlackholedAt(Long userId, LocalDateTime newBlackholedAt); - - /** - * 유저를 정지시킵니다. - * - * @param userId 유저 고유 아이디 - * @param lentType 현재 대여 타입 - * @param startedAt 대여 시작 날짜 - * @param endedAt 대여 종료 날짜 - * @param expiredAt 대여 만료 날짜 - */ - void banUser(Long userId, LentType lentType, LocalDateTime startedAt, LocalDateTime endedAt, - LocalDateTime expiredAt); - - /** - * 유저의 정지를 해제합니다. - * - * @param userId 유저 고유 아이디 - * @param today 현재 날짜 - */ - void deleteRecentBanHistory(Long userId, LocalDateTime today); - - /** - * 연체 중인 유저 리스트를 반환합니다. - * - * @param page 페이지 번호 - * @param size 페이지 당 길이 - */ - OverdueUserCabinetPaginationDto getOverdueUserList(Integer page, Integer size); - - /** - * 동아리 유저 리스트DTO를 반환합니다. - * - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return - */ - ClubUserListDto findAllClubUser(Integer page, Integer size); - - - /** - * 동아리 유저를 삭제합니다. - * - * @param clubId 동아리 고유 아이디 - */ - void deleteClubUser(Long clubId); - - void updateClubUser(Long clubId, String clubName); - - LentExtensionPaginationDto getAllLentExtension(Integer page, Integer size); - - LentExtensionPaginationDto getAllActiveLentExtension(Integer page, Integer size); - - LentExtensionPaginationDto getMyLentExtension(UserSessionDto userSessionDto); - - LentExtensionPaginationDto getMyActiveLentExtensionPage(UserSessionDto userSessionDto); - - void useLentExtension(UserSessionDto userSessionDto); + /** + * 현재 로그인한 유저의 프로필을 반환합니다. 대여한 사물함 아이디 정보가 포합됩니다. + * + * @param user 로그인한 유저의 정보 + * @return {@link MyProfileResponseDto} 현재 로그인한 유저의 정보 + */ + MyProfileResponseDto getMyProfile(UserSessionDto user); + + /** + * 모든 정지 유저를 반환합니다. + * + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @param now 현재 시간 + * @return {@link BlockedUserPaginationDto} 모든 정지 유저 + */ + /* 기존 searchByBanUser와 동일한 역할을 합니다. */ + BlockedUserPaginationDto getAllBanUsers(Integer page, Integer size, LocalDateTime now); + + /** + * 유저 이름의 일부를 입력받아 해당하는 유저들의 프로필을 받아옵니다. + * + * @param name 유저 이름의 일부 + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @return {@link UserProfilePaginationDto} 해당하는 유저들의 프로필 + */ + /*기존 searchByIntraId 메서드와 동일한 역할을 합니다.*/ + UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, + Integer size); + + /** + * 유저 이름의 일부를 입력받아 해당 유저들의 캐비넷 정보를 반환합니다. + * + * @param name 유저 이름의 일부 + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @return {@link UserCabinetPaginationDto} 해당하는 유저들의 캐비넷 정보 + */ + UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, + Integer size); + + /** + * 모든 유저의 정보를 가져옵니다. + * + * @return 모든 유저의 정보를 가져옵니다. + */ + List getAllUsers(); + + /** + * 유저가 존재하는지 확인합니다. + * + * @param name 유저 이름 + * @return 유저가 존재하면 true, 아니면 false + */ + boolean checkUserExists(String name); + + /** + * 유저를 생성합니다. + * + * @param name 유저 이름 + * @param email 유저 이메일 + * @param blackholedAt 유저 블랙홀 날짜 + * @param role 유저 역할 + */ + void createUser(String name, String email, LocalDateTime blackholedAt, UserRole role); + + /** + * @param clubName 동아리 유저 이름 + */ + void createClubUser(String clubName); + + /** + * 관리자가 존재하는지 확인합니다. + * + * @param email 관리자 이메일 + * @return 관리자가 존재하면 true, 아니면 false + */ + boolean checkAdminUserExists(String email); + + /** + * 관리자를 생성합니다. + * + * @param email 관리자 이메일 + */ + void createAdminUser(String email); + + /** + * 유저를 삭제합니다. + * + * @param userId 유저 고유 아이디 + * @param deletedAt 유저 삭제 날짜 + */ + void deleteUser(Long userId, LocalDateTime deletedAt); + + /** + * 관리자를 삭제합니다. + * + * @param adminUserId 관리자 고유 아이디 + */ + void deleteAdminUser(Long adminUserId); + + /** + * 유저의 권한을 변경합니다. + * + * @param adminUserId 관리자 고유 아이디 + * @param role 관리자 권한 + */ + void updateAdminUserRole(Long adminUserId, AdminRole role); + + /** + * 유저를 어드민으로 승격시킵니다. + * + * @param email 유저 이메일 + */ + void promoteUserToAdmin(String email); + + /** + * 유저의 블랙홀 시간을 변경합니다. + * + * @param userId 유저 고유 아이디 + * @param newBlackholedAt 새로운 유저 블랙홀 시간 + */ + void updateUserBlackholedAt(Long userId, LocalDateTime newBlackholedAt); + + /** + * 유저를 정지시킵니다. + * + * @param userId 유저 고유 아이디 + * @param lentType 현재 대여 타입 + * @param startedAt 대여 시작 날짜 + * @param endedAt 대여 종료 날짜 + * @param expiredAt 대여 만료 날짜 + */ + void banUser(Long userId, LentType lentType, LocalDateTime startedAt, LocalDateTime endedAt, + LocalDateTime expiredAt); + + /** + * 유저의 정지를 해제합니다. + * + * @param userId 유저 고유 아이디 + * @param today 현재 날짜 + */ + void deleteRecentBanHistory(Long userId, LocalDateTime today); + + /** + * 연체 중인 유저 리스트를 반환합니다. + * + * @param page 페이지 번호 + * @param size 페이지 당 길이 + */ + OverdueUserCabinetPaginationDto getOverdueUserList(Integer page, Integer size); + + /** + * 동아리 유저 리스트DTO를 반환합니다. + * + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @return + */ + ClubUserListDto findAllClubUser(Integer page, Integer size); + + + /** + * 동아리 유저를 삭제합니다. + * + * @param clubId 동아리 고유 아이디 + */ + void deleteClubUser(Long clubId); + + void updateClubUser(Long clubId, String clubName); + + LentExtensionPaginationDto getAllLentExtension(Integer page, Integer size); + + LentExtensionPaginationDto getAllActiveLentExtension(Integer page, Integer size); + + LentExtensionPaginationDto getMyLentExtension(UserSessionDto userSessionDto); + + LentExtensionPaginationDto getMyActiveLentExtensionPage(UserSessionDto userSessionDto); + + void useLentExtension(UserSessionDto userSessionDto); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java index 50f9442f0..53366f56d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; @@ -72,7 +73,7 @@ public BlockedUserPaginationDto getAllBanUsers(Integer page, Integer size, Local @Override public UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, - Integer size) { + Integer size) { log.debug("Called getUserProfileListByPartialName: {}", name); // todo - size가 0일 때 모든 데이터를 가져오기 if (size <= 0) { @@ -88,7 +89,7 @@ public UserProfilePaginationDto getUserProfileListByPartialName(String name, Int @Override public UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, - Integer size) { + Integer size) { log.debug("Called findUserCabinetListByPartialName: {}", name); // todo - size가 0일 때 모든 데이터를 가져오기 if (size <= 0) { @@ -178,8 +179,8 @@ public void updateUserBlackholedAt(Long userId, LocalDateTime newBlackholedAt) { @Override public void banUser(Long userId, LentType lentType, LocalDateTime startedAt, - LocalDateTime endedAt, - LocalDateTime expiredAt) { + LocalDateTime endedAt, + LocalDateTime expiredAt) { log.debug("Called banUser: {}", userId); userService.banUser(userId, lentType, startedAt, endedAt, expiredAt); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java index 4d688158b..c8e354e4b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java @@ -4,8 +4,7 @@ import java.util.List; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.dto.UserBlackholeInfoDto; -import org.ftclub.cabinet.occupiedtime.UserMonthDataDto; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.UserRole; public interface UserService { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java index c8051cb7f..3356d7309 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java @@ -17,14 +17,14 @@ import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.BanPolicy; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.user.repository.AdminUserRepository; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.ftclub.cabinet.user.repository.LentExtensionRepository; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java index 54b9b16e2..54d8aaffb 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java @@ -5,7 +5,7 @@ import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.config.MasterProperties; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.service.UserService; import org.ftclub.cabinet.utils.DateUtil; import org.ftclub.testutils.TestUtils; @@ -31,7 +31,8 @@ @ExtendWith(MockitoExtension.class) public class TokenValidatorUnitTest { - static final Key mockSigningKey = TestUtils.getSigningKey("SECRET_KEY_MUST_BE_VERYVERYVERYVERYVERYVERYVERYVERYVERY_LONG"); + static final Key mockSigningKey = TestUtils.getSigningKey( + "SECRET_KEY_MUST_BE_VERYVERYVERYVERYVERYVERYVERYVERYVERY_LONG"); @Spy @InjectMocks TokenValidator tokenValidator; @@ -54,7 +55,8 @@ void setup() { @Test @DisplayName("성공: 유효한 토큰 - 유저인 경우") void 성공_isValidRequestWithLevel() throws JsonProcessingException { - String userToken = TestUtils.getTestUserTokenByName(mockSigningKey, LocalDateTime.now(), DateUtil.getInfinityDate(), + String userToken = TestUtils.getTestUserTokenByName(mockSigningKey, LocalDateTime.now(), + DateUtil.getInfinityDate(), "name", "domainname.com"); request.addHeader("Authorization", "Bearer " + userToken); given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); @@ -104,7 +106,8 @@ void setup() { @DisplayName("실패: 유효하지 않은 토큰인 경우") void 실패_isValidRequestWithLevel() throws JsonProcessingException { request.addHeader("Authorization", "Bearer " + "token"); - given(tokenValidator.isTokenValid("token", jwtProperties.getSigningKey())).willReturn(false); + given(tokenValidator.isTokenValid("token", jwtProperties.getSigningKey())).willReturn( + false); assertFalse(tokenValidator.isValidRequestWithLevel(request, AuthLevel.ADMIN_ONLY)); assertFalse(tokenValidator.isValidRequestWithLevel(request, AuthLevel.USER_OR_ADMIN)); @@ -135,7 +138,8 @@ void setup() { @DisplayName("성공: 만료기한과 시그니처가 정상인 경우") void 성공_isTokenValid() { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); - String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), LocalDateTime.now(), DateUtil.getInfinityDate(), "name", "domainname.com"); + String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), + LocalDateTime.now(), DateUtil.getInfinityDate(), "name", "domainname.com"); assertTrue(tokenValidator.isTokenValid(token, jwtProperties.getSigningKey())); } @@ -143,10 +147,12 @@ void setup() { @Test @DisplayName("실패: 시그니처가 잘못된 경우") void 실패_isTokenValid() { - Key wrongKey = TestUtils.getSigningKey("WRONG_KEY_IS_MUST_BE_VERYVERYVERYVERYVERYVERY_LONG_TOO"); + Key wrongKey = TestUtils.getSigningKey( + "WRONG_KEY_IS_MUST_BE_VERYVERYVERYVERYVERYVERY_LONG_TOO"); Key rightKey = mockSigningKey; given(jwtProperties.getSigningKey()).willReturn(rightKey); - String wrongKeyToken = TestUtils.getTestUserTokenByName(wrongKey, LocalDateTime.now(), DateUtil.getInfinityDate(), "name", "domainname.com"); + String wrongKeyToken = TestUtils.getTestUserTokenByName(wrongKey, LocalDateTime.now(), + DateUtil.getInfinityDate(), "name", "domainname.com"); assertFalse(tokenValidator.isTokenValid(wrongKeyToken, jwtProperties.getSigningKey())); } @@ -156,7 +162,8 @@ void setup() { void 실패_isTokenValid2() { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); String expiredToken = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), - LocalDateTime.now().plusDays(-1000), DateUtil.getInfinityDate(), "name", "domainname.com"); + LocalDateTime.now().plusDays(-1000), DateUtil.getInfinityDate(), "name", + "domainname.com"); assertFalse(tokenValidator.isTokenValid(expiredToken, jwtProperties.getSigningKey())); } @@ -165,7 +172,8 @@ void setup() { @DisplayName("성공: 토큰의 페이로드를 JSON으로 변환") void 성공_getPayloadJson() throws JsonProcessingException { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); - String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), LocalDateTime.now(), DateUtil.getInfinityDate(), + String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), + LocalDateTime.now(), DateUtil.getInfinityDate(), "sanan", "domainname.com"); JsonNode payloadJson = tokenValidator.getPayloadJson(token); @@ -176,7 +184,8 @@ void setup() { @DisplayName("실패: 페이로드에 찾는 키가 없을 때") void 실패_getPayloadJson() throws JsonProcessingException { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); - String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), LocalDateTime.now(), DateUtil.getInfinityDate(), + String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), + LocalDateTime.now(), DateUtil.getInfinityDate(), "sanan", "domainname.com"); JsonNode payloadJson = tokenValidator.getPayloadJson(token); @@ -190,7 +199,8 @@ void setup() { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); given(masterProperties.getDomain()).willReturn("master.domain.com"); given(domainProperties.getAdminEmailDomain()).willReturn("admin.domain.com"); - String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), LocalDateTime.now(), DateUtil.getInfinityDate(), + String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), + LocalDateTime.now(), DateUtil.getInfinityDate(), "sanan", "domainname.com"); assertTrue(tokenValidator.isTokenAuthenticatable(token, AuthLevel.USER_ONLY)); @@ -205,7 +215,8 @@ void setup() { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); given(masterProperties.getDomain()).willReturn("master.domain.com"); given(domainProperties.getAdminEmailDomain()).willReturn("admin.domain.com"); - String adminToken = TestUtils.getTestAdminToken(jwtProperties.getSigningKey(), LocalDateTime.now(), "sanan", "admin.domain.com"); + String adminToken = TestUtils.getTestAdminToken(jwtProperties.getSigningKey(), + LocalDateTime.now(), "sanan", "admin.domain.com"); assertTrue(tokenValidator.isTokenAuthenticatable(adminToken, AuthLevel.ADMIN_ONLY)); assertTrue(tokenValidator.isTokenAuthenticatable(adminToken, AuthLevel.USER_OR_ADMIN)); @@ -220,7 +231,8 @@ void setup() { given(masterProperties.getDomain()).willReturn("master.domain.com"); given(domainProperties.getAdminEmailDomain()).willReturn("admin.domain.com"); given(userService.getAdminUserRole("sanan@master.domain.com")).willReturn(AdminRole.MASTER); - String masterToken = TestUtils.getTestMasterToken(jwtProperties.getSigningKey(), LocalDateTime.now(), "sanan", "master.domain.com"); + String masterToken = TestUtils.getTestMasterToken(jwtProperties.getSigningKey(), + LocalDateTime.now(), "sanan", "master.domain.com"); assertTrue(tokenValidator.isTokenAuthenticatable(masterToken, AuthLevel.ADMIN_ONLY)); assertTrue(tokenValidator.isTokenAuthenticatable(masterToken, AuthLevel.USER_OR_ADMIN)); diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java index 6b633c424..979d23feb 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java @@ -2,7 +2,8 @@ import static org.mockito.Mockito.mock; -import org.ftclub.cabinet.statistics.service.StatisticsFacadeService; +import org.ftclub.cabinet.admin.controller.StatisticsController; +import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -23,6 +24,6 @@ public class StatisticsControllerTest { @Test @DisplayName("모든층의 사물함 통계 가져오기") public void 모든층의_사물함_통계_가져오기() { - + } } diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java index a8de4ae5f..90b8a9ec9 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java @@ -10,13 +10,14 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import org.ftclub.cabinet.admin.service.StatisticsFacadeServiceImpl; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.statistics.repository.StatisticsRepository; +import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminUserTest.java b/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminUserTest.java index ea6ab4aa7..b4052ed3b 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminUserTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminUserTest.java @@ -3,6 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.exception.DomainException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminUserRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminUserRepositoryTest.java index fc1102d9a..41de229a0 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminUserRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminUserRepositoryTest.java @@ -6,8 +6,9 @@ import java.util.Optional; import javax.transaction.Transactional; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; @@ -54,7 +55,8 @@ public void setUp() { @Test @DisplayName("어드민 이메일로 어드민 유저 찾기 성공 - 어드민이 존재하는 경우") void findAdminUserByEmail_성공_어드민_유저가_존재하는_경우() { - Optional adminUser = adminUserRepository.findAdminUserByEmail("adminTest@gmail.com"); + Optional adminUser = adminUserRepository.findAdminUserByEmail( + "adminTest@gmail.com"); assertTrue(adminUser.isPresent()); assertEquals(adminUserId, adminUser.get().getAdminUserId()); @@ -73,7 +75,8 @@ public void setUp() { @Test @DisplayName("유저의 이메일로 어드민 유저의 권한 찾기 성공 - 어드민이 존재하는 경우") void findAdminUserRoleByEmail_성공_어드민이_존재하는_경우() { - Optional adminRole = adminUserRepository.findAdminUserRoleByEmail("admin1@gmail.com"); + Optional adminRole = adminUserRepository.findAdminUserRoleByEmail( + "admin1@gmail.com"); assertTrue(adminRole.isPresent()); assertEquals(AdminRole.ADMIN, adminRole.get()); @@ -82,7 +85,8 @@ public void setUp() { @Test @DisplayName("유저의 이메일로 어드민 유저의 권한 찾기 실패 - 어드민이 존재하지 않는 경우") public void findAdminUserRoleByEmail_실패_어드민이_존재하지_않는_경우() { - Optional adminRole = adminUserRepository.findAdminUserRoleByEmail("test@gmail.com"); + Optional adminRole = adminUserRepository.findAdminUserRoleByEmail( + "test@gmail.com"); assertFalse(adminRole.isPresent()); } diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java index 7cc67adee..05a60d3b7 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.user.service; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; @@ -137,7 +138,8 @@ public class UserFacadeServiceTest { LentExtension lentExtension = lentExtensionOptionalFetcher.findAllByUserId( userSessionDto.getUserId()).get(0); - given(userMapper.toMyProfileResponseDto(userSessionDto, null, banHistory1, null)).willReturn( + given(userMapper.toMyProfileResponseDto(userSessionDto, null, banHistory1, + null)).willReturn( new MyProfileResponseDto(2L, "testUser2", null, testDate.plusDays(1), null)); // when diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java index e4c64c429..e10091d5e 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java @@ -6,12 +6,12 @@ import java.time.LocalDateTime; import javax.transaction.Transactional; import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.user.repository.AdminUserRepository; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; import org.junit.jupiter.api.Disabled; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java index efec2404a..5480e1183 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java @@ -22,14 +22,14 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.BanPolicy; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.user.repository.AdminUserRepository; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; import org.ftclub.cabinet.user.repository.UserRepository; diff --git a/backend/src/test/java/org/ftclub/testutils/TestUtils.java b/backend/src/test/java/org/ftclub/testutils/TestUtils.java index 7140a56f6..f7e328804 100644 --- a/backend/src/test/java/org/ftclub/testutils/TestUtils.java +++ b/backend/src/test/java/org/ftclub/testutils/TestUtils.java @@ -6,7 +6,7 @@ import io.jsonwebtoken.SignatureAlgorithm; import java.util.ArrayList; import java.util.List; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.utils.DateUtil; import org.springframework.http.HttpHeaders; @@ -25,7 +25,8 @@ public class TestUtils { - public static String getTestAdminToken(Key signingKey, LocalDateTime now, String emailName, String emailDomain) { + public static String getTestAdminToken(Key signingKey, LocalDateTime now, String emailName, + String emailDomain) { Map claim = new HashMap<>(); claim.put("email", emailName + "@" + emailDomain); claim.put("role", AdminRole.ADMIN); @@ -36,7 +37,8 @@ public static String getTestAdminToken(Key signingKey, LocalDateTime now, String .compact(); } - public static String getTestMasterToken(Key signingKey, LocalDateTime now, String emailName, String emailDomain) { + public static String getTestMasterToken(Key signingKey, LocalDateTime now, String emailName, + String emailDomain) { Map claim = new HashMap<>(); claim.put("email", emailName + "@" + emailDomain); claim.put("role", AdminRole.MASTER); @@ -47,7 +49,8 @@ public static String getTestMasterToken(Key signingKey, LocalDateTime now, Strin .compact(); } - public static String getTestUserTokenByName(Key signingKey, LocalDateTime now, LocalDateTime blackholedAt, String name, String emailDomain) { + public static String getTestUserTokenByName(Key signingKey, LocalDateTime now, + LocalDateTime blackholedAt, String name, String emailDomain) { Map claim = new HashMap<>(); claim.put("name", name); claim.put("email", name + "@" + emailDomain); @@ -78,7 +81,7 @@ public static Key getSigningKey(String secretKey) { } public static MockHttpServletRequestBuilder mockRequest(HttpMethod method, Cookie cookie, - String url, Object... uriVars) { + String url, Object... uriVars) { if (method.equals(HttpMethod.GET)) { return MockMvcRequestBuilders.get(url, uriVars) .cookie(cookie) From b0d205b904aad90f8f883abf77e39d7ab2a078a6 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 13:29:38 +0900 Subject: [PATCH 0102/1029] =?UTF-8?q?[BE]=20admin=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20java=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=8F=99=20=EB=88=84=EB=9D=BD=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{cabinet => admin}/controller/AdminCabinetController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename backend/src/main/java/org/ftclub/cabinet/{cabinet => admin}/controller/AdminCabinetController.java (99%) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java index 5adb31b22..f1b6b5171 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.cabinet.controller; +package org.ftclub.cabinet.admin.controller; import java.util.HashMap; import java.util.Map; From 9a9641f734f49169d798d848010781f890f27665 Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 21 Dec 2023 14:04:43 +0900 Subject: [PATCH 0103/1029] =?UTF-8?q?[FE]=20FIX:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=EC=97=90=20=EC=A0=80=EC=9E=A5/=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EB=B2=84=ED=8A=BC=EC=9D=B4=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=EC=97=90=20=EC=98=81=ED=96=A5=EC=9D=84=20?= =?UTF-8?q?=EC=A3=BC=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95;=20=EC=95=8C=EB=A6=BC=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=ED=95=98=EB=8B=A8=20=EC=97=AC=EB=B0=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/NotificationCard/NotificationCard.container.tsx | 9 ++++++++- .../Card/NotificationCard/NotificationCard.tsx | 2 +- frontend/src/pages/ProfilePage.tsx | 6 +++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx index 7dc12d8ed..08fc45347 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx @@ -83,7 +83,14 @@ const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { isClickable: true, }, ] - : [] + : [ + { + label: "-", + isClickable: false, + color: "var(--lightgray-color)", + backgroundColor: "var(--lightgray-color)", + }, + ] } onToggleChange={handleToggleChange} /> diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.tsx b/frontend/src/components/Card/NotificationCard/NotificationCard.tsx index c97789ddc..0f71533f1 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.tsx +++ b/frontend/src/components/Card/NotificationCard/NotificationCard.tsx @@ -28,7 +28,7 @@ const NotificationCard = ({ title={"알림"} gridArea="notification" width={"350px"} - height={"215px"} + height={"230px"} buttons={buttons} > diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index e72322ea6..c00a39fd5 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -72,14 +72,14 @@ const CardGridWrapper = styled.div` width: 100%; grid-gap: 20px; grid-template-columns: 350px 350px; - grid-template-rows: 163px 183px 215px; + grid-template-rows: 163px 183px 230px; grid-template-areas: "profile lentInfo" // h: 163px h: 366px "extension lentInfo" // h: 183px - "theme notification"; // h: 215px h: 215px + "theme notification"; // h: 230px h: 230px; @media (max-width: 768px) { grid-template-columns: 350px; - grid-template-rows: 163px 366px 183px 215px 215px; + grid-template-rows: 163px 366px 183px 230px 230px; grid-template-areas: "profile" "lentInfo" From ad999581d0de02407cf7ef5483a7436cd84b80d7 Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 21 Dec 2023 14:06:05 +0900 Subject: [PATCH 0104/1029] =?UTF-8?q?[FE]=20FIX:=20=ED=85=8C=EB=A7=88?= =?UTF-8?q?=EC=BB=AC=EB=9F=AC=20=EC=B9=B4=EB=93=9C=20=EB=86=92=EC=9D=B4=20?= =?UTF-8?q?=EC=95=8C=EB=9E=8C=20=EC=B9=B4=EB=93=9C=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx index 6c4b5f07a..85d8a390a 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -47,7 +47,7 @@ const ThemeColorCard = ({ title={"테마 컬러"} gridArea={"theme"} width={"350px"} - height={"215px"} + height={"230px"} buttons={ showColorPicker ? [ @@ -109,6 +109,7 @@ const BackgroundOverlayStyled = styled.div` const ThemeColorCardWrapper = styled.div` z-index: 1; + align-self: start; `; const MainColorButtonStyled = styled.button` From 80a6ad4a7d60bc15f4b8f82d8bb36a42af17ae95 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 21 Dec 2023 17:33:06 +0900 Subject: [PATCH 0105/1029] =?UTF-8?q?[BE]=20FIX:=20AlarmOptOut=20=EB=A0=88?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EA=B0=80=20=EC=97=86=EB=8A=94=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=97=90=EC=84=9C=20User=20=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/user/repository/UserRepository.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index 76e587064..371da349f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -56,14 +56,14 @@ public interface UserRepository extends JpaRepository { */ Page findAllByRoleAndDeletedAtIsNull(@Param("role") UserRole role, Pageable pageable); - /** + /** * 블랙홀에 빠질 위험이 있는 유저들의 정보를 조회합니다. blackholedAt이 현재 시간보다 과거인 유저들을 블랙홀에 빠질 위험이 있는 유저로 판단합니다. * * @return {@link User} 리스트 */ @Query("SELECT u FROM User u WHERE u.blackholedAt IS NOT NULL OR u.blackholedAt <= CURRENT_TIMESTAMP") List findByRiskOfFallingIntoBlackholeUsers(); - + /** * 블랙홀에 빠질 위험이 없는 유저들의 정보를 조회합니다. blackholedAt이 null이거나 현재 시간보다 미래인 유저들을 블랙홀에 빠질 위험이 없는 유저로 * 판단합니다. @@ -77,7 +77,7 @@ public interface UserRepository extends JpaRepository { * 유저의 id로 OptOut 테이블과 결합된 유저 정보를 찾습니다. */ @Query("SELECT u FROM User u " - + "JOIN AlarmOptOut o ON u.userId = o.user.userId " + + "LEFT JOIN AlarmOptOut o ON u.userId = o.user.userId " + "WHERE u.userId = :id") Optional findUserWithOptOutById(@Param("id") Long id); From c8112dd26e6ece650e8709a2459cdddb7026e83e Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 21 Dec 2023 17:40:44 +0900 Subject: [PATCH 0106/1029] [BE] REFACTOR: AlarmOptOut -> AlarmOptIn --- .../alarm/handler/AlarmEventHandler.java | 14 +++++----- .../repository/AlarmOptInRepository.java | 23 ++++++++++++++++ .../repository/AlarmOptOutRepository.java | 13 ---------- .../{AlarmOptOut.java => AlarmOptIn.java} | 15 ++++++----- .../org/ftclub/cabinet/user/domain/User.java | 26 +++++++++---------- .../user/repository/UserRepository.java | 6 ++--- 6 files changed, 54 insertions(+), 43 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptOutRepository.java rename backend/src/main/java/org/ftclub/cabinet/user/domain/{AlarmOptOut.java => AlarmOptIn.java} (81%) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index 1b65b6cfe..fdf28e64d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -13,7 +13,7 @@ import org.ftclub.cabinet.alarm.domain.AlarmType; import org.ftclub.cabinet.alarm.domain.TransactionalAlarmEvent; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.user.domain.AlarmOptOut; +import org.ftclub.cabinet.user.domain.AlarmOptIn; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.context.event.EventListener; @@ -47,18 +47,18 @@ public void handleAlarmEvent(AlarmEvent alarmEvent) { } private void eventProceed(AlarmEvent alarmEvent) { - User receiver = userRepository.findUserWithOptOutById(alarmEvent.getReceiverId()) + User receiver = userRepository.findUserWithOptInById(alarmEvent.getReceiverId()) .orElseThrow(() -> new ServiceException(NOT_FOUND_USER)); - Set alarmOptOuts = receiver.getAlarmOptOuts() - .stream().map(AlarmOptOut::getAlarmType).collect(Collectors.toSet()); + Set alarmOptIns = receiver.getAlarmOptIns() + .stream().map(AlarmOptIn::getAlarmType).collect(Collectors.toSet()); - if (alarmOptOuts.contains(SLACK)) { + if (alarmOptIns.contains(SLACK)) { slackAlarmSender.send(receiver, alarmEvent); } - if (alarmOptOuts.contains(EMAIL)) { + if (alarmOptIns.contains(EMAIL)) { emailAlarmSender.send(receiver, alarmEvent); } - if (alarmOptOuts.contains(PUSH)) { + if (alarmOptIns.contains(PUSH)) { pushAlarmSender.send(receiver, alarmEvent); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java new file mode 100644 index 000000000..8a98f4443 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java @@ -0,0 +1,23 @@ +package org.ftclub.cabinet.alarm.repository; + +import io.lettuce.core.dynamic.annotation.Param; +import java.util.List; +import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.user.domain.AlarmOptIn; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface AlarmOptInRepository extends JpaRepository { + + @Query("SELECT ao FROM AlarmOptIn ao WHERE ao.user.userId = :userId") + List findAllByUserId(Long userId); + + @Modifying + @Query("DELETE FROM AlarmOptIn ao WHERE ao.user.userId = :userId AND ao.alarmType = :alarmType") + void deleteAlarmOptInByUserAndAlarmType(@Param("userId") Long userId, + @Param("alarmType") AlarmType alarmType); + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptOutRepository.java b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptOutRepository.java deleted file mode 100644 index 9b392240c..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptOutRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.ftclub.cabinet.alarm.repository; - -import java.util.List; -import org.ftclub.cabinet.user.domain.AlarmOptOut; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -public interface AlarmOptOutRepository extends JpaRepository { - - @Query("SELECT ao FROM AlarmOptOut ao WHERE ao.user.userId = :userId") - List findAllByUserId(Long userId); - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptIn.java similarity index 81% rename from backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java rename to backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptIn.java index d6bd40e24..bc2cc222f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptOut.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptIn.java @@ -23,11 +23,11 @@ * 유저의 알람 수신 거부 정보 엔티티입니다. */ @Entity -@Table(name = "ALARM_OPT_OUT") +@Table(name = "ALARM_OPT_IN") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @ToString(exclude = {"user"}) -public class AlarmOptOut { +public class AlarmOptIn { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -42,19 +42,20 @@ public class AlarmOptOut { @JoinColumn(name = "USER_ID", nullable = false) private User user; - private AlarmOptOut(User user, AlarmType alarmType) { + private AlarmOptIn(User user, AlarmType alarmType) { this.user = user; this.alarmType = alarmType; } - public static AlarmOptOut of(User user, AlarmType alarmType) { - AlarmOptOut alarmOptOut = new AlarmOptOut(user, alarmType); - ExceptionUtil.throwIfFalse(alarmOptOut.isValid(), + public static AlarmOptIn of(User user, AlarmType alarmType) { + AlarmOptIn alarmOptIn = new AlarmOptIn(user, alarmType); + ExceptionUtil.throwIfFalse(alarmOptIn.isValid(), new DomainException(ExceptionStatus.INVALID_ARGUMENT)); - return alarmOptOut; + return alarmOptIn; } private boolean isValid() { return user != null && alarmType != null; } + } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index 527d673a4..a88b203e0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -30,22 +30,22 @@ @Table(name = "USER") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -@ToString (exclude = {"alarmOptOuts"}) +@ToString(exclude = {"alarmOptIns"}) @Log4j2 public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "USER_ID") - private Long userId; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "USER_ID") + private Long userId; - @NotNull - @Column(name = "NAME", length = 32, unique = true, nullable = false) - private String name; + @NotNull + @Column(name = "NAME", length = 32, unique = true, nullable = false) + private String name; - @Email - @Column(name = "EMAIL", unique = true) - private String email; + @Email + @Column(name = "EMAIL", unique = true) + private String email; @Column(name = "BLACKHOLED_AT") private LocalDateTime blackholedAt = null; @@ -58,7 +58,7 @@ public class User { private UserRole role; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private Set alarmOptOuts = new HashSet<>(); + private final Set alarmOptIns = new HashSet<>(); protected User(String name, String email, LocalDateTime blackholedAt, UserRole userRole) { this.name = name; @@ -68,7 +68,7 @@ protected User(String name, String email, LocalDateTime blackholedAt, UserRole u } public static User of(String name, String email, LocalDateTime blackholedAt, - UserRole userRole) { + UserRole userRole) { User user = new User(name, email, blackholedAt, userRole); ExceptionUtil.throwIfFalse(user.isValid(), new DomainException(ExceptionStatus.INVALID_ARGUMENT)); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index 371da349f..0f6a06be4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -74,12 +74,12 @@ public interface UserRepository extends JpaRepository { List findByNoRiskOfFallingIntoBlackholeUsers(); /** - * 유저의 id로 OptOut 테이블과 결합된 유저 정보를 찾습니다. + * 유저의 id로 OptIn 테이블과 결합된 유저 정보를 찾습니다. */ @Query("SELECT u FROM User u " - + "LEFT JOIN AlarmOptOut o ON u.userId = o.user.userId " + + "LEFT JOIN AlarmOptIn o ON u.userId = o.user.userId " + "WHERE u.userId = :id") - Optional findUserWithOptOutById(@Param("id") Long id); + Optional findUserWithOptInById(@Param("id") Long id); /** * 현재 Active 상태의 Cabi User를 모두 가져옵니다. From 961c08214a0e8a17622ad0ecf59993f6bbc00405 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 21 Dec 2023 17:41:46 +0900 Subject: [PATCH 0107/1029] =?UTF-8?q?[BE]=20FEAT:=20User=EC=9D=98=20?= =?UTF-8?q?=EC=95=8C=EB=9E=8C=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/dto/UpdateAlarmRequestDto.java | 25 ++ .../user/controller/UserController.java | 19 +- .../user/service/UserFacadeService.java | 365 +++++++++--------- .../user/service/UserFacadeServiceImpl.java | 43 ++- .../cabinet/user/service/UserService.java | 11 +- .../cabinet/user/service/UserServiceImpl.java | 6 + 6 files changed, 274 insertions(+), 195 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/UpdateAlarmRequestDto.java diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/UpdateAlarmRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/UpdateAlarmRequestDto.java new file mode 100644 index 000000000..cce110132 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/UpdateAlarmRequestDto.java @@ -0,0 +1,25 @@ +package org.ftclub.cabinet.dto; + +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; +import org.ftclub.cabinet.alarm.domain.AlarmType; + +@ToString +@Getter +@AllArgsConstructor +public class UpdateAlarmRequestDto { + + private final boolean slack; + private final boolean email; + private final boolean push; + + public Map getAlarmTypeStatus() { + return Map.of( + AlarmType.SLACK, slack, + AlarmType.EMAIL, email, + AlarmType.PUSH, push + ); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java index a7f3aa601..0ea765011 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java @@ -6,10 +6,13 @@ import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.MyProfileResponseDto; +import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.user.domain.UserSession; import org.ftclub.cabinet.user.service.UserFacadeService; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -78,12 +81,12 @@ public void useLentExtension( userFacadeService.useLentExtension(userSessionDto); } -// @PutMapping("/me/alarms") -// @AuthGuard(level = AuthLevel.USER_ONLY) -// public void updateMyProfile( -// @UserSession UserSessionDto userSessionDto, -// @RequestBody UpdateAlarmRequestDto updateAlarmRequestDto) { -// log.info("Called updateMyProfile"); -// userFacadeService.updateMyProfile(userSessionDto); -// } + @PutMapping("/me/alarms") + @AuthGuard(level = AuthLevel.USER_ONLY) + public void updateMyProfile( + @UserSession UserSessionDto userSessionDto, + @RequestBody UpdateAlarmRequestDto updateAlarmRequestDto) { + log.info("Called updateMyProfile"); + userFacadeService.updateAlarmState(userSessionDto, updateAlarmRequestDto); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 55bad8521..6e80a9320 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -8,6 +8,7 @@ import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.MyProfileResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.ftclub.cabinet.dto.UserSessionDto; @@ -17,185 +18,187 @@ public interface UserFacadeService { - /** - * 현재 로그인한 유저의 프로필을 반환합니다. 대여한 사물함 아이디 정보가 포합됩니다. - * - * @param user 로그인한 유저의 정보 - * @return {@link MyProfileResponseDto} 현재 로그인한 유저의 정보 - */ - MyProfileResponseDto getMyProfile(UserSessionDto user); - - /** - * 모든 정지 유저를 반환합니다. - * - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @param now 현재 시간 - * @return {@link BlockedUserPaginationDto} 모든 정지 유저 - */ - /* 기존 searchByBanUser와 동일한 역할을 합니다. */ - BlockedUserPaginationDto getAllBanUsers(Integer page, Integer size, LocalDateTime now); - - /** - * 유저 이름의 일부를 입력받아 해당하는 유저들의 프로필을 받아옵니다. - * - * @param name 유저 이름의 일부 - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return {@link UserProfilePaginationDto} 해당하는 유저들의 프로필 - */ - /*기존 searchByIntraId 메서드와 동일한 역할을 합니다.*/ - UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, - Integer size); - - /** - * 유저 이름의 일부를 입력받아 해당 유저들의 캐비넷 정보를 반환합니다. - * - * @param name 유저 이름의 일부 - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return {@link UserCabinetPaginationDto} 해당하는 유저들의 캐비넷 정보 - */ - UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, - Integer size); - - /** - * 모든 유저의 정보를 가져옵니다. - * - * @return 모든 유저의 정보를 가져옵니다. - */ - List getAllUsers(); - - /** - * 유저가 존재하는지 확인합니다. - * - * @param name 유저 이름 - * @return 유저가 존재하면 true, 아니면 false - */ - boolean checkUserExists(String name); - - /** - * 유저를 생성합니다. - * - * @param name 유저 이름 - * @param email 유저 이메일 - * @param blackholedAt 유저 블랙홀 날짜 - * @param role 유저 역할 - */ - void createUser(String name, String email, LocalDateTime blackholedAt, UserRole role); - - /** - * @param clubName 동아리 유저 이름 - */ - void createClubUser(String clubName); - - /** - * 관리자가 존재하는지 확인합니다. - * - * @param email 관리자 이메일 - * @return 관리자가 존재하면 true, 아니면 false - */ - boolean checkAdminUserExists(String email); - - /** - * 관리자를 생성합니다. - * - * @param email 관리자 이메일 - */ - void createAdminUser(String email); - - /** - * 유저를 삭제합니다. - * - * @param userId 유저 고유 아이디 - * @param deletedAt 유저 삭제 날짜 - */ - void deleteUser(Long userId, LocalDateTime deletedAt); - - /** - * 관리자를 삭제합니다. - * - * @param adminUserId 관리자 고유 아이디 - */ - void deleteAdminUser(Long adminUserId); - - /** - * 유저의 권한을 변경합니다. - * - * @param adminUserId 관리자 고유 아이디 - * @param role 관리자 권한 - */ - void updateAdminUserRole(Long adminUserId, AdminRole role); - - /** - * 유저를 어드민으로 승격시킵니다. - * - * @param email 유저 이메일 - */ - void promoteUserToAdmin(String email); - - /** - * 유저의 블랙홀 시간을 변경합니다. - * - * @param userId 유저 고유 아이디 - * @param newBlackholedAt 새로운 유저 블랙홀 시간 - */ - void updateUserBlackholedAt(Long userId, LocalDateTime newBlackholedAt); - - /** - * 유저를 정지시킵니다. - * - * @param userId 유저 고유 아이디 - * @param lentType 현재 대여 타입 - * @param startedAt 대여 시작 날짜 - * @param endedAt 대여 종료 날짜 - * @param expiredAt 대여 만료 날짜 - */ - void banUser(Long userId, LentType lentType, LocalDateTime startedAt, LocalDateTime endedAt, - LocalDateTime expiredAt); - - /** - * 유저의 정지를 해제합니다. - * - * @param userId 유저 고유 아이디 - * @param today 현재 날짜 - */ - void deleteRecentBanHistory(Long userId, LocalDateTime today); - - /** - * 연체 중인 유저 리스트를 반환합니다. - * - * @param page 페이지 번호 - * @param size 페이지 당 길이 - */ - OverdueUserCabinetPaginationDto getOverdueUserList(Integer page, Integer size); - - /** - * 동아리 유저 리스트DTO를 반환합니다. - * - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return - */ - ClubUserListDto findAllClubUser(Integer page, Integer size); - - - /** - * 동아리 유저를 삭제합니다. - * - * @param clubId 동아리 고유 아이디 - */ - void deleteClubUser(Long clubId); - - void updateClubUser(Long clubId, String clubName); - - LentExtensionPaginationDto getAllLentExtension(Integer page, Integer size); - - LentExtensionPaginationDto getAllActiveLentExtension(Integer page, Integer size); - - LentExtensionPaginationDto getMyLentExtension(UserSessionDto userSessionDto); - - LentExtensionPaginationDto getMyActiveLentExtensionPage(UserSessionDto userSessionDto); - - void useLentExtension(UserSessionDto userSessionDto); + /** + * 현재 로그인한 유저의 프로필을 반환합니다. 대여한 사물함 아이디 정보가 포합됩니다. + * + * @param user 로그인한 유저의 정보 + * @return {@link MyProfileResponseDto} 현재 로그인한 유저의 정보 + */ + MyProfileResponseDto getMyProfile(UserSessionDto user); + + /** + * 모든 정지 유저를 반환합니다. + * + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @param now 현재 시간 + * @return {@link BlockedUserPaginationDto} 모든 정지 유저 + */ + /* 기존 searchByBanUser와 동일한 역할을 합니다. */ + BlockedUserPaginationDto getAllBanUsers(Integer page, Integer size, LocalDateTime now); + + /** + * 유저 이름의 일부를 입력받아 해당하는 유저들의 프로필을 받아옵니다. + * + * @param name 유저 이름의 일부 + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @return {@link UserProfilePaginationDto} 해당하는 유저들의 프로필 + */ + /*기존 searchByIntraId 메서드와 동일한 역할을 합니다.*/ + UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, + Integer size); + + /** + * 유저 이름의 일부를 입력받아 해당 유저들의 캐비넷 정보를 반환합니다. + * + * @param name 유저 이름의 일부 + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @return {@link UserCabinetPaginationDto} 해당하는 유저들의 캐비넷 정보 + */ + UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, + Integer size); + + /** + * 모든 유저의 정보를 가져옵니다. + * + * @return 모든 유저의 정보를 가져옵니다. + */ + List getAllUsers(); + + /** + * 유저가 존재하는지 확인합니다. + * + * @param name 유저 이름 + * @return 유저가 존재하면 true, 아니면 false + */ + boolean checkUserExists(String name); + + /** + * 유저를 생성합니다. + * + * @param name 유저 이름 + * @param email 유저 이메일 + * @param blackholedAt 유저 블랙홀 날짜 + * @param role 유저 역할 + */ + void createUser(String name, String email, LocalDateTime blackholedAt, UserRole role); + + /** + * @param clubName 동아리 유저 이름 + */ + void createClubUser(String clubName); + + /** + * 관리자가 존재하는지 확인합니다. + * + * @param email 관리자 이메일 + * @return 관리자가 존재하면 true, 아니면 false + */ + boolean checkAdminUserExists(String email); + + /** + * 관리자를 생성합니다. + * + * @param email 관리자 이메일 + */ + void createAdminUser(String email); + + /** + * 유저를 삭제합니다. + * + * @param userId 유저 고유 아이디 + * @param deletedAt 유저 삭제 날짜 + */ + void deleteUser(Long userId, LocalDateTime deletedAt); + + /** + * 관리자를 삭제합니다. + * + * @param adminUserId 관리자 고유 아이디 + */ + void deleteAdminUser(Long adminUserId); + + /** + * 유저의 권한을 변경합니다. + * + * @param adminUserId 관리자 고유 아이디 + * @param role 관리자 권한 + */ + void updateAdminUserRole(Long adminUserId, AdminRole role); + + /** + * 유저를 어드민으로 승격시킵니다. + * + * @param email 유저 이메일 + */ + void promoteUserToAdmin(String email); + + /** + * 유저의 블랙홀 시간을 변경합니다. + * + * @param userId 유저 고유 아이디 + * @param newBlackholedAt 새로운 유저 블랙홀 시간 + */ + void updateUserBlackholedAt(Long userId, LocalDateTime newBlackholedAt); + + /** + * 유저를 정지시킵니다. + * + * @param userId 유저 고유 아이디 + * @param lentType 현재 대여 타입 + * @param startedAt 대여 시작 날짜 + * @param endedAt 대여 종료 날짜 + * @param expiredAt 대여 만료 날짜 + */ + void banUser(Long userId, LentType lentType, LocalDateTime startedAt, LocalDateTime endedAt, + LocalDateTime expiredAt); + + /** + * 유저의 정지를 해제합니다. + * + * @param userId 유저 고유 아이디 + * @param today 현재 날짜 + */ + void deleteRecentBanHistory(Long userId, LocalDateTime today); + + /** + * 연체 중인 유저 리스트를 반환합니다. + * + * @param page 페이지 번호 + * @param size 페이지 당 길이 + */ + OverdueUserCabinetPaginationDto getOverdueUserList(Integer page, Integer size); + + /** + * 동아리 유저 리스트DTO를 반환합니다. + * + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @return + */ + ClubUserListDto findAllClubUser(Integer page, Integer size); + + + /** + * 동아리 유저를 삭제합니다. + * + * @param clubId 동아리 고유 아이디 + */ + void deleteClubUser(Long clubId); + + void updateClubUser(Long clubId, String clubName); + + LentExtensionPaginationDto getAllLentExtension(Integer page, Integer size); + + LentExtensionPaginationDto getAllActiveLentExtension(Integer page, Integer size); + + LentExtensionPaginationDto getMyLentExtension(UserSessionDto userSessionDto); + + LentExtensionPaginationDto getMyActiveLentExtensionPage(UserSessionDto userSessionDto); + + void useLentExtension(UserSessionDto userSessionDto); + + void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java index 42fe5a60c..3e0f06f90 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java @@ -4,12 +4,16 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import java.util.stream.Collectors; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.domain.AlarmType; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; -import org.ftclub.cabinet.alarm.repository.AlarmOptOutRepository; +import org.ftclub.cabinet.alarm.repository.AlarmOptInRepository; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; @@ -21,6 +25,7 @@ import org.ftclub.cabinet.dto.MyProfileResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; import org.ftclub.cabinet.dto.UserBlockedInfoDto; import org.ftclub.cabinet.dto.UserCabinetDto; import org.ftclub.cabinet.dto.UserCabinetPaginationDto; @@ -31,7 +36,7 @@ import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AlarmOptOut; +import org.ftclub.cabinet.user.domain.AlarmOptIn; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.LentExtension; import org.ftclub.cabinet.user.domain.User; @@ -57,7 +62,7 @@ public class UserFacadeServiceImpl implements UserFacadeService { private final CabinetMapper cabinetMapper; private final LentExtensionService lentExtensionService; private final LentExtensionOptionalFetcher lentExtensionOptionalFetcher; - private final AlarmOptOutRepository alarmOptOutRepository; + private final AlarmOptInRepository alarmOptInRepository; @Override public MyProfileResponseDto getMyProfile(UserSessionDto user) { @@ -70,8 +75,8 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { LentExtensionResponseDto activeLentExtension = lentExtensionService.getActiveLentExtension( user); - List alarmOptOuts = alarmOptOutRepository.findAllByUserId(user.getUserId()); - List alarmTypes = alarmOptOuts.stream().map(AlarmOptOut::getAlarmType) + List alarmOptIns = alarmOptInRepository.findAllByUserId(user.getUserId()); + List alarmTypes = alarmOptIns.stream().map(AlarmOptIn::getAlarmType) .collect(Collectors.toList()); AlarmTypeResponseDto alarmTypeResponseDto = AlarmTypeResponseDto.builder() .alarmTypes(alarmTypes).build(); @@ -309,4 +314,32 @@ public void useLentExtension(UserSessionDto userSessionDto) { log.debug("Called useLentExtension"); lentExtensionService.useLentExtension(userSessionDto.getUserId(), userSessionDto.getName()); } + + @Transactional + @Override + public void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto) { + log.debug("Called updateAlarmState"); + + User findUser = userService.getUserWithAlarmOptIn(user.getUserId()); + Set alarmOptIns = findUser.getAlarmOptIns(); + List currentAlarmTypes = alarmOptIns.stream().map(AlarmOptIn::getAlarmType) + .collect(Collectors.toList()); + + Map alarmTypeStatus = dto.getAlarmTypeStatus(); + //alarmTypeStatus 의 key 값을 순회하며 true일경우 currentAlarmTypes에 없으면 추가, 있으면 아무일도 하지 않는다 + //false일 경우 currentAlarmTypes에 있으면 삭제, 없으면 아무일도 하지 않는다 + for (Entry entry : alarmTypeStatus.entrySet()) { + if (entry.getValue()) { + if (!currentAlarmTypes.contains(entry.getKey())) { + alarmOptInRepository.save(AlarmOptIn.of(findUser, entry.getKey())); + } + } else { + if (currentAlarmTypes.contains(entry.getKey())) { + alarmOptInRepository.deleteAlarmOptInByUserAndAlarmType(findUser.getUserId(), + entry.getKey()); + } + } + } + } + } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java index 4d688158b..9aa7490ab 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java @@ -4,8 +4,8 @@ import java.util.List; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.dto.UserBlackholeInfoDto; -import org.ftclub.cabinet.occupiedtime.UserMonthDataDto; import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; public interface UserService { @@ -68,4 +68,13 @@ void banUser(Long userId, LentType lentType, LocalDateTime startedAt, LocalDateT * @return {@link List} */ List getAllNoRiskOfBlackholeInfo(); + + /** + * userId로 유저와 AlarmOptIn 정보를 가져옵니다. + * + * @param userId + * @return + */ + User getUserWithAlarmOptIn(Long userId); + } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java index c62978d1d..5ad2abf5b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java @@ -219,4 +219,10 @@ public List getAllNoRiskOfBlackholeInfo() { user.getEmail(), user.getBlackholedAt())) .collect(Collectors.toList()); } + + @Override + public User getUserWithAlarmOptIn(Long userId) { + return userRepository.findUserWithOptInById(userId) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } } From d2dc30db1873c74482ee497076ed32d3dfb295fe Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 17:54:33 +0900 Subject: [PATCH 0108/1029] =?UTF-8?q?[BE]=20admin-search=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminCabinetController.java | 2 +- .../admin/controller/AdminController.java | 59 ++++++++ .../controller/AdminLentController.java | 4 +- .../admin/controller/AdminUserController.java | 14 +- .../admin/controller/SearchController.java | 15 +- .../admin/newService/AdminCommandService.java | 12 ++ .../admin/newService/AdminFacadeService.java | 139 ++++++++++++++++++ .../admin/newService/AdminQueryService.java | 12 ++ .../newService/CabinetQueryService.java | 16 +- .../cabinet/repository/CabinetRepository.java | 14 +- .../cabinet/config/CabinetProperties.java | 2 + .../ftclub/cabinet/dto/CabinetSimpleDto.java | 5 +- .../lent/controller/LentController.java | 2 +- .../lent/repository/LentRepository.java | 16 ++ .../LentCommandService.java | 2 +- .../LentFacadeService.java | 69 ++++----- .../LentPolicyService.java | 14 +- .../LentQueryService.java | 16 +- .../LentRedisService.java | 11 +- .../ftclub/cabinet/mapper/CabinetMapper.java | 8 + .../cabinet/redis/ExpirationListener.java | 2 +- .../newService/BanHistoryQueryService.java | 6 + .../user/newService/UserQueryService.java | 6 + .../user/repository/BanHistoryRepository.java | 14 +- .../user/repository/UserOptionalFetcher.java | 6 +- .../user/repository/UserRepository.java | 2 +- .../blackhole/manager/BlackholeManager.java | 2 +- .../leave/absence/LeaveAbsenceManager.java | 48 +++--- .../utils/scheduler/SystemScheduler.java | 60 ++++---- .../user/repository/UserRepositoryTest.java | 2 +- 30 files changed, 436 insertions(+), 144 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java rename backend/src/main/java/org/ftclub/cabinet/{lent => admin}/controller/AdminLentController.java (92%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentCommandService.java (96%) rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentFacadeService.java (87%) rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentPolicyService.java (93%) rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentQueryService.java (63%) rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentRedisService.java (93%) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java index f1b6b5171..f9e667d26 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java @@ -17,7 +17,7 @@ import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java new file mode 100644 index 000000000..a78abf8fc --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java @@ -0,0 +1,59 @@ +package org.ftclub.cabinet.admin.controller; + +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.admin.newService.AdminFacadeService; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/v4/admin") +@RequiredArgsConstructor +public class AdminController { + + private final AdminFacadeService adminFacadeService; + + + @GetMapping("/search/cabinets-simple") + @AuthGuard(level = ADMIN_ONLY) + public CabinetSimplePaginationDto getCabinetsSimpleInfo( + @RequestParam("visibleNum") Integer visibleNum) { + log.info("Called getCabinetsInfo {}", visibleNum); + return adminFacadeService.getCabinetsSimpleInfo(visibleNum); + } + + @GetMapping("/search/cabinets") + @AuthGuard(level = ADMIN_ONLY) + public CabinetInfoPaginationDto getCabinetsInfo( + @RequestParam("visibleNum") Integer visibleNum) { + log.info("Called getCabinetsInfo {}", visibleNum); + return adminFacadeService.getCabinetInfo(visibleNum); + } + + @GetMapping("/search/users-simple") + @AuthGuard(level = ADMIN_ONLY) + public UserProfilePaginationDto getUsersProfile( + @RequestParam("name") String name, Pageable pageable) { + log.info("Called getUsersProfile {}", name); + return adminFacadeService.getUsersProfile(name, pageable); + } + + @GetMapping("/search/users") + @AuthGuard(level = ADMIN_ONLY) + public UserCabinetPaginationDto getCabinetsLentInfo( + @RequestParam("name") String name, Pageable pageable) { + log.info("Called getCabinetsLentInfo {}", name); + return adminFacadeService.getUserLentCabinetInfo(name, pageable); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminLentController.java similarity index 92% rename from backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminLentController.java index a3389c891..e0f75870c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminLentController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.lent.controller; +package org.ftclub.cabinet.admin.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; @@ -7,7 +7,7 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java index b657d87db..e465a2fac 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java @@ -9,10 +9,10 @@ import org.ftclub.cabinet.dto.ClubUserListDto; import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.service.LentExtensionService; import org.ftclub.cabinet.user.service.UserFacadeService; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -51,16 +51,16 @@ public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { /** * 유저의 대여 기록을 반환합니다. * - * @param userId 유저 고유 아이디 - * @param pageRequest 페이지네이션 정보 + * @param userId 유저 고유 아이디 + * @param pageable 페이지네이션 정보 * @return {@link LentHistoryPaginationDto} 유저의 대여 기록 */ @GetMapping("/{userId}/lent-histories") @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentHistoryPaginationDto getLentHistoriesByUserId(@PathVariable("userId") Long userId, - @RequestBody PageRequest pageRequest) { + public LentHistoryPaginationDto getLentHistoriesByUserId( + @PathVariable("userId") Long userId, Pageable pageable) { log.info("Called getLentHistoriesByUserId: {}", userId); - return lentFacadeService.getUserLentHistories(userId, pageRequest); + return lentFacadeService.getUserLentHistories(userId, pageable); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java index 9e8acb1a1..96e8508e7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java @@ -12,13 +12,12 @@ import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.ftclub.cabinet.user.service.UserFacadeService; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor -@RequestMapping("/v4/admin/search") +//@RequestMapping("/v4/admin/search") @Log4j2 public class SearchController { @@ -28,8 +27,7 @@ public class SearchController { @GetMapping("/cabinets") @AuthGuard(level = ADMIN_ONLY) public CabinetInfoPaginationDto getCabinetsInfo( - @RequestParam("visibleNum") Integer visibleNum - ) { + @RequestParam("visibleNum") Integer visibleNum) { log.info("Called getCabinetsInfo {}", visibleNum); return cabinetFacadeService.getCabinetsInfo(visibleNum); } @@ -37,8 +35,7 @@ public CabinetInfoPaginationDto getCabinetsInfo( @GetMapping("/cabinets-simple") @AuthGuard(level = ADMIN_ONLY) public CabinetSimplePaginationDto getCabinetsSimpleInfo( - @RequestParam("visibleNum") Integer visibleNum - ) { + @RequestParam("visibleNum") Integer visibleNum) { log.info("Called getCabinetsInfo {}", visibleNum); return cabinetFacadeService.getCabinetsSimpleInfoByVisibleNum(visibleNum); } @@ -48,8 +45,7 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfo( public UserProfilePaginationDto getUsersProfile( @RequestParam("name") String name, @RequestParam("page") Integer page, - @RequestParam("size") Integer size - ) { + @RequestParam("size") Integer size) { log.info("Called getUsersProfile {}", name); return userFacadeService.getUserProfileListByPartialName(name, page, size); } @@ -59,8 +55,7 @@ public UserProfilePaginationDto getUsersProfile( public UserCabinetPaginationDto getCabinetsLentInfo( @RequestParam("name") String name, @RequestParam("page") Integer page, - @RequestParam("size") Integer size - ) { + @RequestParam("size") Integer size) { log.info("Called getCabinetsLentInfo {}", name); return userFacadeService.findUserCabinetListByPartialName(name, page, size); } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java new file mode 100644 index 000000000..6dcf513d8 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.admin.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminCommandService { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java new file mode 100644 index 000000000..38eec6467 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java @@ -0,0 +1,139 @@ +package org.ftclub.cabinet.admin.newService; + +import static java.util.stream.Collectors.toList; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetSimpleDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.UserBlockedInfoDto; +import org.ftclub.cabinet.dto.UserCabinetDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserProfileDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentQueryService; +import org.ftclub.cabinet.lent.service.LentRedisService; +import org.ftclub.cabinet.mapper.CabinetMapper; +import org.ftclub.cabinet.mapper.LentMapper; +import org.ftclub.cabinet.mapper.UserMapper; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.newService.BanHistoryQueryService; +import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminFacadeService { + + private final AdminQueryService adminQueryService; + private final AdminCommandService adminCommandService; + private final CabinetQueryService cabinetQueryService; + private final UserQueryService userQueryService; + private final BanHistoryQueryService banHistoryQueryService; + private final LentQueryService lentQueryService; + private final LentRedisService lentRedisService; + + private final CabinetMapper cabinetMapper; + private final UserMapper userMapper; + private final LentMapper lentMapper; + + + @Transactional(readOnly = true) + public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { + log.debug("Called getCabinetSimpleInfo {}", visibleNum); + + List cabinets = cabinetQueryService.getCabinets(visibleNum); + List result = cabinets.stream() + .map(cabinetMapper::toCabinetSimpleDto).collect(toList()); + return cabinetMapper.toCabinetSimplePaginationDto(result, (long) cabinets.size()); + } + + @Transactional(readOnly = true) + public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { + log.debug("Called getCabinetInfo {}", visibleNum); + + List cabinets = cabinetQueryService.getCabinets(visibleNum); + List cabinetIds = cabinets.stream().map(Cabinet::getCabinetId) + .collect(toList()); + List lentHistories = + lentQueryService.findCabinetsActiveLentHistories(cabinetIds); + Map> lentHistoriesByCabinetId = lentHistories.stream() + .collect(Collectors.groupingBy(LentHistory::getCabinetId)); + + List result = cabinets.stream() + .map(cabinet -> { + Long cabinetId = cabinet.getCabinetId(); + List lents = lentHistoriesByCabinetId.get(cabinetId).stream() + .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(toList()); + LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); + return cabinetMapper.toCabinetInfoResponseDto(cabinet, lents, sessionExpiredAt); + }).sorted(Comparator.comparingInt(o -> o.getLocation().getFloor())) + .collect(toList()); + return cabinetMapper.toCabinetInfoPaginationDto(result, (long) cabinets.size()); + } + + @Transactional(readOnly = true) + public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pageable) { + log.debug("Called getUsersProfile {}", partialName); + + Page users = userQueryService.getUsers(partialName, pageable); + List result = users.stream() + .map(userMapper::toUserProfileDto).collect(toList()); + return userMapper.toUserProfilePaginationDto(result, users.getTotalElements()); + } + + @Transactional(readOnly = true) + public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, + Pageable pageable) { + log.debug("Called getUserLentCabinetInfo {}", partialName); + + LocalDateTime now = LocalDateTime.now(); + Page users = userQueryService.getUsers(partialName, pageable); + List userIds = users.stream().map(User::getUserId).collect(toList()); + System.out.println("userIds = " + userIds); + + List activeBanHistories = + banHistoryQueryService.findActiveBanHistories(userIds, now); + System.out.println("activeBanHistories = " + activeBanHistories); + List activeLentHistories = + lentQueryService.findUsersActiveLentHistoriesAndCabinet(userIds); + System.out.println("activeLentHistories = " + activeLentHistories); + Map> banHistoriesByUserId = activeBanHistories.stream() + .collect(Collectors.groupingBy(BanHistory::getUserId)); + System.out.println("banHistoriesByUserId = " + banHistoriesByUserId); + Map> lentHistoriesByUserId = activeLentHistories.stream() + .collect(Collectors.groupingBy(LentHistory::getUserId)); + System.out.println("lentHistoriesByUserId = " + lentHistoriesByUserId); + + List result = users.stream().map(user -> { + List banHistories = banHistoriesByUserId.get(user.getUserId()); + List lentHistories = lentHistoriesByUserId.get(user.getUserId()); + BanHistory banHistory = (Objects.nonNull(banHistories) && !banHistories.isEmpty()) + ? banHistories.get(0) : null; + Cabinet cabinet = (Objects.nonNull(lentHistories) && !lentHistories.isEmpty()) + ? lentHistories.get(0).getCabinet() : null; + UserBlockedInfoDto blockedInfoDto = userMapper.toUserBlockedInfoDto(banHistory, user); + CabinetDto cabinetDto = cabinetMapper.toCabinetDto(cabinet); + return cabinetMapper.toUserCabinetDto(blockedInfoDto, cabinetDto); + }).collect(toList()); + return cabinetMapper.toUserCabinetPaginationDto(result, users.getTotalElements()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java new file mode 100644 index 000000000..1d6fa7148 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.admin.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminQueryService { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 9c41def37..33839bfdf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -7,6 +7,8 @@ import org.ftclub.cabinet.cabinet.repository.CabinetRepository; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @Service @@ -15,17 +17,25 @@ public class CabinetQueryService { private final CabinetRepository cabinetRepository; - public Cabinet getCabinet(Long cabinetId) { + public Cabinet getCabinets(Long cabinetId) { Optional cabinet = cabinetRepository.findById(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } - public Cabinet getCabinetWithLock(Long cabinetId) { + public Cabinet getCabinetsWithLock(Long cabinetId) { Optional cabinet = cabinetRepository.findByIdWithLock(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } - public List getCabinetWithLock(List cabinetIds) { + public List getCabinets(Integer visibleNum) { + return cabinetRepository.findAllByVisibleNum(visibleNum); + } + + public Page getCabinets(Integer visibleNum, PageRequest pageable) { + return cabinetRepository.findPaginationByVisibleNum(visibleNum, pageable); + } + + public List getCabinetsWithLock(List cabinetIds) { return cabinetRepository.findAllByIdsWithLock(cabinetIds); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index bbc6b8859..337b05d0f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -59,7 +59,7 @@ public interface CabinetRepository extends JpaRepository, Cabinet @Query("SELECT c " + "FROM Cabinet c " + "WHERE c.cabinetId IN (:cabinetIds)") - List findAllByIdsWithLock(List cabinetIds); + List findAllByIdsWithLock(@Param("cabinetIds") List cabinetIds); /** * userId로 현재 대여 중인 사물함을 조회한다. @@ -67,11 +67,11 @@ public interface CabinetRepository extends JpaRepository, Cabinet * @param userId 사용자 ID * @return 사물함 {@link Optional} */ - @Query("SELECT c " + - "FROM Cabinet c " + - "LEFT JOIN LentHistory lh ON c.cabinetId = lh.cabinetId " + - "LEFT JOIN User u ON u.userId = lh.userId " + - "WHERE u.userId = :userId AND lh.endedAt IS NULL") + @Query("SELECT c " + + "FROM Cabinet c " + + "LEFT JOIN LentHistory lh ON c.cabinetId = lh.cabinetId " + + "LEFT JOIN User u ON u.userId = lh.userId " + + "WHERE u.userId = :userId AND lh.endedAt IS NULL") Optional findByUserIdAndLentHistoryEndedAtIsNull(@Param("userId") Long userId); /** @@ -95,6 +95,8 @@ public interface CabinetRepository extends JpaRepository, Cabinet Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, Pageable pageable); + List findAllByVisibleNum(@Param("visibleNum") Integer visibleNum); + @Query("SELECT c " + "FROM Cabinet c " + "JOIN FETCH c.cabinetPlace p " diff --git a/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java index dfaf5db39..82c4b084f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java @@ -26,4 +26,6 @@ public class CabinetProperties { private Long shareMaxUserCount; @Value("${cabinet.policy.in-session.term}") private Integer inSessionTerm; + @Value("${cabinet.policy.lent.limit.share.max-attempt-count}") + private Long shareMaxAttemptCount; } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimpleDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimpleDto.java index f0aeee6f4..7d5b8d275 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimpleDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimpleDto.java @@ -3,11 +3,14 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; import org.ftclub.cabinet.cabinet.domain.Location; -@AllArgsConstructor @Getter +@AllArgsConstructor +@ToString public class CabinetSimpleDto { + private final Long cabinetId; @JsonUnwrapped private final Location location; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java index eab419be2..55400b379 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java @@ -9,7 +9,7 @@ import org.ftclub.cabinet.dto.MyCabinetResponseDto; import org.ftclub.cabinet.dto.ShareCodeDto; import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.domain.UserSession; import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index c8a0132ce..536016511 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -149,6 +149,15 @@ Page findPaginationByUserIdAndEndedAtNotNull( */ List findAllByCabinetIdAndEndedAtIsNull(@Param("cabinetId") Long cabinetId); + /** + * 여러 사물함을 기준으로 아직 반납하지 않은 {@link LentHistory}를 모두 가져옵니다. + * + * @param cabinetIds 찾으려는 cabinet id {@link List} + * @return 반납하지 않은 {@link LentHistory}의 {@link List} + */ + List findAllByCabinetIdInAndEndedAtIsNull( + @Param("cabinetIds") List cabinetIds); + /** * 대여 중인 사물함을 모두 가져옵니다. * @@ -170,6 +179,13 @@ Page findPaginationByUserIdAndEndedAtNotNull( List findAllByCabinetIdsAfterDate(@Param("date") LocalDate date, @Param("cabinetIds") List cabinetIds); + @Query("SELECT lh " + + "FROM LentHistory lh " + + "LEFT JOIN FETCH lh.cabinet c " + + "WHERE lh.userId IN (:userIds) AND lh.endedAt IS NULL") + List findByUserIdsAndEndedAtIsNullJoinCabinet( + @Param("userIds") List userIds); + /** * 연체되어 있는 사물함을 모두 가져옵니다. * diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java similarity index 96% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java index cb4729576..f5392bf8a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import java.time.LocalDateTime; import java.util.List; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java similarity index 87% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 306a5dbc8..85c43bc2a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import static org.ftclub.cabinet.cabinet.domain.LentType.PRIVATE; import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; @@ -35,6 +35,7 @@ import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -60,12 +61,12 @@ public class LentFacadeService { @Transactional(readOnly = true) - public LentHistoryPaginationDto getUserLentHistories(Long userId, PageRequest pageable) { + public LentHistoryPaginationDto getUserLentHistories(Long userId, Pageable pageable) { log.debug("Called getAllUserLentHistories: {}", userId); userQueryService.getUser(userId); Page lentHistories = - lentQueryService.findUserLentHistories(userId, pageable); + lentQueryService.findUserActiveLentHistories(userId, pageable); List result = lentHistories.stream() .sorted(Comparator.comparing(LentHistory::getStartedAt)) .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) @@ -73,33 +74,12 @@ public LentHistoryPaginationDto getUserLentHistories(Long userId, PageRequest pa return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); } - @Transactional(readOnly = true) - public List getCabinetLentHistories(Long cabinetId) { - log.debug("Called getCabinetLentHistories: {}", cabinetId); - - cabinetQueryService.getCabinet(cabinetId); - List lentHistories = lentQueryService.findCabinetActiveLentHistory(cabinetId); - return lentHistories.stream() - .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) - .collect(Collectors.toList()); - } - - @Transactional(readOnly = true) - public List getCabinetSessionLentHistories(Long cabinetId) { - log.debug("Called getLentDtoListFromRedis: {}", cabinetId); - - List userIdsInCabinet = lentRedisService.findUsersInCabinet(cabinetId); - List userList = userQueryService.getUsers(userIdsInCabinet); - return userList.stream().map(user -> lentMapper.toLentDto(user, null)) - .collect(Collectors.toList()); - } - @Transactional(readOnly = true) public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, PageRequest pageable) { log.debug("Called getMyLentLog: {}", user.getName()); Page lentHistories = - lentQueryService.findUserLentHistories(user.getUserId(), pageable); + lentQueryService.findUserActiveLentHistories(user.getUserId(), pageable); List result = lentHistories.stream() .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) .map(lentHistory -> lentMapper.toLentHistoryDto( @@ -124,17 +104,16 @@ public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { } List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); List userList = userQueryService.getUsers(usersInCabinet); - userActiveCabinet = cabinetQueryService.getCabinet(cabinetId); + userActiveCabinet = cabinetQueryService.getCabinets(cabinetId); lentDtoList = userList.stream() .map(u -> lentMapper.toLentDto(u, null)).collect(Collectors.toList()); } else { cabinetId = userActiveCabinet.getCabinetId(); List lentHistories = - lentQueryService.findCabinetActiveLentHistory(cabinetId); + lentQueryService.findCabinetActiveLentHistories(cabinetId); lentDtoList = lentHistories.stream() .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(Collectors.toList()); } - // TODO : shareCode, sessionExpiredAt, previousUserName이 상황에 맞춰 null이 되는지 확인 String shareCode = lentRedisService.getShareCode(cabinetId); LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); String previousUserName = lentRedisService.getPreviousUserName(cabinetId); @@ -165,7 +144,7 @@ public void startLentCabinet(Long userId, Long cabinetId) { LocalDateTime now = LocalDateTime.now(); User user = userQueryService.getUser(userId); - Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); int lentCount = lentQueryService.countUserActiveLent(userId); List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); int userCount = lentQueryService.countCabinetUser(cabinetId); @@ -188,17 +167,21 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) log.info("Called startLentShareCabinet: {}, {}, {}", userId, cabinetId, shareCode); LocalDateTime now = LocalDateTime.now(); - User user = userQueryService.getUser(userId); - Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); - int lentCount = lentQueryService.countUserActiveLent(userId); - List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); + Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); - lentPolicyService.verifyCabinetLentCount( cabinet.getLentType(), cabinet.getMaxUser(), userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), SHARE); + + List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); + int lentCount = lentQueryService.countUserActiveLent(userId); + User user = userQueryService.getUser(userId); lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + + Long attemptCount = lentRedisService.getAttemptCountOnShareCabinet(cabinetId, userId); + lentPolicyService.verifyAttemptCountOnShareCabinet(attemptCount); + boolean isExist = lentRedisService.isInCabinetSession(cabinetId); if (!isExist) { lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); @@ -223,7 +206,7 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { LocalDateTime now = LocalDateTime.now(); // TODO : ClubUser 추가 이후 userId로 ClubUser 검증 로직 필요(Policy) - Cabinet cabinet = cabinetQueryService.getCabinet(cabinetId); + Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); lentPolicyService.verifyCabinetLentCount( @@ -243,9 +226,9 @@ public void endUserLent(Long userId) { LocalDateTime now = LocalDateTime.now(); LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithLock(userId); List cabinetLentHistories = - lentQueryService.findCabinetActiveLentHistory(userLentHistory.getCabinetId()); + lentQueryService.findCabinetActiveLentHistories(userLentHistory.getCabinetId()); Cabinet cabinet = - cabinetQueryService.getCabinetWithLock(userLentHistory.getCabinetId()); + cabinetQueryService.getCabinetsWithLock(userLentHistory.getCabinetId()); int userRemainCount = cabinetLentHistories.size() - 1; cabinetCommandService.changeUserCount(cabinet, userRemainCount); @@ -275,9 +258,9 @@ public void endUserLent(Long userId, String memo) { LocalDateTime now = LocalDateTime.now(); LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithLock(userId); List cabinetLentHistories = - lentQueryService.findCabinetActiveLentHistory(userLentHistory.getCabinetId()); + lentQueryService.findCabinetActiveLentHistories(userLentHistory.getCabinetId()); Cabinet cabinet = - cabinetQueryService.getCabinetWithLock(userLentHistory.getCabinetId()); + cabinetQueryService.getCabinetsWithLock(userLentHistory.getCabinetId()); int userRemainCount = cabinetLentHistories.size() - 1; cabinetCommandService.changeUserCount(cabinet, userRemainCount); @@ -306,10 +289,10 @@ public void endCabinetLent(List cabinetIds) { log.debug("Called endCabinetsLent: {}", cabinetIds); LocalDateTime now = LocalDateTime.now(); - List cabinets = cabinetQueryService.getCabinetWithLock(cabinetIds); + List cabinets = cabinetQueryService.getCabinetsWithLock(cabinetIds); cabinets.forEach(cabinet -> { List cabinetLentHistories = - lentQueryService.findCabinetActiveLentHistory(cabinet.getCabinetId()); + lentQueryService.findCabinetActiveLentHistories(cabinet.getCabinetId()); cabinetLentHistories.forEach(lh -> lentCommandService.endLent(lh, now)); cabinetCommandService.changeUserCount(cabinet, 0); cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); @@ -333,7 +316,7 @@ public void cancelShareCabinetLent(Long userId, Long cabinetId) { lentRedisService.deleteUserInCabinetSession(cabinetId, userId); if (lentRedisService.isCabinetSessionEmpty(cabinetId)) { - Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); } } @@ -342,7 +325,7 @@ public void cancelShareCabinetLent(Long userId, Long cabinetId) { public void shareCabinetSessionExpired(Long cabinetId) { log.debug("Called shareCabinetSessionExpired: {}", cabinetId); - Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); List usersInCabinetSession = lentRedisService.getUsersInCabinetSession(cabinetId); if (lentPolicyService.verifyUserCountOnShareCabinet(usersInCabinetSession.size())) { LocalDateTime now = LocalDateTime.now(); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java similarity index 93% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java index f381d88e2..13dfac0b6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java @@ -1,7 +1,8 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; @@ -183,4 +184,15 @@ public boolean verifyUserCountOnShareCabinet(int userCount) { long maxUserCount = cabinetProperties.getShareMaxUserCount(); return minUserCount <= userCount && userCount <= maxUserCount; } + + public void verifyAttemptCountOnShareCabinet(Long attemptCount) { + log.debug("Called verifyAttemptCountOnShareCabinet"); + + LentPolicyStatus status = LentPolicyStatus.FINE; + Long shareMaxAttemptCount = cabinetProperties.getShareMaxAttemptCount(); + if (Objects.nonNull(attemptCount) && attemptCount >= shareMaxAttemptCount) { + status = LentPolicyStatus.SHARE_BANNED_USER; + } + handlePolicyStatus(status, null); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java similarity index 63% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 175064fd1..8add80a78 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -1,11 +1,11 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import java.util.List; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentRepository; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @Service @@ -14,14 +14,18 @@ public class LentQueryService { private final LentRepository lentRepository; - public Page findUserLentHistories(Long userId, PageRequest pageable) { + public Page findUserActiveLentHistories(Long userId, Pageable pageable) { return lentRepository.findPaginationByUserId(userId, pageable); } - public List findCabinetActiveLentHistory(Long cabinetId) { + public List findCabinetActiveLentHistories(Long cabinetId) { return lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); } + public List findCabinetsActiveLentHistories(List cabinetIds) { + return lentRepository.findAllByCabinetIdInAndEndedAtIsNull(cabinetIds); + } + public int countUserActiveLent(Long userId) { return lentRepository.countByUserIdAndEndedAtIsNull(userId); } @@ -34,6 +38,10 @@ public LentHistory findUserActiveLentHistoryWithLock(Long userId) { return lentRepository.findByUserIdAndEndedAtIsNullForUpdate(userId).orElse(null); } + public List findUsersActiveLentHistoriesAndCabinet(List userIds) { + return lentRepository.findByUserIdsAndEndedAtIsNullJoinCabinet(userIds); + } + public List findAllActiveLentHistories() { return lentRepository.findAllByEndedAtIsNull(); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java similarity index 93% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java index 213063724..d821b9ddb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import java.time.LocalDateTime; import java.util.Comparator; @@ -52,6 +52,15 @@ public boolean isInCabinetSession(Long cabinetId) { return lentRedis.isExistShadowKey(cabinetId.toString()); } + public Long getAttemptCountOnShareCabinet(Long cabinetId, Long userId) { + String attemptCount = + lentRedis.getAttemptCountInCabinet(cabinetId.toString(), userId.toString()); + if (Objects.isNull(attemptCount)) { + return null; + } + return Long.parseLong(attemptCount); + } + public void joinCabinetSession(Long cabinetId, Long userId, String shareCode) { lentRedis.attemptJoinCabinet(cabinetId.toString(), userId.toString(), shareCode); } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java index 9aef4685d..5c2eaf4e6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java @@ -10,11 +10,13 @@ import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetPaginationDto; import org.ftclub.cabinet.dto.CabinetPendingResponseDto; import org.ftclub.cabinet.dto.CabinetPreviewDto; import org.ftclub.cabinet.dto.CabinetSimpleDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.dto.MyCabinetResponseDto; @@ -97,6 +99,12 @@ MyCabinetResponseDto toMyCabinetResponseDto(Cabinet cabinet, List lents @Mapping(target = "location", source = "cabinet.cabinetPlace.location") CabinetSimpleDto toCabinetSimpleDto(Cabinet cabinet); + CabinetSimplePaginationDto toCabinetSimplePaginationDto( + List result, Long totalLength); + + CabinetInfoPaginationDto toCabinetInfoPaginationDto( + List result, Long totalLength); + CabinetPendingResponseDto toCabinetPendingResponseDto( Map> cabinetInfoResponseDtos); } diff --git a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java b/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java index 2f6a32000..a63f8b3fa 100644 --- a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java +++ b/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.redis; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index f85b0b3e9..e25f30b76 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -27,4 +27,10 @@ public List findActiveBanHistories(Long userId, LocalDateTime date) return banHistoryRepository.findByUserIdAndUnbannedAt(userId, date); } + + public List findActiveBanHistories(List userIds, LocalDateTime date) { + log.debug("Called findActiveBanHistories: {}", userIds); + + return banHistoryRepository.findByUserIdsAndUnbannedAt(userIds, date); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java index 5867d647b..713ec7843 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -8,6 +8,8 @@ import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.repository.UserRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @Service @@ -26,4 +28,8 @@ public User getUser(Long userId) { public List getUsers(List userIdsInCabinet) { return userRepository.findAllByIds(userIdsInCabinet); } + + public Page getUsers(String partialName, Pageable pageable) { + return userRepository.findPaginationByPartialName(partialName, pageable); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java index 6b362fa84..0a2498119 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java @@ -23,8 +23,18 @@ public interface BanHistoryRepository extends JpaRepository { */ @Query("SELECT b FROM BanHistory b WHERE b.user.userId = :userId AND b.unbannedAt > :today") List findByUserIdAndUnbannedAt( - @Param("userId") Long userId, - @Param("today") LocalDateTime today); + @Param("userId") Long userId, @Param("today") LocalDateTime today); + + /** + * 유저 아이디 리스트로 현재 기준 active한 밴 히스토리를 가져옵니다. + * + * @param userIds 유저 고유 아이디 {@link List} + * @param today 현재 날짜 + * @return active {@link BanHistory} 리스트 + */ + @Query("SELECT b FROM BanHistory b WHERE b.user.userId IN :userIds AND b.unbannedAt > :today") + List findByUserIdsAndUnbannedAt( + @Param("userIds") List userIds, @Param("today") LocalDateTime today); /** * 유저 아이디로 밴 히스토리를 가져옵니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index c5a3d0d71..b0b311961 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -5,12 +5,12 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.admin.domain.AdminRole; -import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; @@ -87,7 +87,7 @@ public User findUserByEmail(String email) { */ public Page findUsersByPartialName(String name, Pageable pageable) { log.debug("Called findUsersByPartialName: {}", name); - return userRepository.findByPartialName(name, pageable); + return userRepository.findPaginationByPartialName(name, pageable); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index 2a0105088..5bdccb05b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -49,7 +49,7 @@ public interface UserRepository extends JpaRepository { * @return {@link User} 리스트 */ @Query("SELECT u FROM User u WHERE u.name LIKE %:name%") - Page findByPartialName(@Param("name") String name, Pageable pageable); + Page findPaginationByPartialName(@Param("name") String name, Pageable pageable); /** * 유저의 Id List로 유저들을 찾습니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java index d48051c83..80b97d67f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java @@ -9,7 +9,7 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.exception.UtilException; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.service.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java index 3a0ea8f35..0a5461764 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java @@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.service.FtApiManager; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.service.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -16,29 +16,29 @@ @Log4j2 public class LeaveAbsenceManager { - private final FtApiManager ftAPIManager; - private final LentFacadeService lentFacadeService; - private final UserService userService; + private final FtApiManager ftAPIManager; + private final LentFacadeService lentFacadeService; + private final UserService userService; - private Boolean isLeaveAbsence(JsonNode jsonUserInfo) { - return !jsonUserInfo.get("active?").asBoolean(); - } + private Boolean isLeaveAbsence(JsonNode jsonUserInfo) { + return !jsonUserInfo.get("active?").asBoolean(); + } - public void handleLeaveAbsence(Long userId, String name) { - log.info("called handleLeaveAbsence {} {}", userId, name); - try { - JsonNode jsonUserInfo = ftAPIManager.getFtUsersInfoByName(name); - if (isLeaveAbsence(jsonUserInfo)) { - lentFacadeService.endUserLent(userId); - } - } catch (HttpClientErrorException e) { - log.error("handleLeaveAbsence HttpClientErrorException {}", e.getStatusCode()); - if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { - lentFacadeService.endUserLent(userId); - userService.deleteUser(userId, LocalDateTime.now()); - } - } catch (Exception e) { - log.error("handleLeaveAbsence Exception: {}, {}", userId, name, e); - } - } + public void handleLeaveAbsence(Long userId, String name) { + log.info("called handleLeaveAbsence {} {}", userId, name); + try { + JsonNode jsonUserInfo = ftAPIManager.getFtUsersInfoByName(name); + if (isLeaveAbsence(jsonUserInfo)) { + lentFacadeService.endUserLent(userId); + } + } catch (HttpClientErrorException e) { + log.error("handleLeaveAbsence HttpClientErrorException {}", e.getStatusCode()); + if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { + lentFacadeService.endUserLent(userId); + userService.deleteUser(userId, LocalDateTime.now()); + } + } catch (Exception e) { + log.error("handleLeaveAbsence Exception: {}, {}", userId, name, e); + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 35f660a25..82e8f91b5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -6,7 +6,7 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.UserBlackholeInfoDto; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; import org.ftclub.cabinet.user.service.UserService; import org.ftclub.cabinet.utils.blackhole.manager.BlackholeManager; @@ -26,24 +26,24 @@ @Log4j2 public class SystemScheduler { - private final LeaveAbsenceManager leaveAbsenceManager; + private static final long DELAY_TIME = 2000; + private final LeaveAbsenceManager leaveAbsenceManager; private final OverdueManager overdueManager; private final LentFacadeService lentFacadeService; private final UserService userService; private final BlackholeManager blackholeManager; private final ReleaseManager releaseManager; private final OccupiedTimeManager occupiedTimeManager; - private static final long DELAY_TIME = 2000; - /** - * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") - public void checkAllLents() { - log.info("called checkAllLents"); - List activeLents = lentFacadeService.getAllActiveLentHistories(); - for (ActiveLentHistoryDto activeLent : activeLents) { - overdueManager.handleOverdue(activeLent); + /** + * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") + public void checkAllLents() { + log.info("called checkAllLents"); + List activeLents = lentFacadeService.getAllActiveLentHistories(); + for (ActiveLentHistoryDto activeLent : activeLents) { + overdueManager.handleOverdue(activeLent); /* leaveAbsenceManager.handleLeaveAbsence(activeLent.getUserId(), activeLent.getName()); try { @@ -52,25 +52,25 @@ public void checkAllLents() { log.error(e.getMessage()); } */ - } - } + } + } - /** - * 매주 월요일 자정 42분에 블랙홀에 빠진 유저 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") - public void checkRiskOfBlackhole() { - log.info("called checkRiskOfBlackhole"); - List blackholeInfos = userService.getAllRiskOfBlackholeInfo(); - for (UserBlackholeInfoDto blackholeInfo : blackholeInfos) { - blackholeManager.handleBlackhole(blackholeInfo); - try { - Thread.sleep(DELAY_TIME); - } catch (InterruptedException e) { - log.error(e.getMessage()); - } - } - } + /** + * 매주 월요일 자정 42분에 블랙홀에 빠진 유저 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") + public void checkRiskOfBlackhole() { + log.info("called checkRiskOfBlackhole"); + List blackholeInfos = userService.getAllRiskOfBlackholeInfo(); + for (UserBlackholeInfoDto blackholeInfo : blackholeInfos) { + blackholeManager.handleBlackhole(blackholeInfo); + try { + Thread.sleep(DELAY_TIME); + } catch (InterruptedException e) { + log.error(e.getMessage()); + } + } + } /** * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java index 2cf6d441c..550278f6a 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java @@ -56,7 +56,7 @@ public void testFindByName() { public void testFindByPartialName() { String partialName = "lent"; Pageable pageable = PageRequest.of(0, 10); - Page users = userRepository.findByPartialName(partialName, pageable); + Page users = userRepository.findPaginationByPartialName(partialName, pageable); assertNotNull(users); assertEquals(2, users.getTotalElements()); From 048c2aa35f71f4d73fe9eaef54636dd7c15b7294 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 21 Dec 2023 18:28:31 +0900 Subject: [PATCH 0109/1029] =?UTF-8?q?fix,=20refactor:=20ScribeJava?= =?UTF-8?q?=EB=A1=9C=20OAuth=20=EC=9D=B8=EC=A6=9D=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20lombok=EC=97=90?= =?UTF-8?q?=EC=84=9C=20@Qualifier=EB=A5=BC=20=EC=9D=B8=EC=8B=9D=ED=95=A0?= =?UTF-8?q?=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20config=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 3 +- backend/lombok.config | 1 + .../auth/controller/AuthController.java | 36 ++--- .../auth/domain/ApiRequestManager.java | 3 +- .../cabinet/auth/domain/CookieManager.java | 10 +- .../ftclub/cabinet/auth/domain/FtApi20.java | 29 ++++ .../ftclub/cabinet/auth/domain/FtProfile.java | 22 +++ .../cabinet/auth/domain/OauthConfig.java | 34 ++++ .../auth/service/AuthFacadeService.java | 55 ++++++- .../auth/service/AuthFacadeServiceImpl.java | 67 -------- .../cabinet/auth/service/FtOauthService.java | 87 ++++++++++ .../cabinet/auth/service/OauthService.java | 148 +++++++++--------- .../org/ftclub/cabinet/utils/DateUtil.java | 6 + config | 2 +- 14 files changed, 323 insertions(+), 180 deletions(-) create mode 100644 backend/lombok.config create mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/domain/FtApi20.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/domain/OauthConfig.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java diff --git a/backend/build.gradle b/backend/build.gradle index 8ea3a9f32..4504cb607 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -68,7 +68,8 @@ dependencies { // Firebase implementation 'com.google.firebase:firebase-admin:9.2.0' - // jwt + // authorization + implementation 'com.github.scribejava:scribejava-apis:8.3.3' implementation 'io.jsonwebtoken:jjwt-api:0.11.2' implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' diff --git a/backend/lombok.config b/backend/lombok.config new file mode 100644 index 000000000..eb6db90e9 --- /dev/null +++ b/backend/lombok.config @@ -0,0 +1 @@ +lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java index ca51d7fe1..c57ce72f3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java @@ -1,13 +1,10 @@ package org.ftclub.cabinet.auth.controller; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.auth.domain.CookieManager; -import org.ftclub.cabinet.auth.domain.TokenProvider; +import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.service.AuthFacadeService; -import org.ftclub.cabinet.auth.service.AuthService; import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.FtApiProperties; -import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -17,21 +14,18 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.LocalDateTime; +import java.util.concurrent.ExecutionException; @RestController @RequestMapping("/v4/auth") @RequiredArgsConstructor +@Log4j2 public class AuthController { private final AuthFacadeService authFacadeService; private final DomainProperties DomainProperties; private final FtApiProperties ftApiProperties; - private final AuthService authService; - private final TokenProvider tokenProvider; - private final CookieManager cookieManager; - private final UserRepository userRepository; - /** * 42 API 로그인 페이지로 리다이렉트합니다. * @@ -43,25 +37,19 @@ public void login(HttpServletResponse response) throws IOException { authFacadeService.requestLoginToApi(response, ftApiProperties); } - /* - * 42 API 로그인 성공 시에 콜백을 처리합니다. - *
- * 42 API로부터 받은 인증 코드를 이용하여 42 API에게 인증 토큰을 요청하고, - *
- * 인증 토큰을 이용하여 42 API에게 프로필 정보를 요청합니다. - *
- * 프로필 정보를 이용하여 JWT 토큰을 생성하고, JWT 토큰을 쿠키에 저장합니다. - *
- * 완료되면, 프론트엔드의 메인 화면으로 리다이렉트합니다. + /** + * 42 API 로그인 콜백을 처리합니다. * - * @param code 42 API로부터 쿼리로 받은 인증 코드 - * @param req 요청 시의 서블렛 {@link HttpServletRequest} - * @param res 요청 시의 서블렛 {@link HttpServletResponse} + * @param code 42 API 로그인 콜백 시 발급받은 code + * @param req 요청 시의 서블릿 {@link HttpServletRequest} + * @param res 요청 시의 서블릿 {@link HttpServletResponse} * @throws IOException 입출력 예외 */ @GetMapping("/login/callback") - public void loginCallback(@RequestParam String code, HttpServletRequest req, - HttpServletResponse res) throws IOException { + public void loginCallback( + @RequestParam String code, + HttpServletRequest req, + HttpServletResponse res) throws IOException, ExecutionException, InterruptedException { authFacadeService.handleLogin(code, req, res, ftApiProperties, LocalDateTime.now()); res.sendRedirect(DomainProperties.getFeHost() + "/home"); } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/ApiRequestManager.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/ApiRequestManager.java index 5fd358b29..376128e03 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/ApiRequestManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/ApiRequestManager.java @@ -57,7 +57,8 @@ public MultiValueMap getAccessTokenRequestBodyMap(String code) { } /** - * AccessToken 요청을 위한 RequestBodyMap을 생성합니다. Client Secret을 이용하여 AccessToken을 요청합니다. + * AccessToken 요청을 위한 RequestBodyMap을 생성합니다. + * Client Secret을 이용하여 AccessToken을 요청합니다. * * @return AccessToken 요청을 위한 RequestBodyMap */ diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java index 07766a7db..06e80e9c0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java @@ -1,13 +1,14 @@ package org.ftclub.cabinet.auth.domain; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.JwtProperties; import org.springframework.stereotype.Component; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + /** * 클라이언트의 쿠키를 관리하는 클래스입니다. */ @@ -47,7 +48,7 @@ public String getCookieValue(HttpServletRequest req, String name) { * @param serverName 쿠키 도메인 */ public void setCookieToClient(HttpServletResponse res, Cookie cookie, String path, - String serverName) { + String serverName) { cookie.setMaxAge(60 * 60 * 24 * jwtProperties.getExpiryDays()); cookie.setPath(path); if (serverName.equals(domainProperties.getLocal())) { @@ -55,7 +56,6 @@ public void setCookieToClient(HttpServletResponse res, Cookie cookie, String pat } else { cookie.setDomain(domainProperties.getCookieDomain()); } - System.out.println("?????????????????????? cookie domain = " + cookie.getDomain()); res.addCookie(cookie); } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtApi20.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtApi20.java new file mode 100644 index 000000000..c6846e474 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtApi20.java @@ -0,0 +1,29 @@ +package org.ftclub.cabinet.auth.domain; + +import com.github.scribejava.core.builder.api.DefaultApi20; + +/** + * ScribeJava 라이브러리에서 OAuth2.0 서비스를 생성할 때에 필요한 메타데이터를 담는 클래스입니다. + *

+ * 참고 : {@link com.github.scribejava.apis.GoogleApi20} + */ +public class FtApi20 extends DefaultApi20 { + + public static FtApi20 instance() { + return InstanceHolder.INSTANCE; + } + + @Override + public String getAccessTokenEndpoint() { + return "https://api.intra.42.fr/oauth/token"; + } + + @Override + protected String getAuthorizationBaseUrl() { + return "https://api.intra.42.fr/oauth/authorize"; + } + + private static class InstanceHolder { + private static final FtApi20 INSTANCE = new FtApi20(); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java new file mode 100644 index 000000000..631c7dee4 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java @@ -0,0 +1,22 @@ +package org.ftclub.cabinet.auth.domain; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; +import org.ftclub.cabinet.auth.service.FtOauthService; + +import java.time.LocalDateTime; + +/** + * 42 OAuth 로그인을 통해 서비스에서 사용하는 프로필 정보를 담는 클래스입니다. + *

+ * 정보에 변경이 생겨야 한다면 {@link FtOauthService}#convertJsonStringToProfile 메서드를 수정하세요. + */ +@Builder +@Getter +@ToString +public class FtProfile { + private final String intraName; + private final String email; + private final LocalDateTime blackHoledAt; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/OauthConfig.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/OauthConfig.java new file mode 100644 index 000000000..926d1453d --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/OauthConfig.java @@ -0,0 +1,34 @@ +package org.ftclub.cabinet.auth.domain; + +import com.github.scribejava.apis.GoogleApi20; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.oauth.OAuth20Service; +import org.ftclub.cabinet.config.FtApiProperties; +import org.ftclub.cabinet.config.GoogleApiProperties; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OauthConfig { + public static final String FT_OAUTH_20_SERVICE = "ftOauth20Service"; + public static final String GOOGLE_OAUTH_20_SERVICE = "googleOauth20Service"; + + @Bean + @Qualifier(FT_OAUTH_20_SERVICE) + public OAuth20Service ftOauth20Service(FtApiProperties ftApiProperties) { + return new ServiceBuilder(ftApiProperties.getClientId()) + .apiSecret(ftApiProperties.getClientSecret()) + .callback(ftApiProperties.getRedirectUri()) + .build(FtApi20.instance()); + } + + @Bean + @Qualifier(GOOGLE_OAUTH_20_SERVICE) + public OAuth20Service googleOauth20Service(GoogleApiProperties googleApiProperties) { + return new ServiceBuilder(googleApiProperties.getClientId()) + .apiSecret(googleApiProperties.getClientSecret()) + .callback(googleApiProperties.getRedirectUri()) + .build(GoogleApi20.instance()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index 7026403f9..3bfe31d4f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -1,22 +1,63 @@ package org.ftclub.cabinet.auth.service; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.auth.domain.CookieManager; +import org.ftclub.cabinet.auth.domain.TokenProvider; import org.ftclub.cabinet.config.ApiProperties; +import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.dto.MasterLoginDto; +import org.ftclub.cabinet.exception.ControllerException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.springframework.stereotype.Service; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.LocalDateTime; +import java.util.Map; -public interface AuthFacadeService { +@Service +@RequiredArgsConstructor +public class AuthFacadeService { - void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) throws IOException; + private final JwtProperties jwtProperties; + private final TokenProvider tokenProvider; + private final CookieManager cookieManager; + private final AuthService authService; + private final OauthService oauthService; - void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, - ApiProperties apiProperties, LocalDateTime now); + public void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) + throws IOException { + oauthService.sendCodeRequestToApi(res, apiProperties); + } - void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now); + public void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, + ApiProperties apiProperties, LocalDateTime now) { + String apiToken = oauthService.getTokenByCodeRequest(code, apiProperties); + JsonNode profile = oauthService.getProfileJsonByToken(apiToken, apiProperties); + Map claims = tokenProvider.makeClaimsByProviderProfile( + apiProperties.getProviderName(), profile); + authService.addUserIfNotExistsByClaims(claims); + String accessToken = tokenProvider.createToken(claims, now); + Cookie cookie = cookieManager.cookieOf( + tokenProvider.getTokenNameByProvider(apiProperties.getProviderName()), accessToken); + cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + } - void logout(HttpServletResponse res, ApiProperties apiProperties); + public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, + HttpServletResponse res, LocalDateTime now) { + if (!authService.validateMasterLogin(masterLoginDto)) { + throw new ControllerException(ExceptionStatus.UNAUTHORIZED); + } + String masterToken = tokenProvider.createMasterToken(now); + Cookie cookie = cookieManager.cookieOf(jwtProperties.getAdminTokenName(), masterToken); + cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + } + + public void logout(HttpServletResponse res, ApiProperties apiProperties) { + cookieManager.deleteCookie(res, + tokenProvider.getTokenNameByProvider(apiProperties.getProviderName())); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java deleted file mode 100644 index 8aea67960..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.ftclub.cabinet.auth.service; - -import com.fasterxml.jackson.databind.JsonNode; -import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.auth.domain.CookieManager; -import org.ftclub.cabinet.auth.domain.TokenProvider; -import org.ftclub.cabinet.config.ApiProperties; -import org.ftclub.cabinet.config.JwtProperties; -import org.ftclub.cabinet.dto.MasterLoginDto; -import org.ftclub.cabinet.exception.ControllerException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.springframework.stereotype.Service; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.Map; - -@Service -@RequiredArgsConstructor -public class AuthFacadeServiceImpl implements AuthFacadeService { - - private final JwtProperties jwtProperties; - private final TokenProvider tokenProvider; - private final CookieManager cookieManager; - private final AuthService authService; - private final OauthService oauthService; - - @Override - public void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) - throws IOException { - oauthService.sendCodeRequestToApi(res, apiProperties); - } - - @Override - public void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, - ApiProperties apiProperties, LocalDateTime now) { - String apiToken = oauthService.getTokenByCodeRequest(code, apiProperties); - JsonNode profile = oauthService.getProfileJsonByToken(apiToken, apiProperties); - Map claims = tokenProvider.makeClaimsByProviderProfile( - apiProperties.getProviderName(), profile); - authService.addUserIfNotExistsByClaims(claims); - String accessToken = tokenProvider.createToken(claims, now); - Cookie cookie = cookieManager.cookieOf( - tokenProvider.getTokenNameByProvider(apiProperties.getProviderName()), accessToken); - cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); - } - - @Override - public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now) { - if (!authService.validateMasterLogin(masterLoginDto)) { - throw new ControllerException(ExceptionStatus.UNAUTHORIZED); - } - String masterToken = tokenProvider.createMasterToken(now); - Cookie cookie = cookieManager.cookieOf(jwtProperties.getAdminTokenName(), masterToken); - cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); - } - - @Override - public void logout(HttpServletResponse res, ApiProperties apiProperties) { - cookieManager.deleteCookie(res, - tokenProvider.getTokenNameByProvider(apiProperties.getProviderName())); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java new file mode 100644 index 000000000..446c507ca --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java @@ -0,0 +1,87 @@ +package org.ftclub.cabinet.auth.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.auth.domain.FtProfile; +import org.ftclub.cabinet.auth.domain.OauthConfig; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.utils.DateUtil; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.concurrent.ExecutionException; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class FtOauthService { + + @Qualifier(OauthConfig.FT_OAUTH_20_SERVICE) + private final OAuth20Service ftOAuth20Service; + private final ObjectMapper objectMapper; + + /** + * 42 API 로그인 콜백으로 받은 authorization_code로 유저 프로필 정보를 가져오고, 반환합니다. + * + * @param code 42 API 로그인 콜백 시 발급받은 authorization_code + * @return 유저 프로필 정보 {@link FtProfile} + * @throws IOException HTTP 통신에서 일어나는 입출력 예외 + * @throws ExecutionException 비동기 처리시 스레드에서 발생한 오류 처리 예외 + * @throws InterruptedException 비동기 처리시 스레드 종료를 위한 예외 + * @see 위 예외에 대한 정보 + */ + public FtProfile getProfileByCode(String code) throws IOException, ExecutionException, InterruptedException { + OAuthRequest oAuthRequest = new OAuthRequest(Verb.GET, ftOAuth20Service.getAuthorizationUrl()); + OAuth2AccessToken accessToken = ftOAuth20Service.getAccessToken(code); + ftOAuth20Service.signRequest(accessToken, oAuthRequest); + try { + Response response = ftOAuth20Service.execute(oAuthRequest); + return convertJsonStringToProfile(response.getBody()); + } catch (Exception e) { + if (e instanceof IOException) + log.error("42 API 서버에서 프로필 정보를 가져오는데 실패했습니다." + + "code: {}, message: {}", code, e.getMessage()); + if (e instanceof ExecutionException || e instanceof InterruptedException) + log.error("42 API 서버에서 프로필 정보를 비동기적으로 가져오는데 실패했습니다." + + "code: {}, message: {}", code, e.getMessage()); + throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * String 형태의 JSON 데이터를 {@link FtProfile}로 변환합니다. + * + * @param jsonString String 형태의 JSON 데이터 + * @return 유저 프로필 정보 {@link FtProfile} + * @throws JsonProcessingException JSON 파싱 예외 + * @see 42 API에서 제공하는 Profile Json에 대한 정보 + */ + private FtProfile convertJsonStringToProfile(String jsonString) throws JsonProcessingException { + JsonNode rootNode = objectMapper.readTree(jsonString); + String intraName = rootNode.get("login").asText(); + String email = rootNode.get("email").asText(); + if (intraName == null || email == null) + throw new ServiceException(ExceptionStatus.INCORRECT_ARGUMENT); + + LocalDateTime blackHoledAt = DateUtil.convertStringToDate(rootNode + .get("cursus_users") + .get(1).get("blackholed_at").asText()); + + return FtProfile.builder() + .intraName(intraName) + .email(email) + .blackHoledAt(blackHoledAt) + .build(); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/OauthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/OauthService.java index c337e667d..c3174832d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/OauthService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/OauthService.java @@ -3,8 +3,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.ApiRequestManager; @@ -15,6 +13,9 @@ import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + /** * OAuth를 수행하는 서비스 클래스입니다. */ @@ -23,82 +24,81 @@ @Log4j2 public class OauthService { - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper; - /** - * OAuth 권한 코드를 요청하는 URL을 생성하고, HttpServletResponse에 리다이렉트합니다. - * - * @param response {@link HttpServletResponse} - * @throws IOException 입출력 예외 - */ - public void sendCodeRequestToApi(HttpServletResponse response, ApiProperties apiProperties) - throws IOException { - response.sendRedirect( - ApiRequestManager.of(apiProperties) - .getCodeRequestUri()); - } + /** + * OAuth 권한 코드를 요청하는 URL을 생성하고, HttpServletResponse에 리다이렉트합니다. + * + * @param response {@link HttpServletResponse} + * @throws IOException 입출력 예외 + */ + public void sendCodeRequestToApi(HttpServletResponse response, ApiProperties apiProperties) + throws IOException { + response.sendRedirect(ApiRequestManager.of(apiProperties) + .getCodeRequestUri()); + } - /** - * 유저의 정보를 얻어올 수 있는 OAuth 액세스 토큰을 요청합니다. - * - * @param code 인증 코드 - * @return API 액세스 토큰 - * @throws RuntimeException JSON 파싱 예외 - * @throws ServiceException API 요청에 에러가 반환됐을 때 발생하는 예외 - */ - public String getTokenByCodeRequest(String code, ApiProperties apiProperties) { - return WebClient.create().post() - .uri(apiProperties.getTokenUri()) - .body(BodyInserters.fromFormData( - ApiRequestManager.of(apiProperties) - .getAccessTokenRequestBodyMap(code))) - .retrieve() - .bodyToMono(String.class) - .map(response -> { - try { - return objectMapper.readTree(response) - .get(apiProperties.getAccessTokenName()).asText(); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); - throw new RuntimeException(e); - } - }) - .onErrorResume(e -> { - log.error(e.getMessage()); - throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); - }) - .block(); - } + /** + * 유저의 정보를 얻어올 수 있는 OAuth 액세스 토큰을 요청합니다. + * + * @param code 인증 코드 + * @return API 액세스 토큰 + * @throws RuntimeException JSON 파싱 예외 + * @throws ServiceException API 요청에 에러가 반환됐을 때 발생하는 예외 + */ + public String getTokenByCodeRequest(String code, ApiProperties apiProperties) { + return WebClient.create().post() + .uri(apiProperties.getTokenUri()) + .body(BodyInserters.fromFormData( + ApiRequestManager.of(apiProperties) + .getAccessTokenRequestBodyMap(code))) + .retrieve() + .bodyToMono(String.class) + .map(response -> { + try { + return objectMapper.readTree(response) + .get(apiProperties.getAccessTokenName()).asText(); + } catch (JsonProcessingException e) { + log.error(e.getMessage()); + throw new RuntimeException(e); + } + }) + .onErrorResume(e -> { + log.error(e.getMessage()); + throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); + }) + .block(); + } - /** - * 액세스 토큰을 이용해 OAuth 사용자 정보를 요청합니다. - * - * @param token 토큰 - * @return 사용자 정보 - * @throws RuntimeException JSON 파싱 예외 - * @throws ServiceException API 요청에 에러가 반환됐을 때 발생하는 예외 - */ - public JsonNode getProfileJsonByToken(String token, ApiProperties apiProperties) { - return WebClient.create().get() - .uri(apiProperties.getUserInfoUri()) - .headers(headers -> headers.setBearerAuth(token)) - .retrieve() - .bodyToMono(String.class) - .map(response -> { - try { - return objectMapper.readTree(response); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); + /** + * 액세스 토큰을 이용해 OAuth 사용자 정보를 요청합니다. + * + * @param token 토큰 + * @return 사용자 정보 + * @throws RuntimeException JSON 파싱 예외 + * @throws ServiceException API 요청에 에러가 반환됐을 때 발생하는 예외 + */ + public JsonNode getProfileJsonByToken(String token, ApiProperties apiProperties) { + return WebClient.create().get() + .uri(apiProperties.getUserInfoUri()) + .headers(headers -> headers.setBearerAuth(token)) + .retrieve() + .bodyToMono(String.class) + .map(response -> { + try { + return objectMapper.readTree(response); + } catch (JsonProcessingException e) { + log.error(e.getMessage()); - throw new RuntimeException(e); - } - }) - .onErrorResume(e -> { - log.error(e.getMessage()); + throw new RuntimeException(e); + } + }) + .onErrorResume(e -> { + log.error(e.getMessage()); - throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); - }) - .block(); - } + throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); + }) + .block(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java b/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java index e37af5005..78b3a4e69 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java @@ -7,6 +7,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.regex.Pattern; @@ -44,6 +45,11 @@ public static LocalDateTime stringToDate(String str) { return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); } + public static LocalDateTime convertStringToDate(String string) { + DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME; + return LocalDateTime.parse(string, formatter); + } + /** * 특정 date에서 days만큼 더한 값을 리턴합니다. * diff --git a/config b/config index 868b4f34e..a082d11f1 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 868b4f34e936b36d739a6d68d280c3e3a148c118 +Subproject commit a082d11f180496c9a310028daaf76246b19e2dfa From 04d8d95d65ee8b21e753699fac8a05cdf6c320fb Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 21 Dec 2023 19:35:37 +0900 Subject: [PATCH 0110/1029] =?UTF-8?q?[BE]=20REFACTOR:=20Alarm=20=EB=91=90?= =?UTF-8?q?=EA=B0=80=EC=A7=80=20=EB=B2=84=EC=A0=84=20=ED=98=BC=EC=9E=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/dto/AlarmTypeResponseDto.java | 37 ++++++----- .../repository/AlarmOptInRepository.java | 2 +- .../repository/AlarmStatusRepository.java | 13 ++++ .../alarm/service/AlarmCommandService.java | 47 ++++++++++++++ .../alarm/service/AlarmQueryService.java | 31 ++++++++++ .../cabinet/dto/UpdateAlarmRequestDto.java | 2 +- .../cabinet/user/domain/AlarmStatus.java | 62 +++++++++++++++++++ .../user/service/UserFacadeServiceImpl.java | 45 +++++++------- 8 files changed, 197 insertions(+), 42 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmStatusRepository.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java index 5ca1ce085..6759e4acb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java @@ -1,10 +1,9 @@ package org.ftclub.cabinet.alarm.dto; -import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.user.domain.AlarmStatus; @AllArgsConstructor @Getter @@ -14,20 +13,26 @@ public class AlarmTypeResponseDto { private boolean email; private boolean push; + // @Builder +// public AlarmTypeResponseDto(List alarmTypes) { +// alarmTypes.forEach(alarmType -> { +// switch (alarmType) { +// case SLACK: +// this.slack = true; +// break; +// case EMAIL: +// this.email = true; +// break; +// case PUSH: +// this.push = true; +// break; +// } +// }); +// } @Builder - public AlarmTypeResponseDto(List alarmTypes) { - alarmTypes.forEach(alarmType -> { - switch (alarmType) { - case SLACK: - this.slack = true; - break; - case EMAIL: - this.email = true; - break; - case PUSH: - this.push = true; - break; - } - }); + public AlarmTypeResponseDto(AlarmStatus alarmStatus) { + this.slack = alarmStatus.isSlack(); + this.email = alarmStatus.isEmail(); + this.push = alarmStatus.isPush(); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java index 8a98f4443..0e6eca38c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java @@ -13,7 +13,7 @@ public interface AlarmOptInRepository extends JpaRepository { @Query("SELECT ao FROM AlarmOptIn ao WHERE ao.user.userId = :userId") - List findAllByUserId(Long userId); + List findAllByUserId(@Param("userId") Long userId); @Modifying @Query("DELETE FROM AlarmOptIn ao WHERE ao.user.userId = :userId AND ao.alarmType = :alarmType") diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmStatusRepository.java b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmStatusRepository.java new file mode 100644 index 000000000..90f8d0f39 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmStatusRepository.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.alarm.repository; + +import io.lettuce.core.dynamic.annotation.Param; +import java.util.Optional; +import org.ftclub.cabinet.user.domain.AlarmStatus; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface AlarmStatusRepository extends JpaRepository { + + @Query("SELECT als FROM AlarmStatus as als WHERE als.user.userId = :userId") + Optional findByUserId(@Param("userId") Long userId); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmCommandService.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmCommandService.java new file mode 100644 index 000000000..9a40a9e10 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmCommandService.java @@ -0,0 +1,47 @@ +package org.ftclub.cabinet.alarm.service; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.domain.AlarmType; +import org.ftclub.cabinet.alarm.repository.AlarmOptInRepository; +import org.ftclub.cabinet.alarm.repository.AlarmStatusRepository; +import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; +import org.ftclub.cabinet.user.domain.AlarmOptIn; +import org.ftclub.cabinet.user.domain.AlarmStatus; +import org.ftclub.cabinet.user.domain.User; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class AlarmCommandService { + + private final AlarmOptInRepository alarmOptInRepository; + private final AlarmStatusRepository alarmStatusRepository; + + //alarmTypeStatus 의 key 값을 순회하며 true일경우 currentAlarmTypes에 없으면 추가, 있으면 아무일도 하지 않는다 + //false일 경우 currentAlarmTypes에 있으면 삭제, 없으면 아무일도 하지 않는다 + public void updateAlarmStatus(User user, List currentAlarmTypes, + Map changedAlarmStatus) { + + for (Entry entry : changedAlarmStatus.entrySet()) { + if (entry.getValue()) { + if (!currentAlarmTypes.contains(entry.getKey())) { + alarmOptInRepository.save(AlarmOptIn.of(user, entry.getKey())); + } + } else { + if (currentAlarmTypes.contains(entry.getKey())) { + alarmOptInRepository.deleteAlarmOptInByUserAndAlarmType(user.getUserId(), + entry.getKey()); + } + } + } + } + + public void updateAlarmStatusRe(UpdateAlarmRequestDto dto, AlarmStatus alarmStatus) { + alarmStatus.update(dto); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java new file mode 100644 index 000000000..c3f4abd74 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java @@ -0,0 +1,31 @@ +package org.ftclub.cabinet.alarm.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.repository.AlarmOptInRepository; +import org.ftclub.cabinet.alarm.repository.AlarmStatusRepository; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.user.domain.AlarmOptIn; +import org.ftclub.cabinet.user.domain.AlarmStatus; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class AlarmQueryService { + + private final AlarmOptInRepository alarmOptInRepository; + private final AlarmStatusRepository alarmStatusRepository; + + public List findAllAlarmOptInByUserId(Long userId) { + return alarmOptInRepository.findAllByUserId(userId); + } + + public AlarmStatus findAlarmStatusByUserId(Long userId) { + return alarmStatusRepository.findByUserId(userId).orElseThrow(() -> new ServiceException( + ExceptionStatus.NOT_FOUND_USER)); + } + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/UpdateAlarmRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/UpdateAlarmRequestDto.java index cce110132..d088b6edc 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/UpdateAlarmRequestDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/UpdateAlarmRequestDto.java @@ -7,8 +7,8 @@ import org.ftclub.cabinet.alarm.domain.AlarmType; @ToString -@Getter @AllArgsConstructor +@Getter public class UpdateAlarmRequestDto { private final boolean slack; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java new file mode 100644 index 000000000..c52f4bf64 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java @@ -0,0 +1,62 @@ +package org.ftclub.cabinet.user.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; + +/** + * 유저의 알람 수신 거부 정보 엔티티입니다. + */ +@Entity +@Table(name = "ALARM_STATUS") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@ToString(exclude = {"user"}) +public class AlarmStatus { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private Long id; + + @Column(name = "slack", nullable = false) + private boolean slack; + + @Column(name = "email", nullable = false) + private boolean email; + + @Column(name = "push", nullable = false) + private boolean push; + + @ManyToOne + @JoinColumn(name = "USER_ID", nullable = false) + private User user; + + private AlarmStatus(User user, boolean slack, boolean email, boolean push) { + this.user = user; + this.slack = slack; + this.email = email; + this.push = push; + } + + public static AlarmStatus of(User user, UpdateAlarmRequestDto dto) { + return new AlarmStatus(user, dto.isSlack(), dto.isEmail(), dto.isPush()); + } + + public void update(UpdateAlarmRequestDto dto) { + this.slack = dto.isSlack(); + this.email = dto.isEmail(); + this.push = dto.isPush(); + } + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java index 3e0f06f90..f18fcf8d9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java @@ -5,7 +5,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; import javax.transaction.Transactional; @@ -13,7 +12,8 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.domain.AlarmType; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; -import org.ftclub.cabinet.alarm.repository.AlarmOptInRepository; +import org.ftclub.cabinet.alarm.service.AlarmCommandService; +import org.ftclub.cabinet.alarm.service.AlarmQueryService; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; @@ -37,6 +37,7 @@ import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.AdminRole; import org.ftclub.cabinet.user.domain.AlarmOptIn; +import org.ftclub.cabinet.user.domain.AlarmStatus; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.LentExtension; import org.ftclub.cabinet.user.domain.User; @@ -62,7 +63,8 @@ public class UserFacadeServiceImpl implements UserFacadeService { private final CabinetMapper cabinetMapper; private final LentExtensionService lentExtensionService; private final LentExtensionOptionalFetcher lentExtensionOptionalFetcher; - private final AlarmOptInRepository alarmOptInRepository; + private final AlarmCommandService alarmCommandService; + private final AlarmQueryService alarmQueryService; @Override public MyProfileResponseDto getMyProfile(UserSessionDto user) { @@ -75,14 +77,20 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { LentExtensionResponseDto activeLentExtension = lentExtensionService.getActiveLentExtension( user); - List alarmOptIns = alarmOptInRepository.findAllByUserId(user.getUserId()); - List alarmTypes = alarmOptIns.stream().map(AlarmOptIn::getAlarmType) - .collect(Collectors.toList()); - AlarmTypeResponseDto alarmTypeResponseDto = AlarmTypeResponseDto.builder() - .alarmTypes(alarmTypes).build(); +// List alarmOptIns = alarmQueryService.findAllAlarmOptInByUserId( +// user.getUserId()); +// List alarmTypes = alarmOptIns.stream().map(AlarmOptIn::getAlarmType) +// .collect(Collectors.toList()); +// AlarmTypeResponseDto alarmTypeResponseDto = AlarmTypeResponseDto.builder() +// .alarmTypes(alarmTypes).build(); + + AlarmStatus userAlarmStatus = alarmQueryService.findAlarmStatusByUserId( + user.getUserId()); + AlarmTypeResponseDto.builder().alarmStatus(userAlarmStatus).build(); return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, - activeLentExtension, alarmTypeResponseDto); + activeLentExtension, + AlarmTypeResponseDto.builder().alarmStatus(userAlarmStatus).build()); } @Override @@ -325,21 +333,10 @@ public void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto) { List currentAlarmTypes = alarmOptIns.stream().map(AlarmOptIn::getAlarmType) .collect(Collectors.toList()); - Map alarmTypeStatus = dto.getAlarmTypeStatus(); - //alarmTypeStatus 의 key 값을 순회하며 true일경우 currentAlarmTypes에 없으면 추가, 있으면 아무일도 하지 않는다 - //false일 경우 currentAlarmTypes에 있으면 삭제, 없으면 아무일도 하지 않는다 - for (Entry entry : alarmTypeStatus.entrySet()) { - if (entry.getValue()) { - if (!currentAlarmTypes.contains(entry.getKey())) { - alarmOptInRepository.save(AlarmOptIn.of(findUser, entry.getKey())); - } - } else { - if (currentAlarmTypes.contains(entry.getKey())) { - alarmOptInRepository.deleteAlarmOptInByUserAndAlarmType(findUser.getUserId(), - entry.getKey()); - } - } - } + Map changedAlarmStatus = dto.getAlarmTypeStatus(); + alarmCommandService.updateAlarmStatus(findUser, currentAlarmTypes, changedAlarmStatus); + alarmCommandService.updateAlarmStatusRe(dto, alarmQueryService.findAlarmStatusByUserId( + user.getUserId())); } } From 0565df54e6bd26fde1ed8a96d63554d425769788 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 21 Dec 2023 20:14:46 +0900 Subject: [PATCH 0111/1029] =?UTF-8?q?[BE]=20REFACTOR:=20AlarmOptIn=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=97=90=EC=84=9C=20AlarmStatus=20=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/dto/AlarmTypeResponseDto.java | 16 ----- .../alarm/handler/AlarmEventHandler.java | 19 ++---- .../repository/AlarmOptInRepository.java | 23 ------- .../alarm/service/AlarmCommandService.java | 30 --------- .../alarm/service/AlarmQueryService.java | 8 --- .../cabinet/user/domain/AlarmOptIn.java | 61 ------------------- .../cabinet/user/domain/AlarmStatus.java | 4 +- .../org/ftclub/cabinet/user/domain/User.java | 10 ++- .../user/repository/UserRepository.java | 6 +- .../user/service/UserFacadeServiceImpl.java | 18 ------ .../cabinet/user/service/UserService.java | 4 +- .../cabinet/user/service/UserServiceImpl.java | 4 +- 12 files changed, 19 insertions(+), 184 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptIn.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java index 6759e4acb..d688fc59e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java @@ -13,22 +13,6 @@ public class AlarmTypeResponseDto { private boolean email; private boolean push; - // @Builder -// public AlarmTypeResponseDto(List alarmTypes) { -// alarmTypes.forEach(alarmType -> { -// switch (alarmType) { -// case SLACK: -// this.slack = true; -// break; -// case EMAIL: -// this.email = true; -// break; -// case PUSH: -// this.push = true; -// break; -// } -// }); -// } @Builder public AlarmTypeResponseDto(AlarmStatus alarmStatus) { this.slack = alarmStatus.isSlack(); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index fdf28e64d..7bb3eefb0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -1,19 +1,13 @@ package org.ftclub.cabinet.alarm.handler; -import static org.ftclub.cabinet.alarm.domain.AlarmType.EMAIL; -import static org.ftclub.cabinet.alarm.domain.AlarmType.PUSH; -import static org.ftclub.cabinet.alarm.domain.AlarmType.SLACK; import static org.ftclub.cabinet.exception.ExceptionStatus.NOT_FOUND_USER; -import java.util.Set; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.domain.AlarmEvent; -import org.ftclub.cabinet.alarm.domain.AlarmType; import org.ftclub.cabinet.alarm.domain.TransactionalAlarmEvent; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.user.domain.AlarmOptIn; +import org.ftclub.cabinet.user.domain.AlarmStatus; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.context.event.EventListener; @@ -47,18 +41,17 @@ public void handleAlarmEvent(AlarmEvent alarmEvent) { } private void eventProceed(AlarmEvent alarmEvent) { - User receiver = userRepository.findUserWithOptInById(alarmEvent.getReceiverId()) + User receiver = userRepository.findUserByIdWithAlarmStatus(alarmEvent.getReceiverId()) .orElseThrow(() -> new ServiceException(NOT_FOUND_USER)); - Set alarmOptIns = receiver.getAlarmOptIns() - .stream().map(AlarmOptIn::getAlarmType).collect(Collectors.toSet()); + AlarmStatus alarmStatus = receiver.getAlarmStatus(); - if (alarmOptIns.contains(SLACK)) { + if (alarmStatus.isSlack()) { slackAlarmSender.send(receiver, alarmEvent); } - if (alarmOptIns.contains(EMAIL)) { + if (alarmStatus.isEmail()) { emailAlarmSender.send(receiver, alarmEvent); } - if (alarmOptIns.contains(PUSH)) { + if (alarmStatus.isPush()) { pushAlarmSender.send(receiver, alarmEvent); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java deleted file mode 100644 index 0e6eca38c..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmOptInRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.ftclub.cabinet.alarm.repository; - -import io.lettuce.core.dynamic.annotation.Param; -import java.util.List; -import org.ftclub.cabinet.alarm.domain.AlarmType; -import org.ftclub.cabinet.user.domain.AlarmOptIn; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -@Repository -public interface AlarmOptInRepository extends JpaRepository { - - @Query("SELECT ao FROM AlarmOptIn ao WHERE ao.user.userId = :userId") - List findAllByUserId(@Param("userId") Long userId); - - @Modifying - @Query("DELETE FROM AlarmOptIn ao WHERE ao.user.userId = :userId AND ao.alarmType = :alarmType") - void deleteAlarmOptInByUserAndAlarmType(@Param("userId") Long userId, - @Param("alarmType") AlarmType alarmType); - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmCommandService.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmCommandService.java index 9a40a9e10..8d7314ce7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmCommandService.java @@ -1,17 +1,9 @@ package org.ftclub.cabinet.alarm.service; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.alarm.domain.AlarmType; -import org.ftclub.cabinet.alarm.repository.AlarmOptInRepository; -import org.ftclub.cabinet.alarm.repository.AlarmStatusRepository; import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; -import org.ftclub.cabinet.user.domain.AlarmOptIn; import org.ftclub.cabinet.user.domain.AlarmStatus; -import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Service; @Log4j2 @@ -19,28 +11,6 @@ @RequiredArgsConstructor public class AlarmCommandService { - private final AlarmOptInRepository alarmOptInRepository; - private final AlarmStatusRepository alarmStatusRepository; - - //alarmTypeStatus 의 key 값을 순회하며 true일경우 currentAlarmTypes에 없으면 추가, 있으면 아무일도 하지 않는다 - //false일 경우 currentAlarmTypes에 있으면 삭제, 없으면 아무일도 하지 않는다 - public void updateAlarmStatus(User user, List currentAlarmTypes, - Map changedAlarmStatus) { - - for (Entry entry : changedAlarmStatus.entrySet()) { - if (entry.getValue()) { - if (!currentAlarmTypes.contains(entry.getKey())) { - alarmOptInRepository.save(AlarmOptIn.of(user, entry.getKey())); - } - } else { - if (currentAlarmTypes.contains(entry.getKey())) { - alarmOptInRepository.deleteAlarmOptInByUserAndAlarmType(user.getUserId(), - entry.getKey()); - } - } - } - } - public void updateAlarmStatusRe(UpdateAlarmRequestDto dto, AlarmStatus alarmStatus) { alarmStatus.update(dto); } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java index c3f4abd74..8073773cd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java @@ -1,13 +1,10 @@ package org.ftclub.cabinet.alarm.service; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.alarm.repository.AlarmOptInRepository; import org.ftclub.cabinet.alarm.repository.AlarmStatusRepository; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.user.domain.AlarmOptIn; import org.ftclub.cabinet.user.domain.AlarmStatus; import org.springframework.stereotype.Service; @@ -16,13 +13,8 @@ @RequiredArgsConstructor public class AlarmQueryService { - private final AlarmOptInRepository alarmOptInRepository; private final AlarmStatusRepository alarmStatusRepository; - public List findAllAlarmOptInByUserId(Long userId) { - return alarmOptInRepository.findAllByUserId(userId); - } - public AlarmStatus findAlarmStatusByUserId(Long userId) { return alarmStatusRepository.findByUserId(userId).orElseThrow(() -> new ServiceException( ExceptionStatus.NOT_FOUND_USER)); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptIn.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptIn.java deleted file mode 100644 index bc2cc222f..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmOptIn.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.ftclub.cabinet.user.domain; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; -import org.ftclub.cabinet.alarm.domain.AlarmType; -import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.utils.ExceptionUtil; - -/** - * 유저의 알람 수신 거부 정보 엔티티입니다. - */ -@Entity -@Table(name = "ALARM_OPT_IN") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@ToString(exclude = {"user"}) -public class AlarmOptIn { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "ID") - private Long id; - - @Enumerated(value = EnumType.STRING) - @Column(name = "ALARM_TYPE", length = 32) - private AlarmType alarmType; - - @ManyToOne - @JoinColumn(name = "USER_ID", nullable = false) - private User user; - - private AlarmOptIn(User user, AlarmType alarmType) { - this.user = user; - this.alarmType = alarmType; - } - - public static AlarmOptIn of(User user, AlarmType alarmType) { - AlarmOptIn alarmOptIn = new AlarmOptIn(user, alarmType); - ExceptionUtil.throwIfFalse(alarmOptIn.isValid(), - new DomainException(ExceptionStatus.INVALID_ARGUMENT)); - return alarmOptIn; - } - - private boolean isValid() { - return user != null && alarmType != null; - } - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java index c52f4bf64..96cf7ada7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java @@ -6,7 +6,7 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; import javax.persistence.Table; import lombok.AccessLevel; import lombok.Getter; @@ -38,7 +38,7 @@ public class AlarmStatus { @Column(name = "push", nullable = false) private boolean push; - @ManyToOne + @OneToOne @JoinColumn(name = "USER_ID", nullable = false) private User user; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index a88b203e0..a4993106b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -1,9 +1,7 @@ package org.ftclub.cabinet.user.domain; import java.time.LocalDateTime; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; import java.util.regex.Pattern; import javax.persistence.CascadeType; import javax.persistence.Column; @@ -13,7 +11,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; -import javax.persistence.OneToMany; +import javax.persistence.OneToOne; import javax.persistence.Table; import javax.validation.constraints.Email; import javax.validation.constraints.NotNull; @@ -30,7 +28,7 @@ @Table(name = "USER") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -@ToString(exclude = {"alarmOptIns"}) +@ToString(exclude = {"alarmStatus"}) @Log4j2 public class User { @@ -57,8 +55,8 @@ public class User { @Column(name = "ROLE", length = 32, nullable = false) private UserRole role; - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private final Set alarmOptIns = new HashSet<>(); + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private AlarmStatus alarmStatus; protected User(String name, String email, LocalDateTime blackholedAt, UserRole userRole) { this.name = name; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index 0f6a06be4..fc940908e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -74,12 +74,12 @@ public interface UserRepository extends JpaRepository { List findByNoRiskOfFallingIntoBlackholeUsers(); /** - * 유저의 id로 OptIn 테이블과 결합된 유저 정보를 찾습니다. + * 유저의 id로 AlarmStatus 테이블과 결합된 유저 정보를 찾습니다. */ @Query("SELECT u FROM User u " - + "LEFT JOIN AlarmOptIn o ON u.userId = o.user.userId " + + "LEFT JOIN AlarmStatus o ON u.userId = o.user.userId " + "WHERE u.userId = :id") - Optional findUserWithOptInById(@Param("id") Long id); + Optional findUserByIdWithAlarmStatus(@Param("id") Long id); /** * 현재 Active 상태의 Cabi User를 모두 가져옵니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java index f18fcf8d9..de856bf3f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java @@ -4,13 +4,10 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.alarm.domain.AlarmType; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; import org.ftclub.cabinet.alarm.service.AlarmCommandService; import org.ftclub.cabinet.alarm.service.AlarmQueryService; @@ -36,7 +33,6 @@ import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AlarmOptIn; import org.ftclub.cabinet.user.domain.AlarmStatus; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.LentExtension; @@ -77,13 +73,6 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { LentExtensionResponseDto activeLentExtension = lentExtensionService.getActiveLentExtension( user); -// List alarmOptIns = alarmQueryService.findAllAlarmOptInByUserId( -// user.getUserId()); -// List alarmTypes = alarmOptIns.stream().map(AlarmOptIn::getAlarmType) -// .collect(Collectors.toList()); -// AlarmTypeResponseDto alarmTypeResponseDto = AlarmTypeResponseDto.builder() -// .alarmTypes(alarmTypes).build(); - AlarmStatus userAlarmStatus = alarmQueryService.findAlarmStatusByUserId( user.getUserId()); AlarmTypeResponseDto.builder().alarmStatus(userAlarmStatus).build(); @@ -328,13 +317,6 @@ public void useLentExtension(UserSessionDto userSessionDto) { public void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto) { log.debug("Called updateAlarmState"); - User findUser = userService.getUserWithAlarmOptIn(user.getUserId()); - Set alarmOptIns = findUser.getAlarmOptIns(); - List currentAlarmTypes = alarmOptIns.stream().map(AlarmOptIn::getAlarmType) - .collect(Collectors.toList()); - - Map changedAlarmStatus = dto.getAlarmTypeStatus(); - alarmCommandService.updateAlarmStatus(findUser, currentAlarmTypes, changedAlarmStatus); alarmCommandService.updateAlarmStatusRe(dto, alarmQueryService.findAlarmStatusByUserId( user.getUserId())); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java index 9aa7490ab..cb6f38add 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java @@ -70,11 +70,11 @@ void banUser(Long userId, LentType lentType, LocalDateTime startedAt, LocalDateT List getAllNoRiskOfBlackholeInfo(); /** - * userId로 유저와 AlarmOptIn 정보를 가져옵니다. + * userId로 유저와 AlarmStatus 정보를 가져옵니다. * * @param userId * @return */ - User getUserWithAlarmOptIn(Long userId); + User getUserByIdWithAlarmStatus(Long userId); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java index 5ad2abf5b..c2be461b1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java @@ -221,8 +221,8 @@ public List getAllNoRiskOfBlackholeInfo() { } @Override - public User getUserWithAlarmOptIn(Long userId) { - return userRepository.findUserWithOptInById(userId) + public User getUserByIdWithAlarmStatus(Long userId) { + return userRepository.findUserByIdWithAlarmStatus(userId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); } } From cf7c190aeaacba327a2d5790f720e70a2b4b1782 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 20:31:56 +0900 Subject: [PATCH 0112/1029] =?UTF-8?q?[BE]=20admin-statistics=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 67 ++++++++++++++++- .../admin/controller/SearchController.java | 62 --------------- .../controller/StatisticsController.java | 5 +- .../admin/newService/AdminFacadeService.java | 75 ++++++++++++++++++- .../service/StatisticsFacadeServiceImpl.java | 6 +- .../newService/CabinetQueryService.java | 18 +++++ .../cabinet/repository/CabinetRepository.java | 26 +++++++ .../lent/repository/LentOptionalFetcher.java | 2 +- .../lent/repository/LentRepository.java | 16 ++-- .../lent/service/LentQueryService.java | 19 ++++- .../ftclub/cabinet/mapper/CabinetMapper.java | 8 ++ .../newService/BanHistoryQueryService.java | 8 ++ .../StatisticsFacadeServiceUnitTest.java | 19 ++--- 13 files changed, 243 insertions(+), 88 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java index a78abf8fc..cc812b30c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java @@ -2,15 +2,23 @@ import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.admin.newService.AdminFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.dto.BlockedUserPaginationDto; +import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.springframework.data.domain.Pageable; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -25,11 +33,12 @@ public class AdminController { private final AdminFacadeService adminFacadeService; + /*---------------------------------------- Search ------------------------------------------*/ @GetMapping("/search/cabinets-simple") @AuthGuard(level = ADMIN_ONLY) public CabinetSimplePaginationDto getCabinetsSimpleInfo( @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsInfo {}", visibleNum); + log.info("Called getCabinetsSimpleInfo {}", visibleNum); return adminFacadeService.getCabinetsSimpleInfo(visibleNum); } @@ -56,4 +65,60 @@ public UserCabinetPaginationDto getCabinetsLentInfo( log.info("Called getCabinetsLentInfo {}", name); return adminFacadeService.getUserLentCabinetInfo(name, pageable); } + + /*-------------------------------------- Statistics ----------------------------------------*/ + + /** + * 전 층의 사물함 정보를 가져옵니다. + * + * @return 전 층의 사물함 정보를 반환합니다. + */ + @GetMapping("/statistics/buildings/floors/cabinets") + @AuthGuard(level = ADMIN_ONLY) + public List getAllCabinetsInfo() { + log.info("Called getCabinetsInfoOnAllFloors"); + return adminFacadeService.getAllCabinetsInfo(); + } + + /** + * 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 가져옵니다. + * + * @param startDate 입력할 기간의 시작일 + * @param endDate 입력할 기간의 종료일 + * @return 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 반환합니다. + */ + @GetMapping("/statistics/lent-histories") + @AuthGuard(level = ADMIN_ONLY) + public LentsStatisticsResponseDto getLentCountStatistics( + @RequestParam("startDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime startDate, + @RequestParam("endDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime endDate) { + log.info("Called getCountOnLentAndReturn startDate : {} endDate : {}", startDate, endDate); + return adminFacadeService.getLentCountStatistics(startDate, endDate); + } + + /** + * 차단당한 유저 정보를 가져옵니다. + * + * @param pageable 페이지 정보 + * @return 차단당한 유저 정보를 반환합니다. + */ + @GetMapping("/statistics/users/banned") + @AuthGuard(level = ADMIN_ONLY) + public BlockedUserPaginationDto getUsersBannedInfo(Pageable pageable) { + log.info("Called getUsersBannedInfo"); + return adminFacadeService.getAllBanUsers(pageable); + } + + /** + * 연체중인 유저 리스트를 가져옵니다. + * + * @param pageable 페이지 정보 + * @return 연체중인 유저 리스트를 반환합니다. + */ + @GetMapping("/statistics/users/overdue") + @AuthGuard(level = ADMIN_ONLY) + public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { + log.info("Called getOverdueUsers"); + return adminFacadeService.getOverdueUsers(pageable); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java deleted file mode 100644 index 96e8508e7..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.ftclub.cabinet.admin.controller; - -import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; - -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.auth.domain.AuthGuard; -import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; -import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; -import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; -import org.ftclub.cabinet.dto.UserCabinetPaginationDto; -import org.ftclub.cabinet.dto.UserProfilePaginationDto; -import org.ftclub.cabinet.user.service.UserFacadeService; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -//@RequestMapping("/v4/admin/search") -@Log4j2 -public class SearchController { - - private final CabinetFacadeService cabinetFacadeService; - private final UserFacadeService userFacadeService; - - @GetMapping("/cabinets") - @AuthGuard(level = ADMIN_ONLY) - public CabinetInfoPaginationDto getCabinetsInfo( - @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsInfo {}", visibleNum); - return cabinetFacadeService.getCabinetsInfo(visibleNum); - } - - @GetMapping("/cabinets-simple") - @AuthGuard(level = ADMIN_ONLY) - public CabinetSimplePaginationDto getCabinetsSimpleInfo( - @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsInfo {}", visibleNum); - return cabinetFacadeService.getCabinetsSimpleInfoByVisibleNum(visibleNum); - } - - @GetMapping("/users-simple") - @AuthGuard(level = ADMIN_ONLY) - public UserProfilePaginationDto getUsersProfile( - @RequestParam("name") String name, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getUsersProfile {}", name); - return userFacadeService.getUserProfileListByPartialName(name, page, size); - } - - @GetMapping("/users") - @AuthGuard(level = ADMIN_ONLY) - public UserCabinetPaginationDto getCabinetsLentInfo( - @RequestParam("name") String name, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getCabinetsLentInfo {}", name); - return userFacadeService.findUserCabinetListByPartialName(name, page, size); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java index d03a68d9a..94c8b0e71 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java @@ -6,23 +6,22 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; -import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.ftclub.cabinet.user.service.UserFacadeService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor -@RequestMapping("/v4/admin/statistics") +//@RequestMapping("/v4/admin/statistics") @Log4j2 public class StatisticsController { diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java index 38eec6467..0bd90dd70 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java @@ -1,6 +1,10 @@ package org.ftclub.cabinet.admin.newService; import static java.util.stream.Collectors.toList; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.BROKEN; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.FULL; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.OVERDUE; import java.time.LocalDateTime; import java.util.Comparator; @@ -12,17 +16,24 @@ import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetSimpleDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserBlockedInfoDto; import org.ftclub.cabinet.dto.UserCabinetDto; import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserProfileDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.lent.service.LentRedisService; @@ -33,6 +44,8 @@ import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.newService.BanHistoryQueryService; import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.utils.DateUtil; +import org.ftclub.cabinet.utils.ExceptionUtil; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -81,8 +94,12 @@ public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { List result = cabinets.stream() .map(cabinet -> { Long cabinetId = cabinet.getCabinetId(); - List lents = lentHistoriesByCabinetId.get(cabinetId).stream() - .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(toList()); + List lents = null; + if (lentHistoriesByCabinetId.containsKey(cabinetId)) { + lents = lentHistoriesByCabinetId.get(cabinetId).stream() + .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) + .collect(toList()); + } LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); return cabinetMapper.toCabinetInfoResponseDto(cabinet, lents, sessionExpiredAt); }).sorted(Comparator.comparingInt(o -> o.getLocation().getFloor())) @@ -136,4 +153,58 @@ public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, }).collect(toList()); return cabinetMapper.toUserCabinetPaginationDto(result, users.getTotalElements()); } + + public List getAllCabinetsInfo() { + log.debug("Called getCabinetsInfoOnAllFloors"); + + List buildings = cabinetQueryService.getAllBuildings(); + List floors = cabinetQueryService.getAllFloorsByBuildings(buildings); + return floors.stream().map(floor -> { + Integer used = cabinetQueryService.countCabinets(FULL, floor); + Integer unused = cabinetQueryService.countCabinets(AVAILABLE, floor); + Integer overdue = cabinetQueryService.countCabinets(OVERDUE, floor); + Integer disabled = cabinetQueryService.countCabinets(BROKEN, floor); + Integer total = used + overdue + unused + disabled; + return cabinetMapper.toCabinetFloorStatisticsResponseDto( + floor, total, used, overdue, unused, disabled); + }).collect(Collectors.toList()); + } + + public LentsStatisticsResponseDto getLentCountStatistics( + LocalDateTime startDate, LocalDateTime endDate) { + log.debug("Called getLentCountStatistics startDate : {} endDate : {}", startDate, endDate); + + ExceptionUtil.throwIfFalse(startDate.isBefore(endDate), + new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); + int lentStartCount = lentQueryService.countLentOnDuration(startDate, endDate); + int lentEndCount = lentQueryService.countReturnOnDuration(startDate, endDate); + return cabinetMapper.toLentsStatisticsResponseDto( + startDate, endDate, lentStartCount, lentEndCount); + } + + public BlockedUserPaginationDto getAllBanUsers(Pageable pageable) { + log.debug("Called getAllBanUsers"); + + LocalDateTime now = LocalDateTime.now(); + Page banHistories = + banHistoryQueryService.findActiveBanHistories(now, pageable); + List result = banHistories.stream() + .map(b -> userMapper.toUserBlockedInfoDto(b, b.getUser())) + .collect(Collectors.toList()); + return userMapper.toBlockedUserPaginationDto(result, banHistories.getTotalElements()); + } + + public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { + log.debug("Called getOverdueUsers"); + + LocalDateTime now = LocalDateTime.now(); + List lentHistories = lentQueryService.findOverdueLentHistories(now, pageable); + List result = lentHistories.stream() + .map(lh -> { + Long overdueDays = DateUtil.calculateTwoDateDiff(now, lh.getExpiredAt()); + return cabinetMapper.toOverdueUserCabinetDto( + lh, lh.getUser(), lh.getCabinet(), overdueDays); + }).collect(Collectors.toList()); + return cabinetMapper.toOverdueUserCabinetPaginationDto(result, (long) lentHistories.size()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java index f076e9f74..35326ae3b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java @@ -7,6 +7,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; @@ -14,7 +15,6 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.springframework.stereotype.Service; @Service @@ -71,8 +71,8 @@ public LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDat log.debug("Called getCountOnLentAndReturn"); throwIfFalse(startDate.isBefore(endDate), new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); - Integer lentStartCount = lentRepository.countLentByTimeDuration(startDate, endDate); - Integer lentEndCount = lentRepository.countReturnByTimeDuration(startDate, endDate); + Integer lentStartCount = lentRepository.countLentFromStartDateToEndDate(startDate, endDate); + Integer lentEndCount = lentRepository.countReturnFromStartDateToEndDate(startDate, endDate); return new LentsStatisticsResponseDto(startDate, endDate, lentStartCount, lentEndCount); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 33839bfdf..78ec99856 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -4,6 +4,7 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; @@ -17,6 +18,23 @@ public class CabinetQueryService { private final CabinetRepository cabinetRepository; + + public List getAllBuildings() { + return cabinetRepository.findAllBuildings(); + } + + public List getAllFloorsByBuilding(String building) { + return cabinetRepository.findAllFloorsByBuilding(building); + } + + public int countCabinets(CabinetStatus status, Integer floor) { + return cabinetRepository.countByStatusAndFloor(status, floor); + } + + public List getAllFloorsByBuildings(List buildings) { + return cabinetRepository.findAllFloorsByBuildings(buildings); + } + public Cabinet getCabinets(Long cabinetId) { Optional cabinet = cabinetRepository.findById(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 337b05d0f..13ce8c512 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -8,6 +8,7 @@ import org.ftclub.cabinet.cabinet.domain.LentType; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; @@ -37,6 +38,30 @@ public interface CabinetRepository extends JpaRepository, Cabinet + "WHERE p.location.building = :building") List findAllFloorsByBuilding(@Param("building") String building); + /** + * 여러 층에 걸쳐 CabinetStatus에 맞는 사물함의 개수를 조회한다. + * + * @param status 사물함 상태 + * @param floor 층 + * @return 사물함 개수 {@link List} + */ + @Query("SELECT count(c) " + + "FROM Cabinet c " + + "JOIN c.cabinetPlace p " + + "WHERE c.status = :status AND p.location.floor = :floor") + int countByStatusAndFloor(@Param("status") CabinetStatus status, @Param("floor") Integer floor); + + /** + * 빌딩 리스트의 모든 층을 조회한다. + * + * @param buildings 빌딩 {@link List} + * @return 층 {@link List} + */ + @Query("SELECT DISTINCT p.location.floor " + + "FROM CabinetPlace p " + + "WHERE p.location.building IN (:buildings)") + List findAllFloorsByBuildings(@Param("buildings") List buildings); + /** * cabinetId로 사물함을 조회한다.(조회 이후 업데이트를 위해 X Lock을 건다.) * @@ -95,6 +120,7 @@ public interface CabinetRepository extends JpaRepository, Cabinet Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, Pageable pageable); + @EntityGraph(attributePaths = {"cabinetPlace"}) List findAllByVisibleNum(@Param("visibleNum") Integer visibleNum); @Query("SELECT c " diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index b7b6fecb0..e7edf2bcf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -144,7 +144,7 @@ public List findUserIdsByCabinetIdFromRedis(Long cabinetId) { public List findAllOverdueLent(LocalDateTime date, Pageable pageable) { log.debug("Called findAllOverdueLent: {}", date); - return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNull(date, pageable); + return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNull(date, pageable).toList(); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 536016511..50b98e89d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -67,13 +67,13 @@ public interface LentRepository extends JpaRepository { @Query("SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.startedAt < :endDate AND lh.startedAt >= :startDate") - int countLentByTimeDuration(@Param("startDate") LocalDateTime startDate, + int countLentFromStartDateToEndDate(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); @Query("SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.endedAt < :endDate AND lh.endedAt >= :startDate") - int countReturnByTimeDuration(@Param("startDate") LocalDateTime startDate, + int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); /** @@ -155,7 +155,12 @@ Page findPaginationByUserIdAndEndedAtNotNull( * @param cabinetIds 찾으려는 cabinet id {@link List} * @return 반납하지 않은 {@link LentHistory}의 {@link List} */ - List findAllByCabinetIdInAndEndedAtIsNull( + @Query("SELECT lh " + + "FROM LentHistory lh " + + "LEFT JOIN FETCH lh.user " + + "WHERE lh.cabinetId IN (:cabinetIds) " + + "AND lh.endedAt IS NULL ") + List findAllByCabinetIdInAndEndedAtIsNullJoinUser( @Param("cabinetIds") List cabinetIds); /** @@ -194,9 +199,8 @@ List findByUserIdsAndEndedAtIsNullJoinCabinet( */ @Query("SELECT lh " + "FROM LentHistory lh " - + "WHERE lh.expiredAt < :date AND lh.endedAt is null " - + "ORDER BY lh.expiredAt ASC") - List findAllExpiredAtBeforeAndEndedAtIsNull( + + "WHERE lh.expiredAt < :date AND lh.endedAt IS NULL") + Page findAllExpiredAtBeforeAndEndedAtIsNull( @Param("date") LocalDateTime date, Pageable pageable); @Query("SELECT lh " diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 8add80a78..c20eab2df 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -1,5 +1,9 @@ package org.ftclub.cabinet.lent.service; +import static java.util.stream.Collectors.toList; + +import java.time.LocalDateTime; +import java.util.Comparator; import java.util.List; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.lent.domain.LentHistory; @@ -23,7 +27,7 @@ public List findCabinetActiveLentHistories(Long cabinetId) { } public List findCabinetsActiveLentHistories(List cabinetIds) { - return lentRepository.findAllByCabinetIdInAndEndedAtIsNull(cabinetIds); + return lentRepository.findAllByCabinetIdInAndEndedAtIsNullJoinUser(cabinetIds); } public int countUserActiveLent(Long userId) { @@ -34,6 +38,14 @@ public int countCabinetUser(Long cabinetId) { return lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId); } + public int countLentOnDuration(LocalDateTime startDate, LocalDateTime endDate) { + return lentRepository.countLentFromStartDateToEndDate(startDate, endDate); + } + + public int countReturnOnDuration(LocalDateTime startDate, LocalDateTime endDate) { + return lentRepository.countReturnFromStartDateToEndDate(startDate, endDate); + } + public LentHistory findUserActiveLentHistoryWithLock(Long userId) { return lentRepository.findByUserIdAndEndedAtIsNullForUpdate(userId).orElse(null); } @@ -45,4 +57,9 @@ public List findUsersActiveLentHistoriesAndCabinet(List userI public List findAllActiveLentHistories() { return lentRepository.findAllByEndedAtIsNull(); } + + public List findOverdueLentHistories(LocalDateTime now, Pageable pageable) { + return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNull(now, pageable).stream() + .sorted(Comparator.comparing(LentHistory::getExpiredAt)).collect(toList()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java index 5c2eaf4e6..05a965ab1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java @@ -10,6 +10,7 @@ import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetPaginationDto; @@ -19,6 +20,7 @@ import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.dto.MyCabinetResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; @@ -107,4 +109,10 @@ CabinetInfoPaginationDto toCabinetInfoPaginationDto( CabinetPendingResponseDto toCabinetPendingResponseDto( Map> cabinetInfoResponseDtos); + + CabinetFloorStatisticsResponseDto toCabinetFloorStatisticsResponseDto(Integer floor, + Integer total, Integer used, Integer overdue, Integer unused, Integer disabled); + + LentsStatisticsResponseDto toLentsStatisticsResponseDto(LocalDateTime startDate, + LocalDateTime endDate, int lentStartCount, int lentEndCount); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index e25f30b76..9a1475ccd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -6,6 +6,8 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.repository.BanHistoryRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @Service @@ -33,4 +35,10 @@ public List findActiveBanHistories(List userIds, LocalDateTime return banHistoryRepository.findByUserIdsAndUnbannedAt(userIds, date); } + + public Page findActiveBanHistories(LocalDateTime now, Pageable pageable) { + log.debug("Called findActiveBanHistories: {}", now); + + return banHistoryRepository.findPaginationActiveBanHistories(pageable, now); + } } diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java index 90b8a9ec9..078da47c9 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java @@ -10,6 +10,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.ftclub.cabinet.admin.service.StatisticsFacadeServiceImpl; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; @@ -17,7 +18,6 @@ import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -92,13 +92,14 @@ class StatisticsFacadeServiceUnitTest { public void 대여_반납_개수_세기_성공() { LocalDateTime startDate = LocalDateTime.of(2023, 1, 1, 0, 0); // 2023-01-01 LocalDateTime endDate = LocalDateTime.of(2023, 6, 1, 0, 0); // 2023-06-01 - given(lentRepository.countLentByTimeDuration(startDate, endDate)) + given(lentRepository.countLentFromStartDateToEndDate(startDate, endDate)) .willReturn(12); - given(lentRepository.countReturnByTimeDuration(startDate, endDate)) + given(lentRepository.countReturnFromStartDateToEndDate(startDate, endDate)) .willReturn(2); LentsStatisticsResponseDto lentsStatisticsResponseDtoOrigin = new LentsStatisticsResponseDto( - startDate, endDate, lentRepository.countLentByTimeDuration(startDate, endDate), - lentRepository.countReturnByTimeDuration(startDate, endDate)); + startDate, endDate, + lentRepository.countLentFromStartDateToEndDate(startDate, endDate), + lentRepository.countReturnFromStartDateToEndDate(startDate, endDate)); LentsStatisticsResponseDto lentsStatisticsResponseDtoTested = statisticsFacadeService.getCountOnLentAndReturn( startDate, endDate); @@ -107,8 +108,8 @@ class StatisticsFacadeServiceUnitTest { lentsStatisticsResponseDtoOrigin.getLentStartCount()); assertThat(lentsStatisticsResponseDtoTested.getLentEndCount()).isEqualTo( lentsStatisticsResponseDtoOrigin.getLentEndCount()); - then(lentRepository).should(times(2)).countLentByTimeDuration(startDate, endDate); - then(lentRepository).should(times(2)).countReturnByTimeDuration(startDate, endDate); + then(lentRepository).should(times(2)).countLentFromStartDateToEndDate(startDate, endDate); + then(lentRepository).should(times(2)).countReturnFromStartDateToEndDate(startDate, endDate); } @Test @@ -120,7 +121,7 @@ class StatisticsFacadeServiceUnitTest { Assertions.assertThrows(ServiceException.class, () -> { statisticsFacadeService.getCountOnLentAndReturn(startDate, endDate); }); - then(lentRepository).should(times(0)).countLentByTimeDuration(startDate, endDate); - then(lentRepository).should(times(0)).countReturnByTimeDuration(startDate, endDate); + then(lentRepository).should(times(0)).countLentFromStartDateToEndDate(startDate, endDate); + then(lentRepository).should(times(0)).countReturnFromStartDateToEndDate(startDate, endDate); } } From d4d31480f0042d85a9c083495c0c67ddd91cff55 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 20:33:59 +0900 Subject: [PATCH 0113/1029] =?UTF-8?q?[BE]=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=EC=99=84=EB=A3=8C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 2 +- .../controller/StatisticsController.java | 93 ------------------- .../repository/StatisticsOptionalFetcher.java | 13 --- .../repository/StatisticsRepository.java | 24 ----- .../AdminCommandService.java | 2 +- .../AdminFacadeService.java | 2 +- .../AdminQueryService.java | 2 +- .../service/StatisticsFacadeService.java | 18 ---- .../service/StatisticsFacadeServiceImpl.java | 78 ---------------- .../controller/StatisticsControllerTest.java | 1 - .../StatisticsFacadeServiceUnitTest.java | 1 - 11 files changed, 4 insertions(+), 232 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java rename backend/src/main/java/org/ftclub/cabinet/admin/{newService => service}/AdminCommandService.java (81%) rename backend/src/main/java/org/ftclub/cabinet/admin/{newService => service}/AdminFacadeService.java (99%) rename backend/src/main/java/org/ftclub/cabinet/admin/{newService => service}/AdminQueryService.java (81%) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java index cc812b30c..62e127b06 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java @@ -6,7 +6,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.admin.newService.AdminFacadeService; +import org.ftclub.cabinet.admin.service.AdminFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java deleted file mode 100644 index 94c8b0e71..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.ftclub.cabinet.admin.controller; - -import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; - -import java.time.LocalDateTime; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.admin.service.StatisticsFacadeService; -import org.ftclub.cabinet.auth.domain.AuthGuard; -import org.ftclub.cabinet.dto.BlockedUserPaginationDto; -import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; -import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; -import org.ftclub.cabinet.user.service.UserFacadeService; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.format.annotation.DateTimeFormat.ISO; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -//@RequestMapping("/v4/admin/statistics") -@Log4j2 -public class StatisticsController { - - private final StatisticsFacadeService statisticsFacadeService; - private final UserFacadeService userFacadeService; - - /** - * 전 층의 사물함 정보를 가져옵니다. - * - * @return 전 층의 사물함 정보를 반환합니다. - */ - @GetMapping("/buildings/floors/cabinets") - @AuthGuard(level = ADMIN_ONLY) - public List getCabinetsInfoOnAllFloors() { - log.info("Called getCabinetsInfoOnAllFloors"); - return statisticsFacadeService.getCabinetsCountOnAllFloors(); - } - - /** - * 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 가져옵니다. - * - * @param startDate 입력할 기간의 시작일 - * @param endDate 입력할 기간의 종료일 - * @return 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 반환합니다. - */ - @GetMapping("/lent-histories") - @AuthGuard(level = ADMIN_ONLY) - public LentsStatisticsResponseDto getCountOnLentAndReturn( - @RequestParam("startDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime startDate, - @RequestParam("endDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime endDate - ) { - log.info("Called getCountOnLentAndReturn startDate : {} endDate : {}", startDate, endDate); - return statisticsFacadeService.getCountOnLentAndReturn(startDate, endDate); - } - - /** - * 차단당한 유저 정보를 가져옵니다. - * - * @param page 가져오고자 하는 페이지 - * @param size 가져오고자 하는 페이지의 길이 - * @return 차단당한 유저 정보를 반환합니다. - */ - @GetMapping("/users/banned") - @AuthGuard(level = ADMIN_ONLY) - public BlockedUserPaginationDto getUsersBannedInfo( - @RequestParam("page") Integer page, - @RequestParam("size") Integer size - ) { - log.info("Called getUsersBannedInfo"); - return userFacadeService.getAllBanUsers(page, size, LocalDateTime.now()); - } - - /** - * 연체중인 유저 리스트를 가져옵니다. - * - * @param page 가져오고자 하는 페이지 - * @param size 가져오고자 하는 페이지의 길이 - * @return 연체중인 유저 리스트를 반환합니다. - */ - @GetMapping("/users/overdue") - @AuthGuard(level = ADMIN_ONLY) - public OverdueUserCabinetPaginationDto getOverdueUsers( - @RequestParam("page") Integer page, - @RequestParam("size") Integer size - ) { - log.info("Called getOverdueUsers"); - return userFacadeService.getOverdueUserList(page, size); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java deleted file mode 100644 index 5576f1450..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.ftclub.cabinet.admin.repository; - -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Log4j2 -public class StatisticsOptionalFetcher { - - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java deleted file mode 100644 index 9e2d7f807..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.ftclub.cabinet.admin.repository; - -import java.util.List; -import javax.transaction.Transactional; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -@Repository -@Transactional -public interface StatisticsRepository extends JpaRepository { - - @Query("SELECT COUNT(*) FROM Cabinet c WHERE c.status = :status AND c.cabinetPlace.location.floor = :floor") - Integer getCabinetsCountByStatus(@Param("floor") Integer floor, - @Param("status") CabinetStatus status); - - @Query("SELECT c.cabinetId FROM Cabinet c WHERE (c.status = 'LIMITED_AVAILABLE' or c.status = 'AVAILABLE') AND c.cabinetPlace.location.floor = :floor") - List getAvailableCabinetsId(@Param("floor") Integer floor); -} - - diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminCommandService.java similarity index 81% rename from backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/AdminCommandService.java index 6dcf513d8..6b2a179d9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminCommandService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.newService; +package org.ftclub.cabinet.admin.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java index 0bd90dd70..bc99a8f2a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.newService; +package org.ftclub.cabinet.admin.service; import static java.util.stream.Collectors.toList; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminQueryService.java similarity index 81% rename from backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/AdminQueryService.java index 1d6fa7148..6e32a848a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminQueryService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.newService; +package org.ftclub.cabinet.admin.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java deleted file mode 100644 index fedaa0bdf..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.ftclub.cabinet.admin.service; - -import org.ftclub.cabinet.dto.BlockedUserPaginationDto; -import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; -import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; - -import java.time.LocalDateTime; -import java.util.Date; -import java.util.List; - -public interface StatisticsFacadeService { - - List getCabinetsCountOnAllFloors(); - - LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDate, - LocalDateTime endDate); -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java deleted file mode 100644 index 35326ae3b..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.ftclub.cabinet.admin.service; - -import static org.ftclub.cabinet.utils.ExceptionUtil.throwIfFalse; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.admin.repository.StatisticsRepository; -import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; -import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.lent.repository.LentRepository; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Log4j2 -public class StatisticsFacadeServiceImpl implements StatisticsFacadeService { - - private final StatisticsRepository statisticsRepository; - private final CabinetOptionalFetcher cabinetOptionalFetcher; - private final LentRepository lentRepository; - - // TODO: 로직 수정 필요 - - /** - * @return - */ - @Override - public List getCabinetsCountOnAllFloors() { - log.debug("Called getCabinetsCountOnAllFloors"); - List cabinetFloorStatisticsResponseDtos = new ArrayList<>(); - List floors = cabinetOptionalFetcher.findAllFloorsByBuilding("새롬관"); - throwIfFalse(floors != null, new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); - for (Integer floor : floors) { - Integer used = statisticsRepository.getCabinetsCountByStatus(floor, CabinetStatus.FULL); - List availableCabinetsId = statisticsRepository.getAvailableCabinetsId(floor); - Integer unused = 0; - for (Long cabinetId : availableCabinetsId) { - if (lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId) > 0) { - used++; - } else { - unused++; - } - } - Integer overdue = statisticsRepository.getCabinetsCountByStatus(floor, - CabinetStatus.OVERDUE); - Integer disabled = statisticsRepository.getCabinetsCountByStatus(floor, - CabinetStatus.BROKEN); - Integer total = used + overdue + unused + disabled; - cabinetFloorStatisticsResponseDtos.add( - new CabinetFloorStatisticsResponseDto(floor, total, used, overdue, unused, - disabled)); - } - return cabinetFloorStatisticsResponseDtos; - } - - /** - * @param startDate - * @param endDate - * @return - */ - @Override - public LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDate, - LocalDateTime endDate) { - log.debug("Called getCountOnLentAndReturn"); - throwIfFalse(startDate.isBefore(endDate), - new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); - Integer lentStartCount = lentRepository.countLentFromStartDateToEndDate(startDate, endDate); - Integer lentEndCount = lentRepository.countReturnFromStartDateToEndDate(startDate, endDate); - return new LentsStatisticsResponseDto(startDate, endDate, lentStartCount, lentEndCount); - } -} diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java index 979d23feb..08d2a5401 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java @@ -2,7 +2,6 @@ import static org.mockito.Mockito.mock; -import org.ftclub.cabinet.admin.controller.StatisticsController; import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java index 078da47c9..3101c1547 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java @@ -10,7 +10,6 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.ftclub.cabinet.admin.service.StatisticsFacadeServiceImpl; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; From c9fbe0e7d829842241579f830f817424e10735a6 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 21 Dec 2023 20:41:18 +0900 Subject: [PATCH 0114/1029] =?UTF-8?q?[BE]=20FIX:=20=20config=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 290416e13..16f0fc214 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 290416e13fff18216998886ac1a2c5f6845c9253 +Subproject commit 16f0fc214f39facdea002e68561a809d5a9213e2 From 1a209143b12f8bcf726503cb5c7148e6b5f04745 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 21 Dec 2023 20:43:29 +0900 Subject: [PATCH 0115/1029] [BE] FIX: dev FCM duplicate config delete --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 290416e13..fb620b186 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 290416e13fff18216998886ac1a2c5f6845c9253 +Subproject commit fb620b186204dd0d50e02d8cdfe63bb94ad7c3b0 From a598ec7292e6c96a8ffc859a4584a0c2d3e11cda Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 21 Dec 2023 20:47:24 +0900 Subject: [PATCH 0116/1029] =?UTF-8?q?[FE]=20FIX:=20alarm=20=EC=9D=84=20?= =?UTF-8?q?=EC=98=A4=EB=B8=8C=EC=A0=9D=ED=8A=B8=EB=A1=9C=20=EB=A7=8C?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EC=84=9C=20=EC=95=8C=EB=A6=BC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=9D=B4=20=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/axios/axios.custom.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/api/axios/axios.custom.ts b/frontend/src/api/axios/axios.custom.ts index 211b62df2..fbf546793 100644 --- a/frontend/src/api/axios/axios.custom.ts +++ b/frontend/src/api/axios/axios.custom.ts @@ -62,9 +62,7 @@ export const axiosUseExtension = async (): Promise => { const axiosUpdateAlarmURL = "/v4/users/me/alarms"; export const axiosUpdateAlarm = async (alarm: AlarmInfo): Promise => { try { - const response = await instance.put(axiosUpdateAlarmURL, { - alarm, - }); + const response = await instance.put(axiosUpdateAlarmURL, alarm); return response; } catch (error) { throw error; From 0dc23f46261599f958541ec29c65fc0cdebf6f69 Mon Sep 17 00:00:00 2001 From: jiwon Date: Wed, 20 Dec 2023 14:41:59 +0900 Subject: [PATCH 0117/1029] create branch From 4a7b39d4e9089424adbacde378038c253c6ca4ba Mon Sep 17 00:00:00 2001 From: ldw Date: Wed, 20 Dec 2023 16:05:30 +0900 Subject: [PATCH 0118/1029] [BE] REFACTOR : first commit for refactor --- .../newService/CabinetCommandService.java | 10 +++ .../newService/CabinetFacadeService.java | 28 ++++++++ .../newService/CabinetQueryService.java | 28 ++++++++ .../lent/newService/LentCommandService.java | 14 ++++ .../lent/newService/LentFacadeService.java | 66 +++++++++++++++++++ .../lent/newService/LentQueryService.java | 38 +++++++++++ .../search/controller/SearchController.java | 2 - .../newService/BanHistoryCommandService.java | 12 ++++ .../newService/BanHistoryQueryService.java | 26 ++++++++ .../user/newService/UserCommandService.java | 21 ++++++ .../user/newService/UserFacadeService.java | 23 +++++++ .../user/newService/UserQueryService.java | 31 +++++++++ .../user/repository/UserOptionalFetcher.java | 4 +- .../user/repository/UserRepository.java | 2 +- .../user/repository/UserRepositoryTest.java | 2 +- 15 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java new file mode 100644 index 000000000..28ea6135b --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java @@ -0,0 +1,10 @@ +package org.ftclub.cabinet.cabinet.newService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CabinetCommandService { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java new file mode 100644 index 000000000..a8104f359 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -0,0 +1,28 @@ +package org.ftclub.cabinet.cabinet.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.dto.BuildingFloorsDto; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class CabinetFacadeService { + private final CabinetQueryService cabinetQueryService; + private final CabinetCommandService cabinetCommandService; + + /** + * {@inheritDoc} + *

+ * 존재하는 모든 건물들을 가져오고, 각 건물별 층 정보들을 가져옵니다. + */ + @Transactional(readOnly = true) + public List getBuildingFloorsResponse() { + log.debug("getBuildingFloorsResponse"); + + } + + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java new file mode 100644 index 000000000..687335a9b --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -0,0 +1,28 @@ +package org.ftclub.cabinet.cabinet.newService; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.repository.CabinetRepository; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class CabinetQueryService { + + private final CabinetRepository cabinetRepository; + + public Cabinet getCabinet(Long cabinetId) { + Optional cabinet = cabinetRepository.findById(cabinetId); + return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); + } + + public List findAllBuildings() { + log.debug("Called findAllBuildings"); + + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java new file mode 100644 index 000000000..7a406109a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java @@ -0,0 +1,14 @@ +package org.ftclub.cabinet.lent.newService; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.lent.repository.LentRedis; +import org.ftclub.cabinet.lent.repository.LentRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LentCommandService { + + private final LentRepository lentRepository; + private final LentRedis lentRedis; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java new file mode 100644 index 000000000..7e2b8c2e6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java @@ -0,0 +1,66 @@ +package org.ftclub.cabinet.lent.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentHistoryDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.mapper.LentMapper; +import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LentFacadeService { + + private final LentQueryService lentQueryService; + private final LentCommandService lentCommandService; + private final UserQueryService userQueryService; + private final CabinetQueryService cabinetQueryService; + + private final LentMapper lentMapper; + + + public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size) { + log.debug("Called getAllUserLentHistories: {}", userId); + userQueryService.getUser(userId); + + if (size <= 0) { + size = Integer.MAX_VALUE; + } + PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); + Page lentHistories = lentQueryService.findUserLentHistories(userId, pageable); + List result = lentHistories.stream() + .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) + .collect(Collectors.toList()); + return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); + } + + public List getLentDtoList(Long cabinetId) { + log.debug("Called getLentDtoList: {}", cabinetId); + cabinetQueryService.getCabinet(cabinetId); + +// cabinetOptionalFetcher.getCabinet(cabinetId); +// List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( +// cabinetId); +// return lentHistories.stream().map( +// e -> lentMapper.toLentDto(e.getUser(), e)).collect(Collectors.toList()); +// return lentHistories.stream() +// .map(e -> new LentDto( +// e.getUserId(), +// e.getUser().getName(), +// e.getLentHistoryId(), +// e.getStartedAt(), +// e.getExpiredAt())) +// .collect(Collectors.toList()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java new file mode 100644 index 000000000..13db26ab6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java @@ -0,0 +1,38 @@ +package org.ftclub.cabinet.lent.newService; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.repository.LentRedis; +import org.ftclub.cabinet.lent.repository.LentRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class LentQueryService { + + private final LentRepository lentRepository; + private final LentRedis lentRedis; + + public Page findUserLentHistories(Long userId, PageRequest pageable) { + return lentRepository.findPaginationByUserId(userId, pageable); + } + + public LentHistory findCabinetActiveLentHistory(Long cabinetId) { + List lentHistories = + lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); + if (lentHistories.size() >= 2) { + throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); + } + if (lentHistories.isEmpty()) { + return null; + } + return lentHistories.get(0); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java b/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java index 0e25e5c7b..e081fd786 100644 --- a/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java @@ -64,6 +64,4 @@ public UserCabinetPaginationDto getCabinetsLentInfo( log.info("Called getCabinetsLentInfo {}", name); return userFacadeService.findUserCabinetListByPartialName(name, page, size); } - - } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java new file mode 100644 index 000000000..71218a924 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class BanHistoryCommandService { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java new file mode 100644 index 000000000..d5599237c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -0,0 +1,26 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.repository.BanHistoryRepository; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class BanHistoryQueryService { + + private final BanHistoryRepository banHistoryRepository; + + public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { + log.debug("Called findRecentActiveBanHistory: {}", userId); + + List banHistories = banHistoryRepository.findAll(); + + } + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java new file mode 100644 index 000000000..06aace4e8 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class UserCommandService { + + + + + + + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java new file mode 100644 index 000000000..4b58d6676 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -0,0 +1,23 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.dto.MyProfileResponseDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class UserFacadeService { + + public MyProfileResponseDto getMyProfile(UserSessionDto user) { + log.debug("Called getMyProfile: {}", user.getName()); + + // Cabinet cabinet = lentService.findActiveLentCabinetByUserId(); + + + } + +} + diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java new file mode 100644 index 000000000..1d30c10ef --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -0,0 +1,31 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.repository.BanHistoryRepository; +import org.ftclub.cabinet.user.repository.UserRepository; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class UserQueryService { + + private final UserRepository userRepository; + + public User getUser(Long userId) { + Optional user = userRepository.findById(userId); + return user.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } + + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index d1cd4ba63..ca96376fe 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -53,7 +53,7 @@ public List findAllActiveUsers() { */ public User findUser(Long userId) { log.debug("Called findUser: {}", userId); - return userRepository.findUser(userId).orElse(null); + return userRepository.findById(userId).orElse(null); } /** @@ -172,7 +172,7 @@ public Page findClubUsers(Pageable pageable) { */ public User getUser(Long userId) { log.debug("Called getUser: {}", userId); - return userRepository.findUser(userId) + return userRepository.findById(userId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index fc940908e..61187fdcf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -21,7 +21,7 @@ public interface UserRepository extends JpaRepository { * @return {@link User} */ @Query("SELECT u FROM User u WHERE u.userId = :userId AND u.deletedAt IS NULL") - Optional findUser(@Param("userId") Long userId); + Optional findById(@Param("userId") Long userId); /** * 유저 이름으로 유저를 찾습니다. diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java index a1690f077..2cf6d441c 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java @@ -37,7 +37,7 @@ public class UserRepositoryTest { @Test public void testGetUser() { Long userId = 9L; - Optional user = userRepository.findUser(userId); + Optional user = userRepository.findById(userId); assertTrue(user.isPresent()); assertEquals("user1", user.get().getName()); From 3228e61eb0371bf5c1c3446c21b051db6aaf29ac Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Wed, 20 Dec 2023 21:29:10 +0900 Subject: [PATCH 0119/1029] =?UTF-8?q?fix:=20challenge=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 28 +++---------- .../auth/service/AuthFacadeService.java | 16 ++++--- .../auth/service/AuthFacadeServiceImpl.java | 42 ++++--------------- 3 files changed, 22 insertions(+), 64 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java index ba4ece08e..ca51d7fe1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java @@ -1,9 +1,5 @@ package org.ftclub.cabinet.auth.controller; -import java.io.IOException; -import java.time.LocalDateTime; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.CookieManager; import org.ftclub.cabinet.auth.domain.TokenProvider; @@ -17,6 +13,11 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; + @RestController @RequestMapping("/v4/auth") @RequiredArgsConstructor @@ -60,7 +61,7 @@ public void login(HttpServletResponse response) throws IOException { */ @GetMapping("/login/callback") public void loginCallback(@RequestParam String code, HttpServletRequest req, - HttpServletResponse res) throws IOException { + HttpServletResponse res) throws IOException { authFacadeService.handleLogin(code, req, res, ftApiProperties, LocalDateTime.now()); res.sendRedirect(DomainProperties.getFeHost() + "/home"); } @@ -74,21 +75,4 @@ public void loginCallback(@RequestParam String code, HttpServletRequest req, public void logout(HttpServletResponse res) { authFacadeService.logout(res, ftApiProperties); } - - @GetMapping("/challenge") - public void challengeLogin(HttpServletRequest req, HttpServletResponse res) throws IOException { - // 유저 랜덤생성 && 유저 역할은 일반 유저 - // 토큰 생성 및 response에 쿠키만들어서 심어주기 - authFacadeService.handleTestLogin(req, ftApiProperties, res, LocalDateTime.now()); - System.out.println("??????????????here??????????????????/"); - res.sendRedirect(DomainProperties.getFeHost() + "/home"); -// Map claims = tokenProvider -// String accessToken = tokenProvider.createTokenForTestUser(testUser, LocalDateTime.now()); -// Cookie cookie = cookieManager.cookieOf("access_token", -// accessToken); -// cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); -// System.out.println("?????????????????????? cookie domain = "); -// res.sendRedirect(DomainProperties.getLocal() + "/main"); -// res.sendRedirect(DomainProperties.getFeHost() + "/home"); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index f9670edfc..7026403f9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -1,24 +1,22 @@ package org.ftclub.cabinet.auth.service; -import java.io.IOException; -import java.time.LocalDateTime; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.ftclub.cabinet.config.ApiProperties; import org.ftclub.cabinet.dto.MasterLoginDto; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; + public interface AuthFacadeService { void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) throws IOException; void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, - ApiProperties apiProperties, LocalDateTime now); + ApiProperties apiProperties, LocalDateTime now); void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now); + HttpServletResponse res, LocalDateTime now); void logout(HttpServletResponse res, ApiProperties apiProperties); - - void handleTestLogin(HttpServletRequest req, ApiProperties apiProperties, - HttpServletResponse res, LocalDateTime now); } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java index cd2147986..8aea67960 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java @@ -1,13 +1,6 @@ package org.ftclub.cabinet.auth.service; import com.fasterxml.jackson.databind.JsonNode; -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.CookieManager; import org.ftclub.cabinet.auth.domain.TokenProvider; @@ -16,11 +9,15 @@ import org.ftclub.cabinet.dto.MasterLoginDto; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.user.repository.UserOptionalFetcher; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Map; + @Service @RequiredArgsConstructor public class AuthFacadeServiceImpl implements AuthFacadeService { @@ -31,9 +28,6 @@ public class AuthFacadeServiceImpl implements AuthFacadeService { private final AuthService authService; private final OauthService oauthService; - private final UserOptionalFetcher userOptionalFetcher; - - @Override public void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) throws IOException { @@ -42,7 +36,7 @@ public void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperti @Override public void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, - ApiProperties apiProperties, LocalDateTime now) { + ApiProperties apiProperties, LocalDateTime now) { String apiToken = oauthService.getTokenByCodeRequest(code, apiProperties); JsonNode profile = oauthService.getProfileJsonByToken(apiToken, apiProperties); Map claims = tokenProvider.makeClaimsByProviderProfile( @@ -54,27 +48,9 @@ public void handleLogin(String code, HttpServletRequest req, HttpServletResponse cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); } - @Override - public void handleTestLogin(HttpServletRequest req, ApiProperties apiProperties, - HttpServletResponse res, LocalDateTime now) { - Map claims = new HashMap<>(); - claims.put("name", - "Test:" + userOptionalFetcher.findUsersByPartialName("Test:", Pageable.unpaged()) - .getTotalElements()); - claims.put("email", userOptionalFetcher.findUsersByPartialName("Test:", Pageable.unpaged()) - .getTotalElements() + "Test@student.42seoul.kr"); - claims.put("blackholedAt", null); - claims.put("role", UserRole.USER); - authService.addUserIfNotExistsByClaims(claims); - String accessToken = tokenProvider.createToken(claims, now); - Cookie cookie = cookieManager.cookieOf( - tokenProvider.getTokenNameByProvider(apiProperties.getProviderName()), accessToken); - cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); - } - @Override public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now) { + HttpServletResponse res, LocalDateTime now) { if (!authService.validateMasterLogin(masterLoginDto)) { throw new ControllerException(ExceptionStatus.UNAUTHORIZED); } From 0e0062ac1df212e833d4f83b645ae296605e01df Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 13:23:08 +0900 Subject: [PATCH 0120/1029] =?UTF-8?q?[BE]=20lent=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EB=8F=99=EC=9E=91=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminCabinetController.java | 2 +- .../cabinet/cabinet/domain/Cabinet.java | 21 - .../newService/CabinetCommandService.java | 37 ++ .../newService/CabinetFacadeService.java | 10 +- .../newService/CabinetQueryService.java | 42 +- .../repository/CabinetOptionalFetcher.java | 10 +- .../cabinet/repository/CabinetRepository.java | 33 +- .../service/CabinetFacadeServiceImpl.java | 68 +++- .../cabinet/dto/UserVerifyRequestDto.java | 21 + .../cabinet/exception/ExceptionStatus.java | 1 + .../lent/controller/AdminLentController.java | 15 +- .../lent/controller/LentController.java | 41 +- .../cabinet/lent/domain/LentPolicy.java | 92 ----- .../cabinet/lent/domain/LentPolicyImpl.java | 256 ------------ .../lent/newService/LentCommandService.java | 30 +- .../lent/newService/LentFacadeService.java | 382 +++++++++++++++--- .../lent/newService/LentPolicyService.java | 186 +++++++++ .../lent/newService/LentQueryService.java | 50 +-- .../lent/newService/LentRedisService.java | 119 ++++++ .../lent/repository/LentOptionalFetcher.java | 9 +- .../cabinet/lent/repository/LentRedis.java | 198 +++++---- .../lent/repository/LentRepository.java | 17 +- .../lent/service/LentFacadeService.java | 156 ------- .../lent/service/LentFacadeServiceImpl.java | 261 ------------ .../cabinet/lent/service/LentService.java | 93 ----- .../cabinet/redis/ExpirationListener.java | 13 +- .../user/controller/AdminUserController.java | 201 +++++---- .../newService/BanHistoryCommandService.java | 11 + .../newService/BanHistoryQueryService.java | 22 +- .../user/newService/BanPolicyService.java | 29 ++ .../user/newService/UserFacadeService.java | 16 +- .../user/newService/UserQueryService.java | 22 +- .../user/repository/BanHistoryRepository.java | 2 +- .../user/repository/UserRepository.java | 10 + .../cabinet/user/service/UserServiceImpl.java | 2 +- .../org/ftclub/cabinet/utils/DateUtil.java | 4 + .../blackhole/manager/BlackholeManager.java | 22 +- .../leave/absence/LeaveAbsenceManager.java | 8 +- .../utils/scheduler/SystemScheduler.java | 6 +- .../cabinet/redis/RedisRepositoryTest.java | 4 +- .../repository/BanHistoryRepositoryTest.java | 2 +- .../user/service/UserServiceUnitTest.java | 25 +- 42 files changed, 1218 insertions(+), 1331 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicy.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyImpl.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/LentService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java index 6bcc99d9a..5adb31b22 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java @@ -17,7 +17,7 @@ import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Cabinet.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Cabinet.java index a979beb2f..1da699ad5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Cabinet.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/domain/Cabinet.java @@ -212,25 +212,4 @@ public boolean equals(final Object other) { public int hashCode() { return Objects.hash(this.cabinetId); } - - /** - * 대여 시작/종료에 따른 사용자의 수와 현재 상태에 따라 상태를 변경합니다. - * - * @param userCount 현재 사용자 수 - */ - public void specifyStatusByUserCount(Integer userCount) { - log.info("specifyStatusByUserCount : {}", userCount); - if (this.status.equals(CabinetStatus.BROKEN)) { - throw new DomainException(INVALID_STATUS); - } - if (userCount.equals(0)) { - this.status = CabinetStatus.PENDING; -// this.status = CabinetStatus.AVAILABLE; - return; - } - if (userCount.equals(this.maxUser)) { - this.status = CabinetStatus.FULL; - return; - } - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java index 28ea6135b..88fa7b181 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java @@ -1,10 +1,47 @@ package org.ftclub.cabinet.cabinet.newService; +import static org.ftclub.cabinet.exception.ExceptionStatus.INVALID_STATUS; + import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.repository.CabinetRepository; +import org.ftclub.cabinet.exception.DomainException; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class CabinetCommandService { + private final CabinetRepository cabinetRepository; + + public void changeStatus(Cabinet cabinet, CabinetStatus cabinetStatus) { + cabinet.specifyStatus(cabinetStatus); + cabinetRepository.save(cabinet); + } + + public void changeUserCount(Cabinet cabinet, int userCount) { + if (cabinet.isStatus(CabinetStatus.BROKEN)) { + throw new DomainException(INVALID_STATUS); + } + if (userCount == 0) { + cabinet.specifyStatus(CabinetStatus.PENDING); + cabinet.writeMemo(""); + cabinet.writeTitle(""); + } + if (userCount == cabinet.getMaxUser()) { + cabinet.specifyStatus(CabinetStatus.FULL); + } + cabinetRepository.save(cabinet); + } + + public void updateTitle(Cabinet cabinet, String title) { + cabinet.writeTitle(title); + cabinetRepository.save(cabinet); + } + + public void updateMemo(Cabinet cabinet, String memo) { + cabinet.writeMemo(memo); + cabinetRepository.save(cabinet); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index a8104f359..872b505fa 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -18,11 +18,11 @@ public class CabinetFacadeService { *

* 존재하는 모든 건물들을 가져오고, 각 건물별 층 정보들을 가져옵니다. */ - @Transactional(readOnly = true) - public List getBuildingFloorsResponse() { - log.debug("getBuildingFloorsResponse"); - - } +// @Transactional(readOnly = true) +// public List getBuildingFloorsResponse() { +// log.debug("getBuildingFloorsResponse"); +// +// } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 687335a9b..9c41def37 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -1,28 +1,48 @@ package org.ftclub.cabinet.cabinet.newService; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; -import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.springframework.stereotype.Service; -import java.util.Optional; - @Service @RequiredArgsConstructor public class CabinetQueryService { - private final CabinetRepository cabinetRepository; + private final CabinetRepository cabinetRepository; + + public Cabinet getCabinet(Long cabinetId) { + Optional cabinet = cabinetRepository.findById(cabinetId); + return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); + } + + public Cabinet getCabinetWithLock(Long cabinetId) { + Optional cabinet = cabinetRepository.findByIdWithLock(cabinetId); + return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); + } + + public List getCabinetWithLock(List cabinetIds) { + return cabinetRepository.findAllByIdsWithLock(cabinetIds); + } - public Cabinet getCabinet(Long cabinetId) { - Optional cabinet = cabinetRepository.findById(cabinetId); - return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); - } + public Cabinet getUserActiveCabinetWithLock(Long userId) { + Optional cabinet = + cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNullWithLock(userId); + return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); + } - public List findAllBuildings() { - log.debug("Called findAllBuildings"); + public Cabinet findUserActiveCabinet(Long userId) { + Optional cabinet = + cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId); + return cabinet.orElse(null); + } - } +// public List findAllBuildings() { +// log.debug("Called findAllBuildings"); +// +// } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java index 98834b716..4966939d4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetOptionalFetcher.java @@ -49,7 +49,7 @@ public List findCabinetsActiveLentHistoriesByBuilding */ public Cabinet findLentCabinetByUserId(Long userId) { log.debug("Called findLentCabinetByUserId: {}", userId); - return cabinetRepository.findByUserIdAndEndedAtIsNull(userId).orElse(null); + return cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId).orElse(null); } public List findAllBuildings() { @@ -103,7 +103,7 @@ public List findAllCabinetsByBuildingAndFloor(String building, Integer */ public Cabinet getCabinetForUpdate(Long cabinetId) { log.debug("Called getCabinetForUpdate: {}", cabinetId); - return cabinetRepository.findByCabinetIdForUpdate(cabinetId) + return cabinetRepository.findByIdWithLock(cabinetId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } @@ -129,7 +129,7 @@ public Cabinet getCabinet(Long cabinetId) { */ public Cabinet getLentCabinetByUserId(Long userId) { log.debug("Called getLentCabinetByUserId: {}", userId); - return cabinetRepository.findByUserIdAndEndedAtIsNull(userId) + return cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } @@ -153,8 +153,8 @@ public Cabinet getClubCabinet(Long cabinetId) { /** * building과 status에 맞고 lentType이 아닌 사물함을 찾습니다. * - * @param building 건물 이름 - * @param lentType 사물함 타입 + * @param building 건물 이름 + * @param lentType 사물함 타입 * @param cabinetStatuses 사물함 상태 {@link List} * @return {@link Cabinet} {@link List} */ diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 9daf9672a..bbc6b8859 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -47,7 +47,32 @@ public interface CabinetRepository extends JpaRepository, Cabinet @Query("SELECT c " + "FROM Cabinet c " + "WHERE c.cabinetId = :cabinetId") - Optional findByCabinetIdForUpdate(@Param("cabinetId") Long cabinetId); + Optional findByIdWithLock(@Param("cabinetId") Long cabinetId); + + /** + * cabinetId 리스트로 사물함을 조회한다.(조회 이후 업데이트를 위해 X Lock을 건다.) + * + * @param cabinetIds 사물함 ID 리스트 + * @return 사물함 {@link List} + */ + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("SELECT c " + + "FROM Cabinet c " + + "WHERE c.cabinetId IN (:cabinetIds)") + List findAllByIdsWithLock(List cabinetIds); + + /** + * userId로 현재 대여 중인 사물함을 조회한다. + * + * @param userId 사용자 ID + * @return 사물함 {@link Optional} + */ + @Query("SELECT c " + + "FROM Cabinet c " + + "LEFT JOIN LentHistory lh ON c.cabinetId = lh.cabinetId " + + "LEFT JOIN User u ON u.userId = lh.userId " + + "WHERE u.userId = :userId AND lh.endedAt IS NULL") + Optional findByUserIdAndLentHistoryEndedAtIsNull(@Param("userId") Long userId); /** * userId로 현재 대여 중인 사물함을 조회한다. @@ -55,18 +80,20 @@ public interface CabinetRepository extends JpaRepository, Cabinet * @param userId 사용자 ID * @return 사물함 {@link Optional} */ + @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("SELECT c " + "FROM Cabinet c " + "LEFT JOIN LentHistory lh ON c.cabinetId = lh.cabinetId " + "LEFT JOIN User u ON u.userId = lh.userId " + "WHERE u.userId = :userId AND lh.endedAt IS NULL") - Optional findByUserIdAndEndedAtIsNull(@Param("userId") Long userId); + Optional findByUserIdAndLentHistoryEndedAtIsNullWithLock(@Param("userId") Long userId); Page findPaginationByLentType(@Param("lentType") LentType lentType, Pageable pageable); Page findPaginationByStatus(@Param("status") CabinetStatus status, Pageable pageable); - Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, Pageable pageable); + Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, + Pageable pageable); @Query("SELECT c " + "FROM Cabinet c " diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index d31c5d8e7..05363483c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -1,5 +1,20 @@ package org.ftclub.cabinet.cabinet.service; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toMap; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -7,7 +22,22 @@ import org.ftclub.cabinet.cabinet.domain.Grid; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; +import org.ftclub.cabinet.dto.BuildingFloorsDto; +import org.ftclub.cabinet.dto.CabinetClubStatusRequestDto; +import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetPaginationDto; +import org.ftclub.cabinet.dto.CabinetPendingResponseDto; +import org.ftclub.cabinet.dto.CabinetPreviewDto; +import org.ftclub.cabinet.dto.CabinetSimpleDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.CabinetStatusRequestDto; +import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentHistoryDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; import org.ftclub.cabinet.lent.repository.LentRedis; @@ -21,15 +51,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.*; -import java.util.stream.Collectors; - -import static java.util.stream.Collectors.*; -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; - @Service @RequiredArgsConstructor @Log4j2 @@ -70,9 +91,10 @@ public List getBuildingFloorsResponse() { public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { log.debug("getCabinetInfo"); List lentDtos = new ArrayList<>(); - List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId(cabinetId); + List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( + cabinetId); if (lentHistories.isEmpty()) { - ArrayList users = lentRedis.getUserIdsByCabinetIdInRedis(cabinetId.toString()); + List users = lentRedis.getAllUserInCabinet(cabinetId.toString()); for (String user : users) { String userName = userOptionalFetcher.findUser(Long.valueOf(user)).getName(); lentDtos.add(new LentDto(Long.valueOf(user), userName, null, null, null)); @@ -83,7 +105,7 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { lentDtos.add(lentMapper.toLentDto(findUser, lentHistory)); } return cabinetMapper.toCabinetInfoResponseDto(cabinetOptionalFetcher.findCabinet(cabinetId), - lentDtos, lentRedis.getSessionExpiredAtInRedis(cabinetId)); + lentDtos, lentRedis.getCabinetExpiredAt(cabinetId.toString())); } /** @@ -118,7 +140,8 @@ private String checkCabinetTitle(Cabinet cabinet, List lentHistorie */ @Override @Transactional(readOnly = true) - public List getCabinetsPerSection(String building, Integer floor) { + public List getCabinetsPerSection(String building, + Integer floor) { log.debug("getCabinetsPerSection"); List currentLentCabinets = cabinetOptionalFetcher .findCabinetsActiveLentHistoriesByBuildingAndFloor(building, floor); @@ -138,10 +161,12 @@ public List getCabinetsPerSection(String building cabinetLentHistories.getOrDefault(cabinet, Collections.emptyList()); String title = checkCabinetTitle(cabinet, lentHistories); cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()) - .add(cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), title)); + .add(cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), + title)); }); return cabinetPreviewsBySection.entrySet().stream() - .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), entry.getValue())) + .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), + entry.getValue())) .collect(Collectors.toList()); } @@ -151,7 +176,7 @@ public List getCabinetsPerSection(String building @Override @Transactional(readOnly = true) public CabinetPaginationDto getCabinetPaginationByLentType(LentType lentType, Integer page, - Integer size) { + Integer size) { log.debug("getCabinetPaginationByLentType"); if (size <= 0) { size = Integer.MAX_VALUE; @@ -172,7 +197,7 @@ public CabinetPaginationDto getCabinetPaginationByLentType(LentType lentType, In @Override @Transactional(readOnly = true) public CabinetPaginationDto getCabinetPaginationByStatus(CabinetStatus status, Integer page, - Integer size) { + Integer size) { log.debug("getCabinetPaginationByStatus"); if (size <= 0) { size = Integer.MAX_VALUE; @@ -192,7 +217,7 @@ public CabinetPaginationDto getCabinetPaginationByStatus(CabinetStatus status, I @Override @Transactional(readOnly = true) public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, Integer page, - Integer size) { + Integer size) { log.debug("getCabinetPaginationByVisibleNum"); if (size <= 0) { size = Integer.MAX_VALUE; @@ -212,7 +237,7 @@ public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, @Override @Transactional(readOnly = true) public LentHistoryPaginationDto getCabinetLentHistoriesPagination(Long cabinetId, Integer page, - Integer size) { + Integer size) { log.debug("getCabinetLentHistoriesPagination"); if (size <= 0) { size = Integer.MAX_VALUE; @@ -310,7 +335,8 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { .map(LentHistory::getEndedAt) .max(LocalDateTime::compareTo).orElse(null); if (latestEndedAt != null && latestEndedAt.toLocalDate().isEqual(yesterday)) { - cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); + cabinetFloorMap.get(floor) + .add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); } } }); diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java new file mode 100644 index 000000000..36e50e8f3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.UserRole; + +@Getter +@AllArgsConstructor +public class UserVerifyRequestDto { + + private UserRole userRole; + private LocalDateTime blackholedAt; + private int lentCount; + private Long cabinetId; + private CabinetStatus cabinetStatus; + private List banHistories; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index 4b8b9a730..ea84bfaa0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -54,6 +54,7 @@ public enum ExceptionStatus { SLACK_MESSAGE_SEND_BAD_GATEWAY(HttpStatus.BAD_GATEWAY, "슬랙 메세지 전송 중 에러가 발생했습니다"), SLACK_ID_NOT_FOUND(HttpStatus.NOT_FOUND, "슬랙 아이디를 찾을 수 없습니다."), NOT_FOUND_ALARM(HttpStatus.BAD_REQUEST, "알람이 존재하지 않습니다"), + INVALID_LENT_TYPE(HttpStatus.BAD_REQUEST, "사물함의 대여 타입이 유효하지 않습니다."), ; final private int statusCode; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java index cfc88625d..a3389c891 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java @@ -7,10 +7,9 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; 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; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -29,21 +28,13 @@ public void terminateLentCabinets( @Valid @RequestBody ReturnCabinetsRequestDto returnCabinetsRequestDto) { log.info("Called terminateLentCabinets returnCabinetsRequestDto={}", returnCabinetsRequestDto); - lentFacadeService.terminateLentCabinets(returnCabinetsRequestDto); + lentFacadeService.endCabinetLent(returnCabinetsRequestDto.getCabinetIds()); } @PatchMapping("/return-users/{userId}") @AuthGuard(level = ADMIN_ONLY) public void terminateLentUser(@PathVariable("userId") Long userId) { log.info("Called terminateLentUser userId={}", userId); - lentFacadeService.terminateLentCabinet(userId); - } - - @PostMapping("lent/users/{userId}/cabinets/{cabinetId}") - @AuthGuard(level = ADMIN_ONLY) - public void assignLent(@PathVariable("userId") Long userId, - @PathVariable("cabinetId") Long cabinetId) { - log.info("Called assignLent userId={} cabinetId={}", userId, cabinetId); - lentFacadeService.assignLent(userId, cabinetId); + lentFacadeService.endUserLent(userId); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java index b63617c32..eab419be2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java @@ -8,11 +8,10 @@ import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.dto.MyCabinetResponseDto; import org.ftclub.cabinet.dto.ShareCodeDto; -import org.ftclub.cabinet.dto.UpdateCabinetMemoDto; -import org.ftclub.cabinet.dto.UpdateCabinetTitleDto; import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.user.domain.UserSession; +import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -21,7 +20,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -39,6 +37,7 @@ public void startLentCabinet( log.info("Called startLentCabinet user: {}, cabinetId: {}", user, cabinetId); lentFacadeService.startLentCabinet(user.getUserId(), cabinetId); } + @PostMapping("/cabinets/share/{cabinetId}") public void startLentShareCabinet( @UserSession UserSessionDto user, @@ -54,14 +53,14 @@ public void cancelLentShareCabinet( @UserSession UserSessionDto user, @PathVariable Long cabinetId) { log.info("Called cancelLentShareCabinet user: {}, cabinetId: {}", user, cabinetId); - lentFacadeService.cancelLentShareCabinet(user.getUserId(), cabinetId); + lentFacadeService.cancelShareCabinetLent(user.getUserId(), cabinetId); } @PatchMapping("/return") public void endLent( @UserSession UserSessionDto userSessionDto) { log.info("Called endLent user: {}", userSessionDto); - lentFacadeService.endLentCabinet(userSessionDto); + lentFacadeService.endUserLent(userSessionDto.getUserId()); } @PatchMapping("/return-memo") @@ -70,25 +69,7 @@ public void endLentWithMemo( @Valid @RequestBody LentEndMemoDto lentEndMemoDto) { log.info("Called endLentWithMemo user: {}, lentEndMemoDto: {}", userSessionDto, lentEndMemoDto); - lentFacadeService.endLentCabinetWithMemo(userSessionDto, lentEndMemoDto); - } - - @PatchMapping("/me/memo") - public void updateCabinetMemo( - @UserSession UserSessionDto user, - @Valid @RequestBody UpdateCabinetMemoDto updateCabinetMemoDto) { - log.info("Called updateCabinetMemo user: {}, updateCabinetMemoDto: {}", user, - updateCabinetMemoDto); - lentFacadeService.updateCabinetMemo(user, updateCabinetMemoDto); - } - - @PatchMapping("/me/cabinet-title") - public void updateCabinetTitle( - @UserSession UserSessionDto user, - @Valid @RequestBody UpdateCabinetTitleDto updateCabinetTitleDto) { - log.info("Called updateCabinetTitle user: {}, updateCabinetTitleDto: {}", user, - updateCabinetTitleDto); - lentFacadeService.updateCabinetTitle(user, updateCabinetTitleDto); + lentFacadeService.endUserLent(userSessionDto.getUserId(), lentEndMemoDto.getCabinetMemo()); } @PatchMapping("/me/cabinet") @@ -97,7 +78,8 @@ public void updateCabinetInfo( @RequestBody CabinetInfoRequestDto cabinetInfoRequestDto) { log.info("Called updateCabinetInfo user: {}, cabinetInfoRequestDto: {}", user, cabinetInfoRequestDto); - lentFacadeService.updateCabinetInfo(user, cabinetInfoRequestDto); + lentFacadeService.updateLentCabinetInfo(user.getUserId(), + cabinetInfoRequestDto.getTitle(), cabinetInfoRequestDto.getMemo()); } @GetMapping("/me") @@ -114,9 +96,8 @@ public ResponseEntity getMyLentInfo( @GetMapping("/me/histories") public LentHistoryPaginationDto getMyLentLog( @UserSession UserSessionDto user, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getMyLentLog user: {}, page: {}, size: {}", user, page, size); - return lentFacadeService.getMyLentLog(user, page, size); + @RequestBody @Valid PageRequest pageRequest) { + log.info("Called getMyLentLog user: {}, pageable: {}", user, pageRequest); + return lentFacadeService.getMyLentLog(user, pageRequest); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicy.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicy.java deleted file mode 100644 index 851324d30..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicy.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.ftclub.cabinet.lent.domain; - -import java.time.LocalDateTime; -import java.util.List; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.User; - -/** - * lent정책과 관련된 class - */ -public interface LentPolicy { - - /** - * @param now : 현재 시각 - * @param totalUserCount : 공유사물함에 성공적으로 등록된 유저 수 - * @return - */ - LocalDateTime generateSharedCabinetExpirationDate(LocalDateTime now, - Integer totalUserCount); - - /** - * 적절한 만료일을 만들어냅니다 - * - * @param now 현재 시각 - * @param cabinet 대여하려는 사물함 - * @return cabinet을 빌릴 때 들어가야 하는 만료일 - */ - LocalDateTime generateExpirationDate(LocalDateTime now, Cabinet cabinet); - - /** - * @param now - * @return - */ - LocalDateTime generateExtendedExpirationDate(LocalDateTime now); - - /** - * 만료일을 @{@link LentHistory}에 적용시킵니다. 현재와 과거의 기록들에 적용합니다. - * - * @param curHistory 현재 대여 기록 - * @param expiredAt 적용하려는 만료일 - */ - void applyExpirationDate(LentHistory curHistory, LocalDateTime expiredAt); - - /** - * 대여할 수 있는 유저인지 확인합니다. - * - * @param user 대여를 하려는 유저 - * @param cabinet 대여를 하려는 사물함 - * @param userActiveLentCount 유저가 빌리고 있는 사물함 개수 - * @param userActiveBanList 유저의 벤 당하고 있는 리스트 없다면 빈 리스트 - * @return {@link LentPolicyStatus} 현재 대여의 상태 - */ - LentPolicyStatus verifyUserForLent(User user, Cabinet cabinet, int userActiveLentCount, - List userActiveBanList); - - LentPolicyStatus verifyUserForLentShare(User user, Cabinet cabinet, int userActiveLentCount, - List userActiveBanList); - - /** - * 대여할 수 있는 사물함인지 확인합니다. - * - * @param cabinet 대여하려는 사물함 - * @return {@link LentPolicyStatus} - */ - LentPolicyStatus verifyCabinetForLent(Cabinet cabinet); - - /** - * @return 개인 사물함을 대여 할 수 있는 날 - */ - Integer getDaysForLentTermPrivate(); - - /** - * @return 공유 사물함을 대여 할 수 있는 날 - */ - Integer getDaysForLentTermShare(Integer totalUserCount); - - /** - * @return 만료가 임박하여 공유 사물함을 빌릴 수 없는 날 - */ - Integer getDaysForNearExpiration(); - - /** - * 정책에 대한 결과 상태({@link LentPolicyStatus})에 맞는 적절한 {@link ServiceException}을 throw합니다. - * - * @param status 정책에 대한 결과 상태 - * @param banHistory 유저의 ban history - */ - void handlePolicyStatus(LentPolicyStatus status, List banHistory); - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyImpl.java deleted file mode 100644 index 940687816..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyImpl.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.ftclub.cabinet.lent.domain; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.config.CabinetProperties; -import org.ftclub.cabinet.dto.UserBlackholeInfoDto; -import org.ftclub.cabinet.exception.CustomExceptionStatus; -import org.ftclub.cabinet.exception.CustomServiceException; -import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.lent.repository.LentRedis; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.utils.DateUtil; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -@Log4j2 -public class LentPolicyImpl implements LentPolicy { - - private final CabinetProperties cabinetProperties; - private final ApplicationEventPublisher publisher; - private final LentRedis lentRedis; - - @Override - public LocalDateTime generateSharedCabinetExpirationDate(LocalDateTime now, - Integer totalUserCount) { - log.info("Called generateSharedCabinetExpirationDate now: {}, totalUserCount: {}", now, - totalUserCount); - return now.plusDays(getDaysForLentTermShare(totalUserCount)) - .withHour(23) - .withMinute(59) - .withSecond(0); - } - - @Override - public LocalDateTime generateExpirationDate(LocalDateTime now, Cabinet cabinet) { - log.info("Called generateExpirationDate now: {}, cabinet: {}", now, cabinet); - - if (!DateUtil.isSameDay(now)) { - throw new IllegalArgumentException("현재 시각이 아닙니다."); - } - - LentType lentType = cabinet.getLentType(); - switch (lentType) { - case PRIVATE: - return now.plusDays(getDaysForLentTermPrivate()) - .withHour(23) - .withMinute(59) - .withSecond(59); - case CLUB: - return DateUtil.getInfinityDate(); - } - throw new IllegalArgumentException("대여 상태가 잘못되었습니다."); - } - - @Override - public LocalDateTime generateExtendedExpirationDate(LocalDateTime now) { - log.info("Called generateExtendedExpirationDate now: {}, cabinet: {}", now); - if (DateUtil.isPast(now)) { - throw new DomainException(ExceptionStatus.LENT_EXPIRED); - } - return now.plusDays(getDaysForLentTermPrivate()) - .withHour(23) - .withMinute(59) - .withSecond(0); - } - - @Override - public void applyExpirationDate(LentHistory curHistory, LocalDateTime expiredAt) { - log.info( - "Called applyExpirationDate curHistory: {}, expiredAt: {}", curHistory, expiredAt); - if (expiredAt == null) { - throw new DomainException(ExceptionStatus.INVALID_ARGUMENT); - } - if (DateUtil.isPast(expiredAt)) { - throw new DomainException(ExceptionStatus.INVALID_EXPIRED_AT); - } - curHistory.setExpiredAt(expiredAt); - } - - @Override - public LentPolicyStatus verifyUserForLent(User user, Cabinet cabinet, int userActiveLentCount, - List userActiveBanList) { - log.debug("Called verifyUserForLent"); - if (!user.isUserRole(UserRole.USER)) { - return LentPolicyStatus.NOT_USER; - } - if (userActiveLentCount != 0) { - return LentPolicyStatus.ALREADY_LENT_USER; - } - if (user.getBlackholedAt() != null && user.getBlackholedAt() - .isBefore(LocalDateTime.now())) { - publisher.publishEvent(UserBlackholeInfoDto.of(user)); - if (user.getBlackholedAt() != null && user.getBlackholedAt() - .isBefore(LocalDateTime.now())) { - return LentPolicyStatus.BLACKHOLED_USER; - } - } - - // 유저가 페널티 2 종류 이상 받을 수 있나? <- 실제로 그럴리 없지만 lentPolicy 객체는 그런 사실을 모르고, 유연하게 구현? - if (userActiveBanList == null || userActiveBanList.isEmpty()) { - return LentPolicyStatus.FINE; - } - LentPolicyStatus ret = LentPolicyStatus.FINE; - for (BanHistory banHistory : userActiveBanList) { - switch (banHistory.getBanType()) { - case ALL: - return LentPolicyStatus.ALL_BANNED_USER; - case SHARE: - if (cabinet.isLentType(LentType.SHARE)) { - ret = LentPolicyStatus.SHARE_BANNED_USER; - } - break; - default: - break; - } - } - return ret; - } - - @Override - public LentPolicyStatus verifyUserForLentShare(User user, Cabinet cabinet, - int userActiveLentCount, - List userActiveBanList) { - - LentPolicyStatus ret = verifyUserForLent(user, cabinet, userActiveLentCount, - userActiveBanList); - - // 유저가 패스워드를 3번 이상 틀린 경우 - Long cabinetId = cabinet.getCabinetId(); - Long userId = user.getUserId(); - // 사물함을 빌릴 수 있는 유저라면 공유 사물함 비밀번호 입력 횟수를 확인 - if (ret == LentPolicyStatus.FINE && lentRedis.isShadowKey( - cabinet.getCabinetId())) { - String passwordCount = lentRedis.getPwTrialCountInRedis( - cabinetId.toString(), - userId.toString()); - // 사물함을 빌릴 수 있는 유저면서, 해당 공유사물함에 처음 접근하는 유저인 경우 - if (passwordCount != null && Integer.parseInt(passwordCount) >= 3) { - ret = LentPolicyStatus.SHARE_BANNED_USER; - } - } - return ret; - } - - @Override - public LentPolicyStatus verifyCabinetForLent(Cabinet cabinet) { - log.info("Called verifyCabinetForLent cabinet: {}", cabinet); - // 빌릴 수 있는지 검증. 빌릴 수 없으면 return lentPolicyDto; - switch (cabinet.getStatus()) { - case FULL: - return LentPolicyStatus.FULL_CABINET; - case BROKEN: - return LentPolicyStatus.BROKEN_CABINET; - case OVERDUE: - return LentPolicyStatus.OVERDUE_CABINET; - case PENDING: - return LentPolicyStatus.PENDING_CABINET; - } - if (cabinet.isLentType(LentType.CLUB)) { - return LentPolicyStatus.LENT_CLUB; - } - // 기존의 공유사물함 정책에서 검사해야 되는 부분 -> 현재 필요 x -// if (cabinet.isLentType(LentType.SHARE)) { -// if (cabinetLentHistories == null || cabinetLentHistories.isEmpty()) { -// return LentPolicyStatus.INTERNAL_ERROR; -// } -// Long diffDays = DateUtil.calculateTwoDateDiffAbs( -// cabinetLentHistories.get(0).getExpiredAt(), now); -// if (diffDays <= getDaysForNearExpiration()) { // -// return LentPolicyStatus.IMMINENT_EXPIRATION; -// } -// } - return LentPolicyStatus.FINE; - } - - @Override - public Integer getDaysForLentTermPrivate() { - log.debug("Called getDaysForLentTermPrivate"); - return cabinetProperties.getLentTermPrivate(); - } - - @Override - public Integer getDaysForLentTermShare(Integer totalUserCount) { - log.debug("Called getDaysForLentTermShare"); - return cabinetProperties.getLentTermShareBasic() - + cabinetProperties.getLentTermShare() * totalUserCount; - } - - @Override - public Integer getDaysForNearExpiration() { - log.debug("Called getDaysForNearExpiration"); - return cabinetProperties.getPenaltyDayShare() + cabinetProperties.getPenaltyDayPadding(); - } - - @Override - public void handlePolicyStatus(LentPolicyStatus status, List banHistory) - throws ServiceException { - log.info("Called handlePolicyStatus status: {}", status); - switch (status) { - case FINE: - break; - case BROKEN_CABINET: - throw new ServiceException(ExceptionStatus.LENT_BROKEN); - case FULL_CABINET: - throw new ServiceException(ExceptionStatus.LENT_FULL); - case OVERDUE_CABINET: - throw new ServiceException(ExceptionStatus.LENT_EXPIRED); - case LENT_CLUB: - throw new ServiceException(ExceptionStatus.LENT_CLUB); - case IMMINENT_EXPIRATION: - throw new ServiceException(ExceptionStatus.LENT_EXPIRE_IMMINENT); - case ALREADY_LENT_USER: - throw new ServiceException(ExceptionStatus.LENT_ALREADY_EXISTED); - case ALL_BANNED_USER: - handleBannedUserResponse(status, banHistory.get(0)); - case SHARE_BANNED_USER: - throw new ServiceException(ExceptionStatus.SHARE_CODE_TRIAL_EXCEEDED); - case BLACKHOLED_USER: - throw new ServiceException(ExceptionStatus.BLACKHOLED_USER); - case PENDING_CABINET: - throw new ServiceException(ExceptionStatus.LENT_PENDING); - case NOT_USER: - case INTERNAL_ERROR: - default: - throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); - } - } - - public void handleBannedUserResponse(LentPolicyStatus status, BanHistory banHistory) { - log.info("Called handleBannedUserResponse: {}", status); - - LocalDateTime unbannedAt = banHistory.getUnbannedAt(); - String unbannedTimeString = unbannedAt.format( - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); - - if (status.equals(LentPolicyStatus.ALL_BANNED_USER)) { - throw new CustomServiceException( - new CustomExceptionStatus(ExceptionStatus.ALL_BANNED_USER, unbannedTimeString)); - } else if (status.equals(LentPolicyStatus.SHARE_BANNED_USER)) { - throw new CustomServiceException( - new CustomExceptionStatus(ExceptionStatus.SHARE_BANNED_USER, - unbannedTimeString)); - } - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java index 7a406109a..cb4729576 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java @@ -1,7 +1,9 @@ package org.ftclub.cabinet.lent.newService; +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.lent.repository.LentRedis; +import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentRepository; import org.springframework.stereotype.Service; @@ -9,6 +11,28 @@ @RequiredArgsConstructor public class LentCommandService { - private final LentRepository lentRepository; - private final LentRedis lentRedis; + private final LentRepository lentRepository; + + public void startLent(Long userId, Long cabinetId, LocalDateTime now, + LocalDateTime expiredAt) { + LentHistory lentHistory = LentHistory.of(now, expiredAt, userId, cabinetId); + lentRepository.save(lentHistory); + } + + public void startLent(List userIds, Long cabinetId, LocalDateTime now, + LocalDateTime expiredAt) { + userIds.forEach(userId -> { + LentHistory lentHistory = LentHistory.of(now, expiredAt, userId, cabinetId); + lentRepository.save(lentHistory); + }); + } + + public void endLent(LentHistory lentHistory, LocalDateTime now) { + lentHistory.endLent(now); + lentRepository.save(lentHistory); + } + + public void setExpiredAt(LentHistory lentHistory, LocalDateTime expiredAt) { + lentHistory.setExpiredAt(expiredAt); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java index 7e2b8c2e6..306a5dbc8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java @@ -1,66 +1,358 @@ package org.ftclub.cabinet.lent.newService; +import static org.ftclub.cabinet.cabinet.domain.LentType.PRIVATE; +import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.domain.LentType; +import org.ftclub.cabinet.cabinet.newService.CabinetCommandService; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; +import org.ftclub.cabinet.dto.MyCabinetResponseDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.dto.UserVerifyRequestDto; import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.LentMapper; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserSession; +import org.ftclub.cabinet.user.newService.BanHistoryCommandService; +import org.ftclub.cabinet.user.newService.BanHistoryQueryService; +import org.ftclub.cabinet.user.newService.BanPolicyService; import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; +import org.springframework.transaction.annotation.Transactional; @Slf4j @Service @RequiredArgsConstructor public class LentFacadeService { - private final LentQueryService lentQueryService; - private final LentCommandService lentCommandService; - private final UserQueryService userQueryService; - private final CabinetQueryService cabinetQueryService; - - private final LentMapper lentMapper; - - - public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size) { - log.debug("Called getAllUserLentHistories: {}", userId); - userQueryService.getUser(userId); - - if (size <= 0) { - size = Integer.MAX_VALUE; - } - PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); - Page lentHistories = lentQueryService.findUserLentHistories(userId, pageable); - List result = lentHistories.stream() - .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) - .collect(Collectors.toList()); - return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); - } - - public List getLentDtoList(Long cabinetId) { - log.debug("Called getLentDtoList: {}", cabinetId); - cabinetQueryService.getCabinet(cabinetId); - -// cabinetOptionalFetcher.getCabinet(cabinetId); -// List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( -// cabinetId); -// return lentHistories.stream().map( -// e -> lentMapper.toLentDto(e.getUser(), e)).collect(Collectors.toList()); -// return lentHistories.stream() -// .map(e -> new LentDto( -// e.getUserId(), -// e.getUser().getName(), -// e.getLentHistoryId(), -// e.getStartedAt(), -// e.getExpiredAt())) -// .collect(Collectors.toList()); - } + private final LentRedisService lentRedisService; + private final LentQueryService lentQueryService; + private final LentCommandService lentCommandService; + private final UserQueryService userQueryService; + private final CabinetQueryService cabinetQueryService; + private final CabinetCommandService cabinetCommandService; + private final BanHistoryQueryService banHistoryQueryService; + private final BanHistoryCommandService banHistoryCommandService; + + private final LentPolicyService lentPolicyService; + private final BanPolicyService banPolicyService; + + private final LentMapper lentMapper; + private final CabinetMapper cabinetMapper; + + + @Transactional(readOnly = true) + public LentHistoryPaginationDto getUserLentHistories(Long userId, PageRequest pageable) { + log.debug("Called getAllUserLentHistories: {}", userId); + + userQueryService.getUser(userId); + Page lentHistories = + lentQueryService.findUserLentHistories(userId, pageable); + List result = lentHistories.stream() + .sorted(Comparator.comparing(LentHistory::getStartedAt)) + .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) + .collect(Collectors.toList()); + return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); + } + + @Transactional(readOnly = true) + public List getCabinetLentHistories(Long cabinetId) { + log.debug("Called getCabinetLentHistories: {}", cabinetId); + + cabinetQueryService.getCabinet(cabinetId); + List lentHistories = lentQueryService.findCabinetActiveLentHistory(cabinetId); + return lentHistories.stream() + .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public List getCabinetSessionLentHistories(Long cabinetId) { + log.debug("Called getLentDtoListFromRedis: {}", cabinetId); + + List userIdsInCabinet = lentRedisService.findUsersInCabinet(cabinetId); + List userList = userQueryService.getUsers(userIdsInCabinet); + return userList.stream().map(user -> lentMapper.toLentDto(user, null)) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, PageRequest pageable) { + log.debug("Called getMyLentLog: {}", user.getName()); + + Page lentHistories = + lentQueryService.findUserLentHistories(user.getUserId(), pageable); + List result = lentHistories.stream() + .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) + .map(lentHistory -> lentMapper.toLentHistoryDto( + lentHistory, + lentHistory.getUser(), + lentHistory.getCabinet())) + .collect(Collectors.toList()); + return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); + } + + @Transactional(readOnly = true) + public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { + log.debug("Called getMyLentInfo: {}", user.getName()); + + Cabinet userActiveCabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); + Long cabinetId; + List lentDtoList; + if (Objects.isNull(userActiveCabinet)) { + cabinetId = lentRedisService.findCabinetJoinedUser(user.getUserId()); + if (Objects.isNull(cabinetId)) { + return null; + } + List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); + List userList = userQueryService.getUsers(usersInCabinet); + userActiveCabinet = cabinetQueryService.getCabinet(cabinetId); + lentDtoList = userList.stream() + .map(u -> lentMapper.toLentDto(u, null)).collect(Collectors.toList()); + } else { + cabinetId = userActiveCabinet.getCabinetId(); + List lentHistories = + lentQueryService.findCabinetActiveLentHistory(cabinetId); + lentDtoList = lentHistories.stream() + .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(Collectors.toList()); + } + // TODO : shareCode, sessionExpiredAt, previousUserName이 상황에 맞춰 null이 되는지 확인 + String shareCode = lentRedisService.getShareCode(cabinetId); + LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); + String previousUserName = lentRedisService.getPreviousUserName(cabinetId); + return cabinetMapper.toMyCabinetResponseDto(userActiveCabinet, lentDtoList, + shareCode, sessionExpiredAt, previousUserName); + } + + @Transactional(readOnly = true) + public List getAllActiveLentHistories() { + log.debug("Called getAllActiveLentHistories"); + + LocalDateTime now = LocalDateTime.now(); + List lentHistories = lentQueryService.findAllActiveLentHistories(); + return lentHistories.stream() + .map(lh -> lentMapper.toActiveLentHistoryDto(lh, + lh.getUser(), + lh.getCabinet(), + lh.isExpired(now), + lh.getDaysUntilExpiration(now))) + .collect(Collectors.toList()); + } + + /*------------------------------------------ CUD -------------------------------------------*/ + + @Transactional + public void startLentCabinet(Long userId, Long cabinetId) { + log.debug("Called startLentCabinet: {}, {}", userId, cabinetId); + + LocalDateTime now = LocalDateTime.now(); + User user = userQueryService.getUser(userId); + Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + int lentCount = lentQueryService.countUserActiveLent(userId); + List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); + int userCount = lentQueryService.countCabinetUser(cabinetId); + + lentPolicyService.verifyCabinetLentCount( + cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetType(cabinet.getLentType(), PRIVATE); + lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), + user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); + + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, PRIVATE, 1); + lentCommandService.startLent(user.getUserId(), cabinet.getCabinetId(), now, expiredAt); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); + cabinetCommandService.changeUserCount(cabinet, lentCount + 1); + } + + @Transactional + public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) { + log.info("Called startLentShareCabinet: {}, {}, {}", userId, cabinetId, shareCode); + + LocalDateTime now = LocalDateTime.now(); + User user = userQueryService.getUser(userId); + Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + int lentCount = lentQueryService.countUserActiveLent(userId); + List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); + int userCount = lentQueryService.countCabinetUser(cabinetId); + + lentPolicyService.verifyCabinetLentCount( + cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetType(cabinet.getLentType(), SHARE); + lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), + user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + boolean isExist = lentRedisService.isInCabinetSession(cabinetId); + if (!isExist) { + lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); + shareCode = lentRedisService.createCabinetSession(cabinetId); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.IN_SESSION); + } + lentRedisService.joinCabinetSession(cabinetId, userId, shareCode); + if (lentRedisService.isCabinetSessionFull(cabinetId)) { + List userIdsInCabinet = lentRedisService.getUsersInCabinetSession(cabinetId); + LocalDateTime expiredAt = + lentPolicyService.generateExpirationDate(now, SHARE, userIdsInCabinet.size()); + lentCommandService.startLent(userIdsInCabinet, cabinet.getCabinetId(), now, expiredAt); + lentRedisService.clearCabinetSession(cabinetId); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); + cabinetCommandService.changeUserCount(cabinet, userIdsInCabinet.size()); + } + } + + @Transactional + public void startLentClubCabinet(Long userId, Long cabinetId) { + log.debug("Called startLentClubCabinet: {}, {}", userId, cabinetId); + + LocalDateTime now = LocalDateTime.now(); + // TODO : ClubUser 추가 이후 userId로 ClubUser 검증 로직 필요(Policy) + Cabinet cabinet = cabinetQueryService.getCabinet(cabinetId); + int userCount = lentQueryService.countCabinetUser(cabinetId); + + lentPolicyService.verifyCabinetLentCount( + cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.CLUB); + lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); + LocalDateTime expiredAt = + lentPolicyService.generateExpirationDate(now, cabinet.getLentType(), 1); + lentCommandService.startLent(userId, cabinetId, now, expiredAt); + cabinetCommandService.changeUserCount(cabinet, userCount + 1); + } + + @Transactional + public void endUserLent(Long userId) { + log.debug("Called endLentCabinet: {}", userId); + + LocalDateTime now = LocalDateTime.now(); + LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithLock(userId); + List cabinetLentHistories = + lentQueryService.findCabinetActiveLentHistory(userLentHistory.getCabinetId()); + Cabinet cabinet = + cabinetQueryService.getCabinetWithLock(userLentHistory.getCabinetId()); + + int userRemainCount = cabinetLentHistories.size() - 1; + cabinetCommandService.changeUserCount(cabinet, userRemainCount); + lentCommandService.endLent(userLentHistory, now); + lentRedisService.setPreviousUserName( + cabinet.getCabinetId(), userLentHistory.getUser().getName()); + + LocalDateTime endedAt = userLentHistory.getEndedAt(); + BanType banType = banPolicyService.verifyBan(endedAt, userLentHistory.getExpiredAt()); + if (!banType.equals(BanType.NONE)) { + LocalDateTime unbannedAt = banPolicyService.getUnBannedAt( + endedAt, userLentHistory.getExpiredAt()); + banHistoryCommandService.banUser(userId, endedAt, unbannedAt, banType); + } + if (cabinet.isLentType(SHARE)) { + LocalDateTime expiredAt = lentPolicyService.adjustSharCabinetExpirationDate( + userRemainCount, now, userLentHistory); + cabinetLentHistories.stream().filter(lh -> !lh.equals(userLentHistory)) + .forEach(lh -> lentCommandService.setExpiredAt(lh, expiredAt)); + } + } + + @Transactional + public void endUserLent(Long userId, String memo) { + log.debug("Called endLentCabinet: {}", userId); + + LocalDateTime now = LocalDateTime.now(); + LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithLock(userId); + List cabinetLentHistories = + lentQueryService.findCabinetActiveLentHistory(userLentHistory.getCabinetId()); + Cabinet cabinet = + cabinetQueryService.getCabinetWithLock(userLentHistory.getCabinetId()); + + int userRemainCount = cabinetLentHistories.size() - 1; + cabinetCommandService.changeUserCount(cabinet, userRemainCount); + cabinetCommandService.updateMemo(cabinet, memo); + lentCommandService.endLent(userLentHistory, now); + lentRedisService.setPreviousUserName( + cabinet.getCabinetId(), userLentHistory.getUser().getName()); + + LocalDateTime endedAt = userLentHistory.getEndedAt(); + BanType banType = banPolicyService.verifyBan(endedAt, userLentHistory.getExpiredAt()); + if (!banType.equals(BanType.NONE)) { + LocalDateTime unbannedAt = banPolicyService.getUnBannedAt( + endedAt, userLentHistory.getExpiredAt()); + banHistoryCommandService.banUser(userId, endedAt, unbannedAt, banType); + } + if (cabinet.isLentType(SHARE)) { + LocalDateTime expiredAt = lentPolicyService.adjustSharCabinetExpirationDate( + userRemainCount, now, userLentHistory); + cabinetLentHistories.stream().filter(lh -> !lh.equals(userLentHistory)) + .forEach(lh -> lentCommandService.setExpiredAt(lh, expiredAt)); + } + } + + @Transactional + public void endCabinetLent(List cabinetIds) { + log.debug("Called endCabinetsLent: {}", cabinetIds); + + LocalDateTime now = LocalDateTime.now(); + List cabinets = cabinetQueryService.getCabinetWithLock(cabinetIds); + cabinets.forEach(cabinet -> { + List cabinetLentHistories = + lentQueryService.findCabinetActiveLentHistory(cabinet.getCabinetId()); + cabinetLentHistories.forEach(lh -> lentCommandService.endLent(lh, now)); + cabinetCommandService.changeUserCount(cabinet, 0); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); + lentRedisService.setPreviousUserName( + cabinet.getCabinetId(), cabinetLentHistories.get(0).getUser().getName()); + }); + } + + @Transactional + public void updateLentCabinetInfo(Long userId, String title, String memo) { + log.debug("Called updateLentCabinetInfo: {}, {}, {}", userId, title, memo); + + Cabinet cabinet = cabinetQueryService.getUserActiveCabinetWithLock(userId); + cabinetCommandService.updateTitle(cabinet, title); + cabinetCommandService.updateMemo(cabinet, memo); + } + + @Transactional + public void cancelShareCabinetLent(Long userId, Long cabinetId) { + log.debug("Called cancelShareCabinetLent: {}, {}", userId, cabinetId); + + lentRedisService.deleteUserInCabinetSession(cabinetId, userId); + if (lentRedisService.isCabinetSessionEmpty(cabinetId)) { + Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); + } + } + + @Transactional + public void shareCabinetSessionExpired(Long cabinetId) { + log.debug("Called shareCabinetSessionExpired: {}", cabinetId); + + Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + List usersInCabinetSession = lentRedisService.getUsersInCabinetSession(cabinetId); + if (lentPolicyService.verifyUserCountOnShareCabinet(usersInCabinetSession.size())) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate( + now, SHARE, usersInCabinetSession.size()); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); + lentCommandService.startLent(usersInCabinetSession, cabinetId, now, expiredAt); + } else { + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); + } + lentRedisService.confirmCabinetSession(cabinetId, usersInCabinetSession); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java new file mode 100644 index 000000000..f381d88e2 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java @@ -0,0 +1,186 @@ +package org.ftclub.cabinet.lent.newService; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.domain.LentType; +import org.ftclub.cabinet.config.CabinetProperties; +import org.ftclub.cabinet.dto.UserVerifyRequestDto; +import org.ftclub.cabinet.exception.CustomExceptionStatus; +import org.ftclub.cabinet.exception.CustomServiceException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.domain.LentPolicyStatus; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.user.domain.UserRole; +import org.ftclub.cabinet.utils.DateUtil; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LentPolicyService { + + private final CabinetProperties cabinetProperties; + + + private void handlePolicyStatus(LentPolicyStatus status, LocalDateTime unbannedAt) { + String unbannedAtString = null; + switch (status) { + case FINE: + break; + case BROKEN_CABINET: + throw new ServiceException(ExceptionStatus.LENT_BROKEN); + case FULL_CABINET: + throw new ServiceException(ExceptionStatus.LENT_FULL); + case OVERDUE_CABINET: + throw new ServiceException(ExceptionStatus.LENT_EXPIRED); + case LENT_CLUB: + throw new ServiceException(ExceptionStatus.LENT_CLUB); + case IMMINENT_EXPIRATION: + throw new ServiceException(ExceptionStatus.LENT_EXPIRE_IMMINENT); + case ALREADY_LENT_USER: + throw new ServiceException(ExceptionStatus.LENT_ALREADY_EXISTED); + case ALL_BANNED_USER: + unbannedAtString = unbannedAt.format( + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + throw new CustomServiceException( + new CustomExceptionStatus(ExceptionStatus.ALL_BANNED_USER, + unbannedAtString)); + case SHARE_BANNED_USER: + unbannedAtString = unbannedAt.format( + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + throw new CustomServiceException( + new CustomExceptionStatus(ExceptionStatus.SHARE_CODE_TRIAL_EXCEEDED, + unbannedAtString)); + case BLACKHOLED_USER: + throw new ServiceException(ExceptionStatus.BLACKHOLED_USER); + case PENDING_CABINET: + throw new ServiceException(ExceptionStatus.LENT_PENDING); + case NOT_USER: + case INTERNAL_ERROR: + default: + throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); + } + } + + public void verifyUserForLent(UserVerifyRequestDto requestDto) { + log.debug("Called verifyUser"); + + LocalDateTime now = LocalDateTime.now(); + LentPolicyStatus status = LentPolicyStatus.FINE; + if (!requestDto.getUserRole().equals(UserRole.USER)) { + status = LentPolicyStatus.NOT_USER; + } + if (requestDto.getLentCount() != 0) { + status = LentPolicyStatus.ALREADY_LENT_USER; + } + if (requestDto.getBlackholedAt() != null && requestDto.getBlackholedAt().isBefore(now)) { + status = LentPolicyStatus.BLACKHOLED_USER; + } + if (requestDto.getBanHistories() != null && !requestDto.getBanHistories().isEmpty()) { + for (BanHistory banHistory : requestDto.getBanHistories()) { + if (banHistory.getBanType().equals(BanType.ALL)) { + status = LentPolicyStatus.ALL_BANNED_USER; + break; + } + if (banHistory.getBanType().equals(BanType.SHARE)) { + status = LentPolicyStatus.SHARE_BANNED_USER; + } + } + } + LocalDateTime unbannedAt = null; + if (requestDto.getBanHistories() != null) { + unbannedAt = requestDto.getBanHistories().stream() + .map(BanHistory::getUnbannedAt) + .max(LocalDateTime::compareTo).orElse(null); + } + this.handlePolicyStatus(status, unbannedAt); + } + + public void verifyCabinetForLent(CabinetStatus cabinetStatus, LentType lentType) { + log.debug("Called verifyCabinet"); + + LentPolicyStatus status = LentPolicyStatus.FINE; + if (lentType.equals(LentType.CLUB)) { + status = LentPolicyStatus.LENT_CLUB; + } + switch (cabinetStatus) { + case FULL: + status = LentPolicyStatus.FULL_CABINET; + case BROKEN: + status = LentPolicyStatus.BROKEN_CABINET; + case OVERDUE: + status = LentPolicyStatus.OVERDUE_CABINET; + case PENDING: + status = LentPolicyStatus.PENDING_CABINET; + } + handlePolicyStatus(status, null); + } + + public void verifyCabinetType(LentType cabinetLentType, LentType lentType) { + log.debug("Called verifyCabinetType"); + + if (!cabinetLentType.equals(lentType)) { + throw new ServiceException(ExceptionStatus.INVALID_LENT_TYPE); + } + } + + public void verifyCabinetLentCount(LentType lentType, int maxUserCount, int lentCount) { + log.debug("Called verifyCabinetLentCount"); + + int maxLentCount = 1; + if (lentType.equals(LentType.SHARE)) { + maxLentCount = cabinetProperties.getShareMaxUserCount().intValue(); + } + if (maxUserCount != maxLentCount) { + throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); + } + if (lentCount >= maxLentCount) { + throw new ServiceException(ExceptionStatus.LENT_FULL); + } + } + + public LocalDateTime generateExpirationDate(LocalDateTime now, LentType lentType, + int lentUserCount) { + log.debug("Called generateExpirationDate"); + + if (!DateUtil.isSameDay(now)) { + throw new ServiceException(ExceptionStatus.INVALID_ARGUMENT); + } + int lentTerm = 0; + if (lentType.equals(LentType.PRIVATE)) { + lentTerm = cabinetProperties.getLentTermPrivate(); + } else if (lentType.equals(LentType.SHARE)) { + lentTerm = cabinetProperties.getLentTermShareBasic() + + cabinetProperties.getLentTermShare() * lentUserCount; + } + LocalDateTime expiredAt = DateUtil.setLastTime(now.plusDays(lentTerm)); + if (DateUtil.isPast(expiredAt)) { + throw new ServiceException(ExceptionStatus.INVALID_EXPIRED_AT); + } + return expiredAt; + } + + public LocalDateTime adjustSharCabinetExpirationDate(int userCount, LocalDateTime now, + LentHistory lentHistory) { + log.debug("Called adjustExpriationDate"); + + double daysUntilExpiration = lentHistory.getDaysUntilExpiration(now) * -1; + double secondsUntilExpiration = daysUntilExpiration * 24 * 60 * 60; + long secondsRemaining = Math.round(secondsUntilExpiration * userCount / (userCount + 1)); + return DateUtil.setLastTime(now.plusSeconds(secondsRemaining)); + } + + public boolean verifyUserCountOnShareCabinet(int userCount) { + log.debug("Called verifyUserCountOnShareCabinet"); + + long minUserCount = cabinetProperties.getShareMinUserCount(); + long maxUserCount = cabinetProperties.getShareMaxUserCount(); + return minUserCount <= userCount && userCount <= maxUserCount; + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java index 13db26ab6..175064fd1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java @@ -1,38 +1,40 @@ package org.ftclub.cabinet.lent.newService; +import java.util.List; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.repository.LentRedis; import org.ftclub.cabinet.lent.repository.LentRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; -import java.util.List; - @Service @RequiredArgsConstructor public class LentQueryService { - private final LentRepository lentRepository; - private final LentRedis lentRedis; - - public Page findUserLentHistories(Long userId, PageRequest pageable) { - return lentRepository.findPaginationByUserId(userId, pageable); - } - - public LentHistory findCabinetActiveLentHistory(Long cabinetId) { - List lentHistories = - lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); - if (lentHistories.size() >= 2) { - throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); - } - if (lentHistories.isEmpty()) { - return null; - } - return lentHistories.get(0); - } + private final LentRepository lentRepository; + + public Page findUserLentHistories(Long userId, PageRequest pageable) { + return lentRepository.findPaginationByUserId(userId, pageable); + } + + public List findCabinetActiveLentHistory(Long cabinetId) { + return lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); + } + + public int countUserActiveLent(Long userId) { + return lentRepository.countByUserIdAndEndedAtIsNull(userId); + } + + public int countCabinetUser(Long cabinetId) { + return lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId); + } + + public LentHistory findUserActiveLentHistoryWithLock(Long userId) { + return lentRepository.findByUserIdAndEndedAtIsNullForUpdate(userId).orElse(null); + } + + public List findAllActiveLentHistories() { + return lentRepository.findAllByEndedAtIsNull(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java new file mode 100644 index 000000000..213063724 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java @@ -0,0 +1,119 @@ +package org.ftclub.cabinet.lent.newService; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.config.CabinetProperties; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.repository.LentRedis; +import org.ftclub.cabinet.lent.repository.LentRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LentRedisService { + + private final LentRedis lentRedis; + private final LentRepository lentRepository; + private final CabinetProperties cabinetProperties; + + public List findUsersInCabinet(Long cabinetId) { + List userIdList = lentRedis.getAllUserInCabinet( + cabinetId.toString()); + return userIdList.stream().map(Long::valueOf).collect(Collectors.toList()); + } + + public Long findCabinetJoinedUser(Long userId) { + String cabinetId = lentRedis.findCabinetByUser(userId.toString()); + if (Objects.isNull(cabinetId)) { + return null; + } + return Long.valueOf(cabinetId); + } + + public String getShareCode(Long cabinetId) { + return lentRedis.getShareCode(cabinetId.toString()); + } + + public LocalDateTime getSessionExpired(Long cabinetId) { + return lentRedis.getCabinetExpiredAt(cabinetId.toString()); + } + + public String createCabinetSession(Long cabinetId) { + return lentRedis.setShadowKey(cabinetId.toString()); + } + + public boolean isInCabinetSession(Long cabinetId) { + return lentRedis.isExistShadowKey(cabinetId.toString()); + } + + public void joinCabinetSession(Long cabinetId, Long userId, String shareCode) { + lentRedis.attemptJoinCabinet(cabinetId.toString(), userId.toString(), shareCode); + } + + public boolean isCabinetSessionEmpty(Long cabinetId) { + return lentRedis.countUserInCabinet(cabinetId.toString()) == 0; + } + + public boolean isCabinetSessionFull(Long cabinetId) { + Long userCount = lentRedis.countUserInCabinet(cabinetId.toString()); + return Objects.equals(userCount, cabinetProperties.getShareMaxUserCount()); + } + + public List getUsersInCabinetSession(Long cabinetId) { + List userIdList = lentRedis.getAllUserInCabinet(cabinetId.toString()); + return userIdList.stream().map(Long::valueOf).collect(Collectors.toList()); + } + + public void deleteUserInCabinetSession(Long cabinetId, Long userId) { + String cabinetIdString = cabinetId.toString(); + String userIdString = userId.toString(); + if (!lentRedis.isUserInCabinet(cabinetIdString, userIdString)) { + throw new DomainException(ExceptionStatus.NOT_FOUND_USER); + } + lentRedis.deleteUserInCabinet(cabinetIdString, userIdString); + if (lentRedis.countUserInCabinet(cabinetIdString) == 0) { + lentRedis.deleteShadowKey(cabinetIdString); + } + } + + public void confirmCabinetSession(Long cabinetId, List userIdList) { + String cabinetIdString = cabinetId.toString(); + userIdList.stream().map(Object::toString) + .forEach(userId -> lentRedis.deleteUserInCabinet(cabinetIdString, userId)); + lentRedis.deleteCabinet(cabinetIdString); + } + + public void clearCabinetSession(Long cabinetId) { + String cabinetIdString = cabinetId.toString(); + List userList = lentRedis.getAllUserInCabinet(cabinetIdString); + lentRedis.deleteShadowKey(cabinetIdString); + userList.forEach(userId -> lentRedis.deleteUserInCabinet(cabinetIdString, userId)); + lentRedis.deleteCabinet(cabinetIdString); + } + + public String getPreviousUserName(Long cabinetId) { + String previousUserName = lentRedis.getPreviousUserName(cabinetId.toString()); + if (Objects.isNull(previousUserName)) { + List cabinetLentHistories = + lentRepository.findByCabinetIdAndEndedAtIsNotNull(cabinetId); + previousUserName = cabinetLentHistories.stream() + .sorted(Comparator.comparing(LentHistory::getEndedAt).reversed()) + .limit(1L).map(lh -> lh.getUser().getName()) + .findFirst().orElse(null); + if (Objects.nonNull(previousUserName)) { + lentRedis.setPreviousUserName(cabinetId.toString(), previousUserName); + } + } + return previousUserName; + } + + public void setPreviousUserName(Long cabinetId, String userName) { + lentRedis.setPreviousUserName(cabinetId.toString(), userName); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index 1715a9006..b7b6fecb0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -3,7 +3,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -118,7 +117,7 @@ public List findAllActiveLentHistories() { */ public Cabinet findActiveLentCabinetByUserId(Long userId) { log.debug("Called findActiveLentCabinetByUserId: {}", userId); - return cabinetRepository.findByUserIdAndEndedAtIsNull(userId).orElse(null); + return cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId).orElse(null); } /** @@ -129,7 +128,7 @@ public Cabinet findActiveLentCabinetByUserId(Long userId) { */ public Long findCabinetIdByUserIdFromRedis(Long userId) { log.debug("Called findActiveLentCabinetByUserIdFromRedis: {}", userId); - return lentRedis.findCabinetIdByUserIdInRedis(userId); + return Long.valueOf(lentRedis.findCabinetByUser(userId.toString())); } /** @@ -140,7 +139,7 @@ public Long findCabinetIdByUserIdFromRedis(Long userId) { */ public List findUserIdsByCabinetIdFromRedis(Long cabinetId) { log.debug("Called findActiveLentUserIdsByCabinetId: {}", cabinetId); - return lentRedis.getUserIdsByCabinetIdInRedis(cabinetId.toString()); + return lentRedis.getAllUserInCabinet(cabinetId.toString()); } public List findAllOverdueLent(LocalDateTime date, Pageable pageable) { @@ -162,7 +161,7 @@ public List findAllActiveLentHistoriesByUserId(Long userId) { public List findPreviousLentHistoryByCabinetId(Long cabinetId) { log.debug("Called findPreviousLentUserNameByCabinetId: {}", cabinetId); - return lentRepository.findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc(cabinetId); + return lentRepository.findByCabinetIdAndEndedAtIsNotNull(cabinetId); } public List findAllByCabinetIdsAfterDate(LocalDate date, List cabinetIds) { diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java index 63bec0678..1d8302818 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java @@ -1,23 +1,23 @@ package org.ftclub.cabinet.lent.repository; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.config.CabinetProperties; +import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Map; -import java.util.Objects; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - @Component @Log4j2 public class LentRedis { @@ -28,158 +28,150 @@ public class LentRedis { private static final String VALUE_KEY_SUFFIX = ":user"; private static final String PREVIOUS_USER_SUFFIX = ":previousUser"; - private final HashOperations valueHashOperations; - private final ValueOperations valueOperations; - private final RedisTemplate shadowKeyRedisTemplate; - private final ValueOperations previousUserRedisTemplate; + private final HashOperations shareCabinetTemplate; + private final ValueOperations userCabinetTemplate; + private final RedisTemplate shadowKeyTemplate; + private final ValueOperations previousUserTemplate; private final CabinetProperties cabinetProperties; @Autowired public LentRedis(RedisTemplate valueHashRedisTemplate, RedisTemplate valueRedisTemplate, - RedisTemplate shadowKeyRedisTemplate, - RedisTemplate previousUserRedisTemplate, + RedisTemplate shadowKeyTemplate, + RedisTemplate previousUserTemplate, CabinetProperties cabinetProperties) { - this.valueOperations = valueRedisTemplate.opsForValue(); - this.valueHashOperations = valueHashRedisTemplate.opsForHash(); - this.shadowKeyRedisTemplate = shadowKeyRedisTemplate; - this.previousUserRedisTemplate = previousUserRedisTemplate.opsForValue(); + this.userCabinetTemplate = valueRedisTemplate.opsForValue(); + this.shareCabinetTemplate = valueHashRedisTemplate.opsForHash(); + this.shadowKeyTemplate = shadowKeyTemplate; + this.previousUserTemplate = previousUserTemplate.opsForValue(); this.cabinetProperties = cabinetProperties; } /** - * @param cabinetId : cabinetId - * @param userId : userId - * @param shareCode : 초대코드 - * @param hasShadowKey : 최초 대여인지 아닌지 여부 + * @param cabinetId : cabinetId + * @param userId : userId + * @param shareCode : 초대코드 */ - public void saveUserInRedis(String cabinetId, String userId, String shareCode, - boolean hasShadowKey) { - log.debug("called saveUserInRedis: {}, {}, {}, {}", cabinetId, userId, shareCode, - hasShadowKey); - if (!hasShadowKey || isValidShareCode(Long.valueOf(cabinetId), - shareCode)) { // 방장이거나 초대코드를 맞게 입력한 경우 - valueHashOperations.put(cabinetId, userId, - USER_ENTERED); // userId를 hashKey로 하여 -1을 value로 저장 // TODO: -1 대신 새로운 플래그값 넣어도 될듯? - valueOperations.set(userId + VALUE_KEY_SUFFIX, - cabinetId); // userId를 key로 하여 cabinetId를 value로 저장 - } else { // 초대코드가 틀린 경우 - if (valueHashOperations.hasKey(cabinetId, userId)) { // 이미 존재하는 유저인 경우 - valueHashOperations.increment(cabinetId, userId, 1L); // trialCount를 1 증가시켜서 저장 - } else { // 존재하지 않는 유저인 경우 - valueHashOperations.put(cabinetId, userId, "1"); // trialCount를 1로 저장 + public void attemptJoinCabinet(String cabinetId, String userId, String shareCode) { + log.debug("called saveUserInRedis: {}, {}, {}", cabinetId, userId, shareCode); + + String savedCode = shadowKeyTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX); + if (Objects.equals(savedCode, shareCode)) { + shareCabinetTemplate.put(cabinetId, userId, USER_ENTERED); + userCabinetTemplate.set(userId + VALUE_KEY_SUFFIX, cabinetId); + } else { + if (shareCabinetTemplate.hasKey(cabinetId, userId)) { + shareCabinetTemplate.increment(cabinetId, userId, 1L); + } else { + shareCabinetTemplate.put(cabinetId, userId, "1"); } - throw new ServiceException(ExceptionStatus.WRONG_SHARE_CODE); + throw new DomainException(ExceptionStatus.WRONG_SHARE_CODE); } } - public boolean isValidShareCode(Long cabinetId, String shareCode) { - log.debug("called isValidShareCode: {}, {}", cabinetId, shareCode); - return Objects.equals( - shadowKeyRedisTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX), - shareCode); - } - - public boolean checkPwTrialCount(String cabinetId, String userId) { - log.debug("called checkPwTrialCount: {}, {}", cabinetId, userId); - return Objects.equals(valueHashOperations.get(cabinetId, userId), MAX_SHARE_CODE_TRY); - } - - public Boolean isUserInRedis(String cabinetId, String userId) { + public boolean isUserInCabinet(String cabinetId, String userId) { log.debug("called isUserInRedis: {}, {}", cabinetId, userId); - return valueHashOperations.hasKey(cabinetId, userId); + + return shareCabinetTemplate.hasKey(cabinetId, userId); } - public Long getSizeOfUsersInSession(String cabinetId) { + public Long countUserInCabinet(String cabinetId) { log.debug("called getSizeOfUsersInSession: {}", cabinetId); - Map entries = valueHashOperations.entries(cabinetId); - return entries.values().stream().filter(Objects::nonNull) - .filter(value -> value.equals(USER_ENTERED)) - .count(); + + Collection joinUsers = shareCabinetTemplate.entries(cabinetId).values(); + return joinUsers.parallelStream() + .filter(value -> Objects.nonNull(value) && value.equals(USER_ENTERED)).count(); } - public String getPwTrialCountInRedis(String cabinetId, String userId) { + public String getAttemptCountInCabinet(String cabinetId, String userId) { log.debug("called getPwTrialCountInRedis: {}, {}", cabinetId, userId); - return valueHashOperations.get(cabinetId, userId); + + return shareCabinetTemplate.get(cabinetId, userId); + } + + public boolean isExistShadowKey(String cabinetId) { + log.debug("called isShadowKey: {}", cabinetId); + + Boolean isExist = shadowKeyTemplate.hasKey(cabinetId + SHADOW_KEY_SUFFIX); + return Objects.nonNull(isExist) && isExist; } - public String getShareCode(Long cabinetId) { + public String getShareCode(String cabinetId) { log.debug("called getShareCode: {}", cabinetId); - return shadowKeyRedisTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX); + + return shadowKeyTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX); } - public void setShadowKey(Long cabinetId) { + public String setShadowKey(String cabinetId) { + log.debug("called setShadowKey: {}", cabinetId); + Random rand = new Random(); - Integer shareCode = 1000 + rand.nextInt(9000); + String shareCode = Integer.toString(1000 + rand.nextInt(9000)); String shadowKey = cabinetId + SHADOW_KEY_SUFFIX; - shadowKeyRedisTemplate.opsForValue().set(shadowKey, shareCode.toString()); - // 해당 키가 처음 생성된 것이라면 timeToLive 설정 - log.debug("called setShadowKey: {}, shareCode: {}", shadowKey, shareCode); - shadowKeyRedisTemplate.expire(shadowKey, cabinetProperties.getInSessionTerm(), TimeUnit.MINUTES); -// shadowKeyRedisTemplate.expire(shadowKey, 30, TimeUnit.SECONDS); - } - - public Boolean isShadowKey(Long cabinetId) { - log.debug("called isShadowKey: {}", cabinetId); - // 해당 키가 존재하는지 확인 - return shadowKeyRedisTemplate.hasKey(cabinetId + SHADOW_KEY_SUFFIX); + shadowKeyTemplate.opsForValue().set(shadowKey, shareCode); + shadowKeyTemplate.expire(shadowKey, cabinetProperties.getInSessionTerm(), TimeUnit.MINUTES); + return shareCode; } - public void deleteShadowKey(Long cabinetId) { + public void deleteShadowKey(String cabinetId) { log.debug("called deleteShadowKey: {}", cabinetId); - shadowKeyRedisTemplate.delete(cabinetId + SHADOW_KEY_SUFFIX); + + shadowKeyTemplate.delete(cabinetId + SHADOW_KEY_SUFFIX); } - public void deleteUserInRedis(String cabinetId, String userId) { // user를 지우는 delete + public void deleteUserInCabinet(String cabinetId, String userId) { log.debug("called deleteUserInRedis: {}, {}", cabinetId, userId); - valueHashOperations.delete(cabinetId, userId); - valueOperations.getOperations().delete(userId + VALUE_KEY_SUFFIX); + + shareCabinetTemplate.delete(cabinetId, userId); + userCabinetTemplate.getOperations().delete(userId + VALUE_KEY_SUFFIX); } - public void deleteCabinetIdInRedis(String cabinetId) { + public void deleteCabinet(String cabinetId) { log.debug("called deleteCabinetIdInRedis: {}", cabinetId); - valueHashOperations.getOperations().delete(cabinetId); + + shareCabinetTemplate.getOperations().delete(cabinetId); } - public void deleteUserIdInRedis(Long userId) { + public void deleteUser(Long userId) { log.debug("called deleteUserIdInRedis: {}", userId); - valueOperations.getOperations().delete(userId + VALUE_KEY_SUFFIX); + + userCabinetTemplate.getOperations().delete(userId + VALUE_KEY_SUFFIX); } - public Long findCabinetIdByUserIdInRedis(Long userId) { + public String findCabinetByUser(String userId) { log.debug("Called findCabinetIdByUserIdInRedis: {}", userId); - String cabinetId = valueOperations.get(userId + VALUE_KEY_SUFFIX); - if (cabinetId == null) { - log.info("cabinetId is null"); - return null; - } - return Long.valueOf(cabinetId); + + return userCabinetTemplate.get(userId + VALUE_KEY_SUFFIX); } - public ArrayList getUserIdsByCabinetIdInRedis(String cabinetId) { + public List getAllUserInCabinet(String cabinetId) { log.debug("Called getUserIdsByCabinetIdInRedis: {}", cabinetId); - Map entries = valueHashOperations.entries(cabinetId); - return entries.entrySet().stream().filter(entry -> entry.getValue().equals(USER_ENTERED)) - .map(Map.Entry::getKey).collect(Collectors.toCollection(ArrayList::new)); + + Map entries = shareCabinetTemplate.entries(cabinetId); + return entries.entrySet().stream() + .filter(entry -> entry.getValue().equals(USER_ENTERED)) + .map(Map.Entry::getKey).collect(Collectors.toList()); } - public LocalDateTime getSessionExpiredAtInRedis(Long cabinetId) { + public LocalDateTime getCabinetExpiredAt(String cabinetId) { log.debug("Called getSessionExpiredAtInRedis: {}", cabinetId); - if (isShadowKey(cabinetId)) { - return LocalDateTime.now().plusSeconds( - shadowKeyRedisTemplate.getExpire(cabinetId + SHADOW_KEY_SUFFIX, - TimeUnit.SECONDS).longValue()); + if (this.isExistShadowKey(cabinetId)) { + String shadowKey = cabinetId + SHADOW_KEY_SUFFIX; + long expire = shadowKeyTemplate.getExpire(shadowKey, TimeUnit.SECONDS).longValue(); + return LocalDateTime.now().plusSeconds(expire); } return null; } - public void setPreviousUser(String cabinetId, String userName) { + /*---------------------------------------- Caching -----------------------------------------*/ + + public void setPreviousUserName(String cabinetId, String userName) { log.debug("Called setPreviousUser: {}, {}", cabinetId, userName); - previousUserRedisTemplate.set(cabinetId + PREVIOUS_USER_SUFFIX, userName); + previousUserTemplate.set(cabinetId + PREVIOUS_USER_SUFFIX, userName); } public String getPreviousUserName(String cabinetId) { log.debug("Called getPreviousUser: {}", cabinetId); - return previousUserRedisTemplate.get(cabinetId + PREVIOUS_USER_SUFFIX); + return previousUserTemplate.get(cabinetId + PREVIOUS_USER_SUFFIX); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 151b27870..c8a0132ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -89,8 +89,7 @@ int countReturnByTimeDuration(@Param("startDate") LocalDateTime startDate, * @param cabinetId 찾으려는 cabinet id * @return 반납한 {@link LentHistory}의 {@link Optional} */ - List findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc( - @Param("cabinetId") Long cabinetId); + List findByCabinetIdAndEndedAtIsNotNull(@Param("cabinetId") Long cabinetId); /** * 유저를 기준으로 아직 반납하지 않은 {@link LentHistory}중 하나를 가져옵니다. @@ -113,19 +112,17 @@ List findByCabinetIdAndEndedAtIsNotNullOrderByEndedAtDesc( Optional findByUserIdAndEndedAtIsNullForUpdate(@Param("userId") Long userId); /** - * 사물함의 대여기록 {@link LentHistory}들을 모두 가져옵니다. - * {@link Pageable}이 적용되었습니다. + * 사물함의 대여기록 {@link LentHistory}들을 모두 가져옵니다. {@link Pageable}이 적용되었습니다. * - * @param cabinetId 찾으려는 cabinet id - * @param pageable pagination 정보 + * @param cabinetId 찾으려는 cabinet id + * @param pageable pagination 정보 * @return {@link LentHistory}의 {@link Page} */ Page findPaginationByCabinetId( @Param("cabinetId") Long cabinetId, Pageable pageable); /** - * 유저가 지금까지 빌렸던 {@link LentHistory}들을 모두 가져옵니다. - * {@link Pageable}이 적용되었습니다. + * 유저가 지금까지 빌렸던 {@link LentHistory}들을 모두 가져옵니다. {@link Pageable}이 적용되었습니다. * * @param userId 찾으려는 user id * @param pageable pagination 정보 @@ -134,8 +131,8 @@ Page findPaginationByCabinetId( Page findPaginationByUserId(@Param("userId") Long userId, Pageable pageable); /** - * 유저가 지금까지 빌렸던 {@link LentHistory}들을 가져옵니다.(현재 빌리고 반납하지 않은 기록은 표시하지 않습니다.) - * {@link Pageable}이 적용되었습니다. + * 유저가 지금까지 빌렸던 {@link LentHistory}들을 가져옵니다.(현재 빌리고 반납하지 않은 기록은 표시하지 않습니다.) {@link Pageable}이 + * 적용되었습니다. * * @param userId 찾으려는 user id * @param pageable pagination 정보 diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java deleted file mode 100644 index ae9ab6466..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ /dev/null @@ -1,156 +0,0 @@ -package org.ftclub.cabinet.lent.service; - -import java.util.List; -import org.ftclub.cabinet.dto.CabinetInfoRequestDto; -import org.ftclub.cabinet.dto.LentDto; -import org.ftclub.cabinet.dto.LentEndMemoDto; -import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.dto.MyCabinetResponseDto; -import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.ftclub.cabinet.dto.UpdateCabinetMemoDto; -import org.ftclub.cabinet.dto.UpdateCabinetTitleDto; -import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.user.domain.UserSession; - -/** - * controller에서 사용하는 파사드 서비스 - */ -public interface LentFacadeService { - - /** - * 유저의 대여대기 정보를 가져옵니다. - * - * @param user - * @return - */ - MyCabinetResponseDto getMyLentInfoFromRedis(@UserSession UserSessionDto user); - - /** - * 사물함 대여를 합니다. - * - * @param userId 대여하려는 일반 user id - * @param cabinetId 대여하려는 cabinet id - */ - void startLentCabinet(Long userId, Long cabinetId); - - /*** - * 공유사물함 대여를 합니다. - * - * @param userId 대여하려는 일반 user id - * @param cabinetId 대여하려는 cabinet id - * @param shareCode 10분 간 유지되는 공유사물함 초대 코드 - */ - void startLentShareCabinet(Long userId, Long cabinetId, String shareCode); - - /** - * 동아리 사물함 대여를 합니다. - * - * @param userId 대여하려는 동아리 user id - * @param cabinetId 대여하려는 동아리 cabinet id - */ - void startLentClubCabinet(Long userId, Long cabinetId); - - /** - * 사물함을 반납 합니다. 유저가 정책에 따라 벤이 될 수 있습니다. - * - * @param userSessionDto 요청한 유저 dto - */ - void endLentCabinet(UserSessionDto userSessionDto); - - /** - * 사물함을 반납될 비밀번화와 함께 반납 합니다. - * - * @param userSessionDto 요청한 유저 dto - * @param lentEndMemoDto - */ - void endLentCabinetWithMemo(UserSessionDto userSessionDto, LentEndMemoDto lentEndMemoDto); - - /** - * 공유사물함 대여 대기열을 취소합니다. - * - * @param userId 대여하려는 일반 user id - */ - void cancelLentShareCabinet(Long userId, Long cabinetId); - - - /** - * 사물함을 강제 반납 합니다. 유저가 벤이 되진 않습니다 - * - * @param userId 반납하려는 user id - */ - void terminateLentCabinet(Long userId); - - /** - * 사물함들을 강제 반납 합니다. 유저가 벤이 되진 않습니다 - * - * @param returnCabinetsRequestDto 반납하려는 cabinet id list - */ - void terminateLentCabinets(ReturnCabinetsRequestDto returnCabinetsRequestDto); - - /** - * 페이지네이션을 적용해서 유저가 지금까지 대여했던 모든 기록을 가져옵니다. - * - * @param userId 찾으려는 user id - * @param page 페이지네이션 offset - * @param size 페이지네이션 size - * @return {@link LentHistoryPaginationDto} - */ - LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size); - - MyCabinetResponseDto getMyLentInfo(UserSessionDto user); - - /** - * 아직 반납하지 않은 사물함의 대여기록을 가져옵니다. - * - * @param cabinetId 찾으려는 cabinet id - * @return {@link LentDto}의 {@link List} - */ - List getLentDtoList(Long cabinetId); - - List getLentDtoListFromRedis(Long cabinetId); - - /** - * 내가 대여한 기록들을 페이지네이션 기준으로 가져옵니다. - * - * @param user 유저 정보 - * @param page 페이지 - * @param size 사이즈 - * @return 대여 기록 - */ - LentHistoryPaginationDto getMyLentLog(UserSessionDto user, - Integer page, Integer size); - - /** - * 자신이 대여한 사물함의 메모를 바꿉니다. - * - * @param userSessionDto 요청한 유저 dto - * @param updateCabinetMemoDto 변경할 사물함 제목 - */ - void updateCabinetMemo(UserSessionDto userSessionDto, - UpdateCabinetMemoDto updateCabinetMemoDto); - - /** - * 자신이 대여한 사물함의 제목을 바꿉니다. - * - * @param userSessionDto 요청한 유저 dto - * @param updateCabinetTitleDto 변경할 사물함 제목 - */ - void updateCabinetTitle(UserSessionDto userSessionDto, - UpdateCabinetTitleDto updateCabinetTitleDto); - - /** - * @param userSessionDto 요청한 유저 dto - * @param cabinetInfoRequestDto 변경할 사물함 정보 ( 제목, 메모 ) - */ - void updateCabinetInfo(UserSessionDto userSessionDto, - CabinetInfoRequestDto cabinetInfoRequestDto); - - /** - * 어드민으로 유저를 지정하여 캐비넷을 대여 시킵니다. - * - * @param userId 대여시킬 유저 Id - * @param cabinetId 대여시킬 캐비넷 Id - */ - void assignLent(Long userId, Long cabinetId); - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java deleted file mode 100644 index 37ff9ab53..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeServiceImpl.java +++ /dev/null @@ -1,261 +0,0 @@ -package org.ftclub.cabinet.lent.service; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import javax.transaction.Transactional; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.cabinet.service.CabinetService; -import org.ftclub.cabinet.config.CabinetProperties; -import org.ftclub.cabinet.dto.CabinetInfoRequestDto; -import org.ftclub.cabinet.dto.LentDto; -import org.ftclub.cabinet.dto.LentEndMemoDto; -import org.ftclub.cabinet.dto.LentHistoryDto; -import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.dto.MyCabinetResponseDto; -import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.ftclub.cabinet.dto.UpdateCabinetMemoDto; -import org.ftclub.cabinet.dto.UpdateCabinetTitleDto; -import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; -import org.ftclub.cabinet.lent.repository.LentRedis; -import org.ftclub.cabinet.mapper.CabinetMapper; -import org.ftclub.cabinet.mapper.LentMapper; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserSession; -import org.ftclub.cabinet.user.repository.UserOptionalFetcher; -import org.ftclub.cabinet.user.service.UserService; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; - -@Service -@AllArgsConstructor -@Transactional -@Log4j2 -public class LentFacadeServiceImpl implements LentFacadeService { - - private final UserOptionalFetcher userOptionalFetcher; - private final CabinetOptionalFetcher cabinetOptionalFetcher; - private final LentOptionalFetcher lentOptionalFetcher; - private final LentService lentService; - private final LentMapper lentMapper; - private final CabinetService cabinetService; - private final CabinetMapper cabinetMapper; - private final LentRedis lentRedis; - private final CabinetProperties cabinetProperties; - private final UserService userService; - - /*-------------------------------------------READ-------------------------------------------*/ - - @Override - public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, - Integer size) { - log.debug("Called getAllUserLentHistories: {}", userId); - userOptionalFetcher.findUser(userId); - //todo: 예쁘게 수정 - if (size <= 0) { - size = Integer.MAX_VALUE; - } - PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); - Page lentHistories = lentOptionalFetcher.findPaginationByUserId(userId, - pageable); - return generateLentHistoryPaginationDto(lentHistories.toList(), - lentHistories.getTotalElements()); - } - - @Override - public List getLentDtoList(Long cabinetId) { - log.debug("Called getLentDtoList: {}", cabinetId); - cabinetOptionalFetcher.getCabinet(cabinetId); - List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( - cabinetId); - return lentHistories.stream().map( - e -> lentMapper.toLentDto(e.getUser(), e)).collect(Collectors.toList()); -// return lentHistories.stream() -// .map(e -> new LentDto( -// e.getUserId(), -// e.getUser().getName(), -// e.getLentHistoryId(), -// e.getStartedAt(), -// e.getExpiredAt())) -// .collect(Collectors.toList()); - } - - @Override - public List getLentDtoListFromRedis(Long cabinetId) { - log.debug("Called getLentDtoListFromRedis: {}", cabinetId); - - List userIds = lentOptionalFetcher.findUserIdsByCabinetIdFromRedis(cabinetId); - return userIds.stream().map( - userId -> { - User user = userOptionalFetcher.findUser(Long.valueOf(userId)); - return new LentDto( - user.getUserId(), - user.getName(), - null, - null, - null); - }).collect(Collectors.toList()); - } - - /** - * {@InheritDocs} - * - * @param user 유저 정보 - * @param page 페이지 - * @param size 사이즈 - * @return LentHistoryPaginationDto 본인의 lent log - */ - @Override - public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, - Integer page, Integer size) { - log.debug("Called getMyLentLog: {}", user.getName()); - PageRequest pageable = PageRequest.of(page, size, - Sort.by(Sort.Direction.DESC, "startedAt")); - List myLentHistories = lentOptionalFetcher.findAllByUserIdAndEndedAtNotNull( - user.getUserId(), pageable); - List result = myLentHistories.stream() - .map(lentHistory -> lentMapper.toLentHistoryDto( - lentHistory, - lentHistory.getUser(), - lentHistory.getCabinet())) - .collect(Collectors.toList()); - return lentMapper.toLentHistoryPaginationDto(result, Long.valueOf(result.size())); - } - - private LentHistoryPaginationDto generateLentHistoryPaginationDto( - List lentHistories, Long totalLength) { - List lentHistoryDto = lentHistories.stream() - .map(e -> lentMapper.toLentHistoryDto(e, e.getUser(), e.getCabinet())) - .collect(Collectors.toList()); - return new LentHistoryPaginationDto(lentHistoryDto, totalLength); - } - - @Override - public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { - log.debug("Called getMyLentInfo: {}", user.getName()); - Cabinet myCabinet = lentOptionalFetcher.findActiveLentCabinetByUserId(user.getUserId()); - if (myCabinet == null) { // 대여 기록이 없거나 대여 대기 중인 경우 - return getMyLentInfoFromRedis(user); - } - Long cabinetId = myCabinet.getCabinetId(); - List lentDtoList = getLentDtoList(cabinetId); - String previousUserName = lentRedis.getPreviousUserName( - myCabinet.getCabinetId().toString()); - if (previousUserName == null) { - List previousLentHistory = lentOptionalFetcher.findPreviousLentHistoryByCabinetId( - cabinetId); - if (!previousLentHistory.isEmpty()) { - previousUserName = previousLentHistory.get(0).getUser().getName(); - } - } - return cabinetMapper.toMyCabinetResponseDto(myCabinet, lentDtoList, - null, null, previousUserName); - } - - @Override - public MyCabinetResponseDto getMyLentInfoFromRedis(@UserSession UserSessionDto user) { - log.debug("Called getMyLentInfoFromRedis: {}", user.getName()); - Long userId = user.getUserId(); - Long cabinetId = lentOptionalFetcher.findCabinetIdByUserIdFromRedis(userId); - log.debug("cabinetId: {}", cabinetId); - if (cabinetId == null) { - log.info("cabinetId is null"); - return null; - } - Cabinet cabinet = cabinetOptionalFetcher.getCabinet(cabinetId); - cabinet.specifyMaxUser(Math.toIntExact(cabinetProperties.getShareMaxUserCount())); - List lentDtoList = getLentDtoListFromRedis(cabinetId); - return cabinetMapper.toMyCabinetResponseDto(cabinet, lentDtoList, - lentRedis.getShareCode(cabinetId), - lentRedis.getSessionExpiredAtInRedis(cabinetId), null); - } - - - /*--------------------------------------------CUD--------------------------------------------*/ - - @Override - public void startLentCabinet(Long userId, Long cabinetId) { - lentService.startLentCabinet(userId, cabinetId); - } - - @Override - public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) { - lentService.startLentShareCabinet(userId, cabinetId, shareCode); - } - - @Override - public void startLentClubCabinet(Long userId, Long cabinetId) { - lentService.startLentClubCabinet(userId, cabinetId); - } - - @Override - public void endLentCabinet(UserSessionDto user) { - lentService.endLentCabinet(user.getUserId()); - } - - @Override - public void endLentCabinetWithMemo(UserSessionDto user, LentEndMemoDto lentEndMemoDto) { - log.debug("Called endLentCabinetWithMemo: {}", user.getName()); - Cabinet cabinet = cabinetService.getLentCabinetByUserId(user.getUserId()); - lentService.endLentCabinet(user.getUserId()); - cabinetService.updateMemo(cabinet.getCabinetId(), lentEndMemoDto.getCabinetMemo()); - } - - @Override - public void cancelLentShareCabinet(Long userId, Long cabinetId) { - lentService.cancelLentShareCabinet(userId, cabinetId); - } - - @Override - public void terminateLentCabinet(Long userId) { - log.debug("Called terminateLentCabinet {}", userId); - lentService.terminateLentCabinet(userId); - } - - @Override - public void terminateLentCabinets(ReturnCabinetsRequestDto returnCabinetsRequestDto) { - log.debug("Called terminateLentCabinets"); - returnCabinetsRequestDto.getCabinetIds().stream() - .forEach(lentService::terminateLentByCabinetId); - } - - @Override - public void updateCabinetMemo(UserSessionDto user, UpdateCabinetMemoDto updateCabinetMemoDto) { - log.debug("Called updateCabinetMemo: {}", user.getName()); - Cabinet myCabinet = cabinetService.getLentCabinetByUserId((user.getUserId())); - cabinetService.updateMemo(myCabinet.getCabinetId(), updateCabinetMemoDto.getMemo()); - } - - @Override - public void updateCabinetTitle(UserSessionDto user, - UpdateCabinetTitleDto updateCabinetTitleDto) { - log.debug("Called updateCabinetTitle: {}", user.getName()); - Cabinet myCabinet = cabinetService.getLentCabinetByUserId(user.getUserId()); - cabinetService.updateTitle(myCabinet.getCabinetId(), - updateCabinetTitleDto.getCabinetTitle()); - } - - @Override - public void updateCabinetInfo(UserSessionDto user, - CabinetInfoRequestDto cabinetInfoRequestDto) { - log.debug("Called updateCabinetInfo: {}", user.getName()); - - Cabinet myCabinet = cabinetService.getLentCabinetByUserId(user.getUserId()); - - cabinetService.updateTitleAndMemo(myCabinet.getCabinetId(), - cabinetInfoRequestDto.getTitle(), - cabinetInfoRequestDto.getMemo()); - } - - @Override - public void assignLent(Long userId, Long cabinetId) { - lentService.assignLent(userId, cabinetId); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentService.java deleted file mode 100644 index 3910e10b6..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentService.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.ftclub.cabinet.lent.service; - -import java.util.List; -import org.ftclub.cabinet.dto.ActiveLentHistoryDto; - -/** - * 대여 관련된 서비스 - */ -public interface LentService { - - /** - * 사물함 대여를 합니다. - * - * @param userId 대여하려는 일반 user id - * @param cabinetId 대여하려는 cabinet id - */ - void startLentCabinet(Long userId, Long cabinetId); - - /** - * 공유사물함 대여를 합니다. - * - * @param userId 대여하려는 일반 user id - * @param cabinetId 대여하려는 cabinet id - */ - void startLentShareCabinet(Long userId, Long cabinetId, String shareCode); - - /** - * 동아리 사물함 대여를 합니다. - * - * @param userId 대여하려는 동아리 user id - * @param cabinetId 대여하려는 동아리 cabinet id - */ - void startLentClubCabinet(Long userId, Long cabinetId); - - /** - * 사물함을 반납 합니다. 유저가 정책에 따라 벤이 될 수 있습니다. - * - * @param userId 반납하려는 user id - */ - void endLentCabinet(Long userId); - - /** - * 공유사물함 대기열에서 취소합니다. - * - * @param userId - 취소하려는 user id - */ - void cancelLentShareCabinet(Long userId, Long cabinetId); - - /** - * 사물함을 강제 반납 합니다. 유저가 벤이 되진 않습니다 - * - * @param userId 반납하려는 user id - */ - void terminateLentCabinet(Long userId); - - /** - * cabinet id로 사물함을 강제 반납 합니다. 유저가 벤이 되진 않습니다 - * - * @param cabinetId 반납하려는 cabinet id - */ - void terminateLentByCabinetId(Long cabinetId); - - /** - * 어드민으로 유저에게 사물함을 대여 시킵니다. - * - * @param userId 대여시킬 유저 Id - * @param cabinetId 대여시킬 캐비넷 Id - */ - void assignLent(Long userId, Long cabinetId); - - /** - * Redis에 저장된 대여 정보를 DB에 저장하고 Redis에서 삭제합니다. - * - * @param cabinetIdString Redis에 저장된 대여 정보의 키 - */ - void handleLentFromRedisExpired(String cabinetIdString); - - /** - * 현재 대여중인 모든 사물함의 대여기록을 가져옵니다. - * - * @return {@link ActiveLentHistoryDto}의 {@link List} - */ - List getAllActiveLentHistories(); - - /** - * 현재 대여중인 사물함의 모든 대여기록을 가져온 후, expiredAt을 갱신시키고, user 의 is_extensible 을 false 한다 userId로, - * cabinet 을 조회하고, cabinetId로 LentHistory를 조회한다. LentHistory의 expiredAt을 user의 isExtensible로 - * 갱신한다. - * - * @param userId - */ - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java b/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java index 77ba3f2a1..2f6a32000 100644 --- a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java +++ b/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.redis; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.lent.service.LentServiceImpl; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; @@ -14,20 +14,20 @@ @Log4j2 public class ExpirationListener extends KeyExpirationEventMessageListener { - private final LentServiceImpl lentServiceImpl; + private final LentFacadeService lentFacadeService; /** * Creates new {@link MessageListener} for {@code __keyevent@*__:expired} messages. * * @param listenerContainer must not be {@literal null}. - * @param lentServiceImpl must not be {@literal null}. + * @param lentFacadeService must not be {@literal null}. */ public ExpirationListener( @Qualifier("redisMessageListenerContainer") RedisMessageListenerContainer listenerContainer, - LentServiceImpl lentServiceImpl) { + LentFacadeService lentFacadeService) { super(listenerContainer); - this.lentServiceImpl = lentServiceImpl; + this.lentFacadeService = lentFacadeService; } /** @@ -38,7 +38,6 @@ public ExpirationListener( public void onMessage(Message message, byte[] pattern) { log.debug("Called onMessage: {}, {}", message.toString(), pattern); String cabinetIdString = message.toString().split(":")[0]; - log.debug("cabinetIdWithSuffix: {}", cabinetIdString); - lentServiceImpl.handleLentFromRedisExpired(cabinetIdString); + lentFacadeService.shareCabinetSessionExpired(Long.valueOf(cabinetIdString)); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java index 98c10f7ef..572230da7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java @@ -9,9 +9,10 @@ import org.ftclub.cabinet.dto.ClubUserListDto; import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.user.service.LentExtensionService; import org.ftclub.cabinet.user.service.UserFacadeService; +import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -31,115 +32,113 @@ @Log4j2 public class AdminUserController { - private final UserFacadeService userFacadeService; - private final LentFacadeService lentFacadeService; - private final LentExtensionService lentExtensionService; + private final UserFacadeService userFacadeService; + private final LentFacadeService lentFacadeService; + private final LentExtensionService lentExtensionService; - /** - * 현재 유저가 차단된 상태일 때, 차단을 해제합니다. - * - * @param userId 유저 고유 아이디 - */ - @DeleteMapping("/{userId}/ban-history") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { - log.info("Called deleteBanHistoryByUserId: {}", userId); - userFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); - } + /** + * 현재 유저가 차단된 상태일 때, 차단을 해제합니다. + * + * @param userId 유저 고유 아이디 + */ + @DeleteMapping("/{userId}/ban-history") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { + log.info("Called deleteBanHistoryByUserId: {}", userId); + userFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); + } - /** - * 유저의 대여 기록을 반환합니다. - * - * @param userId 유저 고유 아이디 - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return {@link LentHistoryPaginationDto} 유저의 대여 기록 - */ - @GetMapping("/{userId}/lent-histories") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentHistoryPaginationDto getLentHistoriesByUserId(@PathVariable("userId") Long userId, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getLentHistoriesByUserId: {}", userId); - return lentFacadeService.getAllUserLentHistories(userId, page, size); - } + /** + * 유저의 대여 기록을 반환합니다. + * + * @param userId 유저 고유 아이디 + * @param pageRequest 페이지네이션 정보 + * @return {@link LentHistoryPaginationDto} 유저의 대여 기록 + */ + @GetMapping("/{userId}/lent-histories") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public LentHistoryPaginationDto getLentHistoriesByUserId(@PathVariable("userId") Long userId, + @RequestBody PageRequest pageRequest) { + log.info("Called getLentHistoriesByUserId: {}", userId); + return lentFacadeService.getUserLentHistories(userId, pageRequest); + } - /** - * 유저를 어드민으로 승격시킵니다. - * - * @param email 유저 이메일 - * @return redirect:cabi.42seoul.io/admin/login - */ - @GetMapping("/admins/promote") - @AuthGuard(level = AuthLevel.MASTER_ONLY) - public void promoteUserToAdmin(@RequestParam("email") String email) { - log.info("Called promoteUserToAdmin: {}", email); - userFacadeService.promoteUserToAdmin(email); - } + /** + * 유저를 어드민으로 승격시킵니다. + * + * @param email 유저 이메일 + * @return redirect:cabi.42seoul.io/admin/login + */ + @GetMapping("/admins/promote") + @AuthGuard(level = AuthLevel.MASTER_ONLY) + public void promoteUserToAdmin(@RequestParam("email") String email) { + log.info("Called promoteUserToAdmin: {}", email); + userFacadeService.promoteUserToAdmin(email); + } - /** - * 동아리 유저를 생성합니다. - * - * @param body - */ - @PostMapping("/club") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void createClubUser(@RequestBody HashMap body) { - log.info("Called createClub"); - String clubName = body.get("clubName"); - userFacadeService.createClubUser(clubName); - } + /** + * 동아리 유저를 생성합니다. + * + * @param body + */ + @PostMapping("/club") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void createClubUser(@RequestBody HashMap body) { + log.info("Called createClub"); + String clubName = body.get("clubName"); + userFacadeService.createClubUser(clubName); + } - /** - * 동아리 유저를 삭제합니다. - * - * @param clubId 동아리 고유 아이디 - */ - @DeleteMapping("/club/{clubId}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void deleteClubUser(@PathVariable("clubId") Long clubId) { - log.info("Called deleteClub"); - userFacadeService.deleteClubUser(clubId); - } + /** + * 동아리 유저를 삭제합니다. + * + * @param clubId 동아리 고유 아이디 + */ + @DeleteMapping("/club/{clubId}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void deleteClubUser(@PathVariable("clubId") Long clubId) { + log.info("Called deleteClub"); + userFacadeService.deleteClubUser(clubId); + } - @GetMapping("/clubs") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public ClubUserListDto findClubs(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getClubs"); - return userFacadeService.findAllClubUser(page, size); - } + @GetMapping("/clubs") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public ClubUserListDto findClubs(@RequestParam("page") Integer page, + @RequestParam("size") Integer size) { + log.info("Called getClubs"); + return userFacadeService.findAllClubUser(page, size); + } - @PatchMapping("/club/{clubId}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void updateClubUser(@PathVariable("clubId") Long clubId, - @RequestBody HashMap body) { - log.info("Called updateClub"); - String clubName = body.get("clubName"); - userFacadeService.updateClubUser(clubId, clubName); - } + @PatchMapping("/club/{clubId}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void updateClubUser(@PathVariable("clubId") Long clubId, + @RequestBody HashMap body) { + log.info("Called updateClub"); + String clubName = body.get("clubName"); + userFacadeService.updateClubUser(clubId, clubName); + } - @GetMapping("/lent-extensions") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentExtensionPaginationDto getAllLentExtension(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getAllLentExtension"); - return userFacadeService.getAllLentExtension(page, size); - } + @GetMapping("/lent-extensions") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public LentExtensionPaginationDto getAllLentExtension(@RequestParam("page") Integer page, + @RequestParam("size") Integer size) { + log.info("Called getAllLentExtension"); + return userFacadeService.getAllLentExtension(page, size); + } - @GetMapping("/lent-extensions/active") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getAllActiveLentExtension"); - return userFacadeService.getAllActiveLentExtension(page, size); - } + @GetMapping("/lent-extensions/active") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page") Integer page, + @RequestParam("size") Integer size) { + log.info("Called getAllActiveLentExtension"); + return userFacadeService.getAllActiveLentExtension(page, size); + } - @PostMapping("/lent-extensions/{user}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void issueLentExtension(@PathVariable("user") String username) { - log.info("Called issueLentExtension"); - lentExtensionService.assignLentExtension(username); + @PostMapping("/lent-extensions/{user}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void issueLentExtension(@PathVariable("user") String username) { + log.info("Called issueLentExtension"); + lentExtensionService.assignLentExtension(username); - } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java index 71218a924..e360c4447 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java @@ -1,7 +1,11 @@ package org.ftclub.cabinet.user.newService; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.springframework.stereotype.Service; @Service @@ -9,4 +13,11 @@ @Log4j2 public class BanHistoryCommandService { + private final BanHistoryRepository banHistoryRepository; + + public void banUser(Long userId, LocalDateTime endedAt, + LocalDateTime unBannedAt, BanType banType) { + BanHistory banHistory = BanHistory.of(endedAt, unBannedAt, banType, userId); + banHistoryRepository.save(banHistory); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index d5599237c..f85b0b3e9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -1,26 +1,30 @@ package org.ftclub.cabinet.user.newService; +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; - @Service @RequiredArgsConstructor @Log4j2 public class BanHistoryQueryService { - private final BanHistoryRepository banHistoryRepository; - - public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { - log.debug("Called findRecentActiveBanHistory: {}", userId); + private final BanHistoryRepository banHistoryRepository; - List banHistories = banHistoryRepository.findAll(); +// public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { +// log.debug("Called findRecentActiveBanHistory: {}", userId); +// +// List banHistories = banHistoryRepository.findAll(); +// +// } - } + public List findActiveBanHistories(Long userId, LocalDateTime date) { + log.debug("Called findActiveBanHistories: {}", userId); + return banHistoryRepository.findByUserIdAndUnbannedAt(userId, date); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java new file mode 100644 index 000000000..30c520b02 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java @@ -0,0 +1,29 @@ +package org.ftclub.cabinet.user.newService; + +import java.time.LocalDateTime; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.utils.DateUtil; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class BanPolicyService { + + public BanType verifyBan(LocalDateTime endedAt, LocalDateTime expiredAt) { + log.debug("Called verifyBan"); + + if (expiredAt.isBefore(endedAt)) { + return BanType.ALL; + } + return BanType.NONE; + } + + public LocalDateTime getUnBannedAt(LocalDateTime endedAt, LocalDateTime expiredAt) { + log.debug("Called getBanDate"); + + long recentBanDays = DateUtil.calculateTwoDateDiffCeil(expiredAt, endedAt); + double squaredBanDays = Math.pow(recentBanDays, 2); + return endedAt.plusDays((long) squaredBanDays); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 4b58d6676..71559d2a2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -2,8 +2,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.dto.MyProfileResponseDto; -import org.ftclub.cabinet.dto.UserSessionDto; import org.springframework.stereotype.Service; @Service @@ -11,13 +9,13 @@ @Log4j2 public class UserFacadeService { - public MyProfileResponseDto getMyProfile(UserSessionDto user) { - log.debug("Called getMyProfile: {}", user.getName()); - - // Cabinet cabinet = lentService.findActiveLentCabinetByUserId(); - - - } +// public MyProfileResponseDto getMyProfile(UserSessionDto user) { +// log.debug("Called getMyProfile: {}", user.getName()); +// +// // Cabinet cabinet = lentService.findActiveLentCabinetByUserId(); +// +// +// } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java index 1d30c10ef..5867d647b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -1,31 +1,29 @@ package org.ftclub.cabinet.user.newService; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - @Service @RequiredArgsConstructor @Log4j2 public class UserQueryService { - private final UserRepository userRepository; + private final UserRepository userRepository; - public User getUser(Long userId) { - Optional user = userRepository.findById(userId); - return user.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); - } + public User getUser(Long userId) { + Optional user = userRepository.findById(userId); + return user.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } + public List getUsers(List userIdsInCabinet) { + return userRepository.findAllByIds(userIdsInCabinet); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java index b1494b03d..6b362fa84 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java @@ -22,7 +22,7 @@ public interface BanHistoryRepository extends JpaRepository { * @return active {@link BanHistory} 리스트 */ @Query("SELECT b FROM BanHistory b WHERE b.user.userId = :userId AND b.unbannedAt > :today") - List findUserActiveBanList( + List findByUserIdAndUnbannedAt( @Param("userId") Long userId, @Param("today") LocalDateTime today); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index 61187fdcf..d469c10e0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -51,6 +51,16 @@ public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.name LIKE %:name%") Page findByPartialName(@Param("name") String name, Pageable pageable); + /** + * 유저의 Id List로 유저들을 찾습니다. + * + * @param userIds 유저 Id {@link List} + * @return {@link User} 리스트 + */ + @Query("SELECT u FROM User u " + + "WHERE u.userId IN :userIds AND u.deletedAt IS NULL") + List findAllByIds(List userIds); + /** * */ diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java index c2be461b1..261a4e82d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java @@ -178,7 +178,7 @@ public void deleteRecentBanHistory(Long userId, LocalDateTime today) { @Override public boolean checkUserIsBanned(Long userId, LocalDateTime today) { log.debug("Called checkUserIsBanned: {}", userId); - List banHistory = banHistoryRepository.findUserActiveBanList(userId, + List banHistory = banHistoryRepository.findByUserIdAndUnbannedAt(userId, today); return (banHistory.size() != 0); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java b/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java index be8605e38..e37af5005 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java @@ -138,4 +138,8 @@ public static Long calculateTwoDateDiffCeil(LocalDateTime day1, LocalDateTime da System.out.println("diffInMillis = " + diffInMillis); return (long) Math.ceil(diffInMillis / 1000.0 / 60 / 60 / 24); } + + public static LocalDateTime setLastTime(LocalDateTime date) { + return date.withHour(23).withMinute(59).withSecond(59); + } } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java index 92fcb5379..d48051c83 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java @@ -9,7 +9,7 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.exception.UtilException; -import org.ftclub.cabinet.lent.service.LentService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.user.service.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -21,7 +21,7 @@ public class BlackholeManager { private final FtApiManager ftApiManager; - private final LentService lentService; + private final LentFacadeService lentFacadeService; private final UserService userService; /** @@ -61,11 +61,7 @@ private LocalDateTime parseBlackholedAt(JsonNode jsonUserInfo) { private boolean isBlackholed(LocalDateTime blackholedAtDate) { log.info("isBlackholed {} {}", blackholedAtDate); LocalDateTime now = LocalDateTime.now(); - if (blackholedAtDate == null || blackholedAtDate.isAfter(now)) { - return false; - } else { - return true; - } + return blackholedAtDate != null && !blackholedAtDate.isAfter(now); } /** @@ -76,7 +72,7 @@ private boolean isBlackholed(LocalDateTime blackholedAtDate) { */ private void handleNotCadet(UserBlackholeInfoDto userBlackholeInfoDto, LocalDateTime now) { log.warn("{}는 카뎃이 아닙니다.", userBlackholeInfoDto); - lentService.terminateLentCabinet(userBlackholeInfoDto.getUserId()); + lentFacadeService.endUserLent(userBlackholeInfoDto.getUserId()); userService.deleteUser(userBlackholeInfoDto.getUserId(), now); } @@ -88,7 +84,7 @@ private void handleNotCadet(UserBlackholeInfoDto userBlackholeInfoDto, LocalDate private void handleBlackholed(UserBlackholeInfoDto userBlackholeInfoDto) { log.info("{}는 블랙홀에 빠졌습니다.", userBlackholeInfoDto); LocalDateTime now = LocalDateTime.now(); - lentService.terminateLentCabinet(userBlackholeInfoDto.getUserId()); + lentFacadeService.endUserLent(userBlackholeInfoDto.getUserId()); userService.deleteUser(userBlackholeInfoDto.getUserId(), now); } @@ -116,7 +112,7 @@ private void handleHttpClientError(UserBlackholeInfoDto userBlackholeInfoDto, Lo log.error("handleBlackhole HttpClientErrorException {}", e.getStatusCode()); if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { log.warn("{}는 42에서 찾을 수 없습니다.", userBlackholeInfoDto); - lentService.terminateLentCabinet(userBlackholeInfoDto.getUserId()); + lentFacadeService.endUserLent(userBlackholeInfoDto.getUserId()); userService.deleteUser(userBlackholeInfoDto.getUserId(), now); } } @@ -192,10 +188,10 @@ public void handleBlackhole(UserBlackholeInfoDto userInfoDto) { } catch (ServiceException e) { if (e.getStatus().equals(ExceptionStatus.NO_LENT_CABINET)) { userService.deleteUser(userInfoDto.getUserId(), now); - } - else if (e.getStatus().equals(ExceptionStatus.OAUTH_BAD_GATEWAY)) + } else if (e.getStatus().equals(ExceptionStatus.OAUTH_BAD_GATEWAY)) { log.info("handleBlackhole ServiceException {}", e.getStatus()); - throw new UtilException(e.getStatus()); + } + throw new UtilException(e.getStatus()); } catch (Exception e) { log.error("handleBlackhole Exception: {}", userInfoDto, e); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java index b08c14e29..3a0ea8f35 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java @@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.service.FtApiManager; -import org.ftclub.cabinet.lent.service.LentService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.user.service.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -17,7 +17,7 @@ public class LeaveAbsenceManager { private final FtApiManager ftAPIManager; - private final LentService lentService; + private final LentFacadeService lentFacadeService; private final UserService userService; private Boolean isLeaveAbsence(JsonNode jsonUserInfo) { @@ -29,12 +29,12 @@ public void handleLeaveAbsence(Long userId, String name) { try { JsonNode jsonUserInfo = ftAPIManager.getFtUsersInfoByName(name); if (isLeaveAbsence(jsonUserInfo)) { - lentService.terminateLentCabinet(userId); + lentFacadeService.endUserLent(userId); } } catch (HttpClientErrorException e) { log.error("handleLeaveAbsence HttpClientErrorException {}", e.getStatusCode()); if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { - lentService.terminateLentCabinet(userId); + lentFacadeService.endUserLent(userId); userService.deleteUser(userId, LocalDateTime.now()); } } catch (Exception e) { diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 5450037a0..35f660a25 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -6,7 +6,7 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.UserBlackholeInfoDto; -import org.ftclub.cabinet.lent.service.LentService; +import org.ftclub.cabinet.lent.newService.LentFacadeService; import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; import org.ftclub.cabinet.user.service.UserService; import org.ftclub.cabinet.utils.blackhole.manager.BlackholeManager; @@ -28,7 +28,7 @@ public class SystemScheduler { private final LeaveAbsenceManager leaveAbsenceManager; private final OverdueManager overdueManager; - private final LentService lentService; + private final LentFacadeService lentFacadeService; private final UserService userService; private final BlackholeManager blackholeManager; private final ReleaseManager releaseManager; @@ -41,7 +41,7 @@ public class SystemScheduler { @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") public void checkAllLents() { log.info("called checkAllLents"); - List activeLents = lentService.getAllActiveLentHistories(); + List activeLents = lentFacadeService.getAllActiveLentHistories(); for (ActiveLentHistoryDto activeLent : activeLents) { overdueManager.handleOverdue(activeLent); /* diff --git a/backend/src/test/java/org/ftclub/cabinet/redis/RedisRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/redis/RedisRepositoryTest.java index 4d83d5b5a..3fe7991ff 100644 --- a/backend/src/test/java/org/ftclub/cabinet/redis/RedisRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/redis/RedisRepositoryTest.java @@ -24,8 +24,8 @@ void test() { Long cabinetId = 16L; lentRedis.setShadowKey(cabinetId); - lentRedis.saveUserInRedis("16L", "1234L", "1000", false); - lentRedis.saveUserInRedis("16L", "5678L", "1000", true); + lentRedis.attemptJoinCabinet("16L", "1234L", "1000", false); + lentRedis.attemptJoinCabinet("16L", "5678L", "1000", true); try { Thread.sleep(10000); diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/BanHistoryRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/BanHistoryRepositoryTest.java index 3214033ea..2333f2e58 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/BanHistoryRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/BanHistoryRepositoryTest.java @@ -31,7 +31,7 @@ public class BanHistoryRepositoryTest { // ban history 한 개 존재 Long userId = 1L; - List activeBanList = banHistoryRepository.findUserActiveBanList(userId, + List activeBanList = banHistoryRepository.findByUserIdAndUnbannedAt(userId, testDate); assertNotNull(activeBanList); diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java index 32b50bf6a..efec2404a 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java @@ -1,7 +1,10 @@ package org.ftclub.cabinet.user.service; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -67,7 +70,7 @@ class UserServiceUnitTest { boolean result = userService.checkUserExists("user1@student.42seoul.kr"); - assertEquals(true, result); + assertTrue(result); then(userOptionalFetcher).should(times(1)).findUserByEmail("user1@student.42seoul.kr"); } @@ -79,7 +82,7 @@ class UserServiceUnitTest { boolean result = userService.checkUserExists("notUser@student.42seoul.kr"); - assertEquals(false, result); + assertFalse(result); then(userOptionalFetcher).should(times(1)).findUserByEmail("notUser@student.42seoul.kr"); } @@ -103,7 +106,7 @@ class UserServiceUnitTest { boolean result = userService.checkAdminUserExists("admin@admin.com"); - assertEquals(true, result); + assertTrue(result); then(userOptionalFetcher).should(times(1)).findAdminUserByEmail("admin@admin.com"); } @@ -115,7 +118,7 @@ class UserServiceUnitTest { boolean result = userService.checkAdminUserExists("notAdmin@admin.com"); - assertEquals(false, result); + assertFalse(result); then(userOptionalFetcher).should(times(1)).findAdminUserByEmail("notAdmin@admin.com"); } @@ -531,12 +534,12 @@ void createAdminUser() { BanHistory banHistory = mock(BanHistory.class); list.add(banHistory); - given(banHistoryRepository.findUserActiveBanList(userId, now)).willReturn(list); + given(banHistoryRepository.findByUserIdAndUnbannedAt(userId, now)).willReturn(list); boolean result = userService.checkUserIsBanned(userId, now); - then(banHistoryRepository).should(times(1)).findUserActiveBanList(userId, now); - assertEquals(result, true); + then(banHistoryRepository).should(times(1)).findByUserIdAndUnbannedAt(userId, now); + assertTrue(result); } @Test @@ -546,12 +549,12 @@ void createAdminUser() { Long userId = 2L; LocalDateTime now = LocalDateTime.now(); - given(banHistoryRepository.findUserActiveBanList(userId, now)).willReturn(list); + given(banHistoryRepository.findByUserIdAndUnbannedAt(userId, now)).willReturn(list); boolean result = userService.checkUserIsBanned(userId, now); - then(banHistoryRepository).should(times(1)).findUserActiveBanList(userId, now); - assertEquals(result, false); + then(banHistoryRepository).should(times(1)).findByUserIdAndUnbannedAt(userId, now); + assertFalse(result); } @Test @@ -578,6 +581,6 @@ void createAdminUser() { AdminRole result = userService.getAdminUserRole(email); then(userOptionalFetcher).should(times(1)).findAdminUserRoleByEmail(email); - assertEquals(result, null); + assertNull(result); } } \ No newline at end of file From 72c54721e03ef59d9b024839a5958e3974da0eb6 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 13:29:05 +0900 Subject: [PATCH 0121/1029] =?UTF-8?q?[BE]=20admin=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20java=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminUserController.java | 2 +- .../controller/SearchController.java | 2 +- .../controller/StatisticsController.java | 4 +- .../{user => admin}/domain/AdminRole.java | 2 +- .../cabinet/admin/domain/AdminUser.java | 90 ++++ .../repository/AdminUserRepository.java | 7 +- .../repository/StatisticsOptionalFetcher.java | 2 +- .../repository/StatisticsRepository.java | 2 +- .../service/StatisticsFacadeService.java | 7 +- .../service/StatisticsFacadeServiceImpl.java | 4 +- .../cabinet/auth/domain/TokenProvider.java | 2 +- .../cabinet/auth/domain/TokenValidator.java | 4 +- .../ftclub/cabinet/user/domain/AdminUser.java | 93 ---- .../user/repository/UserOptionalFetcher.java | 419 +++++++++--------- .../user/service/UserFacadeService.java | 2 +- .../user/service/UserFacadeServiceImpl.java | 24 +- .../cabinet/user/service/UserService.java | 2 +- .../cabinet/user/service/UserServiceImpl.java | 6 +- .../auth/domain/TokenValidatorUnitTest.java | 38 +- .../controller/StatisticsControllerTest.java | 5 +- .../StatisticsFacadeServiceUnitTest.java | 3 +- .../cabinet/user/domain/AdminUserTest.java | 2 + .../repository/AdminUserRepositoryTest.java | 14 +- .../user/service/UserFacadeServiceTest.java | 1 + .../cabinet/user/service/UserServiceTest.java | 6 +- .../user/service/UserServiceUnitTest.java | 6 +- .../java/org/ftclub/testutils/TestUtils.java | 13 +- 27 files changed, 384 insertions(+), 378 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/{user => admin}/controller/AdminUserController.java (99%) rename backend/src/main/java/org/ftclub/cabinet/{search => admin}/controller/SearchController.java (98%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/controller/StatisticsController.java (96%) rename backend/src/main/java/org/ftclub/cabinet/{user => admin}/domain/AdminRole.java (89%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminUser.java rename backend/src/main/java/org/ftclub/cabinet/{user => admin}/repository/AdminUserRepository.java (88%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/repository/StatisticsOptionalFetcher.java (80%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/repository/StatisticsRepository.java (94%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/service/StatisticsFacadeService.java (62%) rename backend/src/main/java/org/ftclub/cabinet/{statistics => admin}/service/StatisticsFacadeServiceImpl.java (96%) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/user/domain/AdminUser.java diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java index 572230da7..b657d87db 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.controller; +package org.ftclub.cabinet.admin.controller; import java.time.LocalDateTime; import java.util.HashMap; diff --git a/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java index e081fd786..9e8acb1a1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/search/controller/SearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.search.controller; +package org.ftclub.cabinet.admin.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/controller/StatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java similarity index 96% rename from backend/src/main/java/org/ftclub/cabinet/statistics/controller/StatisticsController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java index 2c54b0313..d03a68d9a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/controller/StatisticsController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.controller; +package org.ftclub.cabinet.admin.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; @@ -11,7 +11,7 @@ import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; -import org.ftclub.cabinet.statistics.service.StatisticsFacadeService; +import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.ftclub.cabinet.user.service.UserFacadeService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminRole.java b/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminRole.java similarity index 89% rename from backend/src/main/java/org/ftclub/cabinet/user/domain/AdminRole.java rename to backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminRole.java index c9941c4ff..ea9facd3e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminRole.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminRole.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.domain; +package org.ftclub.cabinet.admin.domain; /** * 어드민의 권한을 나타내는 열거형 클래스입니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminUser.java b/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminUser.java new file mode 100644 index 000000000..7105ebdda --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminUser.java @@ -0,0 +1,90 @@ +package org.ftclub.cabinet.admin.domain; + +import java.util.Objects; +import java.util.regex.Pattern; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.utils.ExceptionUtil; +import lombok.extern.log4j.Log4j2; + +/** + * 관리자 엔티티 클래스입니다. + */ +@Entity +@Table(name = "ADMIN_USER") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Log4j2 +public class AdminUser { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ADMIN_USER_ID") + private Long adminUserId; + + /** + * OAuth 방식을 사용하기 때문에 비밀번호 없이 이메일만 저장합니다. + */ + @NotNull + @Email + @Column(name = "EMAIL", length = 128, unique = true, nullable = false) + private String email; + + /** + * {@link AdminRole} + */ + @Enumerated(value = EnumType.STRING) + @Column(name = "ROLE", length = 16, nullable = false) + private AdminRole role = AdminRole.NONE; + + protected AdminUser(String email, AdminRole role) { + this.email = email; + this.role = role; + } + + public static AdminUser of(String email, AdminRole role) { + AdminUser adminUser = new AdminUser(email, role); + ExceptionUtil.throwIfFalse(adminUser.isValid(), + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + return adminUser; + } + + private boolean isValid() { + return role != null && role.isValid() && email != null && + Pattern.matches( + "^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\\.[A-Za-z0-9\\-]+\\.*[A-Za-z0-9\\-]*", + email); + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof AdminUser)) { + return false; + } + AdminUser adminUser = (AdminUser) other; + return Objects.equals(adminUserId, adminUser.adminUserId); + } + + public void changeAdminRole(AdminRole role) { + log.info("Called changedAdminRole - role from {} to {}", this.role, role); + this.role = role; + ExceptionUtil.throwIfFalse(this.isValid(), + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/AdminUserRepository.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/AdminUserRepository.java similarity index 88% rename from backend/src/main/java/org/ftclub/cabinet/user/repository/AdminUserRepository.java rename to backend/src/main/java/org/ftclub/cabinet/admin/repository/AdminUserRepository.java index ede9a0c67..9574770cd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/AdminUserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/repository/AdminUserRepository.java @@ -1,8 +1,8 @@ -package org.ftclub.cabinet.user.repository; +package org.ftclub.cabinet.admin.repository; import java.util.Optional; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -29,6 +29,7 @@ public interface AdminUserRepository extends JpaRepository { /** * 유저의 이메일로 어드민 유저를 찾고 어드민 유저의 권한을 반환합니다. + * * @param email * @return {@link AdminRole} */ diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java similarity index 80% rename from backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsOptionalFetcher.java rename to backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java index 0071f78cb..5576f1450 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.repository; +package org.ftclub.cabinet.admin.repository; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsRepository.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java similarity index 94% rename from backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsRepository.java rename to backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java index c541e7b92..9e2d7f807 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/repository/StatisticsRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.repository; +package org.ftclub.cabinet.admin.repository; import java.util.List; import javax.transaction.Transactional; diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java similarity index 62% rename from backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java index 5352673ee..fedaa0bdf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.service; +package org.ftclub.cabinet.admin.service; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; @@ -11,7 +11,8 @@ public interface StatisticsFacadeService { - List getCabinetsCountOnAllFloors(); + List getCabinetsCountOnAllFloors(); - LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDate, LocalDateTime endDate); + LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDate, + LocalDateTime endDate); } diff --git a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java similarity index 96% rename from backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java index db9f7d312..f076e9f74 100644 --- a/backend/src/main/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.statistics.service; +package org.ftclub.cabinet.admin.service; import static org.ftclub.cabinet.utils.ExceptionUtil.throwIfFalse; @@ -14,7 +14,7 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.statistics.repository.StatisticsRepository; +import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.springframework.stereotype.Service; @Service diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java index 6dba0652c..04f0d8f4e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java @@ -16,7 +16,7 @@ import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.UserRole; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java index 5c44c7d4b..7471a6e2b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java @@ -1,6 +1,6 @@ package org.ftclub.cabinet.auth.domain; -import static org.ftclub.cabinet.user.domain.AdminRole.MASTER; +import static org.ftclub.cabinet.admin.domain.AdminRole.MASTER; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -19,7 +19,7 @@ import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.service.UserService; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminUser.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminUser.java deleted file mode 100644 index 090a457f5..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AdminUser.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.ftclub.cabinet.user.domain; - -import java.util.Objects; -import java.util.regex.Pattern; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.validation.Valid; -import javax.validation.constraints.Email; -import javax.validation.constraints.Max; -import javax.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.apache.el.util.ExceptionUtils; -import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.utils.ExceptionUtil; -import lombok.extern.log4j.Log4j2; - -/** - * 관리자 엔티티 클래스입니다. - */ -@Entity -@Table(name = "ADMIN_USER") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@Log4j2 -public class AdminUser { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "ADMIN_USER_ID") - private Long adminUserId; - - /** - * OAuth 방식을 사용하기 때문에 비밀번호 없이 이메일만 저장합니다. - */ - @NotNull - @Email - @Column(name = "EMAIL", length = 128, unique = true, nullable = false) - private String email; - - /** - * {@link AdminRole} - */ - @Enumerated(value = EnumType.STRING) - @Column(name = "ROLE", length = 16, nullable = false) - private AdminRole role = AdminRole.NONE; - - private boolean isValid() { - return role != null && role.isValid() && email != null && - Pattern.matches( - "^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\\.[A-Za-z0-9\\-]+\\.*[A-Za-z0-9\\-]*", - email); - } - - protected AdminUser(String email, AdminRole role) { - this.email = email; - this.role = role; - } - - public static AdminUser of(String email, AdminRole role) { - AdminUser adminUser = new AdminUser(email, role); - ExceptionUtil.throwIfFalse(adminUser.isValid(), - new DomainException(ExceptionStatus.INVALID_ARGUMENT)); - return adminUser; - } - - @Override - public boolean equals(final Object other) { - if (other == this) { - return true; - } - if (!(other instanceof AdminUser)) { - return false; - } - AdminUser adminUser = (AdminUser) other; - return Objects.equals(adminUserId, adminUser.adminUserId); - } - - public void changeAdminRole(AdminRole role) { - log.info("Called changedAdminRole - role from {} to {}", this.role, role); - this.role = role; - ExceptionUtil.throwIfFalse(this.isValid(), - new DomainException(ExceptionStatus.INVALID_ARGUMENT)); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index ca96376fe..c5a3d0d71 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -5,11 +5,12 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; @@ -23,234 +24,234 @@ @Log4j2 public class UserOptionalFetcher { - private final UserRepository userRepository; - private final AdminUserRepository adminUserRepository; - private final BanHistoryRepository banHistoryRepository; + private final UserRepository userRepository; + private final AdminUserRepository adminUserRepository; + private final BanHistoryRepository banHistoryRepository; - /*-------------------------------------------FIND-------------------------------------------*/ + /*-------------------------------------------FIND-------------------------------------------*/ - /** - * 유저 전체 목록을 가져옵니다. - * - * @return {@link List} of {@link User} - */ - public List findAllUsers() { - log.debug("Called findAllUsers"); - return userRepository.findAll(); - } + /** + * 유저 전체 목록을 가져옵니다. + * + * @return {@link List} of {@link User} + */ + public List findAllUsers() { + log.debug("Called findAllUsers"); + return userRepository.findAll(); + } - public List findAllActiveUsers() { + public List findAllActiveUsers() { - log.debug("Called findAllActiveUsers"); - return userRepository.findAllByDeletedAtIsNull(); - } + log.debug("Called findAllActiveUsers"); + return userRepository.findAllByDeletedAtIsNull(); + } - /** - * 유저가 존재하는지 확인하고 존재하지 않으면 null을 반환합니다. - * - * @param userId 유저의 고유 ID - * @return {@link User} - */ - public User findUser(Long userId) { - log.debug("Called findUser: {}", userId); - return userRepository.findById(userId).orElse(null); - } + /** + * 유저가 존재하는지 확인하고 존재하지 않으면 null을 반환합니다. + * + * @param userId 유저의 고유 ID + * @return {@link User} + */ + public User findUser(Long userId) { + log.debug("Called findUser: {}", userId); + return userRepository.findById(userId).orElse(null); + } - /** - * 유저가 존재하는지 확인하고 유저의 고유 ID를 반환합니다. 존재하지 않으면 null을 반환합니다. - * - * @param name 찾을 유저의 이름 - * @return 찾은 유저의 고유 id - */ - public User findUserByName(String name) { - log.debug("Called findUserByName: {}", name); - return userRepository.findByName(name).orElse(null); - } + /** + * 유저가 존재하는지 확인하고 유저의 고유 ID를 반환합니다. 존재하지 않으면 null을 반환합니다. + * + * @param name 찾을 유저의 이름 + * @return 찾은 유저의 고유 id + */ + public User findUserByName(String name) { + log.debug("Called findUserByName: {}", name); + return userRepository.findByName(name).orElse(null); + } - /** - * 유저의 이메일로 유저를 검색합니다. - * - * @param email 유저의 이메일 - */ - public User findUserByEmail(String email) { - log.debug("Called findUserByEmail: {}", email); - return userRepository.findByEmail(email).orElse(null); - } + /** + * 유저의 이메일로 유저를 검색합니다. + * + * @param email 유저의 이메일 + */ + public User findUserByEmail(String email) { + log.debug("Called findUserByEmail: {}", email); + return userRepository.findByEmail(email).orElse(null); + } - /** - * 유저의 이름 일부분으로 유저를 검색합니다. - * - * @param name 유저의 이름 일부분 - * @param pageable 페이지 정보 - * @return {@link Page} of {@link User} - */ - public Page findUsersByPartialName(String name, Pageable pageable) { - log.debug("Called findUsersByPartialName: {}", name); - return userRepository.findByPartialName(name, pageable); - } + /** + * 유저의 이름 일부분으로 유저를 검색합니다. + * + * @param name 유저의 이름 일부분 + * @param pageable 페이지 정보 + * @return {@link Page} of {@link User} + */ + public Page findUsersByPartialName(String name, Pageable pageable) { + log.debug("Called findUsersByPartialName: {}", name); + return userRepository.findByPartialName(name, pageable); + } - /** - * 어드민 유저 아이디로 어드민 유저를 찾습니다. - * - * @param adminUserId 어드민 유저 아이디 - * @return {@link AdminUser} - */ - public AdminUser findAdminUser(Long adminUserId) { - log.debug("Called findAdminUser: {}", adminUserId); - return adminUserRepository.findAdminUser(adminUserId).orElse(null); - } + /** + * 어드민 유저 아이디로 어드민 유저를 찾습니다. + * + * @param adminUserId 어드민 유저 아이디 + * @return {@link AdminUser} + */ + public AdminUser findAdminUser(Long adminUserId) { + log.debug("Called findAdminUser: {}", adminUserId); + return adminUserRepository.findAdminUser(adminUserId).orElse(null); + } - /** - * 어드민 유저의 이메일로 어드민 유저를 찾습니다. - * - * @param email 어드민 유저의 이메일 - * @return {@link AdminUser} - */ - public AdminUser findAdminUserByEmail(String email) { - log.debug("Called findAdminUserByEmail: {}", email); - return adminUserRepository.findAdminUserByEmail(email).orElse(null); - } + /** + * 어드민 유저의 이메일로 어드민 유저를 찾습니다. + * + * @param email 어드민 유저의 이메일 + * @return {@link AdminUser} + */ + public AdminUser findAdminUserByEmail(String email) { + log.debug("Called findAdminUserByEmail: {}", email); + return adminUserRepository.findAdminUserByEmail(email).orElse(null); + } - /** - * - */ - public AdminRole findAdminUserRoleByEmail(String email) { - log.debug("Called findAdminUserRoleByEmail: {}", email); - return adminUserRepository.findAdminUserRoleByEmail(email) - .orElse(null); - } + /** + * + */ + public AdminRole findAdminUserRoleByEmail(String email) { + log.debug("Called findAdminUserRoleByEmail: {}", email); + return adminUserRepository.findAdminUserRoleByEmail(email) + .orElse(null); + } - /** - * 유저의 BanHistory 목록을 가져옵니다. - * - * @param pageable 페이지 정보 - * @param now 현재 시간 - * @return {@link Page} of {@link BanHistory} - */ - public Page findPaginationActiveBanHistories(Pageable pageable, LocalDateTime now) { - log.debug("Called findPaginationActiveBanHistories"); - return banHistoryRepository.findPaginationActiveBanHistories(pageable, now); - } + /** + * 유저의 BanHistory 목록을 가져옵니다. + * + * @param pageable 페이지 정보 + * @param now 현재 시간 + * @return {@link Page} of {@link BanHistory} + */ + public Page findPaginationActiveBanHistories(Pageable pageable, LocalDateTime now) { + log.debug("Called findPaginationActiveBanHistories"); + return banHistoryRepository.findPaginationActiveBanHistories(pageable, now); + } - /** - * 유저의 가장 최근 BanHistory를 가져옵니다. 없으면 null을 반환합니다. - * - * @param userId 유저의 고유 ID - * @param now 현재 시간 - * @return {@link BanHistory} - */ - //TO-DO : isEmpty, List에 대한 0 인덱스 가져오기 등 리팩터링 매우 필요.. - public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { - log.debug("Called findRecentActiveBanHistory: {}", userId); - List banHistories = banHistoryRepository.findRecentBanHistoryByUserId(userId, - now, - PageRequest.of(0, 1)); - if (banHistories.isEmpty()) { - return null; - } - return banHistories.get(0); - } + /** + * 유저의 가장 최근 BanHistory를 가져옵니다. 없으면 null을 반환합니다. + * + * @param userId 유저의 고유 ID + * @param now 현재 시간 + * @return {@link BanHistory} + */ + //TO-DO : isEmpty, List에 대한 0 인덱스 가져오기 등 리팩터링 매우 필요.. + public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { + log.debug("Called findRecentActiveBanHistory: {}", userId); + List banHistories = banHistoryRepository.findRecentBanHistoryByUserId(userId, + now, + PageRequest.of(0, 1)); + if (banHistories.isEmpty()) { + return null; + } + return banHistories.get(0); + } - /** - * ROLE 이 동아리(CLUB)인 유저를 가져옵니다 - * - * @param pageable - * @return {@link Page} - */ - public Page findClubUsers(Pageable pageable) { - log.debug("Called findClubUsers"); - return userRepository.findAllByRoleAndDeletedAtIsNull(UserRole.CLUB, pageable); - } + /** + * ROLE 이 동아리(CLUB)인 유저를 가져옵니다 + * + * @param pageable + * @return {@link Page} + */ + public Page findClubUsers(Pageable pageable) { + log.debug("Called findClubUsers"); + return userRepository.findAllByRoleAndDeletedAtIsNull(UserRole.CLUB, pageable); + } - /*-------------------------------------------GET--------------------------------------------*/ + /*-------------------------------------------GET--------------------------------------------*/ - /** - * 유저가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. - * - * @param userId 유저의 고유 ID - * @return {@link User} - */ - public User getUser(Long userId) { - log.debug("Called getUser: {}", userId); - return userRepository.findById(userId) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); - } + /** + * 유저가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. + * + * @param userId 유저의 고유 ID + * @return {@link User} + */ + public User getUser(Long userId) { + log.debug("Called getUser: {}", userId); + return userRepository.findById(userId) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } - /** - * 유저가 존재하는지 확인하고 유저의 고유 ID를 반환합니다. 존재하지 않으면 예외를 발생시킵니다. - * - * @param name 찾을 유저의 이름 - * @return 찾은 유저의 고유 id - */ - public User getUserByName(String name) { - log.debug("Called getUserByName: {}", name); - return userRepository.findByName(name) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); - } + /** + * 유저가 존재하는지 확인하고 유저의 고유 ID를 반환합니다. 존재하지 않으면 예외를 발생시킵니다. + * + * @param name 찾을 유저의 이름 + * @return 찾은 유저의 고유 id + */ + public User getUserByName(String name) { + log.debug("Called getUserByName: {}", name); + return userRepository.findByName(name) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } - /** - * 동아리가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. - * - * @param userId 동아리의 고유 ID - * @return {@link User} - */ - public User getClubUser(Long userId) { - log.debug("Called getClubUser: {}", userId); - User user = getUser(userId); - if (!user.isUserRole(UserRole.CLUB)) { - throw new ServiceException(ExceptionStatus.NOT_FOUND_USER); - } - return user; - } + /** + * 동아리가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. + * + * @param userId 동아리의 고유 ID + * @return {@link User} + */ + public User getClubUser(Long userId) { + log.debug("Called getClubUser: {}", userId); + User user = getUser(userId); + if (!user.isUserRole(UserRole.CLUB)) { + throw new ServiceException(ExceptionStatus.NOT_FOUND_USER); + } + return user; + } - /** - * 관리자가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. - * - * @param adminUserId 관리자의 고유 ID - * @return {@link AdminUser} - */ - public AdminUser getAdminUser(Long adminUserId) { - log.debug("Called getAdminUser: {}", adminUserId); - return adminUserRepository.findAdminUser(adminUserId) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); - } + /** + * 관리자가 존재하는지 확인하고 존재하지 않으면 예외를 발생시킵니다. + * + * @param adminUserId 관리자의 고유 ID + * @return {@link AdminUser} + */ + public AdminUser getAdminUser(Long adminUserId) { + log.debug("Called getAdminUser: {}", adminUserId); + return adminUserRepository.findAdminUser(adminUserId) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); + } - /** - * 이메일을 통해 어드민 유저가 존재하는지 확인하고 유저를 반환합니다. 존재하지 않으면 예외를 발생시킵니다. - * - * @param adminUserEmail 찾을 어드민 유저의 이메일 주소 - * @return {@link User} - */ - public AdminUser getAdminUserByEmail(String adminUserEmail) { - log.debug("Called getAdminUserByEmail: {}", adminUserEmail); - return adminUserRepository.findAdminUserByEmail(adminUserEmail) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); - } + /** + * 이메일을 통해 어드민 유저가 존재하는지 확인하고 유저를 반환합니다. 존재하지 않으면 예외를 발생시킵니다. + * + * @param adminUserEmail 찾을 어드민 유저의 이메일 주소 + * @return {@link User} + */ + public AdminUser getAdminUserByEmail(String adminUserEmail) { + log.debug("Called getAdminUserByEmail: {}", adminUserEmail); + return adminUserRepository.findAdminUserByEmail(adminUserEmail) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); + } - /** - * 최근 BanHistory를 가져옵니다. 없으면 예외를 발생시킵니다. - * - * @param userId 유저의 고유 ID - * @return {@link BanHistory} - */ - //to-do : if null exception - public BanHistory getRecentBanHistory(Long userId) { - log.debug("Called getRecentBanHistory: {}", userId); - List banHistory = banHistoryRepository.findRecentBanHistoryByUserId(userId, - LocalDateTime.now(), - PageRequest.of(0, 1)); - if (banHistory.isEmpty()) { - throw new DomainException(ExceptionStatus.NOT_FOUND_BAN_HISTORY); - } - return banHistory.get(0); - } + /** + * 최근 BanHistory를 가져옵니다. 없으면 예외를 발생시킵니다. + * + * @param userId 유저의 고유 ID + * @return {@link BanHistory} + */ + //to-do : if null exception + public BanHistory getRecentBanHistory(Long userId) { + log.debug("Called getRecentBanHistory: {}", userId); + List banHistory = banHistoryRepository.findRecentBanHistoryByUserId(userId, + LocalDateTime.now(), + PageRequest.of(0, 1)); + if (banHistory.isEmpty()) { + throw new DomainException(ExceptionStatus.NOT_FOUND_BAN_HISTORY); + } + return banHistory.get(0); + } - public BanHistory getActiveBanHistory(Long userId) { - log.debug("Called getActiveBanHistory: {}", userId); - Optional banHistory = banHistoryRepository.findRecentActiveBanHistoryByUserId( - userId, - LocalDateTime.now()); - return banHistory.get(); - } + public BanHistory getActiveBanHistory(Long userId) { + log.debug("Called getActiveBanHistory: {}", userId); + Optional banHistory = banHistoryRepository.findRecentActiveBanHistoryByUserId( + userId, + LocalDateTime.now()); + return banHistory.get(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 6e80a9320..d0eb58ecf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -12,7 +12,7 @@ import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java index de856bf3f..bf9092f64 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java @@ -11,33 +11,15 @@ import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; import org.ftclub.cabinet.alarm.service.AlarmCommandService; import org.ftclub.cabinet.alarm.service.AlarmQueryService; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.dto.BlockedUserPaginationDto; -import org.ftclub.cabinet.dto.CabinetDto; -import org.ftclub.cabinet.dto.ClubUserListDto; -import org.ftclub.cabinet.dto.LentExtensionPaginationDto; -import org.ftclub.cabinet.dto.LentExtensionResponseDto; -import org.ftclub.cabinet.dto.MyProfileResponseDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; -import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; -import org.ftclub.cabinet.dto.UserBlockedInfoDto; -import org.ftclub.cabinet.dto.UserCabinetDto; -import org.ftclub.cabinet.dto.UserCabinetPaginationDto; -import org.ftclub.cabinet.dto.UserProfileDto; -import org.ftclub.cabinet.dto.UserProfilePaginationDto; -import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.dto.*; import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AlarmStatus; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.LentExtension; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; +import org.ftclub.cabinet.user.domain.*; import org.ftclub.cabinet.user.repository.LentExtensionOptionalFetcher; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; import org.ftclub.cabinet.utils.DateUtil; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java index cb6f38add..31e732ed6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java @@ -4,7 +4,7 @@ import java.util.List; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.dto.UserBlackholeInfoDto; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java index 261a4e82d..5b687078c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java @@ -17,14 +17,14 @@ import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.BanPolicy; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.user.repository.AdminUserRepository; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.ftclub.cabinet.user.repository.LentExtensionRepository; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java index 54b9b16e2..54d8aaffb 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java @@ -5,7 +5,7 @@ import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.config.MasterProperties; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.service.UserService; import org.ftclub.cabinet.utils.DateUtil; import org.ftclub.testutils.TestUtils; @@ -31,7 +31,8 @@ @ExtendWith(MockitoExtension.class) public class TokenValidatorUnitTest { - static final Key mockSigningKey = TestUtils.getSigningKey("SECRET_KEY_MUST_BE_VERYVERYVERYVERYVERYVERYVERYVERYVERY_LONG"); + static final Key mockSigningKey = TestUtils.getSigningKey( + "SECRET_KEY_MUST_BE_VERYVERYVERYVERYVERYVERYVERYVERYVERY_LONG"); @Spy @InjectMocks TokenValidator tokenValidator; @@ -54,7 +55,8 @@ void setup() { @Test @DisplayName("성공: 유효한 토큰 - 유저인 경우") void 성공_isValidRequestWithLevel() throws JsonProcessingException { - String userToken = TestUtils.getTestUserTokenByName(mockSigningKey, LocalDateTime.now(), DateUtil.getInfinityDate(), + String userToken = TestUtils.getTestUserTokenByName(mockSigningKey, LocalDateTime.now(), + DateUtil.getInfinityDate(), "name", "domainname.com"); request.addHeader("Authorization", "Bearer " + userToken); given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); @@ -104,7 +106,8 @@ void setup() { @DisplayName("실패: 유효하지 않은 토큰인 경우") void 실패_isValidRequestWithLevel() throws JsonProcessingException { request.addHeader("Authorization", "Bearer " + "token"); - given(tokenValidator.isTokenValid("token", jwtProperties.getSigningKey())).willReturn(false); + given(tokenValidator.isTokenValid("token", jwtProperties.getSigningKey())).willReturn( + false); assertFalse(tokenValidator.isValidRequestWithLevel(request, AuthLevel.ADMIN_ONLY)); assertFalse(tokenValidator.isValidRequestWithLevel(request, AuthLevel.USER_OR_ADMIN)); @@ -135,7 +138,8 @@ void setup() { @DisplayName("성공: 만료기한과 시그니처가 정상인 경우") void 성공_isTokenValid() { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); - String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), LocalDateTime.now(), DateUtil.getInfinityDate(), "name", "domainname.com"); + String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), + LocalDateTime.now(), DateUtil.getInfinityDate(), "name", "domainname.com"); assertTrue(tokenValidator.isTokenValid(token, jwtProperties.getSigningKey())); } @@ -143,10 +147,12 @@ void setup() { @Test @DisplayName("실패: 시그니처가 잘못된 경우") void 실패_isTokenValid() { - Key wrongKey = TestUtils.getSigningKey("WRONG_KEY_IS_MUST_BE_VERYVERYVERYVERYVERYVERY_LONG_TOO"); + Key wrongKey = TestUtils.getSigningKey( + "WRONG_KEY_IS_MUST_BE_VERYVERYVERYVERYVERYVERY_LONG_TOO"); Key rightKey = mockSigningKey; given(jwtProperties.getSigningKey()).willReturn(rightKey); - String wrongKeyToken = TestUtils.getTestUserTokenByName(wrongKey, LocalDateTime.now(), DateUtil.getInfinityDate(), "name", "domainname.com"); + String wrongKeyToken = TestUtils.getTestUserTokenByName(wrongKey, LocalDateTime.now(), + DateUtil.getInfinityDate(), "name", "domainname.com"); assertFalse(tokenValidator.isTokenValid(wrongKeyToken, jwtProperties.getSigningKey())); } @@ -156,7 +162,8 @@ void setup() { void 실패_isTokenValid2() { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); String expiredToken = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), - LocalDateTime.now().plusDays(-1000), DateUtil.getInfinityDate(), "name", "domainname.com"); + LocalDateTime.now().plusDays(-1000), DateUtil.getInfinityDate(), "name", + "domainname.com"); assertFalse(tokenValidator.isTokenValid(expiredToken, jwtProperties.getSigningKey())); } @@ -165,7 +172,8 @@ void setup() { @DisplayName("성공: 토큰의 페이로드를 JSON으로 변환") void 성공_getPayloadJson() throws JsonProcessingException { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); - String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), LocalDateTime.now(), DateUtil.getInfinityDate(), + String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), + LocalDateTime.now(), DateUtil.getInfinityDate(), "sanan", "domainname.com"); JsonNode payloadJson = tokenValidator.getPayloadJson(token); @@ -176,7 +184,8 @@ void setup() { @DisplayName("실패: 페이로드에 찾는 키가 없을 때") void 실패_getPayloadJson() throws JsonProcessingException { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); - String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), LocalDateTime.now(), DateUtil.getInfinityDate(), + String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), + LocalDateTime.now(), DateUtil.getInfinityDate(), "sanan", "domainname.com"); JsonNode payloadJson = tokenValidator.getPayloadJson(token); @@ -190,7 +199,8 @@ void setup() { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); given(masterProperties.getDomain()).willReturn("master.domain.com"); given(domainProperties.getAdminEmailDomain()).willReturn("admin.domain.com"); - String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), LocalDateTime.now(), DateUtil.getInfinityDate(), + String token = TestUtils.getTestUserTokenByName(jwtProperties.getSigningKey(), + LocalDateTime.now(), DateUtil.getInfinityDate(), "sanan", "domainname.com"); assertTrue(tokenValidator.isTokenAuthenticatable(token, AuthLevel.USER_ONLY)); @@ -205,7 +215,8 @@ void setup() { given(jwtProperties.getSigningKey()).willReturn(mockSigningKey); given(masterProperties.getDomain()).willReturn("master.domain.com"); given(domainProperties.getAdminEmailDomain()).willReturn("admin.domain.com"); - String adminToken = TestUtils.getTestAdminToken(jwtProperties.getSigningKey(), LocalDateTime.now(), "sanan", "admin.domain.com"); + String adminToken = TestUtils.getTestAdminToken(jwtProperties.getSigningKey(), + LocalDateTime.now(), "sanan", "admin.domain.com"); assertTrue(tokenValidator.isTokenAuthenticatable(adminToken, AuthLevel.ADMIN_ONLY)); assertTrue(tokenValidator.isTokenAuthenticatable(adminToken, AuthLevel.USER_OR_ADMIN)); @@ -220,7 +231,8 @@ void setup() { given(masterProperties.getDomain()).willReturn("master.domain.com"); given(domainProperties.getAdminEmailDomain()).willReturn("admin.domain.com"); given(userService.getAdminUserRole("sanan@master.domain.com")).willReturn(AdminRole.MASTER); - String masterToken = TestUtils.getTestMasterToken(jwtProperties.getSigningKey(), LocalDateTime.now(), "sanan", "master.domain.com"); + String masterToken = TestUtils.getTestMasterToken(jwtProperties.getSigningKey(), + LocalDateTime.now(), "sanan", "master.domain.com"); assertTrue(tokenValidator.isTokenAuthenticatable(masterToken, AuthLevel.ADMIN_ONLY)); assertTrue(tokenValidator.isTokenAuthenticatable(masterToken, AuthLevel.USER_OR_ADMIN)); diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java index 6b633c424..979d23feb 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java @@ -2,7 +2,8 @@ import static org.mockito.Mockito.mock; -import org.ftclub.cabinet.statistics.service.StatisticsFacadeService; +import org.ftclub.cabinet.admin.controller.StatisticsController; +import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -23,6 +24,6 @@ public class StatisticsControllerTest { @Test @DisplayName("모든층의 사물함 통계 가져오기") public void 모든층의_사물함_통계_가져오기() { - + } } diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java index a8de4ae5f..90b8a9ec9 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java @@ -10,13 +10,14 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import org.ftclub.cabinet.admin.service.StatisticsFacadeServiceImpl; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.statistics.repository.StatisticsRepository; +import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminUserTest.java b/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminUserTest.java index ea6ab4aa7..b4052ed3b 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminUserTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminUserTest.java @@ -3,6 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.exception.DomainException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminUserRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminUserRepositoryTest.java index fc1102d9a..41de229a0 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminUserRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminUserRepositoryTest.java @@ -6,8 +6,9 @@ import java.util.Optional; import javax.transaction.Transactional; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; @@ -54,7 +55,8 @@ public void setUp() { @Test @DisplayName("어드민 이메일로 어드민 유저 찾기 성공 - 어드민이 존재하는 경우") void findAdminUserByEmail_성공_어드민_유저가_존재하는_경우() { - Optional adminUser = adminUserRepository.findAdminUserByEmail("adminTest@gmail.com"); + Optional adminUser = adminUserRepository.findAdminUserByEmail( + "adminTest@gmail.com"); assertTrue(adminUser.isPresent()); assertEquals(adminUserId, adminUser.get().getAdminUserId()); @@ -73,7 +75,8 @@ public void setUp() { @Test @DisplayName("유저의 이메일로 어드민 유저의 권한 찾기 성공 - 어드민이 존재하는 경우") void findAdminUserRoleByEmail_성공_어드민이_존재하는_경우() { - Optional adminRole = adminUserRepository.findAdminUserRoleByEmail("admin1@gmail.com"); + Optional adminRole = adminUserRepository.findAdminUserRoleByEmail( + "admin1@gmail.com"); assertTrue(adminRole.isPresent()); assertEquals(AdminRole.ADMIN, adminRole.get()); @@ -82,7 +85,8 @@ public void setUp() { @Test @DisplayName("유저의 이메일로 어드민 유저의 권한 찾기 실패 - 어드민이 존재하지 않는 경우") public void findAdminUserRoleByEmail_실패_어드민이_존재하지_않는_경우() { - Optional adminRole = adminUserRepository.findAdminUserRoleByEmail("test@gmail.com"); + Optional adminRole = adminUserRepository.findAdminUserRoleByEmail( + "test@gmail.com"); assertFalse(adminRole.isPresent()); } diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java index d3219a02a..8be91e029 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.user.service; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java index e4c64c429..e10091d5e 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java @@ -6,12 +6,12 @@ import java.time.LocalDateTime; import javax.transaction.Transactional; import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.user.repository.AdminUserRepository; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; import org.junit.jupiter.api.Disabled; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java index efec2404a..5480e1183 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java @@ -22,14 +22,14 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; -import org.ftclub.cabinet.user.domain.AdminRole; -import org.ftclub.cabinet.user.domain.AdminUser; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.BanPolicy; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.user.repository.AdminUserRepository; +import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; import org.ftclub.cabinet.user.repository.UserRepository; diff --git a/backend/src/test/java/org/ftclub/testutils/TestUtils.java b/backend/src/test/java/org/ftclub/testutils/TestUtils.java index 7140a56f6..f7e328804 100644 --- a/backend/src/test/java/org/ftclub/testutils/TestUtils.java +++ b/backend/src/test/java/org/ftclub/testutils/TestUtils.java @@ -6,7 +6,7 @@ import io.jsonwebtoken.SignatureAlgorithm; import java.util.ArrayList; import java.util.List; -import org.ftclub.cabinet.user.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.utils.DateUtil; import org.springframework.http.HttpHeaders; @@ -25,7 +25,8 @@ public class TestUtils { - public static String getTestAdminToken(Key signingKey, LocalDateTime now, String emailName, String emailDomain) { + public static String getTestAdminToken(Key signingKey, LocalDateTime now, String emailName, + String emailDomain) { Map claim = new HashMap<>(); claim.put("email", emailName + "@" + emailDomain); claim.put("role", AdminRole.ADMIN); @@ -36,7 +37,8 @@ public static String getTestAdminToken(Key signingKey, LocalDateTime now, String .compact(); } - public static String getTestMasterToken(Key signingKey, LocalDateTime now, String emailName, String emailDomain) { + public static String getTestMasterToken(Key signingKey, LocalDateTime now, String emailName, + String emailDomain) { Map claim = new HashMap<>(); claim.put("email", emailName + "@" + emailDomain); claim.put("role", AdminRole.MASTER); @@ -47,7 +49,8 @@ public static String getTestMasterToken(Key signingKey, LocalDateTime now, Strin .compact(); } - public static String getTestUserTokenByName(Key signingKey, LocalDateTime now, LocalDateTime blackholedAt, String name, String emailDomain) { + public static String getTestUserTokenByName(Key signingKey, LocalDateTime now, + LocalDateTime blackholedAt, String name, String emailDomain) { Map claim = new HashMap<>(); claim.put("name", name); claim.put("email", name + "@" + emailDomain); @@ -78,7 +81,7 @@ public static Key getSigningKey(String secretKey) { } public static MockHttpServletRequestBuilder mockRequest(HttpMethod method, Cookie cookie, - String url, Object... uriVars) { + String url, Object... uriVars) { if (method.equals(HttpMethod.GET)) { return MockMvcRequestBuilders.get(url, uriVars) .cookie(cookie) From f72c96e82845f2e36847c31bb6fe482fe9559342 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 13:29:38 +0900 Subject: [PATCH 0122/1029] =?UTF-8?q?[BE]=20admin=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20java=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=8F=99=20=EB=88=84=EB=9D=BD=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{cabinet => admin}/controller/AdminCabinetController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename backend/src/main/java/org/ftclub/cabinet/{cabinet => admin}/controller/AdminCabinetController.java (99%) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java index 5adb31b22..f1b6b5171 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.cabinet.controller; +package org.ftclub.cabinet.admin.controller; import java.util.HashMap; import java.util.Map; From 6ad4dc7620baa09bd304780fbf2f703ee3d9d8ca Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 17:54:33 +0900 Subject: [PATCH 0123/1029] =?UTF-8?q?[BE]=20admin-search=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminCabinetController.java | 2 +- .../admin/controller/AdminController.java | 59 ++++++++ .../controller/AdminLentController.java | 4 +- .../admin/controller/AdminUserController.java | 14 +- .../admin/controller/SearchController.java | 15 +- .../admin/newService/AdminCommandService.java | 12 ++ .../admin/newService/AdminFacadeService.java | 139 ++++++++++++++++++ .../admin/newService/AdminQueryService.java | 12 ++ .../newService/CabinetQueryService.java | 16 +- .../cabinet/repository/CabinetRepository.java | 14 +- .../cabinet/config/CabinetProperties.java | 2 + .../ftclub/cabinet/dto/CabinetSimpleDto.java | 5 +- .../lent/controller/LentController.java | 2 +- .../lent/repository/LentRepository.java | 16 ++ .../LentCommandService.java | 2 +- .../LentFacadeService.java | 69 ++++----- .../LentPolicyService.java | 14 +- .../LentQueryService.java | 16 +- .../LentRedisService.java | 11 +- .../ftclub/cabinet/mapper/CabinetMapper.java | 8 + .../cabinet/redis/ExpirationListener.java | 2 +- .../newService/BanHistoryQueryService.java | 6 + .../user/newService/UserQueryService.java | 6 + .../user/repository/BanHistoryRepository.java | 14 +- .../user/repository/UserOptionalFetcher.java | 6 +- .../user/repository/UserRepository.java | 2 +- .../blackhole/manager/BlackholeManager.java | 2 +- .../leave/absence/LeaveAbsenceManager.java | 48 +++--- .../utils/scheduler/SystemScheduler.java | 60 ++++---- .../user/repository/UserRepositoryTest.java | 2 +- 30 files changed, 436 insertions(+), 144 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java rename backend/src/main/java/org/ftclub/cabinet/{lent => admin}/controller/AdminLentController.java (92%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentCommandService.java (96%) rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentFacadeService.java (87%) rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentPolicyService.java (93%) rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentQueryService.java (63%) rename backend/src/main/java/org/ftclub/cabinet/lent/{newService => service}/LentRedisService.java (93%) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java index f1b6b5171..f9e667d26 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java @@ -17,7 +17,7 @@ import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java new file mode 100644 index 000000000..a78abf8fc --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java @@ -0,0 +1,59 @@ +package org.ftclub.cabinet.admin.controller; + +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.admin.newService.AdminFacadeService; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/v4/admin") +@RequiredArgsConstructor +public class AdminController { + + private final AdminFacadeService adminFacadeService; + + + @GetMapping("/search/cabinets-simple") + @AuthGuard(level = ADMIN_ONLY) + public CabinetSimplePaginationDto getCabinetsSimpleInfo( + @RequestParam("visibleNum") Integer visibleNum) { + log.info("Called getCabinetsInfo {}", visibleNum); + return adminFacadeService.getCabinetsSimpleInfo(visibleNum); + } + + @GetMapping("/search/cabinets") + @AuthGuard(level = ADMIN_ONLY) + public CabinetInfoPaginationDto getCabinetsInfo( + @RequestParam("visibleNum") Integer visibleNum) { + log.info("Called getCabinetsInfo {}", visibleNum); + return adminFacadeService.getCabinetInfo(visibleNum); + } + + @GetMapping("/search/users-simple") + @AuthGuard(level = ADMIN_ONLY) + public UserProfilePaginationDto getUsersProfile( + @RequestParam("name") String name, Pageable pageable) { + log.info("Called getUsersProfile {}", name); + return adminFacadeService.getUsersProfile(name, pageable); + } + + @GetMapping("/search/users") + @AuthGuard(level = ADMIN_ONLY) + public UserCabinetPaginationDto getCabinetsLentInfo( + @RequestParam("name") String name, Pageable pageable) { + log.info("Called getCabinetsLentInfo {}", name); + return adminFacadeService.getUserLentCabinetInfo(name, pageable); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminLentController.java similarity index 92% rename from backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminLentController.java index a3389c891..e0f75870c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/AdminLentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminLentController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.lent.controller; +package org.ftclub.cabinet.admin.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; @@ -7,7 +7,7 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java index b657d87db..e465a2fac 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminUserController.java @@ -9,10 +9,10 @@ import org.ftclub.cabinet.dto.ClubUserListDto; import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.service.LentExtensionService; import org.ftclub.cabinet.user.service.UserFacadeService; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -51,16 +51,16 @@ public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { /** * 유저의 대여 기록을 반환합니다. * - * @param userId 유저 고유 아이디 - * @param pageRequest 페이지네이션 정보 + * @param userId 유저 고유 아이디 + * @param pageable 페이지네이션 정보 * @return {@link LentHistoryPaginationDto} 유저의 대여 기록 */ @GetMapping("/{userId}/lent-histories") @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentHistoryPaginationDto getLentHistoriesByUserId(@PathVariable("userId") Long userId, - @RequestBody PageRequest pageRequest) { + public LentHistoryPaginationDto getLentHistoriesByUserId( + @PathVariable("userId") Long userId, Pageable pageable) { log.info("Called getLentHistoriesByUserId: {}", userId); - return lentFacadeService.getUserLentHistories(userId, pageRequest); + return lentFacadeService.getUserLentHistories(userId, pageable); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java index 9e8acb1a1..96e8508e7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java @@ -12,13 +12,12 @@ import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.ftclub.cabinet.user.service.UserFacadeService; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor -@RequestMapping("/v4/admin/search") +//@RequestMapping("/v4/admin/search") @Log4j2 public class SearchController { @@ -28,8 +27,7 @@ public class SearchController { @GetMapping("/cabinets") @AuthGuard(level = ADMIN_ONLY) public CabinetInfoPaginationDto getCabinetsInfo( - @RequestParam("visibleNum") Integer visibleNum - ) { + @RequestParam("visibleNum") Integer visibleNum) { log.info("Called getCabinetsInfo {}", visibleNum); return cabinetFacadeService.getCabinetsInfo(visibleNum); } @@ -37,8 +35,7 @@ public CabinetInfoPaginationDto getCabinetsInfo( @GetMapping("/cabinets-simple") @AuthGuard(level = ADMIN_ONLY) public CabinetSimplePaginationDto getCabinetsSimpleInfo( - @RequestParam("visibleNum") Integer visibleNum - ) { + @RequestParam("visibleNum") Integer visibleNum) { log.info("Called getCabinetsInfo {}", visibleNum); return cabinetFacadeService.getCabinetsSimpleInfoByVisibleNum(visibleNum); } @@ -48,8 +45,7 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfo( public UserProfilePaginationDto getUsersProfile( @RequestParam("name") String name, @RequestParam("page") Integer page, - @RequestParam("size") Integer size - ) { + @RequestParam("size") Integer size) { log.info("Called getUsersProfile {}", name); return userFacadeService.getUserProfileListByPartialName(name, page, size); } @@ -59,8 +55,7 @@ public UserProfilePaginationDto getUsersProfile( public UserCabinetPaginationDto getCabinetsLentInfo( @RequestParam("name") String name, @RequestParam("page") Integer page, - @RequestParam("size") Integer size - ) { + @RequestParam("size") Integer size) { log.info("Called getCabinetsLentInfo {}", name); return userFacadeService.findUserCabinetListByPartialName(name, page, size); } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java new file mode 100644 index 000000000..6dcf513d8 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.admin.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminCommandService { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java new file mode 100644 index 000000000..38eec6467 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java @@ -0,0 +1,139 @@ +package org.ftclub.cabinet.admin.newService; + +import static java.util.stream.Collectors.toList; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetSimpleDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.UserBlockedInfoDto; +import org.ftclub.cabinet.dto.UserCabinetDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserProfileDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentQueryService; +import org.ftclub.cabinet.lent.service.LentRedisService; +import org.ftclub.cabinet.mapper.CabinetMapper; +import org.ftclub.cabinet.mapper.LentMapper; +import org.ftclub.cabinet.mapper.UserMapper; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.newService.BanHistoryQueryService; +import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminFacadeService { + + private final AdminQueryService adminQueryService; + private final AdminCommandService adminCommandService; + private final CabinetQueryService cabinetQueryService; + private final UserQueryService userQueryService; + private final BanHistoryQueryService banHistoryQueryService; + private final LentQueryService lentQueryService; + private final LentRedisService lentRedisService; + + private final CabinetMapper cabinetMapper; + private final UserMapper userMapper; + private final LentMapper lentMapper; + + + @Transactional(readOnly = true) + public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { + log.debug("Called getCabinetSimpleInfo {}", visibleNum); + + List cabinets = cabinetQueryService.getCabinets(visibleNum); + List result = cabinets.stream() + .map(cabinetMapper::toCabinetSimpleDto).collect(toList()); + return cabinetMapper.toCabinetSimplePaginationDto(result, (long) cabinets.size()); + } + + @Transactional(readOnly = true) + public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { + log.debug("Called getCabinetInfo {}", visibleNum); + + List cabinets = cabinetQueryService.getCabinets(visibleNum); + List cabinetIds = cabinets.stream().map(Cabinet::getCabinetId) + .collect(toList()); + List lentHistories = + lentQueryService.findCabinetsActiveLentHistories(cabinetIds); + Map> lentHistoriesByCabinetId = lentHistories.stream() + .collect(Collectors.groupingBy(LentHistory::getCabinetId)); + + List result = cabinets.stream() + .map(cabinet -> { + Long cabinetId = cabinet.getCabinetId(); + List lents = lentHistoriesByCabinetId.get(cabinetId).stream() + .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(toList()); + LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); + return cabinetMapper.toCabinetInfoResponseDto(cabinet, lents, sessionExpiredAt); + }).sorted(Comparator.comparingInt(o -> o.getLocation().getFloor())) + .collect(toList()); + return cabinetMapper.toCabinetInfoPaginationDto(result, (long) cabinets.size()); + } + + @Transactional(readOnly = true) + public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pageable) { + log.debug("Called getUsersProfile {}", partialName); + + Page users = userQueryService.getUsers(partialName, pageable); + List result = users.stream() + .map(userMapper::toUserProfileDto).collect(toList()); + return userMapper.toUserProfilePaginationDto(result, users.getTotalElements()); + } + + @Transactional(readOnly = true) + public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, + Pageable pageable) { + log.debug("Called getUserLentCabinetInfo {}", partialName); + + LocalDateTime now = LocalDateTime.now(); + Page users = userQueryService.getUsers(partialName, pageable); + List userIds = users.stream().map(User::getUserId).collect(toList()); + System.out.println("userIds = " + userIds); + + List activeBanHistories = + banHistoryQueryService.findActiveBanHistories(userIds, now); + System.out.println("activeBanHistories = " + activeBanHistories); + List activeLentHistories = + lentQueryService.findUsersActiveLentHistoriesAndCabinet(userIds); + System.out.println("activeLentHistories = " + activeLentHistories); + Map> banHistoriesByUserId = activeBanHistories.stream() + .collect(Collectors.groupingBy(BanHistory::getUserId)); + System.out.println("banHistoriesByUserId = " + banHistoriesByUserId); + Map> lentHistoriesByUserId = activeLentHistories.stream() + .collect(Collectors.groupingBy(LentHistory::getUserId)); + System.out.println("lentHistoriesByUserId = " + lentHistoriesByUserId); + + List result = users.stream().map(user -> { + List banHistories = banHistoriesByUserId.get(user.getUserId()); + List lentHistories = lentHistoriesByUserId.get(user.getUserId()); + BanHistory banHistory = (Objects.nonNull(banHistories) && !banHistories.isEmpty()) + ? banHistories.get(0) : null; + Cabinet cabinet = (Objects.nonNull(lentHistories) && !lentHistories.isEmpty()) + ? lentHistories.get(0).getCabinet() : null; + UserBlockedInfoDto blockedInfoDto = userMapper.toUserBlockedInfoDto(banHistory, user); + CabinetDto cabinetDto = cabinetMapper.toCabinetDto(cabinet); + return cabinetMapper.toUserCabinetDto(blockedInfoDto, cabinetDto); + }).collect(toList()); + return cabinetMapper.toUserCabinetPaginationDto(result, users.getTotalElements()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java new file mode 100644 index 000000000..1d6fa7148 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java @@ -0,0 +1,12 @@ +package org.ftclub.cabinet.admin.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminQueryService { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 9c41def37..33839bfdf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -7,6 +7,8 @@ import org.ftclub.cabinet.cabinet.repository.CabinetRepository; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @Service @@ -15,17 +17,25 @@ public class CabinetQueryService { private final CabinetRepository cabinetRepository; - public Cabinet getCabinet(Long cabinetId) { + public Cabinet getCabinets(Long cabinetId) { Optional cabinet = cabinetRepository.findById(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } - public Cabinet getCabinetWithLock(Long cabinetId) { + public Cabinet getCabinetsWithLock(Long cabinetId) { Optional cabinet = cabinetRepository.findByIdWithLock(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } - public List getCabinetWithLock(List cabinetIds) { + public List getCabinets(Integer visibleNum) { + return cabinetRepository.findAllByVisibleNum(visibleNum); + } + + public Page getCabinets(Integer visibleNum, PageRequest pageable) { + return cabinetRepository.findPaginationByVisibleNum(visibleNum, pageable); + } + + public List getCabinetsWithLock(List cabinetIds) { return cabinetRepository.findAllByIdsWithLock(cabinetIds); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index bbc6b8859..337b05d0f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -59,7 +59,7 @@ public interface CabinetRepository extends JpaRepository, Cabinet @Query("SELECT c " + "FROM Cabinet c " + "WHERE c.cabinetId IN (:cabinetIds)") - List findAllByIdsWithLock(List cabinetIds); + List findAllByIdsWithLock(@Param("cabinetIds") List cabinetIds); /** * userId로 현재 대여 중인 사물함을 조회한다. @@ -67,11 +67,11 @@ public interface CabinetRepository extends JpaRepository, Cabinet * @param userId 사용자 ID * @return 사물함 {@link Optional} */ - @Query("SELECT c " + - "FROM Cabinet c " + - "LEFT JOIN LentHistory lh ON c.cabinetId = lh.cabinetId " + - "LEFT JOIN User u ON u.userId = lh.userId " + - "WHERE u.userId = :userId AND lh.endedAt IS NULL") + @Query("SELECT c " + + "FROM Cabinet c " + + "LEFT JOIN LentHistory lh ON c.cabinetId = lh.cabinetId " + + "LEFT JOIN User u ON u.userId = lh.userId " + + "WHERE u.userId = :userId AND lh.endedAt IS NULL") Optional findByUserIdAndLentHistoryEndedAtIsNull(@Param("userId") Long userId); /** @@ -95,6 +95,8 @@ public interface CabinetRepository extends JpaRepository, Cabinet Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, Pageable pageable); + List findAllByVisibleNum(@Param("visibleNum") Integer visibleNum); + @Query("SELECT c " + "FROM Cabinet c " + "JOIN FETCH c.cabinetPlace p " diff --git a/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java b/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java index dfaf5db39..82c4b084f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/CabinetProperties.java @@ -26,4 +26,6 @@ public class CabinetProperties { private Long shareMaxUserCount; @Value("${cabinet.policy.in-session.term}") private Integer inSessionTerm; + @Value("${cabinet.policy.lent.limit.share.max-attempt-count}") + private Long shareMaxAttemptCount; } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimpleDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimpleDto.java index f0aeee6f4..7d5b8d275 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimpleDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimpleDto.java @@ -3,11 +3,14 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; import org.ftclub.cabinet.cabinet.domain.Location; -@AllArgsConstructor @Getter +@AllArgsConstructor +@ToString public class CabinetSimpleDto { + private final Long cabinetId; @JsonUnwrapped private final Location location; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java index eab419be2..55400b379 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java @@ -9,7 +9,7 @@ import org.ftclub.cabinet.dto.MyCabinetResponseDto; import org.ftclub.cabinet.dto.ShareCodeDto; import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.domain.UserSession; import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index c8a0132ce..536016511 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -149,6 +149,15 @@ Page findPaginationByUserIdAndEndedAtNotNull( */ List findAllByCabinetIdAndEndedAtIsNull(@Param("cabinetId") Long cabinetId); + /** + * 여러 사물함을 기준으로 아직 반납하지 않은 {@link LentHistory}를 모두 가져옵니다. + * + * @param cabinetIds 찾으려는 cabinet id {@link List} + * @return 반납하지 않은 {@link LentHistory}의 {@link List} + */ + List findAllByCabinetIdInAndEndedAtIsNull( + @Param("cabinetIds") List cabinetIds); + /** * 대여 중인 사물함을 모두 가져옵니다. * @@ -170,6 +179,13 @@ Page findPaginationByUserIdAndEndedAtNotNull( List findAllByCabinetIdsAfterDate(@Param("date") LocalDate date, @Param("cabinetIds") List cabinetIds); + @Query("SELECT lh " + + "FROM LentHistory lh " + + "LEFT JOIN FETCH lh.cabinet c " + + "WHERE lh.userId IN (:userIds) AND lh.endedAt IS NULL") + List findByUserIdsAndEndedAtIsNullJoinCabinet( + @Param("userIds") List userIds); + /** * 연체되어 있는 사물함을 모두 가져옵니다. * diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java similarity index 96% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java index cb4729576..f5392bf8a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import java.time.LocalDateTime; import java.util.List; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java similarity index 87% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 306a5dbc8..85c43bc2a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import static org.ftclub.cabinet.cabinet.domain.LentType.PRIVATE; import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; @@ -35,6 +35,7 @@ import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -60,12 +61,12 @@ public class LentFacadeService { @Transactional(readOnly = true) - public LentHistoryPaginationDto getUserLentHistories(Long userId, PageRequest pageable) { + public LentHistoryPaginationDto getUserLentHistories(Long userId, Pageable pageable) { log.debug("Called getAllUserLentHistories: {}", userId); userQueryService.getUser(userId); Page lentHistories = - lentQueryService.findUserLentHistories(userId, pageable); + lentQueryService.findUserActiveLentHistories(userId, pageable); List result = lentHistories.stream() .sorted(Comparator.comparing(LentHistory::getStartedAt)) .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) @@ -73,33 +74,12 @@ public LentHistoryPaginationDto getUserLentHistories(Long userId, PageRequest pa return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); } - @Transactional(readOnly = true) - public List getCabinetLentHistories(Long cabinetId) { - log.debug("Called getCabinetLentHistories: {}", cabinetId); - - cabinetQueryService.getCabinet(cabinetId); - List lentHistories = lentQueryService.findCabinetActiveLentHistory(cabinetId); - return lentHistories.stream() - .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) - .collect(Collectors.toList()); - } - - @Transactional(readOnly = true) - public List getCabinetSessionLentHistories(Long cabinetId) { - log.debug("Called getLentDtoListFromRedis: {}", cabinetId); - - List userIdsInCabinet = lentRedisService.findUsersInCabinet(cabinetId); - List userList = userQueryService.getUsers(userIdsInCabinet); - return userList.stream().map(user -> lentMapper.toLentDto(user, null)) - .collect(Collectors.toList()); - } - @Transactional(readOnly = true) public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, PageRequest pageable) { log.debug("Called getMyLentLog: {}", user.getName()); Page lentHistories = - lentQueryService.findUserLentHistories(user.getUserId(), pageable); + lentQueryService.findUserActiveLentHistories(user.getUserId(), pageable); List result = lentHistories.stream() .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) .map(lentHistory -> lentMapper.toLentHistoryDto( @@ -124,17 +104,16 @@ public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { } List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); List userList = userQueryService.getUsers(usersInCabinet); - userActiveCabinet = cabinetQueryService.getCabinet(cabinetId); + userActiveCabinet = cabinetQueryService.getCabinets(cabinetId); lentDtoList = userList.stream() .map(u -> lentMapper.toLentDto(u, null)).collect(Collectors.toList()); } else { cabinetId = userActiveCabinet.getCabinetId(); List lentHistories = - lentQueryService.findCabinetActiveLentHistory(cabinetId); + lentQueryService.findCabinetActiveLentHistories(cabinetId); lentDtoList = lentHistories.stream() .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(Collectors.toList()); } - // TODO : shareCode, sessionExpiredAt, previousUserName이 상황에 맞춰 null이 되는지 확인 String shareCode = lentRedisService.getShareCode(cabinetId); LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); String previousUserName = lentRedisService.getPreviousUserName(cabinetId); @@ -165,7 +144,7 @@ public void startLentCabinet(Long userId, Long cabinetId) { LocalDateTime now = LocalDateTime.now(); User user = userQueryService.getUser(userId); - Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); int lentCount = lentQueryService.countUserActiveLent(userId); List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); int userCount = lentQueryService.countCabinetUser(cabinetId); @@ -188,17 +167,21 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) log.info("Called startLentShareCabinet: {}, {}, {}", userId, cabinetId, shareCode); LocalDateTime now = LocalDateTime.now(); - User user = userQueryService.getUser(userId); - Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); - int lentCount = lentQueryService.countUserActiveLent(userId); - List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); + Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); - lentPolicyService.verifyCabinetLentCount( cabinet.getLentType(), cabinet.getMaxUser(), userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), SHARE); + + List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); + int lentCount = lentQueryService.countUserActiveLent(userId); + User user = userQueryService.getUser(userId); lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + + Long attemptCount = lentRedisService.getAttemptCountOnShareCabinet(cabinetId, userId); + lentPolicyService.verifyAttemptCountOnShareCabinet(attemptCount); + boolean isExist = lentRedisService.isInCabinetSession(cabinetId); if (!isExist) { lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); @@ -223,7 +206,7 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { LocalDateTime now = LocalDateTime.now(); // TODO : ClubUser 추가 이후 userId로 ClubUser 검증 로직 필요(Policy) - Cabinet cabinet = cabinetQueryService.getCabinet(cabinetId); + Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); lentPolicyService.verifyCabinetLentCount( @@ -243,9 +226,9 @@ public void endUserLent(Long userId) { LocalDateTime now = LocalDateTime.now(); LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithLock(userId); List cabinetLentHistories = - lentQueryService.findCabinetActiveLentHistory(userLentHistory.getCabinetId()); + lentQueryService.findCabinetActiveLentHistories(userLentHistory.getCabinetId()); Cabinet cabinet = - cabinetQueryService.getCabinetWithLock(userLentHistory.getCabinetId()); + cabinetQueryService.getCabinetsWithLock(userLentHistory.getCabinetId()); int userRemainCount = cabinetLentHistories.size() - 1; cabinetCommandService.changeUserCount(cabinet, userRemainCount); @@ -275,9 +258,9 @@ public void endUserLent(Long userId, String memo) { LocalDateTime now = LocalDateTime.now(); LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithLock(userId); List cabinetLentHistories = - lentQueryService.findCabinetActiveLentHistory(userLentHistory.getCabinetId()); + lentQueryService.findCabinetActiveLentHistories(userLentHistory.getCabinetId()); Cabinet cabinet = - cabinetQueryService.getCabinetWithLock(userLentHistory.getCabinetId()); + cabinetQueryService.getCabinetsWithLock(userLentHistory.getCabinetId()); int userRemainCount = cabinetLentHistories.size() - 1; cabinetCommandService.changeUserCount(cabinet, userRemainCount); @@ -306,10 +289,10 @@ public void endCabinetLent(List cabinetIds) { log.debug("Called endCabinetsLent: {}", cabinetIds); LocalDateTime now = LocalDateTime.now(); - List cabinets = cabinetQueryService.getCabinetWithLock(cabinetIds); + List cabinets = cabinetQueryService.getCabinetsWithLock(cabinetIds); cabinets.forEach(cabinet -> { List cabinetLentHistories = - lentQueryService.findCabinetActiveLentHistory(cabinet.getCabinetId()); + lentQueryService.findCabinetActiveLentHistories(cabinet.getCabinetId()); cabinetLentHistories.forEach(lh -> lentCommandService.endLent(lh, now)); cabinetCommandService.changeUserCount(cabinet, 0); cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); @@ -333,7 +316,7 @@ public void cancelShareCabinetLent(Long userId, Long cabinetId) { lentRedisService.deleteUserInCabinetSession(cabinetId, userId); if (lentRedisService.isCabinetSessionEmpty(cabinetId)) { - Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); } } @@ -342,7 +325,7 @@ public void cancelShareCabinetLent(Long userId, Long cabinetId) { public void shareCabinetSessionExpired(Long cabinetId) { log.debug("Called shareCabinetSessionExpired: {}", cabinetId); - Cabinet cabinet = cabinetQueryService.getCabinetWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); List usersInCabinetSession = lentRedisService.getUsersInCabinetSession(cabinetId); if (lentPolicyService.verifyUserCountOnShareCabinet(usersInCabinetSession.size())) { LocalDateTime now = LocalDateTime.now(); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java similarity index 93% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java index f381d88e2..13dfac0b6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentPolicyService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java @@ -1,7 +1,8 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; @@ -183,4 +184,15 @@ public boolean verifyUserCountOnShareCabinet(int userCount) { long maxUserCount = cabinetProperties.getShareMaxUserCount(); return minUserCount <= userCount && userCount <= maxUserCount; } + + public void verifyAttemptCountOnShareCabinet(Long attemptCount) { + log.debug("Called verifyAttemptCountOnShareCabinet"); + + LentPolicyStatus status = LentPolicyStatus.FINE; + Long shareMaxAttemptCount = cabinetProperties.getShareMaxAttemptCount(); + if (Objects.nonNull(attemptCount) && attemptCount >= shareMaxAttemptCount) { + status = LentPolicyStatus.SHARE_BANNED_USER; + } + handlePolicyStatus(status, null); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java similarity index 63% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 175064fd1..8add80a78 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -1,11 +1,11 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import java.util.List; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentRepository; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @Service @@ -14,14 +14,18 @@ public class LentQueryService { private final LentRepository lentRepository; - public Page findUserLentHistories(Long userId, PageRequest pageable) { + public Page findUserActiveLentHistories(Long userId, Pageable pageable) { return lentRepository.findPaginationByUserId(userId, pageable); } - public List findCabinetActiveLentHistory(Long cabinetId) { + public List findCabinetActiveLentHistories(Long cabinetId) { return lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); } + public List findCabinetsActiveLentHistories(List cabinetIds) { + return lentRepository.findAllByCabinetIdInAndEndedAtIsNull(cabinetIds); + } + public int countUserActiveLent(Long userId) { return lentRepository.countByUserIdAndEndedAtIsNull(userId); } @@ -34,6 +38,10 @@ public LentHistory findUserActiveLentHistoryWithLock(Long userId) { return lentRepository.findByUserIdAndEndedAtIsNullForUpdate(userId).orElse(null); } + public List findUsersActiveLentHistoriesAndCabinet(List userIds) { + return lentRepository.findByUserIdsAndEndedAtIsNullJoinCabinet(userIds); + } + public List findAllActiveLentHistories() { return lentRepository.findAllByEndedAtIsNull(); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java similarity index 93% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java index 213063724..d821b9ddb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentRedisService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.lent.newService; +package org.ftclub.cabinet.lent.service; import java.time.LocalDateTime; import java.util.Comparator; @@ -52,6 +52,15 @@ public boolean isInCabinetSession(Long cabinetId) { return lentRedis.isExistShadowKey(cabinetId.toString()); } + public Long getAttemptCountOnShareCabinet(Long cabinetId, Long userId) { + String attemptCount = + lentRedis.getAttemptCountInCabinet(cabinetId.toString(), userId.toString()); + if (Objects.isNull(attemptCount)) { + return null; + } + return Long.parseLong(attemptCount); + } + public void joinCabinetSession(Long cabinetId, Long userId, String shareCode) { lentRedis.attemptJoinCabinet(cabinetId.toString(), userId.toString(), shareCode); } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java index 9aef4685d..5c2eaf4e6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java @@ -10,11 +10,13 @@ import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetPaginationDto; import org.ftclub.cabinet.dto.CabinetPendingResponseDto; import org.ftclub.cabinet.dto.CabinetPreviewDto; import org.ftclub.cabinet.dto.CabinetSimpleDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.dto.MyCabinetResponseDto; @@ -97,6 +99,12 @@ MyCabinetResponseDto toMyCabinetResponseDto(Cabinet cabinet, List lents @Mapping(target = "location", source = "cabinet.cabinetPlace.location") CabinetSimpleDto toCabinetSimpleDto(Cabinet cabinet); + CabinetSimplePaginationDto toCabinetSimplePaginationDto( + List result, Long totalLength); + + CabinetInfoPaginationDto toCabinetInfoPaginationDto( + List result, Long totalLength); + CabinetPendingResponseDto toCabinetPendingResponseDto( Map> cabinetInfoResponseDtos); } diff --git a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java b/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java index 2f6a32000..a63f8b3fa 100644 --- a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java +++ b/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.redis; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index f85b0b3e9..e25f30b76 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -27,4 +27,10 @@ public List findActiveBanHistories(Long userId, LocalDateTime date) return banHistoryRepository.findByUserIdAndUnbannedAt(userId, date); } + + public List findActiveBanHistories(List userIds, LocalDateTime date) { + log.debug("Called findActiveBanHistories: {}", userIds); + + return banHistoryRepository.findByUserIdsAndUnbannedAt(userIds, date); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java index 5867d647b..713ec7843 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -8,6 +8,8 @@ import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.repository.UserRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @Service @@ -26,4 +28,8 @@ public User getUser(Long userId) { public List getUsers(List userIdsInCabinet) { return userRepository.findAllByIds(userIdsInCabinet); } + + public Page getUsers(String partialName, Pageable pageable) { + return userRepository.findPaginationByPartialName(partialName, pageable); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java index 6b362fa84..0a2498119 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java @@ -23,8 +23,18 @@ public interface BanHistoryRepository extends JpaRepository { */ @Query("SELECT b FROM BanHistory b WHERE b.user.userId = :userId AND b.unbannedAt > :today") List findByUserIdAndUnbannedAt( - @Param("userId") Long userId, - @Param("today") LocalDateTime today); + @Param("userId") Long userId, @Param("today") LocalDateTime today); + + /** + * 유저 아이디 리스트로 현재 기준 active한 밴 히스토리를 가져옵니다. + * + * @param userIds 유저 고유 아이디 {@link List} + * @param today 현재 날짜 + * @return active {@link BanHistory} 리스트 + */ + @Query("SELECT b FROM BanHistory b WHERE b.user.userId IN :userIds AND b.unbannedAt > :today") + List findByUserIdsAndUnbannedAt( + @Param("userIds") List userIds, @Param("today") LocalDateTime today); /** * 유저 아이디로 밴 히스토리를 가져옵니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index c5a3d0d71..b0b311961 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -5,12 +5,12 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.admin.repository.AdminUserRepository; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.admin.domain.AdminRole; -import org.ftclub.cabinet.admin.domain.AdminUser; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; @@ -87,7 +87,7 @@ public User findUserByEmail(String email) { */ public Page findUsersByPartialName(String name, Pageable pageable) { log.debug("Called findUsersByPartialName: {}", name); - return userRepository.findByPartialName(name, pageable); + return userRepository.findPaginationByPartialName(name, pageable); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index d469c10e0..fd2b366ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -49,7 +49,7 @@ public interface UserRepository extends JpaRepository { * @return {@link User} 리스트 */ @Query("SELECT u FROM User u WHERE u.name LIKE %:name%") - Page findByPartialName(@Param("name") String name, Pageable pageable); + Page findPaginationByPartialName(@Param("name") String name, Pageable pageable); /** * 유저의 Id List로 유저들을 찾습니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java index d48051c83..80b97d67f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java @@ -9,7 +9,7 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.exception.UtilException; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.service.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java index 3a0ea8f35..0a5461764 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java @@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.service.FtApiManager; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.service.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -16,29 +16,29 @@ @Log4j2 public class LeaveAbsenceManager { - private final FtApiManager ftAPIManager; - private final LentFacadeService lentFacadeService; - private final UserService userService; + private final FtApiManager ftAPIManager; + private final LentFacadeService lentFacadeService; + private final UserService userService; - private Boolean isLeaveAbsence(JsonNode jsonUserInfo) { - return !jsonUserInfo.get("active?").asBoolean(); - } + private Boolean isLeaveAbsence(JsonNode jsonUserInfo) { + return !jsonUserInfo.get("active?").asBoolean(); + } - public void handleLeaveAbsence(Long userId, String name) { - log.info("called handleLeaveAbsence {} {}", userId, name); - try { - JsonNode jsonUserInfo = ftAPIManager.getFtUsersInfoByName(name); - if (isLeaveAbsence(jsonUserInfo)) { - lentFacadeService.endUserLent(userId); - } - } catch (HttpClientErrorException e) { - log.error("handleLeaveAbsence HttpClientErrorException {}", e.getStatusCode()); - if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { - lentFacadeService.endUserLent(userId); - userService.deleteUser(userId, LocalDateTime.now()); - } - } catch (Exception e) { - log.error("handleLeaveAbsence Exception: {}, {}", userId, name, e); - } - } + public void handleLeaveAbsence(Long userId, String name) { + log.info("called handleLeaveAbsence {} {}", userId, name); + try { + JsonNode jsonUserInfo = ftAPIManager.getFtUsersInfoByName(name); + if (isLeaveAbsence(jsonUserInfo)) { + lentFacadeService.endUserLent(userId); + } + } catch (HttpClientErrorException e) { + log.error("handleLeaveAbsence HttpClientErrorException {}", e.getStatusCode()); + if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { + lentFacadeService.endUserLent(userId); + userService.deleteUser(userId, LocalDateTime.now()); + } + } catch (Exception e) { + log.error("handleLeaveAbsence Exception: {}, {}", userId, name, e); + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 35f660a25..82e8f91b5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -6,7 +6,7 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.UserBlackholeInfoDto; -import org.ftclub.cabinet.lent.newService.LentFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; import org.ftclub.cabinet.user.service.UserService; import org.ftclub.cabinet.utils.blackhole.manager.BlackholeManager; @@ -26,24 +26,24 @@ @Log4j2 public class SystemScheduler { - private final LeaveAbsenceManager leaveAbsenceManager; + private static final long DELAY_TIME = 2000; + private final LeaveAbsenceManager leaveAbsenceManager; private final OverdueManager overdueManager; private final LentFacadeService lentFacadeService; private final UserService userService; private final BlackholeManager blackholeManager; private final ReleaseManager releaseManager; private final OccupiedTimeManager occupiedTimeManager; - private static final long DELAY_TIME = 2000; - /** - * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") - public void checkAllLents() { - log.info("called checkAllLents"); - List activeLents = lentFacadeService.getAllActiveLentHistories(); - for (ActiveLentHistoryDto activeLent : activeLents) { - overdueManager.handleOverdue(activeLent); + /** + * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") + public void checkAllLents() { + log.info("called checkAllLents"); + List activeLents = lentFacadeService.getAllActiveLentHistories(); + for (ActiveLentHistoryDto activeLent : activeLents) { + overdueManager.handleOverdue(activeLent); /* leaveAbsenceManager.handleLeaveAbsence(activeLent.getUserId(), activeLent.getName()); try { @@ -52,25 +52,25 @@ public void checkAllLents() { log.error(e.getMessage()); } */ - } - } + } + } - /** - * 매주 월요일 자정 42분에 블랙홀에 빠진 유저 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") - public void checkRiskOfBlackhole() { - log.info("called checkRiskOfBlackhole"); - List blackholeInfos = userService.getAllRiskOfBlackholeInfo(); - for (UserBlackholeInfoDto blackholeInfo : blackholeInfos) { - blackholeManager.handleBlackhole(blackholeInfo); - try { - Thread.sleep(DELAY_TIME); - } catch (InterruptedException e) { - log.error(e.getMessage()); - } - } - } + /** + * 매주 월요일 자정 42분에 블랙홀에 빠진 유저 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") + public void checkRiskOfBlackhole() { + log.info("called checkRiskOfBlackhole"); + List blackholeInfos = userService.getAllRiskOfBlackholeInfo(); + for (UserBlackholeInfoDto blackholeInfo : blackholeInfos) { + blackholeManager.handleBlackhole(blackholeInfo); + try { + Thread.sleep(DELAY_TIME); + } catch (InterruptedException e) { + log.error(e.getMessage()); + } + } + } /** * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java index 2cf6d441c..550278f6a 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/UserRepositoryTest.java @@ -56,7 +56,7 @@ public void testFindByName() { public void testFindByPartialName() { String partialName = "lent"; Pageable pageable = PageRequest.of(0, 10); - Page users = userRepository.findByPartialName(partialName, pageable); + Page users = userRepository.findPaginationByPartialName(partialName, pageable); assertNotNull(users); assertEquals(2, users.getTotalElements()); From 343526019dc7b4c382ac63005f40e88a051925c3 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Thu, 21 Dec 2023 18:28:31 +0900 Subject: [PATCH 0124/1029] =?UTF-8?q?fix,=20refactor:=20ScribeJava?= =?UTF-8?q?=EB=A1=9C=20OAuth=20=EC=9D=B8=EC=A6=9D=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20lombok=EC=97=90?= =?UTF-8?q?=EC=84=9C=20@Qualifier=EB=A5=BC=20=EC=9D=B8=EC=8B=9D=ED=95=A0?= =?UTF-8?q?=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20config=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 3 +- backend/lombok.config | 1 + .../auth/controller/AuthController.java | 36 ++--- .../auth/domain/ApiRequestManager.java | 3 +- .../cabinet/auth/domain/CookieManager.java | 10 +- .../ftclub/cabinet/auth/domain/FtApi20.java | 29 ++++ .../ftclub/cabinet/auth/domain/FtProfile.java | 22 +++ .../cabinet/auth/domain/OauthConfig.java | 34 ++++ .../auth/service/AuthFacadeService.java | 55 ++++++- .../auth/service/AuthFacadeServiceImpl.java | 67 -------- .../cabinet/auth/service/FtOauthService.java | 87 ++++++++++ .../cabinet/auth/service/OauthService.java | 148 +++++++++--------- .../org/ftclub/cabinet/utils/DateUtil.java | 6 + 13 files changed, 322 insertions(+), 179 deletions(-) create mode 100644 backend/lombok.config create mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/domain/FtApi20.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/domain/OauthConfig.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java diff --git a/backend/build.gradle b/backend/build.gradle index 720b555b5..a45dfc8e9 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -77,7 +77,8 @@ dependencies { // Firebase implementation 'com.google.firebase:firebase-admin:9.2.0' - // jwt + // authorization + implementation 'com.github.scribejava:scribejava-apis:8.3.3' implementation 'io.jsonwebtoken:jjwt-api:0.11.2' implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' diff --git a/backend/lombok.config b/backend/lombok.config new file mode 100644 index 000000000..eb6db90e9 --- /dev/null +++ b/backend/lombok.config @@ -0,0 +1 @@ +lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java index ca51d7fe1..c57ce72f3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java @@ -1,13 +1,10 @@ package org.ftclub.cabinet.auth.controller; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.auth.domain.CookieManager; -import org.ftclub.cabinet.auth.domain.TokenProvider; +import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.service.AuthFacadeService; -import org.ftclub.cabinet.auth.service.AuthService; import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.FtApiProperties; -import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -17,21 +14,18 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.LocalDateTime; +import java.util.concurrent.ExecutionException; @RestController @RequestMapping("/v4/auth") @RequiredArgsConstructor +@Log4j2 public class AuthController { private final AuthFacadeService authFacadeService; private final DomainProperties DomainProperties; private final FtApiProperties ftApiProperties; - private final AuthService authService; - private final TokenProvider tokenProvider; - private final CookieManager cookieManager; - private final UserRepository userRepository; - /** * 42 API 로그인 페이지로 리다이렉트합니다. * @@ -43,25 +37,19 @@ public void login(HttpServletResponse response) throws IOException { authFacadeService.requestLoginToApi(response, ftApiProperties); } - /* - * 42 API 로그인 성공 시에 콜백을 처리합니다. - *
- * 42 API로부터 받은 인증 코드를 이용하여 42 API에게 인증 토큰을 요청하고, - *
- * 인증 토큰을 이용하여 42 API에게 프로필 정보를 요청합니다. - *
- * 프로필 정보를 이용하여 JWT 토큰을 생성하고, JWT 토큰을 쿠키에 저장합니다. - *
- * 완료되면, 프론트엔드의 메인 화면으로 리다이렉트합니다. + /** + * 42 API 로그인 콜백을 처리합니다. * - * @param code 42 API로부터 쿼리로 받은 인증 코드 - * @param req 요청 시의 서블렛 {@link HttpServletRequest} - * @param res 요청 시의 서블렛 {@link HttpServletResponse} + * @param code 42 API 로그인 콜백 시 발급받은 code + * @param req 요청 시의 서블릿 {@link HttpServletRequest} + * @param res 요청 시의 서블릿 {@link HttpServletResponse} * @throws IOException 입출력 예외 */ @GetMapping("/login/callback") - public void loginCallback(@RequestParam String code, HttpServletRequest req, - HttpServletResponse res) throws IOException { + public void loginCallback( + @RequestParam String code, + HttpServletRequest req, + HttpServletResponse res) throws IOException, ExecutionException, InterruptedException { authFacadeService.handleLogin(code, req, res, ftApiProperties, LocalDateTime.now()); res.sendRedirect(DomainProperties.getFeHost() + "/home"); } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/ApiRequestManager.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/ApiRequestManager.java index 5fd358b29..376128e03 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/ApiRequestManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/ApiRequestManager.java @@ -57,7 +57,8 @@ public MultiValueMap getAccessTokenRequestBodyMap(String code) { } /** - * AccessToken 요청을 위한 RequestBodyMap을 생성합니다. Client Secret을 이용하여 AccessToken을 요청합니다. + * AccessToken 요청을 위한 RequestBodyMap을 생성합니다. + * Client Secret을 이용하여 AccessToken을 요청합니다. * * @return AccessToken 요청을 위한 RequestBodyMap */ diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java index 07766a7db..06e80e9c0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java @@ -1,13 +1,14 @@ package org.ftclub.cabinet.auth.domain; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.JwtProperties; import org.springframework.stereotype.Component; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + /** * 클라이언트의 쿠키를 관리하는 클래스입니다. */ @@ -47,7 +48,7 @@ public String getCookieValue(HttpServletRequest req, String name) { * @param serverName 쿠키 도메인 */ public void setCookieToClient(HttpServletResponse res, Cookie cookie, String path, - String serverName) { + String serverName) { cookie.setMaxAge(60 * 60 * 24 * jwtProperties.getExpiryDays()); cookie.setPath(path); if (serverName.equals(domainProperties.getLocal())) { @@ -55,7 +56,6 @@ public void setCookieToClient(HttpServletResponse res, Cookie cookie, String pat } else { cookie.setDomain(domainProperties.getCookieDomain()); } - System.out.println("?????????????????????? cookie domain = " + cookie.getDomain()); res.addCookie(cookie); } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtApi20.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtApi20.java new file mode 100644 index 000000000..c6846e474 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtApi20.java @@ -0,0 +1,29 @@ +package org.ftclub.cabinet.auth.domain; + +import com.github.scribejava.core.builder.api.DefaultApi20; + +/** + * ScribeJava 라이브러리에서 OAuth2.0 서비스를 생성할 때에 필요한 메타데이터를 담는 클래스입니다. + *

+ * 참고 : {@link com.github.scribejava.apis.GoogleApi20} + */ +public class FtApi20 extends DefaultApi20 { + + public static FtApi20 instance() { + return InstanceHolder.INSTANCE; + } + + @Override + public String getAccessTokenEndpoint() { + return "https://api.intra.42.fr/oauth/token"; + } + + @Override + protected String getAuthorizationBaseUrl() { + return "https://api.intra.42.fr/oauth/authorize"; + } + + private static class InstanceHolder { + private static final FtApi20 INSTANCE = new FtApi20(); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java new file mode 100644 index 000000000..631c7dee4 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java @@ -0,0 +1,22 @@ +package org.ftclub.cabinet.auth.domain; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; +import org.ftclub.cabinet.auth.service.FtOauthService; + +import java.time.LocalDateTime; + +/** + * 42 OAuth 로그인을 통해 서비스에서 사용하는 프로필 정보를 담는 클래스입니다. + *

+ * 정보에 변경이 생겨야 한다면 {@link FtOauthService}#convertJsonStringToProfile 메서드를 수정하세요. + */ +@Builder +@Getter +@ToString +public class FtProfile { + private final String intraName; + private final String email; + private final LocalDateTime blackHoledAt; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/OauthConfig.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/OauthConfig.java new file mode 100644 index 000000000..926d1453d --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/OauthConfig.java @@ -0,0 +1,34 @@ +package org.ftclub.cabinet.auth.domain; + +import com.github.scribejava.apis.GoogleApi20; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.oauth.OAuth20Service; +import org.ftclub.cabinet.config.FtApiProperties; +import org.ftclub.cabinet.config.GoogleApiProperties; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OauthConfig { + public static final String FT_OAUTH_20_SERVICE = "ftOauth20Service"; + public static final String GOOGLE_OAUTH_20_SERVICE = "googleOauth20Service"; + + @Bean + @Qualifier(FT_OAUTH_20_SERVICE) + public OAuth20Service ftOauth20Service(FtApiProperties ftApiProperties) { + return new ServiceBuilder(ftApiProperties.getClientId()) + .apiSecret(ftApiProperties.getClientSecret()) + .callback(ftApiProperties.getRedirectUri()) + .build(FtApi20.instance()); + } + + @Bean + @Qualifier(GOOGLE_OAUTH_20_SERVICE) + public OAuth20Service googleOauth20Service(GoogleApiProperties googleApiProperties) { + return new ServiceBuilder(googleApiProperties.getClientId()) + .apiSecret(googleApiProperties.getClientSecret()) + .callback(googleApiProperties.getRedirectUri()) + .build(GoogleApi20.instance()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index 7026403f9..3bfe31d4f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -1,22 +1,63 @@ package org.ftclub.cabinet.auth.service; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.auth.domain.CookieManager; +import org.ftclub.cabinet.auth.domain.TokenProvider; import org.ftclub.cabinet.config.ApiProperties; +import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.dto.MasterLoginDto; +import org.ftclub.cabinet.exception.ControllerException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.springframework.stereotype.Service; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.LocalDateTime; +import java.util.Map; -public interface AuthFacadeService { +@Service +@RequiredArgsConstructor +public class AuthFacadeService { - void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) throws IOException; + private final JwtProperties jwtProperties; + private final TokenProvider tokenProvider; + private final CookieManager cookieManager; + private final AuthService authService; + private final OauthService oauthService; - void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, - ApiProperties apiProperties, LocalDateTime now); + public void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) + throws IOException { + oauthService.sendCodeRequestToApi(res, apiProperties); + } - void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now); + public void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, + ApiProperties apiProperties, LocalDateTime now) { + String apiToken = oauthService.getTokenByCodeRequest(code, apiProperties); + JsonNode profile = oauthService.getProfileJsonByToken(apiToken, apiProperties); + Map claims = tokenProvider.makeClaimsByProviderProfile( + apiProperties.getProviderName(), profile); + authService.addUserIfNotExistsByClaims(claims); + String accessToken = tokenProvider.createToken(claims, now); + Cookie cookie = cookieManager.cookieOf( + tokenProvider.getTokenNameByProvider(apiProperties.getProviderName()), accessToken); + cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + } - void logout(HttpServletResponse res, ApiProperties apiProperties); + public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, + HttpServletResponse res, LocalDateTime now) { + if (!authService.validateMasterLogin(masterLoginDto)) { + throw new ControllerException(ExceptionStatus.UNAUTHORIZED); + } + String masterToken = tokenProvider.createMasterToken(now); + Cookie cookie = cookieManager.cookieOf(jwtProperties.getAdminTokenName(), masterToken); + cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + } + + public void logout(HttpServletResponse res, ApiProperties apiProperties) { + cookieManager.deleteCookie(res, + tokenProvider.getTokenNameByProvider(apiProperties.getProviderName())); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java deleted file mode 100644 index 8aea67960..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImpl.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.ftclub.cabinet.auth.service; - -import com.fasterxml.jackson.databind.JsonNode; -import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.auth.domain.CookieManager; -import org.ftclub.cabinet.auth.domain.TokenProvider; -import org.ftclub.cabinet.config.ApiProperties; -import org.ftclub.cabinet.config.JwtProperties; -import org.ftclub.cabinet.dto.MasterLoginDto; -import org.ftclub.cabinet.exception.ControllerException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.springframework.stereotype.Service; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.Map; - -@Service -@RequiredArgsConstructor -public class AuthFacadeServiceImpl implements AuthFacadeService { - - private final JwtProperties jwtProperties; - private final TokenProvider tokenProvider; - private final CookieManager cookieManager; - private final AuthService authService; - private final OauthService oauthService; - - @Override - public void requestLoginToApi(HttpServletResponse res, ApiProperties apiProperties) - throws IOException { - oauthService.sendCodeRequestToApi(res, apiProperties); - } - - @Override - public void handleLogin(String code, HttpServletRequest req, HttpServletResponse res, - ApiProperties apiProperties, LocalDateTime now) { - String apiToken = oauthService.getTokenByCodeRequest(code, apiProperties); - JsonNode profile = oauthService.getProfileJsonByToken(apiToken, apiProperties); - Map claims = tokenProvider.makeClaimsByProviderProfile( - apiProperties.getProviderName(), profile); - authService.addUserIfNotExistsByClaims(claims); - String accessToken = tokenProvider.createToken(claims, now); - Cookie cookie = cookieManager.cookieOf( - tokenProvider.getTokenNameByProvider(apiProperties.getProviderName()), accessToken); - cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); - } - - @Override - public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now) { - if (!authService.validateMasterLogin(masterLoginDto)) { - throw new ControllerException(ExceptionStatus.UNAUTHORIZED); - } - String masterToken = tokenProvider.createMasterToken(now); - Cookie cookie = cookieManager.cookieOf(jwtProperties.getAdminTokenName(), masterToken); - cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); - } - - @Override - public void logout(HttpServletResponse res, ApiProperties apiProperties) { - cookieManager.deleteCookie(res, - tokenProvider.getTokenNameByProvider(apiProperties.getProviderName())); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java new file mode 100644 index 000000000..446c507ca --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java @@ -0,0 +1,87 @@ +package org.ftclub.cabinet.auth.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.auth.domain.FtProfile; +import org.ftclub.cabinet.auth.domain.OauthConfig; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.utils.DateUtil; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.concurrent.ExecutionException; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class FtOauthService { + + @Qualifier(OauthConfig.FT_OAUTH_20_SERVICE) + private final OAuth20Service ftOAuth20Service; + private final ObjectMapper objectMapper; + + /** + * 42 API 로그인 콜백으로 받은 authorization_code로 유저 프로필 정보를 가져오고, 반환합니다. + * + * @param code 42 API 로그인 콜백 시 발급받은 authorization_code + * @return 유저 프로필 정보 {@link FtProfile} + * @throws IOException HTTP 통신에서 일어나는 입출력 예외 + * @throws ExecutionException 비동기 처리시 스레드에서 발생한 오류 처리 예외 + * @throws InterruptedException 비동기 처리시 스레드 종료를 위한 예외 + * @see 위 예외에 대한 정보 + */ + public FtProfile getProfileByCode(String code) throws IOException, ExecutionException, InterruptedException { + OAuthRequest oAuthRequest = new OAuthRequest(Verb.GET, ftOAuth20Service.getAuthorizationUrl()); + OAuth2AccessToken accessToken = ftOAuth20Service.getAccessToken(code); + ftOAuth20Service.signRequest(accessToken, oAuthRequest); + try { + Response response = ftOAuth20Service.execute(oAuthRequest); + return convertJsonStringToProfile(response.getBody()); + } catch (Exception e) { + if (e instanceof IOException) + log.error("42 API 서버에서 프로필 정보를 가져오는데 실패했습니다." + + "code: {}, message: {}", code, e.getMessage()); + if (e instanceof ExecutionException || e instanceof InterruptedException) + log.error("42 API 서버에서 프로필 정보를 비동기적으로 가져오는데 실패했습니다." + + "code: {}, message: {}", code, e.getMessage()); + throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * String 형태의 JSON 데이터를 {@link FtProfile}로 변환합니다. + * + * @param jsonString String 형태의 JSON 데이터 + * @return 유저 프로필 정보 {@link FtProfile} + * @throws JsonProcessingException JSON 파싱 예외 + * @see 42 API에서 제공하는 Profile Json에 대한 정보 + */ + private FtProfile convertJsonStringToProfile(String jsonString) throws JsonProcessingException { + JsonNode rootNode = objectMapper.readTree(jsonString); + String intraName = rootNode.get("login").asText(); + String email = rootNode.get("email").asText(); + if (intraName == null || email == null) + throw new ServiceException(ExceptionStatus.INCORRECT_ARGUMENT); + + LocalDateTime blackHoledAt = DateUtil.convertStringToDate(rootNode + .get("cursus_users") + .get(1).get("blackholed_at").asText()); + + return FtProfile.builder() + .intraName(intraName) + .email(email) + .blackHoledAt(blackHoledAt) + .build(); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/OauthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/OauthService.java index c337e667d..c3174832d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/OauthService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/OauthService.java @@ -3,8 +3,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.ApiRequestManager; @@ -15,6 +13,9 @@ import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + /** * OAuth를 수행하는 서비스 클래스입니다. */ @@ -23,82 +24,81 @@ @Log4j2 public class OauthService { - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper; - /** - * OAuth 권한 코드를 요청하는 URL을 생성하고, HttpServletResponse에 리다이렉트합니다. - * - * @param response {@link HttpServletResponse} - * @throws IOException 입출력 예외 - */ - public void sendCodeRequestToApi(HttpServletResponse response, ApiProperties apiProperties) - throws IOException { - response.sendRedirect( - ApiRequestManager.of(apiProperties) - .getCodeRequestUri()); - } + /** + * OAuth 권한 코드를 요청하는 URL을 생성하고, HttpServletResponse에 리다이렉트합니다. + * + * @param response {@link HttpServletResponse} + * @throws IOException 입출력 예외 + */ + public void sendCodeRequestToApi(HttpServletResponse response, ApiProperties apiProperties) + throws IOException { + response.sendRedirect(ApiRequestManager.of(apiProperties) + .getCodeRequestUri()); + } - /** - * 유저의 정보를 얻어올 수 있는 OAuth 액세스 토큰을 요청합니다. - * - * @param code 인증 코드 - * @return API 액세스 토큰 - * @throws RuntimeException JSON 파싱 예외 - * @throws ServiceException API 요청에 에러가 반환됐을 때 발생하는 예외 - */ - public String getTokenByCodeRequest(String code, ApiProperties apiProperties) { - return WebClient.create().post() - .uri(apiProperties.getTokenUri()) - .body(BodyInserters.fromFormData( - ApiRequestManager.of(apiProperties) - .getAccessTokenRequestBodyMap(code))) - .retrieve() - .bodyToMono(String.class) - .map(response -> { - try { - return objectMapper.readTree(response) - .get(apiProperties.getAccessTokenName()).asText(); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); - throw new RuntimeException(e); - } - }) - .onErrorResume(e -> { - log.error(e.getMessage()); - throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); - }) - .block(); - } + /** + * 유저의 정보를 얻어올 수 있는 OAuth 액세스 토큰을 요청합니다. + * + * @param code 인증 코드 + * @return API 액세스 토큰 + * @throws RuntimeException JSON 파싱 예외 + * @throws ServiceException API 요청에 에러가 반환됐을 때 발생하는 예외 + */ + public String getTokenByCodeRequest(String code, ApiProperties apiProperties) { + return WebClient.create().post() + .uri(apiProperties.getTokenUri()) + .body(BodyInserters.fromFormData( + ApiRequestManager.of(apiProperties) + .getAccessTokenRequestBodyMap(code))) + .retrieve() + .bodyToMono(String.class) + .map(response -> { + try { + return objectMapper.readTree(response) + .get(apiProperties.getAccessTokenName()).asText(); + } catch (JsonProcessingException e) { + log.error(e.getMessage()); + throw new RuntimeException(e); + } + }) + .onErrorResume(e -> { + log.error(e.getMessage()); + throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); + }) + .block(); + } - /** - * 액세스 토큰을 이용해 OAuth 사용자 정보를 요청합니다. - * - * @param token 토큰 - * @return 사용자 정보 - * @throws RuntimeException JSON 파싱 예외 - * @throws ServiceException API 요청에 에러가 반환됐을 때 발생하는 예외 - */ - public JsonNode getProfileJsonByToken(String token, ApiProperties apiProperties) { - return WebClient.create().get() - .uri(apiProperties.getUserInfoUri()) - .headers(headers -> headers.setBearerAuth(token)) - .retrieve() - .bodyToMono(String.class) - .map(response -> { - try { - return objectMapper.readTree(response); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); + /** + * 액세스 토큰을 이용해 OAuth 사용자 정보를 요청합니다. + * + * @param token 토큰 + * @return 사용자 정보 + * @throws RuntimeException JSON 파싱 예외 + * @throws ServiceException API 요청에 에러가 반환됐을 때 발생하는 예외 + */ + public JsonNode getProfileJsonByToken(String token, ApiProperties apiProperties) { + return WebClient.create().get() + .uri(apiProperties.getUserInfoUri()) + .headers(headers -> headers.setBearerAuth(token)) + .retrieve() + .bodyToMono(String.class) + .map(response -> { + try { + return objectMapper.readTree(response); + } catch (JsonProcessingException e) { + log.error(e.getMessage()); - throw new RuntimeException(e); - } - }) - .onErrorResume(e -> { - log.error(e.getMessage()); + throw new RuntimeException(e); + } + }) + .onErrorResume(e -> { + log.error(e.getMessage()); - throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); - }) - .block(); - } + throw new ServiceException(ExceptionStatus.OAUTH_BAD_GATEWAY); + }) + .block(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java b/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java index e37af5005..78b3a4e69 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/DateUtil.java @@ -7,6 +7,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.regex.Pattern; @@ -44,6 +45,11 @@ public static LocalDateTime stringToDate(String str) { return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); } + public static LocalDateTime convertStringToDate(String string) { + DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME; + return LocalDateTime.parse(string, formatter); + } + /** * 특정 date에서 days만큼 더한 값을 리턴합니다. * From 4c4968796a91f7bb3a5138d8bb20ad3b06e89427 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 20:31:56 +0900 Subject: [PATCH 0125/1029] =?UTF-8?q?[BE]=20admin-statistics=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 67 ++++++++++++++++- .../admin/controller/SearchController.java | 62 --------------- .../controller/StatisticsController.java | 5 +- .../admin/newService/AdminFacadeService.java | 75 ++++++++++++++++++- .../service/StatisticsFacadeServiceImpl.java | 6 +- .../newService/CabinetQueryService.java | 18 +++++ .../cabinet/repository/CabinetRepository.java | 26 +++++++ .../lent/repository/LentOptionalFetcher.java | 2 +- .../lent/repository/LentRepository.java | 16 ++-- .../lent/service/LentQueryService.java | 19 ++++- .../ftclub/cabinet/mapper/CabinetMapper.java | 8 ++ .../newService/BanHistoryQueryService.java | 8 ++ .../StatisticsFacadeServiceUnitTest.java | 19 ++--- 13 files changed, 243 insertions(+), 88 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java index a78abf8fc..cc812b30c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java @@ -2,15 +2,23 @@ import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.admin.newService.AdminFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.dto.BlockedUserPaginationDto; +import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.springframework.data.domain.Pageable; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -25,11 +33,12 @@ public class AdminController { private final AdminFacadeService adminFacadeService; + /*---------------------------------------- Search ------------------------------------------*/ @GetMapping("/search/cabinets-simple") @AuthGuard(level = ADMIN_ONLY) public CabinetSimplePaginationDto getCabinetsSimpleInfo( @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsInfo {}", visibleNum); + log.info("Called getCabinetsSimpleInfo {}", visibleNum); return adminFacadeService.getCabinetsSimpleInfo(visibleNum); } @@ -56,4 +65,60 @@ public UserCabinetPaginationDto getCabinetsLentInfo( log.info("Called getCabinetsLentInfo {}", name); return adminFacadeService.getUserLentCabinetInfo(name, pageable); } + + /*-------------------------------------- Statistics ----------------------------------------*/ + + /** + * 전 층의 사물함 정보를 가져옵니다. + * + * @return 전 층의 사물함 정보를 반환합니다. + */ + @GetMapping("/statistics/buildings/floors/cabinets") + @AuthGuard(level = ADMIN_ONLY) + public List getAllCabinetsInfo() { + log.info("Called getCabinetsInfoOnAllFloors"); + return adminFacadeService.getAllCabinetsInfo(); + } + + /** + * 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 가져옵니다. + * + * @param startDate 입력할 기간의 시작일 + * @param endDate 입력할 기간의 종료일 + * @return 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 반환합니다. + */ + @GetMapping("/statistics/lent-histories") + @AuthGuard(level = ADMIN_ONLY) + public LentsStatisticsResponseDto getLentCountStatistics( + @RequestParam("startDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime startDate, + @RequestParam("endDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime endDate) { + log.info("Called getCountOnLentAndReturn startDate : {} endDate : {}", startDate, endDate); + return adminFacadeService.getLentCountStatistics(startDate, endDate); + } + + /** + * 차단당한 유저 정보를 가져옵니다. + * + * @param pageable 페이지 정보 + * @return 차단당한 유저 정보를 반환합니다. + */ + @GetMapping("/statistics/users/banned") + @AuthGuard(level = ADMIN_ONLY) + public BlockedUserPaginationDto getUsersBannedInfo(Pageable pageable) { + log.info("Called getUsersBannedInfo"); + return adminFacadeService.getAllBanUsers(pageable); + } + + /** + * 연체중인 유저 리스트를 가져옵니다. + * + * @param pageable 페이지 정보 + * @return 연체중인 유저 리스트를 반환합니다. + */ + @GetMapping("/statistics/users/overdue") + @AuthGuard(level = ADMIN_ONLY) + public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { + log.info("Called getOverdueUsers"); + return adminFacadeService.getOverdueUsers(pageable); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java deleted file mode 100644 index 96e8508e7..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/SearchController.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.ftclub.cabinet.admin.controller; - -import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; - -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.auth.domain.AuthGuard; -import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; -import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; -import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; -import org.ftclub.cabinet.dto.UserCabinetPaginationDto; -import org.ftclub.cabinet.dto.UserProfilePaginationDto; -import org.ftclub.cabinet.user.service.UserFacadeService; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -//@RequestMapping("/v4/admin/search") -@Log4j2 -public class SearchController { - - private final CabinetFacadeService cabinetFacadeService; - private final UserFacadeService userFacadeService; - - @GetMapping("/cabinets") - @AuthGuard(level = ADMIN_ONLY) - public CabinetInfoPaginationDto getCabinetsInfo( - @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsInfo {}", visibleNum); - return cabinetFacadeService.getCabinetsInfo(visibleNum); - } - - @GetMapping("/cabinets-simple") - @AuthGuard(level = ADMIN_ONLY) - public CabinetSimplePaginationDto getCabinetsSimpleInfo( - @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsInfo {}", visibleNum); - return cabinetFacadeService.getCabinetsSimpleInfoByVisibleNum(visibleNum); - } - - @GetMapping("/users-simple") - @AuthGuard(level = ADMIN_ONLY) - public UserProfilePaginationDto getUsersProfile( - @RequestParam("name") String name, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getUsersProfile {}", name); - return userFacadeService.getUserProfileListByPartialName(name, page, size); - } - - @GetMapping("/users") - @AuthGuard(level = ADMIN_ONLY) - public UserCabinetPaginationDto getCabinetsLentInfo( - @RequestParam("name") String name, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getCabinetsLentInfo {}", name); - return userFacadeService.findUserCabinetListByPartialName(name, page, size); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java index d03a68d9a..94c8b0e71 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java @@ -6,23 +6,22 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; -import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.ftclub.cabinet.user.service.UserFacadeService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor -@RequestMapping("/v4/admin/statistics") +//@RequestMapping("/v4/admin/statistics") @Log4j2 public class StatisticsController { diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java index 38eec6467..0bd90dd70 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java @@ -1,6 +1,10 @@ package org.ftclub.cabinet.admin.newService; import static java.util.stream.Collectors.toList; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.BROKEN; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.FULL; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.OVERDUE; import java.time.LocalDateTime; import java.util.Comparator; @@ -12,17 +16,24 @@ import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetSimpleDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserBlockedInfoDto; import org.ftclub.cabinet.dto.UserCabinetDto; import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserProfileDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.lent.service.LentRedisService; @@ -33,6 +44,8 @@ import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.newService.BanHistoryQueryService; import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.utils.DateUtil; +import org.ftclub.cabinet.utils.ExceptionUtil; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -81,8 +94,12 @@ public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { List result = cabinets.stream() .map(cabinet -> { Long cabinetId = cabinet.getCabinetId(); - List lents = lentHistoriesByCabinetId.get(cabinetId).stream() - .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(toList()); + List lents = null; + if (lentHistoriesByCabinetId.containsKey(cabinetId)) { + lents = lentHistoriesByCabinetId.get(cabinetId).stream() + .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) + .collect(toList()); + } LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); return cabinetMapper.toCabinetInfoResponseDto(cabinet, lents, sessionExpiredAt); }).sorted(Comparator.comparingInt(o -> o.getLocation().getFloor())) @@ -136,4 +153,58 @@ public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, }).collect(toList()); return cabinetMapper.toUserCabinetPaginationDto(result, users.getTotalElements()); } + + public List getAllCabinetsInfo() { + log.debug("Called getCabinetsInfoOnAllFloors"); + + List buildings = cabinetQueryService.getAllBuildings(); + List floors = cabinetQueryService.getAllFloorsByBuildings(buildings); + return floors.stream().map(floor -> { + Integer used = cabinetQueryService.countCabinets(FULL, floor); + Integer unused = cabinetQueryService.countCabinets(AVAILABLE, floor); + Integer overdue = cabinetQueryService.countCabinets(OVERDUE, floor); + Integer disabled = cabinetQueryService.countCabinets(BROKEN, floor); + Integer total = used + overdue + unused + disabled; + return cabinetMapper.toCabinetFloorStatisticsResponseDto( + floor, total, used, overdue, unused, disabled); + }).collect(Collectors.toList()); + } + + public LentsStatisticsResponseDto getLentCountStatistics( + LocalDateTime startDate, LocalDateTime endDate) { + log.debug("Called getLentCountStatistics startDate : {} endDate : {}", startDate, endDate); + + ExceptionUtil.throwIfFalse(startDate.isBefore(endDate), + new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); + int lentStartCount = lentQueryService.countLentOnDuration(startDate, endDate); + int lentEndCount = lentQueryService.countReturnOnDuration(startDate, endDate); + return cabinetMapper.toLentsStatisticsResponseDto( + startDate, endDate, lentStartCount, lentEndCount); + } + + public BlockedUserPaginationDto getAllBanUsers(Pageable pageable) { + log.debug("Called getAllBanUsers"); + + LocalDateTime now = LocalDateTime.now(); + Page banHistories = + banHistoryQueryService.findActiveBanHistories(now, pageable); + List result = banHistories.stream() + .map(b -> userMapper.toUserBlockedInfoDto(b, b.getUser())) + .collect(Collectors.toList()); + return userMapper.toBlockedUserPaginationDto(result, banHistories.getTotalElements()); + } + + public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { + log.debug("Called getOverdueUsers"); + + LocalDateTime now = LocalDateTime.now(); + List lentHistories = lentQueryService.findOverdueLentHistories(now, pageable); + List result = lentHistories.stream() + .map(lh -> { + Long overdueDays = DateUtil.calculateTwoDateDiff(now, lh.getExpiredAt()); + return cabinetMapper.toOverdueUserCabinetDto( + lh, lh.getUser(), lh.getCabinet(), overdueDays); + }).collect(Collectors.toList()); + return cabinetMapper.toOverdueUserCabinetPaginationDto(result, (long) lentHistories.size()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java index f076e9f74..35326ae3b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java @@ -7,6 +7,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; @@ -14,7 +15,6 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.springframework.stereotype.Service; @Service @@ -71,8 +71,8 @@ public LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDat log.debug("Called getCountOnLentAndReturn"); throwIfFalse(startDate.isBefore(endDate), new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); - Integer lentStartCount = lentRepository.countLentByTimeDuration(startDate, endDate); - Integer lentEndCount = lentRepository.countReturnByTimeDuration(startDate, endDate); + Integer lentStartCount = lentRepository.countLentFromStartDateToEndDate(startDate, endDate); + Integer lentEndCount = lentRepository.countReturnFromStartDateToEndDate(startDate, endDate); return new LentsStatisticsResponseDto(startDate, endDate, lentStartCount, lentEndCount); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 33839bfdf..78ec99856 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -4,6 +4,7 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; @@ -17,6 +18,23 @@ public class CabinetQueryService { private final CabinetRepository cabinetRepository; + + public List getAllBuildings() { + return cabinetRepository.findAllBuildings(); + } + + public List getAllFloorsByBuilding(String building) { + return cabinetRepository.findAllFloorsByBuilding(building); + } + + public int countCabinets(CabinetStatus status, Integer floor) { + return cabinetRepository.countByStatusAndFloor(status, floor); + } + + public List getAllFloorsByBuildings(List buildings) { + return cabinetRepository.findAllFloorsByBuildings(buildings); + } + public Cabinet getCabinets(Long cabinetId) { Optional cabinet = cabinetRepository.findById(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 337b05d0f..13ce8c512 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -8,6 +8,7 @@ import org.ftclub.cabinet.cabinet.domain.LentType; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; @@ -37,6 +38,30 @@ public interface CabinetRepository extends JpaRepository, Cabinet + "WHERE p.location.building = :building") List findAllFloorsByBuilding(@Param("building") String building); + /** + * 여러 층에 걸쳐 CabinetStatus에 맞는 사물함의 개수를 조회한다. + * + * @param status 사물함 상태 + * @param floor 층 + * @return 사물함 개수 {@link List} + */ + @Query("SELECT count(c) " + + "FROM Cabinet c " + + "JOIN c.cabinetPlace p " + + "WHERE c.status = :status AND p.location.floor = :floor") + int countByStatusAndFloor(@Param("status") CabinetStatus status, @Param("floor") Integer floor); + + /** + * 빌딩 리스트의 모든 층을 조회한다. + * + * @param buildings 빌딩 {@link List} + * @return 층 {@link List} + */ + @Query("SELECT DISTINCT p.location.floor " + + "FROM CabinetPlace p " + + "WHERE p.location.building IN (:buildings)") + List findAllFloorsByBuildings(@Param("buildings") List buildings); + /** * cabinetId로 사물함을 조회한다.(조회 이후 업데이트를 위해 X Lock을 건다.) * @@ -95,6 +120,7 @@ public interface CabinetRepository extends JpaRepository, Cabinet Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, Pageable pageable); + @EntityGraph(attributePaths = {"cabinetPlace"}) List findAllByVisibleNum(@Param("visibleNum") Integer visibleNum); @Query("SELECT c " diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index b7b6fecb0..e7edf2bcf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -144,7 +144,7 @@ public List findUserIdsByCabinetIdFromRedis(Long cabinetId) { public List findAllOverdueLent(LocalDateTime date, Pageable pageable) { log.debug("Called findAllOverdueLent: {}", date); - return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNull(date, pageable); + return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNull(date, pageable).toList(); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 536016511..50b98e89d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -67,13 +67,13 @@ public interface LentRepository extends JpaRepository { @Query("SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.startedAt < :endDate AND lh.startedAt >= :startDate") - int countLentByTimeDuration(@Param("startDate") LocalDateTime startDate, + int countLentFromStartDateToEndDate(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); @Query("SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.endedAt < :endDate AND lh.endedAt >= :startDate") - int countReturnByTimeDuration(@Param("startDate") LocalDateTime startDate, + int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); /** @@ -155,7 +155,12 @@ Page findPaginationByUserIdAndEndedAtNotNull( * @param cabinetIds 찾으려는 cabinet id {@link List} * @return 반납하지 않은 {@link LentHistory}의 {@link List} */ - List findAllByCabinetIdInAndEndedAtIsNull( + @Query("SELECT lh " + + "FROM LentHistory lh " + + "LEFT JOIN FETCH lh.user " + + "WHERE lh.cabinetId IN (:cabinetIds) " + + "AND lh.endedAt IS NULL ") + List findAllByCabinetIdInAndEndedAtIsNullJoinUser( @Param("cabinetIds") List cabinetIds); /** @@ -194,9 +199,8 @@ List findByUserIdsAndEndedAtIsNullJoinCabinet( */ @Query("SELECT lh " + "FROM LentHistory lh " - + "WHERE lh.expiredAt < :date AND lh.endedAt is null " - + "ORDER BY lh.expiredAt ASC") - List findAllExpiredAtBeforeAndEndedAtIsNull( + + "WHERE lh.expiredAt < :date AND lh.endedAt IS NULL") + Page findAllExpiredAtBeforeAndEndedAtIsNull( @Param("date") LocalDateTime date, Pageable pageable); @Query("SELECT lh " diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 8add80a78..c20eab2df 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -1,5 +1,9 @@ package org.ftclub.cabinet.lent.service; +import static java.util.stream.Collectors.toList; + +import java.time.LocalDateTime; +import java.util.Comparator; import java.util.List; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.lent.domain.LentHistory; @@ -23,7 +27,7 @@ public List findCabinetActiveLentHistories(Long cabinetId) { } public List findCabinetsActiveLentHistories(List cabinetIds) { - return lentRepository.findAllByCabinetIdInAndEndedAtIsNull(cabinetIds); + return lentRepository.findAllByCabinetIdInAndEndedAtIsNullJoinUser(cabinetIds); } public int countUserActiveLent(Long userId) { @@ -34,6 +38,14 @@ public int countCabinetUser(Long cabinetId) { return lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId); } + public int countLentOnDuration(LocalDateTime startDate, LocalDateTime endDate) { + return lentRepository.countLentFromStartDateToEndDate(startDate, endDate); + } + + public int countReturnOnDuration(LocalDateTime startDate, LocalDateTime endDate) { + return lentRepository.countReturnFromStartDateToEndDate(startDate, endDate); + } + public LentHistory findUserActiveLentHistoryWithLock(Long userId) { return lentRepository.findByUserIdAndEndedAtIsNullForUpdate(userId).orElse(null); } @@ -45,4 +57,9 @@ public List findUsersActiveLentHistoriesAndCabinet(List userI public List findAllActiveLentHistories() { return lentRepository.findAllByEndedAtIsNull(); } + + public List findOverdueLentHistories(LocalDateTime now, Pageable pageable) { + return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNull(now, pageable).stream() + .sorted(Comparator.comparing(LentHistory::getExpiredAt)).collect(toList()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java index 5c2eaf4e6..05a965ab1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/CabinetMapper.java @@ -10,6 +10,7 @@ import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetPaginationDto; @@ -19,6 +20,7 @@ import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.dto.MyCabinetResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; @@ -107,4 +109,10 @@ CabinetInfoPaginationDto toCabinetInfoPaginationDto( CabinetPendingResponseDto toCabinetPendingResponseDto( Map> cabinetInfoResponseDtos); + + CabinetFloorStatisticsResponseDto toCabinetFloorStatisticsResponseDto(Integer floor, + Integer total, Integer used, Integer overdue, Integer unused, Integer disabled); + + LentsStatisticsResponseDto toLentsStatisticsResponseDto(LocalDateTime startDate, + LocalDateTime endDate, int lentStartCount, int lentEndCount); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index e25f30b76..9a1475ccd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -6,6 +6,8 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.repository.BanHistoryRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @Service @@ -33,4 +35,10 @@ public List findActiveBanHistories(List userIds, LocalDateTime return banHistoryRepository.findByUserIdsAndUnbannedAt(userIds, date); } + + public Page findActiveBanHistories(LocalDateTime now, Pageable pageable) { + log.debug("Called findActiveBanHistories: {}", now); + + return banHistoryRepository.findPaginationActiveBanHistories(pageable, now); + } } diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java index 90b8a9ec9..078da47c9 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java @@ -10,6 +10,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.ftclub.cabinet.admin.service.StatisticsFacadeServiceImpl; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; @@ -17,7 +18,6 @@ import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -92,13 +92,14 @@ class StatisticsFacadeServiceUnitTest { public void 대여_반납_개수_세기_성공() { LocalDateTime startDate = LocalDateTime.of(2023, 1, 1, 0, 0); // 2023-01-01 LocalDateTime endDate = LocalDateTime.of(2023, 6, 1, 0, 0); // 2023-06-01 - given(lentRepository.countLentByTimeDuration(startDate, endDate)) + given(lentRepository.countLentFromStartDateToEndDate(startDate, endDate)) .willReturn(12); - given(lentRepository.countReturnByTimeDuration(startDate, endDate)) + given(lentRepository.countReturnFromStartDateToEndDate(startDate, endDate)) .willReturn(2); LentsStatisticsResponseDto lentsStatisticsResponseDtoOrigin = new LentsStatisticsResponseDto( - startDate, endDate, lentRepository.countLentByTimeDuration(startDate, endDate), - lentRepository.countReturnByTimeDuration(startDate, endDate)); + startDate, endDate, + lentRepository.countLentFromStartDateToEndDate(startDate, endDate), + lentRepository.countReturnFromStartDateToEndDate(startDate, endDate)); LentsStatisticsResponseDto lentsStatisticsResponseDtoTested = statisticsFacadeService.getCountOnLentAndReturn( startDate, endDate); @@ -107,8 +108,8 @@ class StatisticsFacadeServiceUnitTest { lentsStatisticsResponseDtoOrigin.getLentStartCount()); assertThat(lentsStatisticsResponseDtoTested.getLentEndCount()).isEqualTo( lentsStatisticsResponseDtoOrigin.getLentEndCount()); - then(lentRepository).should(times(2)).countLentByTimeDuration(startDate, endDate); - then(lentRepository).should(times(2)).countReturnByTimeDuration(startDate, endDate); + then(lentRepository).should(times(2)).countLentFromStartDateToEndDate(startDate, endDate); + then(lentRepository).should(times(2)).countReturnFromStartDateToEndDate(startDate, endDate); } @Test @@ -120,7 +121,7 @@ class StatisticsFacadeServiceUnitTest { Assertions.assertThrows(ServiceException.class, () -> { statisticsFacadeService.getCountOnLentAndReturn(startDate, endDate); }); - then(lentRepository).should(times(0)).countLentByTimeDuration(startDate, endDate); - then(lentRepository).should(times(0)).countReturnByTimeDuration(startDate, endDate); + then(lentRepository).should(times(0)).countLentFromStartDateToEndDate(startDate, endDate); + then(lentRepository).should(times(0)).countReturnFromStartDateToEndDate(startDate, endDate); } } From afe5a306ded4434412c6bc80751570631d87d2a9 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 20:33:59 +0900 Subject: [PATCH 0126/1029] =?UTF-8?q?[BE]=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=EC=99=84=EB=A3=8C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 2 +- .../controller/StatisticsController.java | 93 ------------------- .../repository/StatisticsOptionalFetcher.java | 13 --- .../repository/StatisticsRepository.java | 24 ----- .../AdminCommandService.java | 2 +- .../AdminFacadeService.java | 2 +- .../AdminQueryService.java | 2 +- .../service/StatisticsFacadeService.java | 18 ---- .../service/StatisticsFacadeServiceImpl.java | 78 ---------------- .../controller/StatisticsControllerTest.java | 1 - .../StatisticsFacadeServiceUnitTest.java | 1 - 11 files changed, 4 insertions(+), 232 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java rename backend/src/main/java/org/ftclub/cabinet/admin/{newService => service}/AdminCommandService.java (81%) rename backend/src/main/java/org/ftclub/cabinet/admin/{newService => service}/AdminFacadeService.java (99%) rename backend/src/main/java/org/ftclub/cabinet/admin/{newService => service}/AdminQueryService.java (81%) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java index cc812b30c..62e127b06 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java @@ -6,7 +6,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.admin.newService.AdminFacadeService; +import org.ftclub.cabinet.admin.service.AdminFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java deleted file mode 100644 index 94c8b0e71..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/StatisticsController.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.ftclub.cabinet.admin.controller; - -import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; - -import java.time.LocalDateTime; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.admin.service.StatisticsFacadeService; -import org.ftclub.cabinet.auth.domain.AuthGuard; -import org.ftclub.cabinet.dto.BlockedUserPaginationDto; -import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; -import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; -import org.ftclub.cabinet.user.service.UserFacadeService; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.format.annotation.DateTimeFormat.ISO; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -//@RequestMapping("/v4/admin/statistics") -@Log4j2 -public class StatisticsController { - - private final StatisticsFacadeService statisticsFacadeService; - private final UserFacadeService userFacadeService; - - /** - * 전 층의 사물함 정보를 가져옵니다. - * - * @return 전 층의 사물함 정보를 반환합니다. - */ - @GetMapping("/buildings/floors/cabinets") - @AuthGuard(level = ADMIN_ONLY) - public List getCabinetsInfoOnAllFloors() { - log.info("Called getCabinetsInfoOnAllFloors"); - return statisticsFacadeService.getCabinetsCountOnAllFloors(); - } - - /** - * 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 가져옵니다. - * - * @param startDate 입력할 기간의 시작일 - * @param endDate 입력할 기간의 종료일 - * @return 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 반환합니다. - */ - @GetMapping("/lent-histories") - @AuthGuard(level = ADMIN_ONLY) - public LentsStatisticsResponseDto getCountOnLentAndReturn( - @RequestParam("startDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime startDate, - @RequestParam("endDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime endDate - ) { - log.info("Called getCountOnLentAndReturn startDate : {} endDate : {}", startDate, endDate); - return statisticsFacadeService.getCountOnLentAndReturn(startDate, endDate); - } - - /** - * 차단당한 유저 정보를 가져옵니다. - * - * @param page 가져오고자 하는 페이지 - * @param size 가져오고자 하는 페이지의 길이 - * @return 차단당한 유저 정보를 반환합니다. - */ - @GetMapping("/users/banned") - @AuthGuard(level = ADMIN_ONLY) - public BlockedUserPaginationDto getUsersBannedInfo( - @RequestParam("page") Integer page, - @RequestParam("size") Integer size - ) { - log.info("Called getUsersBannedInfo"); - return userFacadeService.getAllBanUsers(page, size, LocalDateTime.now()); - } - - /** - * 연체중인 유저 리스트를 가져옵니다. - * - * @param page 가져오고자 하는 페이지 - * @param size 가져오고자 하는 페이지의 길이 - * @return 연체중인 유저 리스트를 반환합니다. - */ - @GetMapping("/users/overdue") - @AuthGuard(level = ADMIN_ONLY) - public OverdueUserCabinetPaginationDto getOverdueUsers( - @RequestParam("page") Integer page, - @RequestParam("size") Integer size - ) { - log.info("Called getOverdueUsers"); - return userFacadeService.getOverdueUserList(page, size); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java deleted file mode 100644 index 5576f1450..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsOptionalFetcher.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.ftclub.cabinet.admin.repository; - -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Log4j2 -public class StatisticsOptionalFetcher { - - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java b/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java deleted file mode 100644 index 9e2d7f807..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/repository/StatisticsRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.ftclub.cabinet.admin.repository; - -import java.util.List; -import javax.transaction.Transactional; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -@Repository -@Transactional -public interface StatisticsRepository extends JpaRepository { - - @Query("SELECT COUNT(*) FROM Cabinet c WHERE c.status = :status AND c.cabinetPlace.location.floor = :floor") - Integer getCabinetsCountByStatus(@Param("floor") Integer floor, - @Param("status") CabinetStatus status); - - @Query("SELECT c.cabinetId FROM Cabinet c WHERE (c.status = 'LIMITED_AVAILABLE' or c.status = 'AVAILABLE') AND c.cabinetPlace.location.floor = :floor") - List getAvailableCabinetsId(@Param("floor") Integer floor); -} - - diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminCommandService.java similarity index 81% rename from backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/AdminCommandService.java index 6dcf513d8..6b2a179d9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminCommandService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.newService; +package org.ftclub.cabinet.admin.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java index 0bd90dd70..bc99a8f2a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.newService; +package org.ftclub.cabinet.admin.service; import static java.util.stream.Collectors.toList; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminQueryService.java similarity index 81% rename from backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/service/AdminQueryService.java index 1d6fa7148..6e32a848a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/newService/AdminQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminQueryService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.newService; +package org.ftclub.cabinet.admin.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java deleted file mode 100644 index fedaa0bdf..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeService.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.ftclub.cabinet.admin.service; - -import org.ftclub.cabinet.dto.BlockedUserPaginationDto; -import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; -import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; - -import java.time.LocalDateTime; -import java.util.Date; -import java.util.List; - -public interface StatisticsFacadeService { - - List getCabinetsCountOnAllFloors(); - - LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDate, - LocalDateTime endDate); -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java deleted file mode 100644 index 35326ae3b..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/StatisticsFacadeServiceImpl.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.ftclub.cabinet.admin.service; - -import static org.ftclub.cabinet.utils.ExceptionUtil.throwIfFalse; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.admin.repository.StatisticsRepository; -import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; -import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.lent.repository.LentRepository; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Log4j2 -public class StatisticsFacadeServiceImpl implements StatisticsFacadeService { - - private final StatisticsRepository statisticsRepository; - private final CabinetOptionalFetcher cabinetOptionalFetcher; - private final LentRepository lentRepository; - - // TODO: 로직 수정 필요 - - /** - * @return - */ - @Override - public List getCabinetsCountOnAllFloors() { - log.debug("Called getCabinetsCountOnAllFloors"); - List cabinetFloorStatisticsResponseDtos = new ArrayList<>(); - List floors = cabinetOptionalFetcher.findAllFloorsByBuilding("새롬관"); - throwIfFalse(floors != null, new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); - for (Integer floor : floors) { - Integer used = statisticsRepository.getCabinetsCountByStatus(floor, CabinetStatus.FULL); - List availableCabinetsId = statisticsRepository.getAvailableCabinetsId(floor); - Integer unused = 0; - for (Long cabinetId : availableCabinetsId) { - if (lentRepository.countByCabinetIdAndEndedAtIsNull(cabinetId) > 0) { - used++; - } else { - unused++; - } - } - Integer overdue = statisticsRepository.getCabinetsCountByStatus(floor, - CabinetStatus.OVERDUE); - Integer disabled = statisticsRepository.getCabinetsCountByStatus(floor, - CabinetStatus.BROKEN); - Integer total = used + overdue + unused + disabled; - cabinetFloorStatisticsResponseDtos.add( - new CabinetFloorStatisticsResponseDto(floor, total, used, overdue, unused, - disabled)); - } - return cabinetFloorStatisticsResponseDtos; - } - - /** - * @param startDate - * @param endDate - * @return - */ - @Override - public LentsStatisticsResponseDto getCountOnLentAndReturn(LocalDateTime startDate, - LocalDateTime endDate) { - log.debug("Called getCountOnLentAndReturn"); - throwIfFalse(startDate.isBefore(endDate), - new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); - Integer lentStartCount = lentRepository.countLentFromStartDateToEndDate(startDate, endDate); - Integer lentEndCount = lentRepository.countReturnFromStartDateToEndDate(startDate, endDate); - return new LentsStatisticsResponseDto(startDate, endDate, lentStartCount, lentEndCount); - } -} diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java index 979d23feb..08d2a5401 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/controller/StatisticsControllerTest.java @@ -2,7 +2,6 @@ import static org.mockito.Mockito.mock; -import org.ftclub.cabinet.admin.controller.StatisticsController; import org.ftclub.cabinet.admin.service.StatisticsFacadeService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java index 078da47c9..3101c1547 100644 --- a/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/statistics/service/StatisticsFacadeServiceUnitTest.java @@ -10,7 +10,6 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import org.ftclub.cabinet.admin.repository.StatisticsRepository; import org.ftclub.cabinet.admin.service.StatisticsFacadeServiceImpl; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; From 77d6ee0dcff0ebd466a3ac171050b983956ddce8 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 21 Dec 2023 21:16:47 +0900 Subject: [PATCH 0127/1029] =?UTF-8?q?[BE]=20=EC=95=8C=EB=9E=8C=20PR=20?= =?UTF-8?q?=EC=9D=B4=ED=9B=84=20dev=20pull=20=EB=B0=8F=20rebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/modules.xml | 1 + .../lent/service/LentFacadeService.java | 8 + .../cabinet/lent/service/LentServiceImpl.java | 288 ------------------ config | 2 +- 4 files changed, 10 insertions(+), 289 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java diff --git a/.idea/modules.xml b/.idea/modules.xml index 3ba732021..094b3411f 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -4,6 +4,7 @@ + diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 85c43bc2a..288024f69 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -10,6 +10,8 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; @@ -33,6 +35,7 @@ import org.ftclub.cabinet.user.newService.BanHistoryQueryService; import org.ftclub.cabinet.user.newService.BanPolicyService; import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -55,6 +58,7 @@ public class LentFacadeService { private final LentPolicyService lentPolicyService; private final BanPolicyService banPolicyService; + private final ApplicationEventPublisher eventPublisher; private final LentMapper lentMapper; private final CabinetMapper cabinetMapper; @@ -197,6 +201,10 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) lentRedisService.clearCabinetSession(cabinetId); cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); cabinetCommandService.changeUserCount(cabinet, userIdsInCabinet.size()); + + LentSuccessAlarm alarm = new LentSuccessAlarm( + cabinet.getCabinetPlace().getLocation(), cabinet.getVisibleNum(), expiredAt); + eventPublisher.publishEvent(AlarmEvent.of(userId, alarm)); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java deleted file mode 100644 index 032782328..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java +++ /dev/null @@ -1,288 +0,0 @@ -package org.ftclub.cabinet.lent.service; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.alarm.domain.AlarmEvent; -import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; -import org.ftclub.cabinet.config.CabinetProperties; -import org.ftclub.cabinet.dto.ActiveLentHistoryDto; -import org.ftclub.cabinet.exception.DomainException; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.domain.LentPolicy; -import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; -import org.ftclub.cabinet.lent.repository.LentRedis; -import org.ftclub.cabinet.lent.repository.LentRepository; -import org.ftclub.cabinet.mapper.LentMapper; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.repository.BanHistoryRepository; -import org.ftclub.cabinet.user.repository.UserOptionalFetcher; -import org.ftclub.cabinet.user.service.UserService; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional -@Log4j2 -public class LentServiceImpl implements LentService { - - private final LentRepository lentRepository; - private final LentPolicy lentPolicy; - private final LentOptionalFetcher lentOptionalFetcher; - private final CabinetOptionalFetcher cabinetOptionalFetcher; - private final UserOptionalFetcher userOptionalFetcher; - private final UserService userService; - private final BanHistoryRepository banHistoryRepository; - private final LentMapper lentMapper; - private final LentRedis lentRedis; - private final CabinetProperties cabinetProperties; - private final ApplicationEventPublisher eventPublisher; - - @Override - public void startLentCabinet(Long userId, Long cabinetId) { - log.info("Called startLentCabinet: {}, {}", userId, cabinetId); - LocalDateTime now = LocalDateTime.now(); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - User user = userOptionalFetcher.getUser(userId); - int userActiveLentCount = lentRepository.countByUserIdAndEndedAtIsNull(userId); - List userActiveBanList = banHistoryRepository.findUserActiveBanList(userId, - now); - // 대여 가능한 유저인지 확인 - lentPolicy.handlePolicyStatus( - lentPolicy.verifyUserForLent(user, cabinet, userActiveLentCount, userActiveBanList), - userActiveBanList); - // 대여 가능한 캐비넷인지 확인 - lentPolicy.handlePolicyStatus(lentPolicy.verifyCabinetForLent(cabinet), userActiveBanList); - // 캐비넷 상태 변경 - cabinet.specifyStatus(CabinetStatus.FULL); - // 만료 시간 적용 - LocalDateTime expiredAt = lentPolicy.generateExpirationDate(now, cabinet); - LentHistory lentHistory = LentHistory.of(now, expiredAt, userId, cabinetId); - lentPolicy.applyExpirationDate(lentHistory, expiredAt); - lentRepository.save(lentHistory); - eventPublisher.publishEvent(AlarmEvent.of(userId, - new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), cabinet.getVisibleNum(), expiredAt))); - } - - @Override - public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) { - log.info("Called startLentShareCabinet: {}, {}, {}", userId, cabinetId, shareCode); - LocalDateTime now = LocalDateTime.now(); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - User user = userOptionalFetcher.getUser(userId); - int userActiveLentCount = lentRepository.countByUserIdAndEndedAtIsNull(userId); - List userActiveBanList = banHistoryRepository.findUserActiveBanList(userId, - now); - // 대여 가능한 유저인지 확인 - lentPolicy.handlePolicyStatus( - lentPolicy.verifyUserForLentShare(user, cabinet, userActiveLentCount, - userActiveBanList), userActiveBanList); - boolean hasShadowKey = lentRedis.isShadowKey(cabinetId); - if (!hasShadowKey) {// 최초 대여인 경우 - lentPolicy.handlePolicyStatus(lentPolicy.verifyCabinetForLent(cabinet), - userActiveBanList); - cabinet.specifyStatus(CabinetStatus.IN_SESSION); - lentRedis.setShadowKey(cabinetId); - } - lentRedis.saveUserInRedis(cabinetId.toString(), userId.toString(), shareCode, - hasShadowKey); - // 4번째 (마지막) 대여자인 경우 - if (Objects.equals(lentRedis.getSizeOfUsersInSession(cabinetId.toString()), - cabinetProperties.getShareMaxUserCount())) { - cabinet.specifyStatus(CabinetStatus.FULL); - saveLentHistories(now, cabinetId); - // cabinetId에 대한 shadowKey, valueKey 삭제 - lentRedis.deleteShadowKey(cabinetId); - ArrayList userIds = lentRedis.getUserIdsByCabinetIdInRedis( - cabinetId.toString()); - for (String id : userIds) { - lentRedis.deleteUserIdInRedis(Long.valueOf(id)); - } - lentRedis.deleteCabinetIdInRedis(cabinetId.toString()); - } - } - - @Override - public void startLentClubCabinet(Long userId, Long cabinetId) { - log.debug("Called startLentClubCabinet: {}, {}", userId, cabinetId); - Cabinet cabinet = cabinetOptionalFetcher.getClubCabinet(cabinetId); - lentOptionalFetcher.checkExistedSpace(cabinetId); - LocalDateTime expirationDate = lentPolicy.generateExpirationDate(LocalDateTime.now(), - cabinet); - LentHistory result = - LentHistory.of(LocalDateTime.now(), expirationDate, userId, cabinetId); - lentRepository.save(result); - cabinet.specifyStatusByUserCount(1); // todo : policy에서 관리 - } - - @Override - public void endLentCabinet(Long userId) { - log.debug("Called endLentCabinet: {}", userId); - List allActiveLentHistoriesByUserId = - lentOptionalFetcher.findAllActiveLentHistoriesByUserId(userId); - LentHistory lentHistory = returnCabinetByUserId(userId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(lentHistory.getCabinetId()); - // cabinetType도 인자로 전달하면 좋을 거 같습니다 (공유사물함 3일이내 반납 페널티) - userService.banUser(userId, cabinet.getLentType(), lentHistory.getStartedAt(), - lentHistory.getEndedAt(), lentHistory.getExpiredAt()); - // 공유 사물함 반납 시 남은 대여일 수 차감 (원래 남은 대여일 수 * (남은 인원 / 원래 있던 인원)) - if (cabinet.getLentType().equals(LentType.SHARE)) { - double usersInShareCabinet = lentRepository.countByCabinetIdAndEndedAtIsNull( - cabinet.getCabinetId()); - double daysUntilExpiration = - lentHistory.getDaysUntilExpiration(LocalDateTime.now()).doubleValue() * -1.0; - double secondsRemaining = - daysUntilExpiration * (usersInShareCabinet / (usersInShareCabinet + 1.0) * 24.0 - * 60.0 * 60.0); - LocalDateTime expiredAt = LocalDateTime.now().plusSeconds(Math.round(secondsRemaining)) - .withHour(23) - .withMinute(59) - .withSecond(0); // 23:59:59 - allActiveLentHistoriesByUserId.forEach(e -> { - e.setExpiredAt(expiredAt); - }); - } - lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), lentHistory.getUser().getName()); - } - - @Override - public void terminateLentCabinet(Long userId) { - log.debug("Called terminateLentCabinet: {}", userId); - returnCabinetByUserId(userId); - } - - @Override - public void terminateLentByCabinetId(Long cabinetId) { - log.debug("Called terminateLentByCabinetId: {}", cabinetId); - returnCabinetByCabinetId(cabinetId); - } - - @Override - public void cancelLentShareCabinet(Long userId, Long cabinetId) { - log.debug("Called cancelLentShareCabinet: {}, {}", userId, cabinetId); - if (!lentRedis.isUserInRedis(cabinetId.toString(), userId.toString())) { - throw new DomainException(ExceptionStatus.NOT_FOUND_USER); - } - lentRedis.deleteUserInRedis(cabinetId.toString(), userId.toString()); - // 유저가 나갔을 때, 해당 키에 다른 유저가 없다면 전체 키 삭제 - if (lentRedis.getSizeOfUsersInSession(cabinetId.toString()) == 0) { - lentRedis.deleteShadowKey(cabinetId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - cabinet.specifyStatus(CabinetStatus.AVAILABLE); - } - } - - // cabinetId로 return하는 경우에서, 공유 사물함과 개인 사물함의 경우에 대한 분기가 되어 있지 않음. - // 또한 어드민의 경우에서 사용하는 returnByCabinetId와 유저가 사용하는 returnByCabinetId가 다른 상황이므로 - // (어드민의 경우에는 뭐든지 전체 반납, 유저가 사용하는 경우에는 본인이 사용하는 사물함에 대한 반납) - // 유저가 사용하는 경우에 대해서는 userId로만 쓰게하든, 한 방식으로만 사용하게끔 해야함 - 함수를 쪼갤 가능성도 있음. - // 우선 현재 관리자만 쓰고 있고, 한 군데에서만 사용되므로 List로 전체 반납을 하도록 구현, 이에 대한 논의는 TO-DO - private List returnCabinetByCabinetId(Long cabinetId) { - log.debug("Called returnCabinetByCabinetId: {}", cabinetId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( - cabinetId); // todo : 현재 returnCabinetByCabinetId는 개인사물함 반납에 대해서만 사용되고 있기 때문에 lentHistory에 대한 list로 받을 필요가 없음 - 추후 추가 확인 후 로직 수정 필요 - lentHistories.forEach(lentHistory -> lentHistory.endLent(LocalDateTime.now())); - cabinet.specifyStatusByUserCount(0); // policy로 빼는게..? -// log.info("cabinet status {}",cabinet.getStatus()); - cabinet.writeMemo(""); - cabinet.writeTitle(""); - lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), - lentHistories.get(0).getUser().getName()); - return lentHistories; - } - - private LentHistory returnCabinetByUserId(Long userId) { - log.debug("Called returnCabinet: {}", userId); - LentHistory lentHistory = lentOptionalFetcher.getActiveLentHistoryWithUserIdForUpdate( - userId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(lentHistory.getCabinetId()); - int activeLentCount = lentRepository.countByCabinetIdAndEndedAtIsNull(lentHistory.getCabinetId()); - lentHistory.endLent(LocalDateTime.now()); - cabinet.specifyStatusByUserCount(activeLentCount - 1); // policy로 빠질만한 부분인듯? - if (activeLentCount - 1 == 0) { - cabinet.writeMemo(""); - cabinet.writeTitle(""); - } - lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), - lentHistory.getUser().getName()); - return lentHistory; - } - - @Override - public void assignLent(Long userId, Long cabinetId) { - log.debug("Called assignLent: {}, {}", userId, cabinetId); - userOptionalFetcher.getUser(userId); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - lentOptionalFetcher.checkExistedSpace(cabinetId); - LocalDateTime expirationDate = lentPolicy.generateExpirationDate(LocalDateTime.now(), - cabinet); - LentHistory result = LentHistory.of(LocalDateTime.now(), expirationDate, userId, cabinetId); - cabinet.specifyStatusByUserCount(1); - lentRepository.save(result); - } - - @Override - public void handleLentFromRedisExpired(String cabinetIdString) { - Long cabinetId = Long.parseLong(cabinetIdString); - Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(cabinetId); - Long userCount = lentRedis.getSizeOfUsersInSession(cabinetId.toString()); - if (cabinetProperties.getShareMinUserCount() <= userCount - && userCount <= cabinetProperties.getShareMaxUserCount()) { // 2명 이상 4명 이하: 대여 성공 - LocalDateTime now = LocalDateTime.now(); - cabinet.specifyStatus(CabinetStatus.FULL); - saveLentHistories(now, cabinetId); - } else { - cabinet.specifyStatus(CabinetStatus.AVAILABLE); - } - ArrayList userIds = lentRedis.getUserIdsByCabinetIdInRedis( - cabinetId.toString()); - for (String userId : userIds) { - lentRedis.deleteUserIdInRedis(Long.valueOf(userId)); - } - lentRedis.deleteCabinetIdInRedis(cabinetId.toString()); - } - - @Override - public List getAllActiveLentHistories() { - log.debug("Called getAllActiveLentHistories"); - List lentHistories = lentOptionalFetcher.findAllActiveLentHistories(); - LocalDateTime now = LocalDateTime.now(); - return lentHistories.stream() - .map(e -> lentMapper.toActiveLentHistoryDto(e, - e.getUser(), - e.getCabinet(), - e.isExpired(now), - e.getDaysUntilExpiration(now) - )) - .collect(Collectors.toList()); - } - - public void saveLentHistories(LocalDateTime now, Long cabinetId) { - ArrayList userIdList = lentRedis.getUserIdsByCabinetIdInRedis( - cabinetId.toString()); - LocalDateTime expiredAt = lentPolicy.generateSharedCabinetExpirationDate(now, - userIdList.size()); - // userId 반복문 돌면서 수행 - userIdList.stream() - .map(userId -> LentHistory.of(now, expiredAt, Long.parseLong(userId), cabinetId)) - .forEach(lentHistory -> { - lentPolicy.applyExpirationDate(lentHistory, expiredAt); - lentRepository.save(lentHistory); - }); - } -} - diff --git a/config b/config index fb620b186..9966240f4 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit fb620b186204dd0d50e02d8cdfe63bb94ad7c3b0 +Subproject commit 9966240f4da856ccc865a040d2139865d137b861 From 5e65422a1903dbc95c4e265fb43dc026a8e415b4 Mon Sep 17 00:00:00 2001 From: moonseonghui Date: Thu, 21 Dec 2023 22:00:16 +0900 Subject: [PATCH 0128/1029] =?UTF-8?q?[FE]=20FEAT=20:=20=EC=84=9C=EB=B8=8C?= =?UTF-8?q?=EC=BB=AC=EB=9F=AC,=20=EB=82=B4=20=EC=82=AC=EB=AC=BC=ED=95=A8?= =?UTF-8?q?=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80#1484?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- .../Card/ThemeColorCard/ColorPicker.tsx | 29 ++++++++ .../ThemeColorCard.container.tsx | 70 +++++++++++++++---- .../Card/ThemeColorCard/ThemeColorCard.tsx | 48 +++++++++---- frontend/src/pages/Layout.tsx | 13 ++-- frontend/src/pages/admin/AdminClubPage.tsx | 5 +- frontend/src/pages/admin/AdminLayout.tsx | 5 +- frontend/src/types/enum/color.type.enum.ts | 7 ++ 8 files changed, 141 insertions(+), 38 deletions(-) create mode 100644 frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx create mode 100644 frontend/src/types/enum/color.type.enum.ts diff --git a/config b/config index 290416e13..868b4f34e 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 290416e13fff18216998886ac1a2c5f6845c9253 +Subproject commit 868b4f34e936b36d739a6d68d280c3e3a148c118 diff --git a/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx b/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx new file mode 100644 index 000000000..aec92109f --- /dev/null +++ b/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx @@ -0,0 +1,29 @@ +import { TwitterPicker } from "react-color"; +import styled from "styled-components"; + +interface ColorPickerProps { + color: string; + onChange: (color: { hex: string }) => void; + customColors: string[]; +} + +const ColorPicker = ({ color, onChange, customColors }: ColorPickerProps) => { + return ( + + + + ); +}; + +const TwitterPickerWrapper = styled.div` + margin: 0 auto; + margin-top: 10px; +`; + +export default ColorPicker; diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx index 6bdf81584..9751c6107 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx @@ -1,36 +1,64 @@ import { useEffect, useState } from "react"; import ThemeColorCard from "@/components/Card/ThemeColorCard/ThemeColorCard"; +import ColorType from "@/types/enum/color.type.enum"; const ThemeColorCardContainer = () => { - const savedColor = localStorage.getItem("mainColor"); - const defaultColor = "#9747ff"; + const savedMainColor = localStorage.getItem("main-color") || ColorType.MAIN; + const savedSubColor = localStorage.getItem("sub-color") || ColorType.SUB; + const savedMineColor = localStorage.getItem("mine-color") || ColorType.MINE; + const [mainColor, setMainColor] = useState( - savedColor ? savedColor : defaultColor + savedMainColor ? savedMainColor : ColorType.MAIN + ); + const [subColor, setSubColor] = useState( + savedSubColor ? savedSubColor : ColorType.SUB ); + const [mineColor, setMineColor] = useState( + savedMineColor ? savedMineColor : ColorType.MINE + ); + const [showColorPicker, setShowColorPicker] = useState(false); const root: HTMLElement = document.documentElement; - const handleChange = (mainColor: { hex: string }) => { + const handleChange = (mainColor: { hex: string }, type: string) => { const selectedColor: string = mainColor.hex; - setMainColor(selectedColor); + if (type === "main") { + setMainColor(selectedColor); + } else if (type === "sub") { + setSubColor(selectedColor); + } else if (type === "mine") { + setMineColor(selectedColor); + } }; const handleReset = () => { - setMainColor(defaultColor); - root.style.setProperty("--main-color", defaultColor); - root.style.setProperty("--lightpurple-color", "#b18cff"); - localStorage.setItem("mainColor", defaultColor); + setMainColor(ColorType.MAIN); + setSubColor(ColorType.SUB); + setMineColor(ColorType.MINE); + root.style.setProperty("--main-color", ColorType.MAIN); + root.style.setProperty("--lightpurple-color", ColorType.SUB); + root.style.setProperty("--mine", ColorType.MINE); + localStorage.setItem("main-color", ColorType.MAIN); + localStorage.setItem("sub-color", ColorType.SUB); + localStorage.setItem("mine-color", ColorType.MINE); }; const handleSave = () => { - localStorage.setItem("mainColor", mainColor); + localStorage.setItem("main-color", mainColor); root.style.setProperty("--main-color", mainColor); + localStorage.setItem("sub-color", subColor); + root.style.setProperty("--lightpurple-color", subColor); + localStorage.setItem("mine-color", mineColor); + root.style.setProperty("--mine", mineColor); toggleColorPicker(true); }; const handleCancel = () => { - const savedColor = localStorage.getItem("mainColor"); - root.style.setProperty("--main-color", savedColor); + root.style.setProperty("--main-color", savedMainColor); + root.style.setProperty("--mine", savedMineColor); + setMainColor(savedMainColor); + setSubColor(savedSubColor); + setMineColor(savedMineColor); toggleColorPicker(true); }; @@ -39,29 +67,41 @@ const ThemeColorCardContainer = () => { }; const confirmBeforeUnload = (e: BeforeUnloadEvent) => { - if (mainColor !== localStorage.getItem("mainColor")) { + { e.returnValue = "변경된 색상이 저장되지 않을 수 있습니다. 계속하시겠습니까?"; } }; + const [selectedColorType, setSelectedColorType] = useState(""); + + const handleColorButtonClick = (colorType: string) => { + if (showColorPicker) return; + setSelectedColorType(colorType); + setShowColorPicker(!showColorPicker); + }; + useEffect(() => { root.style.setProperty("--main-color", mainColor); + root.style.setProperty("--mine", mineColor); window.addEventListener("beforeunload", confirmBeforeUnload); return () => { window.removeEventListener("beforeunload", confirmBeforeUnload); }; - }, [mainColor]); + }, [mainColor, mineColor]); return ( ); }; diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx index 85d8a390a..87dab0d15 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -1,5 +1,3 @@ -import React from "react"; -import { TwitterPicker } from "react-color"; import styled from "styled-components"; import Card from "@/components/Card/Card"; import { @@ -7,25 +5,32 @@ import { CardContentWrapper, ContentInfoStyled, } from "@/components/Card/CardStyles"; +import ColorPicker from "@/components/Card/ThemeColorCard/ColorPicker"; interface ThemeColorProps { showColorPicker: boolean; - setShowColorPicker: React.Dispatch>; - handleChange: (mainColor: { hex: string }) => void; + handleChange: (mainColor: { hex: string }, type: string) => void; handleReset: () => void; handleSave: () => void; handleCancel: () => void; mainColor: string; + subColor: string; + mineColor: string; + handleColorButtonClick: (colorType: string) => void; + selectedColorType: string; } const ThemeColorCard = ({ showColorPicker, - setShowColorPicker, handleChange, handleReset, handleSave, handleCancel, mainColor, + subColor, + mineColor, + handleColorButtonClick, + selectedColorType, }: ThemeColorProps) => { const customColors = [ "#FF4589", @@ -39,6 +44,7 @@ const ThemeColorCard = ({ "#a29bfe", "#9747FF", ]; + return ( <> {showColorPicker && } @@ -74,19 +80,33 @@ const ThemeColorCard = ({ } > <> - + 메인 컬러 - setShowColorPicker(!showColorPicker)} + handleColorButtonClick("main")} + color={mainColor} + /> + + + 서브 컬러 + handleColorButtonClick("sub")} + color={subColor} + /> + + + 내 사물함 + handleColorButtonClick("mine")} + color={mineColor} /> {showColorPicker && ( - handleChange(color, selectedColorType)} + customColors={customColors} /> )} @@ -112,10 +132,10 @@ const ThemeColorCardWrapper = styled.div` align-self: start; `; -const MainColorButtonStyled = styled.button` +const ColorButtonStyled = styled.button<{ color: string }>` width: 28px; height: 28px; - background-color: var(--main-color); + background-color: ${(props) => props.color}; border-radius: 8px; `; diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index f19d4628c..a68c2a5b6 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -13,6 +13,7 @@ import OverduePenaltyModal from "@/components/Modals/OverduePenaltyModal/Overdue import TopNav from "@/components/TopNav/TopNav.container"; import { additionalModalType } from "@/assets/data/maps"; import { UserDto, UserInfo } from "@/types/dto/user.dto"; +import ColorType from "@/types/enum/color.type.enum"; import { axiosMyInfo } from "@/api/axios/axios.custom"; import { getCookie } from "@/api/react_cookie/cookies"; import useMenu from "@/hooks/useMenu"; @@ -63,7 +64,9 @@ const Layout = (): JSX.Element => { } }; - const savedColor = localStorage.getItem("mainColor"); + const savedMainColor = localStorage.getItem("main-color"); + const savedSubColor = localStorage.getItem("sub-color"); + const savedMineColor = localStorage.getItem("mine-color"); const root: HTMLElement = document.documentElement; useEffect(() => { @@ -80,10 +83,10 @@ const Layout = (): JSX.Element => { }, []); useEffect(() => { - root.style.setProperty("--main-color", savedColor); - if (savedColor !== "#9747ff") - root.style.setProperty("--lightpurple-color", "#7b7b7b"); - }, [savedColor]); + root.style.setProperty("--main-color", savedMainColor); + root.style.setProperty("--lightpurple-color", savedSubColor); + root.style.setProperty("--mine", savedMineColor); + }, [savedMainColor, savedSubColor, savedMineColor]); const { closeAll } = useMenu(); diff --git a/frontend/src/pages/admin/AdminClubPage.tsx b/frontend/src/pages/admin/AdminClubPage.tsx index 510c60892..e08216176 100644 --- a/frontend/src/pages/admin/AdminClubPage.tsx +++ b/frontend/src/pages/admin/AdminClubPage.tsx @@ -34,7 +34,10 @@ const AdminClubPage = () => { 현재 등록된 동아리 목록을 확인할 수 있습니다. - + {pendingCabinetsList.length !== 0 ? ( @@ -52,7 +52,7 @@ const FloorContainerStyled = styled.div` margin-top: 50px; `; -const FloorTitleStyled = styled.h2<{ isToggled: boolean }>` +const FloorTitleStyled = styled.div<{ isToggled: boolean }>` display: flex; justify-content: space-between; font-size: 1.1rem; From 1f4d8260e0a7d6a8b6f694a8edd403a5e34377d6 Mon Sep 17 00:00:00 2001 From: hyungseok Date: Fri, 22 Dec 2023 15:40:26 +0900 Subject: [PATCH 0154/1029] =?UTF-8?q?[FE]=20HOTFIX:=20pendingPage=20Refres?= =?UTF-8?q?h=20=EB=B2=84=ED=8A=BC=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20#4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/PendingPage/components/FloorContainer.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/PendingPage/components/FloorContainer.tsx b/frontend/src/pages/PendingPage/components/FloorContainer.tsx index 2ec7d9286..0f8bc7d60 100644 --- a/frontend/src/pages/PendingPage/components/FloorContainer.tsx +++ b/frontend/src/pages/PendingPage/components/FloorContainer.tsx @@ -61,10 +61,12 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` padding-right: 5px; border-bottom: 1.5px solid #d9d9d9; button { + all: initial; + cursor: pointer; z-index: 2; height: 30px; - width: 10px; - background: url(/src/assets/images/select.svg) no-repeat 100%; + width: 20px; + background: url(/src/assets/images/select.svg) no-repeat center center; transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; } From 187a7b32ad4556780d3757c04b39d0603b10c454 Mon Sep 17 00:00:00 2001 From: jiwon Date: Fri, 22 Dec 2023 15:53:11 +0900 Subject: [PATCH 0155/1029] =?UTF-8?q?[BE]=20N+1=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20=EB=B0=8F=20AlarmStatus=20=EB=8B=A8?= =?UTF-8?q?=EB=B0=A9=ED=96=A5=20=EB=A7=A4=ED=95=91=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/database/cabi_local.sql | 8 +++++ .../AdminSearchStatisticsController.java | 31 +++++++++++++------ .../admin/service/AdminFacadeService.java | 2 +- .../alarm/handler/AlarmEventHandler.java | 15 +++++---- .../repository/AlarmStatusRepository.java | 6 ++++ .../cabinet/repository/CabinetRepository.java | 1 + .../lent/repository/LentRepository.java | 2 -- .../cabinet/user/domain/AlarmStatus.java | 14 +++++---- .../org/ftclub/cabinet/user/domain/User.java | 4 --- .../newService/AlarmStatusQueryService.java | 23 ++++++++++++++ .../newService/BanHistoryQueryService.java | 2 +- .../user/repository/BanHistoryRepository.java | 8 +++-- .../user/repository/UserOptionalFetcher.java | 9 +++--- 13 files changed, 87 insertions(+), 38 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java diff --git a/backend/database/cabi_local.sql b/backend/database/cabi_local.sql index 987b67744..3c316ae49 100644 --- a/backend/database/cabi_local.sql +++ b/backend/database/cabi_local.sql @@ -677,6 +677,14 @@ CREATE TABLE `lent_extension` COLLATE = utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `alarm_status` WRITE; +/*!40000 ALTER TABLE `alarm_status` + DISABLE KEYS */; +/*!40000 ALTER TABLE `alarm_status` + ENABLE KEYS */; +UNLOCK TABLES; + create table alarm_status ( id bigint auto_increment diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminSearchStatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminSearchStatisticsController.java index f1895312a..3e95b7184 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminSearchStatisticsController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminSearchStatisticsController.java @@ -1,20 +1,33 @@ package org.ftclub.cabinet.admin.controller; +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +import java.time.LocalDateTime; +import java.util.List; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.admin.service.AdminFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.BlockedUserPaginationDto; +import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; +import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.springframework.data.domain.Pageable; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import java.time.LocalDateTime; -import java.util.List; - -import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @@ -68,7 +81,7 @@ public UserCabinetPaginationDto getCabinetsLentInfo( @GetMapping("/statistics/buildings/floors/cabinets") @AuthGuard(level = ADMIN_ONLY) public List getAllCabinetsInfo() { - log.info("Called getCabinetsInfoOnAllFloors"); + log.info("Called getAllCabinetsInfo"); return adminFacadeService.getAllCabinetsInfo(); } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java index b0fbdf495..b38c4fe3a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java @@ -169,7 +169,7 @@ public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, } public List getAllCabinetsInfo() { - log.debug("Called getCabinetsInfoOnAllFloors"); + log.debug("Called getAllCabinetsInfo"); List buildings = cabinetQueryService.getAllBuildings(); List floors = cabinetQueryService.getAllFloorsByBuildings(buildings); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index 7bb3eefb0..c32f16f1d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -1,15 +1,13 @@ package org.ftclub.cabinet.alarm.handler; -import static org.ftclub.cabinet.exception.ExceptionStatus.NOT_FOUND_USER; - import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.TransactionalAlarmEvent; -import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.AlarmStatus; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.repository.UserRepository; +import org.ftclub.cabinet.user.newService.AlarmStatusQueryService; +import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; @@ -19,7 +17,8 @@ @Log4j2 public class AlarmEventHandler { - private final UserRepository userRepository; + private final UserQueryService userQueryService; + private final AlarmStatusQueryService alarmStatusQueryService; private final SlackAlarmSender slackAlarmSender; private final EmailAlarmSender emailAlarmSender; private final PushAlarmSender pushAlarmSender; @@ -41,9 +40,9 @@ public void handleAlarmEvent(AlarmEvent alarmEvent) { } private void eventProceed(AlarmEvent alarmEvent) { - User receiver = userRepository.findUserByIdWithAlarmStatus(alarmEvent.getReceiverId()) - .orElseThrow(() -> new ServiceException(NOT_FOUND_USER)); - AlarmStatus alarmStatus = receiver.getAlarmStatus(); + AlarmStatus alarmStatus = + alarmStatusQueryService.getUserAlarmStatus(alarmEvent.getReceiverId()); + User receiver = alarmStatus.getUser(); if (alarmStatus.isSlack()) { slackAlarmSender.send(receiver, alarmEvent); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmStatusRepository.java b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmStatusRepository.java index 90f8d0f39..84ec70b21 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmStatusRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/repository/AlarmStatusRepository.java @@ -10,4 +10,10 @@ public interface AlarmStatusRepository extends JpaRepository @Query("SELECT als FROM AlarmStatus as als WHERE als.user.userId = :userId") Optional findByUserId(@Param("userId") Long userId); + + @Query("SELECT a " + + "FROM AlarmStatus a " + + "JOIN FETCH a.user u " + + "WHERE u.userId = (:userId)") + Optional findByUserIdJoinUser(@Param("userId") Long userId); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 13ce8c512..7eb4f643a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -115,6 +115,7 @@ public interface CabinetRepository extends JpaRepository, Cabinet Page findPaginationByLentType(@Param("lentType") LentType lentType, Pageable pageable); + @EntityGraph(attributePaths = {"cabinetPlace"}) Page findPaginationByStatus(@Param("status") CabinetStatus status, Pageable pageable); Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index fa1996caa..8caf463a9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -109,7 +109,6 @@ int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDat @Query("SELECT lh " + "FROM LentHistory lh " + "LEFT JOIN FETCH lh.user u " - + "LEFT JOIN FETCH u.alarmStatus " + "WHERE lh.userId = :userId AND lh.endedAt is null") Optional findByUserIdAndEndedAtIsNullForUpdate(@Param("userId") Long userId); @@ -160,7 +159,6 @@ Page findPaginationByUserIdAndEndedAtNotNull( @Query("SELECT lh " + "FROM LentHistory lh " + "LEFT JOIN FETCH lh.user u " - + "LEFT JOIN FETCH u.alarmStatus " + "WHERE lh.cabinetId IN (:cabinetIds) " + "AND lh.endedAt IS NULL ") List findAllByCabinetIdInAndEndedAtIsNullJoinUser( diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java index 96cf7ada7..d415452f4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java @@ -1,5 +1,7 @@ package org.ftclub.cabinet.user.domain; +import static javax.persistence.FetchType.LAZY; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -26,19 +28,19 @@ public class AlarmStatus { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "ID") - private Long id; + @Column(name = "ALARM_STATUS_ID") + private Long alarmStatusId; - @Column(name = "slack", nullable = false) + @Column(name = "SLACK", nullable = false) private boolean slack; - @Column(name = "email", nullable = false) + @Column(name = "EMAIL", nullable = false) private boolean email; - @Column(name = "push", nullable = false) + @Column(name = "PUSH", nullable = false) private boolean push; - @OneToOne + @OneToOne(fetch = LAZY) @JoinColumn(name = "USER_ID", nullable = false) private User user; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index a4993106b..db32b0e45 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -3,7 +3,6 @@ import java.time.LocalDateTime; import java.util.Objects; import java.util.regex.Pattern; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -11,7 +10,6 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; -import javax.persistence.OneToOne; import javax.persistence.Table; import javax.validation.constraints.Email; import javax.validation.constraints.NotNull; @@ -55,8 +53,6 @@ public class User { @Column(name = "ROLE", length = 32, nullable = false) private UserRole role; - @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private AlarmStatus alarmStatus; protected User(String name, String email, LocalDateTime blackholedAt, UserRole userRole) { this.name = name; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java new file mode 100644 index 000000000..1a59147fa --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java @@ -0,0 +1,23 @@ +package org.ftclub.cabinet.user.newService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.alarm.repository.AlarmStatusRepository; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.user.domain.AlarmStatus; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AlarmStatusQueryService { + + private final AlarmStatusRepository alarmStatusRepository; + + + public AlarmStatus getUserAlarmStatus(Long userId) { + return alarmStatusRepository.findByUserIdJoinUser(userId) + .orElseThrow(() -> new ServiceException(ExceptionStatus.INVALID_STATUS)); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index 9a1475ccd..8d1fc4fae 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -39,6 +39,6 @@ public List findActiveBanHistories(List userIds, LocalDateTime public Page findActiveBanHistories(LocalDateTime now, Pageable pageable) { log.debug("Called findActiveBanHistories: {}", now); - return banHistoryRepository.findPaginationActiveBanHistories(pageable, now); + return banHistoryRepository.findPaginationActiveBanHistoriesJoinUser(pageable, now); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java index 0a2498119..1c1eb7dea 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java @@ -6,6 +6,7 @@ import org.ftclub.cabinet.user.domain.BanHistory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -52,8 +53,11 @@ List findByUserIdsAndUnbannedAt( * @param today 현재 날짜 * @return active {@link BanHistory} 리스트 */ - @Query("SELECT b FROM BanHistory b WHERE b.unbannedAt > :today ") - Page findPaginationActiveBanHistories(Pageable pageable, + @EntityGraph(attributePaths = {"user"}) + @Query("SELECT b " + + "FROM BanHistory b " + + "WHERE b.unbannedAt > :today ") + Page findPaginationActiveBanHistoriesJoinUser(Pageable pageable, @Param("today") LocalDateTime today); /** diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index 7374ff906..f78f8f30f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -1,5 +1,8 @@ package org.ftclub.cabinet.user.repository; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.admin.domain.Admin; @@ -16,10 +19,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - @Service @RequiredArgsConstructor @Log4j2 @@ -131,7 +130,7 @@ public AdminRole findAdminUserRoleByEmail(String email) { */ public Page findPaginationActiveBanHistories(Pageable pageable, LocalDateTime now) { log.debug("Called findPaginationActiveBanHistories"); - return banHistoryRepository.findPaginationActiveBanHistories(pageable, now); + return banHistoryRepository.findPaginationActiveBanHistoriesJoinUser(pageable, now); } /** From 796572e8791124051728623427eb92d3ac1781ae Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 22 Dec 2023 17:33:20 +0900 Subject: [PATCH 0156/1029] =?UTF-8?q?[BE]=20HOTFIX:=20dev=EC=97=90?= =?UTF-8?q?=EC=84=9C=20alarm=20=EC=95=88=EB=B3=B4=EB=82=B4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/alarm/config/AlarmProperties.java | 3 +++ .../ftclub/cabinet/alarm/handler/AlarmEventHandler.java | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java index c4ca699c7..ede44b922 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -8,6 +8,9 @@ @Getter public class AlarmProperties { + @Value("${cabinet.production}") + private Boolean isProduction; + /*===================== lentSuccess =========================*/ @Value("${cabinet.alarm.mail.lentSuccess.subject}") private String lentSuccessSubject; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index 7bb3eefb0..9c6161ec1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.TransactionalAlarmEvent; import org.ftclub.cabinet.exception.ServiceException; @@ -23,9 +24,13 @@ public class AlarmEventHandler { private final SlackAlarmSender slackAlarmSender; private final EmailAlarmSender emailAlarmSender; private final PushAlarmSender pushAlarmSender; + private final AlarmProperties alarmProperties; @TransactionalEventListener public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactionalAlarmEvent) { + if (!alarmProperties.getIsProduction()) { + return; + } log.info("handleAlarmEventWithTransactional = {}", transactionalAlarmEvent); if (!(transactionalAlarmEvent instanceof TransactionalAlarmEvent)) { return; @@ -37,6 +42,9 @@ public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactio @EventListener public void handleAlarmEvent(AlarmEvent alarmEvent) { log.info("handleAlarmEvent = {}", alarmEvent); + if (!alarmProperties.getIsProduction()) { + return; + } eventProceed(alarmEvent); } From 7707fbc99b79c4dbab47e081af1e30ceb7672a23 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 22 Dec 2023 17:34:02 +0900 Subject: [PATCH 0157/1029] [BE] FIX: config sql fix --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index fb620b186..689668fd3 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit fb620b186204dd0d50e02d8cdfe63bb94ad7c3b0 +Subproject commit 689668fd3c05aa5d591c8711d6793d46617a6a0b From 9bf5067b34e965f52a22e8db428c35ed5b8cbd32 Mon Sep 17 00:00:00 2001 From: moonseonghui Date: Fri, 22 Dec 2023 19:35:53 +0900 Subject: [PATCH 0158/1029] =?UTF-8?q?[FE]=20FIX:=20=EC=BB=AC=EB=9F=AC=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=EC=9D=84=20=EB=88=8C=EB=9F=AC=EC=84=9C=20?= =?UTF-8?q?=ED=85=8C=EB=A7=88=20=EC=83=89=EC=83=81=EC=9D=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=98=EA=B3=A0=20=ED=95=9C=EB=B2=88=EC=97=90=20?= =?UTF-8?q?=EC=97=AC=EB=9F=AC=EA=B0=9C=20=EB=B3=80=EA=B2=BD=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95#1484?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/ThemeColorCard/ThemeColorCard.container.tsx | 7 ++++--- .../Card/ThemeColorCard/ThemeColorCard.tsx | 12 ++++++++---- frontend/src/pages/admin/AdminLayout.tsx | 12 +++++++----- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx index 2ecc8e97f..a0662bd6f 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx @@ -66,12 +66,13 @@ const ThemeColorCardContainer = () => { if (isChange) setShowColorPicker(!showColorPicker); }; - const [selectedColorType, setSelectedColorType] = useState(""); + const [selectedColorType, setSelectedColorType] = useState( + ColorType.MAIN + ); const handleColorButtonClick = (colorType: string) => { - if (showColorPicker) return; setSelectedColorType(colorType); - setShowColorPicker(!showColorPicker); + setShowColorPicker(true); }; useEffect(() => { diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx index 9d0d76ded..9e13feacd 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -1,4 +1,4 @@ -import styled from "styled-components"; +import styled, { css } from "styled-components"; import Card from "@/components/Card/Card"; import { CardContentStyled, @@ -75,13 +75,15 @@ const ThemeColorCard = ({ handleColorButtonClick(type)} > {title} handleColorButtonClick(type)} color={getColor({ mainColor, subColor, mineColor })} - isClickable={showColorPicker} + isSelected={type === selectedColorType && showColorPicker} + showColorPicker={showColorPicker} /> ))} @@ -117,13 +119,15 @@ const ThemeColorCardWrapper = styled.div` const ColorButtonStyled = styled.button<{ color: string; - isClickable: boolean; + isSelected: boolean; + showColorPicker: boolean; }>` width: 28px; height: 28px; background-color: ${(props) => props.color}; border-radius: 8px; - cursor: ${(props) => (props.isClickable ? "not-allowed" : "pointer")}; + box-shadow: ${(props) => + props.isSelected ? `${props.color} 0px 0px 4px` : "none"}; `; export default ThemeColorCard; diff --git a/frontend/src/pages/admin/AdminLayout.tsx b/frontend/src/pages/admin/AdminLayout.tsx index 954b47350..e0ec0e3b2 100644 --- a/frontend/src/pages/admin/AdminLayout.tsx +++ b/frontend/src/pages/admin/AdminLayout.tsx @@ -40,13 +40,15 @@ const Layout = (): JSX.Element => { } }, []); - const savedColor = localStorage.getItem("main-color"); + const savedMainColor = localStorage.getItem("main-color"); + const savedSubColor = localStorage.getItem("sub-color"); + const savedMineColor = localStorage.getItem("mine-color"); const root: HTMLElement = document.documentElement; useEffect(() => { - root.style.setProperty("--main-color", savedColor); - if (savedColor !== var(--default-main-color)) - root.style.setProperty("--sub-color", "#7b7b7b"); - }, [savedColor]); + root.style.setProperty("--main-color", savedMainColor); + root.style.setProperty("--sub-color", savedSubColor); + root.style.setProperty("--mine", savedMineColor); + }, [savedMainColor, savedSubColor, savedMineColor]); const { closeAll } = useMenu(); From c3d9b439db7e25c9f01a9a5115140b364ac7d82c Mon Sep 17 00:00:00 2001 From: moonseonghui Date: Fri, 22 Dec 2023 19:41:47 +0900 Subject: [PATCH 0159/1029] =?UTF-8?q?[FE]=20ETC=20:=20config=20=EC=B6=A9?= =?UTF-8?q?=EB=8F=8C=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index c6b12936e..689668fd3 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit c6b12936e8eb2fed5cc072959591ec71eb0bf41b +Subproject commit 689668fd3c05aa5d591c8711d6793d46617a6a0b From 013a50bd4ee86e172318d1ba2f26550b58acac9a Mon Sep 17 00:00:00 2001 From: ldw Date: Fri, 22 Dec 2023 20:21:39 +0900 Subject: [PATCH 0160/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20getMyProfile?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20MyP?= =?UTF-8?q?rofileResponseDto=EC=97=90=20alarmTypeResponseDto=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/dto/AlarmTypeResponseDto.java | 1 + .../alarm/service/AlarmQueryService.java | 2 +- .../user/newService/UserFacadeService.java | 21 +++++++++++-------- .../user/service/UserFacadeServiceImpl.java | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java index d688fc59e..e28095c5f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java @@ -7,6 +7,7 @@ @AllArgsConstructor @Getter +@Builder public class AlarmTypeResponseDto { private boolean slack; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java index 8073773cd..efec91db9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/service/AlarmQueryService.java @@ -15,7 +15,7 @@ public class AlarmQueryService { private final AlarmStatusRepository alarmStatusRepository; - public AlarmStatus findAlarmStatusByUserId(Long userId) { + public AlarmStatus findAlarmStatus(Long userId) { return alarmStatusRepository.findByUserId(userId).orElseThrow(() -> new ServiceException( ExceptionStatus.NOT_FOUND_USER)); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 2fd6100ff..b5e08d020 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -3,24 +3,19 @@ import io.netty.util.internal.StringUtil; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; +import org.ftclub.cabinet.alarm.service.AlarmQueryService; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; import org.ftclub.cabinet.dto.*; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.LentExtension; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; +import org.ftclub.cabinet.user.domain.*; import org.springframework.stereotype.Service; import java.time.LocalDateTime; -import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -33,6 +28,7 @@ public class UserFacadeService { private final UserQueryService userQueryService; private final UserCommandService userCommandService; private final UserMapper userMapper; + private final AlarmQueryService alarmQueryService; public MyProfileResponseDto getMyProfile(UserSessionDto user) { log.debug("Called getMyProfile: {}", user.getName()); @@ -48,7 +44,14 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { .lentExtensionType(lentExtension.getLentExtensionType()) .build(); - return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, lentExtensionResponseDto); + AlarmStatus alarmStatus = alarmQueryService.findAlarmStatus(user.getUserId()); + AlarmTypeResponseDto alarmTypeResponseDto = AlarmTypeResponseDto.builder() + .slack(alarmStatus.isSlack()) + .email(alarmStatus.isEmail()) + .push(alarmStatus.isPush()) + .build(); + + return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, lentExtensionResponseDto, alarmTypeResponseDto); } public void createClubUser(String clubName) { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java index bf9092f64..e5bd4ecd5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java @@ -55,7 +55,7 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { LentExtensionResponseDto activeLentExtension = lentExtensionService.getActiveLentExtension( user); - AlarmStatus userAlarmStatus = alarmQueryService.findAlarmStatusByUserId( + AlarmStatus userAlarmStatus = alarmQueryService.findAlarmStatus( user.getUserId()); AlarmTypeResponseDto.builder().alarmStatus(userAlarmStatus).build(); @@ -299,7 +299,7 @@ public void useLentExtension(UserSessionDto userSessionDto) { public void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto) { log.debug("Called updateAlarmState"); - alarmCommandService.updateAlarmStatusRe(dto, alarmQueryService.findAlarmStatusByUserId( + alarmCommandService.updateAlarmStatusRe(dto, alarmQueryService.findAlarmStatus( user.getUserId())); } From 590f634fa95d9a338375cedf5968a2b50477c06f Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Fri, 22 Dec 2023 20:55:25 +0900 Subject: [PATCH 0161/1029] =?UTF-8?q?fix=20:=20config=20update,=20?= =?UTF-8?q?=EB=B0=94=EB=80=90=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20=EC=BB=B4=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=95=88=EB=90=98=EB=8A=94=20=EB=B6=80=EB=B6=84?= =?UTF-8?q?=EB=93=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 1 - ...deService.java => LentFacadeService2.java} | 49 ++++++++++--------- .../cabinet/user/domain/BanPolicyImpl.java | 11 +++-- config | 2 +- 4 files changed, 33 insertions(+), 30 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/lent/newService/{LentFacadeService.java => LentFacadeService2.java} (51%) diff --git a/.idea/misc.xml b/.idea/misc.xml index e0797ed03..00556c162 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,5 @@ - diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java similarity index 51% rename from backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java index 7e2b8c2e6..81be2750e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java @@ -7,6 +7,8 @@ import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentCommandService; +import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.data.domain.Page; @@ -20,34 +22,34 @@ @Slf4j @Service @RequiredArgsConstructor -public class LentFacadeService { +public class LentFacadeService2 { - private final LentQueryService lentQueryService; - private final LentCommandService lentCommandService; - private final UserQueryService userQueryService; - private final CabinetQueryService cabinetQueryService; + private final LentQueryService lentQueryService; + private final LentCommandService lentCommandService; + private final UserQueryService userQueryService; + private final CabinetQueryService cabinetQueryService; - private final LentMapper lentMapper; + private final LentMapper lentMapper; - public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size) { - log.debug("Called getAllUserLentHistories: {}", userId); - userQueryService.getUser(userId); + public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size) { + log.debug("Called getAllUserLentHistories: {}", userId); + userQueryService.getUser(userId); - if (size <= 0) { - size = Integer.MAX_VALUE; - } - PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); - Page lentHistories = lentQueryService.findUserLentHistories(userId, pageable); - List result = lentHistories.stream() - .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) - .collect(Collectors.toList()); - return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); - } + if (size <= 0) { + size = Integer.MAX_VALUE; + } + PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); + Page lentHistories = lentQueryService.findUserActiveLentHistories(userId, pageable); + List result = lentHistories.stream() + .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) + .collect(Collectors.toList()); + return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); + } - public List getLentDtoList(Long cabinetId) { - log.debug("Called getLentDtoList: {}", cabinetId); - cabinetQueryService.getCabinet(cabinetId); + public List getLentDtoList(Long cabinetId) { + log.debug("Called getLentDtoList: {}", cabinetId); + cabinetQueryService.getCabinets(cabinetId); // cabinetOptionalFetcher.getCabinet(cabinetId); // List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( @@ -62,5 +64,6 @@ public List getLentDtoList(Long cabinetId) { // e.getStartedAt(), // e.getExpiredAt())) // .collect(Collectors.toList()); - } + return null; + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/BanPolicyImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/BanPolicyImpl.java index 89b51f48e..33ab6ec5d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/BanPolicyImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/BanPolicyImpl.java @@ -1,7 +1,5 @@ package org.ftclub.cabinet.user.domain; -import java.time.LocalDateTime; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.LentType; @@ -9,6 +7,9 @@ import org.ftclub.cabinet.utils.DateUtil; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; +import java.util.List; + @Component @RequiredArgsConstructor @Log4j2 @@ -18,7 +19,7 @@ public class BanPolicyImpl implements BanPolicy { @Override public BanType verifyForBanType(LentType lentType, LocalDateTime startAt, LocalDateTime endedAt, - LocalDateTime expiredAt) { + LocalDateTime expiredAt) { log.debug("Called verifyForBanType"); if (checkAlreadyExpired(endedAt, expiredAt)) { return BanType.ALL; @@ -28,7 +29,7 @@ public BanType verifyForBanType(LentType lentType, LocalDateTime startAt, LocalD @Override public LocalDateTime getBanDate(BanType banType, LocalDateTime endedAt, LocalDateTime expiredAt, - Long userId) { + Long userId) { log.debug("Called getBanDate"); Double currentBanDays = DateUtil.calculateTwoDateDiffCeil(expiredAt, endedAt) .doubleValue(); @@ -51,7 +52,7 @@ public boolean isActiveBanHistory(LocalDateTime unbannedAt, LocalDateTime now) { @Override public Long getAccumulateBanDaysByUserId(Long userId) { log.debug("Called getAccumulateBanDaysByUserId"); - List banHistories = banHistoryRepository.findBanHistoriesByUserId(userId); + List banHistories = banHistoryRepository.findByUserId(userId); Long accumulateDays = 0L; for (BanHistory history : banHistories) { accumulateDays += DateUtil.calculateTwoDateDiffAbs(history.getBannedAt(), diff --git a/config b/config index 868b4f34e..689668fd3 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 868b4f34e936b36d739a6d68d280c3e3a148c118 +Subproject commit 689668fd3c05aa5d591c8711d6793d46617a6a0b From 3a1c77ab5f022abe0207552a879b14a23594185c Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Fri, 22 Dec 2023 21:38:01 +0900 Subject: [PATCH 0162/1029] =?UTF-8?q?fix=20:=20alarmResponse=20=EB=B9=8C?= =?UTF-8?q?=EB=8D=94=20=EC=9D=B4=EC=83=81=ED=95=9C=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20ExceptionController=EC=97=90?= =?UTF-8?q?=EC=84=9C=20printStackTrace=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/alarm/dto/AlarmTypeResponseDto.java | 1 - .../ftclub/cabinet/auth/service/FtOauthService.java | 13 ++++++------- .../cabinet/exception/ExceptionController.java | 2 ++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java index e28095c5f..d688fc59e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/AlarmTypeResponseDto.java @@ -7,7 +7,6 @@ @AllArgsConstructor @Getter -@Builder public class AlarmTypeResponseDto { private boolean slack; diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java index b7b618cc8..c83e3ee74 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java @@ -49,12 +49,12 @@ public OAuth2AccessToken issueAccessTokenByCredentialsGrant() throws IOException public FtProfile getProfileByIntraName(String accessToken, String intraName) throws JsonProcessingException { log.info("Called getProfileByIntraName {}", intraName); - String result = WebClient.create().get() + JsonNode result = WebClient.create().get() .uri(ftApiProperties.getUsersInfoUri() + '/' + intraName) .accept(MediaType.APPLICATION_JSON) .headers(h -> h.setBearerAuth(accessToken)) .retrieve() - .bodyToMono(String.class) + .bodyToMono(JsonNode.class) .block(); return convertJsonStringToProfile(result); } @@ -75,7 +75,7 @@ public FtProfile getProfileByCode(String code) throws IOException, ExecutionExce ftOAuth20Service.signRequest(accessToken, oAuthRequest); try { Response response = ftOAuth20Service.execute(oAuthRequest); - return convertJsonStringToProfile(response.getBody()); + return convertJsonStringToProfile(objectMapper.readTree(response.getBody())); } catch (Exception e) { if (e instanceof IOException) log.error("42 API 서버에서 프로필 정보를 가져오는데 실패했습니다." @@ -95,8 +95,7 @@ public FtProfile getProfileByCode(String code) throws IOException, ExecutionExce * @throws JsonProcessingException JSON 파싱 예외 * @see 42 API에서 제공하는 Profile Json에 대한 정보 */ - private FtProfile convertJsonStringToProfile(String jsonString) throws JsonProcessingException { - JsonNode rootNode = objectMapper.readTree(jsonString); + private FtProfile convertJsonStringToProfile(JsonNode rootNode) throws JsonProcessingException { String intraName = rootNode.get("login").asText(); String email = rootNode.get("email").asText(); if (intraName == null || email == null) @@ -130,8 +129,8 @@ private FtRole determineFtRole(JsonNode rootNode, LocalDateTime blackHoledAt) { return (blackHoledAt == null) ? FtRole.MEMBER : FtRole.CADET; } - private LocalDateTime determineBlackHoledAt(JsonNode cursusUsersNode) { - JsonNode blackHoledAtNode = cursusUsersNode.get(CURSUS_INDEX).get("blackholed_at"); + private LocalDateTime determineBlackHoledAt(JsonNode rootNode) { + JsonNode blackHoledAtNode = rootNode.get("cursus_users").get(CURSUS_INDEX).get("blackholed_at"); if (blackHoledAtNode.isNull() || blackHoledAtNode.isEmpty()) return null; return DateUtil.convertStringToDate(blackHoledAtNode.asText()); diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java index 8b11cc9c5..14f146483 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java @@ -12,6 +12,7 @@ public class ExceptionController { @ExceptionHandler(ControllerException.class) public ResponseEntity controllerExceptionHandler(ControllerException e) { log.info("[ControllerException] {} : {}", e.status.getError(), e.status.getMessage()); + e.printStackTrace(); return ResponseEntity .status(e.status.getStatusCode()) .body(e.status); @@ -20,6 +21,7 @@ public ResponseEntity controllerExceptionHandler(ControllerException e) { @ExceptionHandler(ServiceException.class) public ResponseEntity serviceExceptionHandler(ServiceException e) { log.info("[ServiceException] {} : {}", e.status.getError(), e.status.getMessage()); + e.printStackTrace(); return ResponseEntity .status(e.status.getStatusCode()) .body(e.status); From 99a5ddc93ebbf2e30eaf38ffeaed81fd690d91cb Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Fri, 22 Dec 2023 21:38:50 +0900 Subject: [PATCH 0163/1029] =?UTF-8?q?fix=20:=20alarmResponse=20=EB=B9=8C?= =?UTF-8?q?=EB=8D=94=20=EC=9D=B4=EC=83=81=ED=95=9C=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/newService/UserFacadeService.java | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index b5e08d020..72179de17 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -7,7 +7,9 @@ import org.ftclub.cabinet.alarm.service.AlarmQueryService; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.LentExtensionResponseDto; +import org.ftclub.cabinet.dto.MyProfileResponseDto; +import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.mapper.UserMapper; @@ -22,52 +24,50 @@ @Log4j2 public class UserFacadeService { - private final BanHistoryQueryService banHistoryQueryService; - private final LentExtensionQueryService lentExtensionQueryService; - private final CabinetQueryService cabinetQueryService; - private final UserQueryService userQueryService; - private final UserCommandService userCommandService; - private final UserMapper userMapper; - private final AlarmQueryService alarmQueryService; + private final BanHistoryQueryService banHistoryQueryService; + private final LentExtensionQueryService lentExtensionQueryService; + private final CabinetQueryService cabinetQueryService; + private final UserQueryService userQueryService; + private final UserCommandService userCommandService; + private final UserMapper userMapper; + private final AlarmQueryService alarmQueryService; - public MyProfileResponseDto getMyProfile(UserSessionDto user) { - log.debug("Called getMyProfile: {}", user.getName()); + public MyProfileResponseDto getMyProfile(UserSessionDto user) { + log.debug("Called getMyProfile: {}", user.getName()); - Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); - BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), LocalDateTime.now()); - LentExtension lentExtension = lentExtensionQueryService.getActiveLentExtension(user); - LentExtensionResponseDto lentExtensionResponseDto = LentExtensionResponseDto.builder() - .lentExtensionId(lentExtension.getLentExtensionId()) - .name(lentExtension.getName()) - .extensionPeriod(lentExtension.getExtensionPeriod()) - .expiredAt(lentExtension.getExpiredAt().toString()) - .lentExtensionType(lentExtension.getLentExtensionType()) - .build(); + Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); + BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), LocalDateTime.now()); + LentExtension lentExtension = lentExtensionQueryService.getActiveLentExtension(user); + LentExtensionResponseDto lentExtensionResponseDto = LentExtensionResponseDto.builder() + .lentExtensionId(lentExtension.getLentExtensionId()) + .name(lentExtension.getName()) + .extensionPeriod(lentExtension.getExtensionPeriod()) + .expiredAt(lentExtension.getExpiredAt().toString()) + .lentExtensionType(lentExtension.getLentExtensionType()) + .build(); - AlarmStatus alarmStatus = alarmQueryService.findAlarmStatus(user.getUserId()); - AlarmTypeResponseDto alarmTypeResponseDto = AlarmTypeResponseDto.builder() - .slack(alarmStatus.isSlack()) - .email(alarmStatus.isEmail()) - .push(alarmStatus.isPush()) - .build(); - - return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, lentExtensionResponseDto, alarmTypeResponseDto); - } + AlarmStatus alarmStatus = alarmQueryService.findAlarmStatus(user.getUserId()); + AlarmTypeResponseDto alarmTypeResponseDto = AlarmTypeResponseDto.builder() + .alarmStatus(alarmStatus) + .build(); - public void createClubUser(String clubName) { - log.debug("Called createClubUser: {}", clubName); - User user = userQueryService.findUser(clubName); - if (StringUtil.isNullOrEmpty(clubName)) { - throw new ControllerException(ExceptionStatus.INVALID_ARGUMENT); - } else if (user != null && user.getDeletedAt() == null) { - throw new ControllerException(ExceptionStatus.EXISTED_CLUB_USER); - } else if (user != null) { - user.setDeletedAt(null); - } else { - String randomUUID = UUID.randomUUID().toString(); - User newUser = User.of(clubName, randomUUID + "@ftc.co.kr", null, UserRole.CLUB); - userCommandService.createUser(newUser); - } - } + return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, lentExtensionResponseDto, alarmTypeResponseDto); + } + + public void createClubUser(String clubName) { + log.debug("Called createClubUser: {}", clubName); + User user = userQueryService.findUser(clubName); + if (StringUtil.isNullOrEmpty(clubName)) { + throw new ControllerException(ExceptionStatus.INVALID_ARGUMENT); + } else if (user != null && user.getDeletedAt() == null) { + throw new ControllerException(ExceptionStatus.EXISTED_CLUB_USER); + } else if (user != null) { + user.setDeletedAt(null); + } else { + String randomUUID = UUID.randomUUID().toString(); + User newUser = User.of(clubName, randomUUID + "@ftc.co.kr", null, UserRole.CLUB); + userCommandService.createUser(newUser); + } + } } From c65b649b7296f91558968b83700a6331065adf32 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 22 Dec 2023 21:56:10 +0900 Subject: [PATCH 0164/1029] =?UTF-8?q?[BE]=20REFACTOR:=20getCabinetInfo=20?= =?UTF-8?q?=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../newService/CabinetFacadeService.java | 39 +++++++++++++++++-- .../service/CabinetFacadeServiceImpl.java | 22 +++++++---- .../java/org/ftclub/cabinet/dto/LentDto.java | 5 ++- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index e4af4e946..cc61fbef6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -1,12 +1,20 @@ package org.ftclub.cabinet.cabinet.newService; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentQueryService; +import org.ftclub.cabinet.lent.service.LentRedisService; import org.ftclub.cabinet.mapper.CabinetMapper; +import org.ftclub.cabinet.mapper.LentMapper; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,7 +25,13 @@ public class CabinetFacadeService { private final CabinetCommandService cabinetCommandService; private final CabinetQueryService cabinetQueryService; + + private final LentQueryService lentQueryService; + private final LentRedisService lentRedisService; + private final UserQueryService userQueryService; + private final CabinetMapper cabinetMapper; + private final LentMapper lentMapper; /** * {@inheritDoc} @@ -36,12 +50,29 @@ public List getBuildingFloorsResponse() { } /** - * {@inheritDoc} 사물함 id로 사물함 정보를 가져옵니다. + * {@inheritDoc} 사물함 id로 사물함 정보를 가져옵니다. active 대여기록이 없는경우, IN_SESSION 상태의 사물함인지 확인합니다. */ - public CabinetInfoResponseDto getCabinetInfoResponse(Long cabinetId) { - log.debug("getCabinetInfoResponse: {}", cabinetId); + public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { + log.debug("getCabinetInfo: {}", cabinetId); + List lentDtos = new ArrayList<>(); + + List cabinetActiveLentHistories = lentQueryService.findCabinetActiveLentHistories( + cabinetId); + + cabinetActiveLentHistories.stream().map(lentHistory -> lentDtos.add( + lentMapper.toLentDto(lentHistory.getUser(), lentHistory))); + + if (cabinetActiveLentHistories.isEmpty()) { + List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); + List users = userQueryService.getUsers(usersInCabinet); + users.stream().map(user -> lentDtos.add( + LentDto.builder().userId(user.getUserId()).name(user.getName()).build() + )); + } - return null; + return cabinetMapper.toCabinetInfoResponseDto( + cabinetQueryService.findUserActiveCabinet(cabinetId), lentDtos, + lentRedisService.getSessionExpired(cabinetId)); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java index 197827d14..68077541f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeServiceImpl.java @@ -84,6 +84,16 @@ public List getBuildingFloorsResponse() { .collect(Collectors.toList()); } + private List findShareInSessionUsers(String cabinetId) { + List lentDtos = new ArrayList<>(); + List userIds = lentRedis.getAllUserInCabinet(cabinetId); + for (String userId : userIds) { + String userName = userOptionalFetcher.findUser(Long.valueOf(userId)).getName(); + lentDtos.add(new LentDto(Long.valueOf(userId), userName, null, null, null)); + } + return lentDtos; + } + /** * {@inheritDoc} */ @@ -94,17 +104,15 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { List lentDtos = new ArrayList<>(); List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( cabinetId); - if (lentHistories.isEmpty()) { - List users = lentRedis.getAllUserInCabinet(cabinetId.toString()); - for (String user : users) { - String userName = userOptionalFetcher.findUser(Long.valueOf(user)).getName(); - lentDtos.add(new LentDto(Long.valueOf(user), userName, null, null, null)); - } - } + // 현재 대여중인 사물함이 아닌경우, 패스 (lentDto 에 Null) for (LentHistory lentHistory : lentHistories) { User findUser = lentHistory.getUser(); lentDtos.add(lentMapper.toLentDto(findUser, lentHistory)); } + if (lentHistories.isEmpty()) { + // 세션중인 공유사물함인지 확인 + lentDtos = findShareInSessionUsers(cabinetId.toString()); + } return cabinetMapper.toCabinetInfoResponseDto(cabinetOptionalFetcher.findCabinet(cabinetId), lentDtos, lentRedis.getCabinetExpiredAt(cabinetId.toString())); } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/LentDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/LentDto.java index 8d7e7fe03..1b71725af 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/LentDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/LentDto.java @@ -1,15 +1,16 @@ package org.ftclub.cabinet.dto; import java.time.LocalDateTime; -import java.util.Date; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; /** * 대여 정보를 반환하는 DTO입니다. */ -@AllArgsConstructor +@Builder @Getter +@AllArgsConstructor public class LentDto { private final Long userId; From 7c2b2f8e034cd36101d2dd1e1624e0e991a2fd45 Mon Sep 17 00:00:00 2001 From: ldw Date: Fri, 22 Dec 2023 22:07:52 +0900 Subject: [PATCH 0165/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20=EB=B3=B8?= =?UTF-8?q?=EC=9D=B8=EC=9D=98=20=EC=97=B0=EC=9E=A5=EA=B6=8C=EC=9D=84=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20UserFacadeService?= =?UTF-8?q?=EC=9D=98=20getMyLentExtension=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/newService/LentExtensionQueryService.java | 14 ++++++++++++-- .../cabinet/user/newService/UserFacadeService.java | 13 +++++++++++++ .../repository/LentExtensionOptionalFetcher.java | 2 +- .../user/repository/LentExtensionRepository.java | 4 ++-- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java index 190ca852c..7b28c5ace 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java @@ -2,14 +2,15 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.dto.LentExtensionResponseDto; import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.user.domain.LentExtension; import org.ftclub.cabinet.user.domain.LentExtensions; import org.ftclub.cabinet.user.repository.LentExtensionRepository; import org.springframework.stereotype.Service; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -20,10 +21,19 @@ public class LentExtensionQueryService { public LentExtension getActiveLentExtension(UserSessionDto userSessionDto) { log.debug("Called getActiveLentExtension: {}", userSessionDto.getName()); - List lentExtensions = lentExtensionRepository.findAllByUserId(userSessionDto.getUserId()); + List lentExtensions = lentExtensionRepository.findAll(userSessionDto.getUserId()); return LentExtensions.builder() .lentExtensions(lentExtensions) .build() .findImminentActiveLentExtension(); } + + public List getMyLentExtensionSorted(Long userId) { + log.debug("Called getMyLentExtensionSorted: {}", userId); + + return lentExtensionRepository.findAll(userId) + .stream() + .sorted(Comparator.comparing(LentExtension::getExpiredAt)) + .collect(Collectors.toList()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index b5e08d020..d753434f6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -15,7 +15,10 @@ import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -69,5 +72,15 @@ public void createClubUser(String clubName) { userCommandService.createUser(newUser); } } + + public LentExtensionPaginationDto getMyLentExtension(UserSessionDto userSessionDto) { + log.debug("Called getMyLentExtension"); + + List lentExtensionResponseDtos = lentExtensionQueryService.getMyLentExtensionSorted(userSessionDto.getUserId()) + .stream() + .map(userMapper::toLentExtensionResponseDto) + .collect(Collectors.toList()); + return userMapper.toLentExtensionPaginationDto(lentExtensionResponseDtos, (long) lentExtensionResponseDtos.size()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionOptionalFetcher.java index ec26e7a4e..6e96d1bf2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionOptionalFetcher.java @@ -35,7 +35,7 @@ public List findAllNotExpired() { @Transactional(readOnly = true) public List findAllByUserId(Long userId) { - return lentExtensionRepository.findAllByUserId(userId); + return lentExtensionRepository.findAll(userId); } @Transactional(readOnly = true) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java index c285db9ff..cfbd702f2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.user.repository; import java.util.List; -import java.util.Optional; + import org.ftclub.cabinet.user.domain.LentExtension; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -25,7 +25,7 @@ public interface LentExtensionRepository extends JpaRepository findAllByUserId(@Param("userId") Long userId); + List findAll(@Param("userId") Long userId); List findAllByUserIdAndUsedAtIsNull(@Param("userId") Long userId); } From 7c29ae2eb5a77da5cff86f05eb60bb50994a6aa4 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 22 Dec 2023 22:09:14 +0900 Subject: [PATCH 0166/1029] =?UTF-8?q?[BE]=20REFACTOR:=20getCabinetsSimpleI?= =?UTF-8?q?nfoByVisibleNum=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/modules.xml | 3 +++ .../newService/CabinetFacadeService.java | 17 +++++++++++++++++ .../cabinet/dto/CabinetSimplePaginationDto.java | 5 ++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.idea/modules.xml b/.idea/modules.xml index 3ba732021..6f4484194 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,7 +3,10 @@ + + + diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index cc61fbef6..949767675 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -5,8 +5,11 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetSimpleDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; @@ -75,5 +78,19 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { lentRedisService.getSessionExpired(cabinetId)); } + public CabinetSimplePaginationDto getCabinetsSimpleInfoByVisibleNum(Integer visibleNum) { + log.debug("getCabinetsSimpleInfoByVisibleNum: {}", visibleNum); + + List cabinets = cabinetQueryService.getCabinets(visibleNum); + + List cabinetSimpleDtos = cabinets.stream() + .map(cabinetMapper::toCabinetSimpleDto) + .collect(Collectors.toList()); + + return CabinetSimplePaginationDto.builder() + .totalLength((long) cabinets.size()) + .result(cabinetSimpleDtos) + .build(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimplePaginationDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimplePaginationDto.java index 20444e4c6..ce04c3b92 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimplePaginationDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetSimplePaginationDto.java @@ -2,11 +2,14 @@ import java.util.List; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; -@AllArgsConstructor @Getter +@Builder +@AllArgsConstructor public class CabinetSimplePaginationDto { + private final List result; private final Long totalLength; } From a04d58f6765e707b352ac301a62f3afb6064b452 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 23 Dec 2023 10:49:34 +0900 Subject: [PATCH 0167/1029] docs : modules.xml --- .idea/modules.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/modules.xml b/.idea/modules.xml index 6f4484194..ed9d272db 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,6 +2,7 @@ + From 7f14fdcc68bd810adcbb614b2e11bb03fc1ad262 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 23 Dec 2023 13:28:39 +0900 Subject: [PATCH 0168/1029] =?UTF-8?q?fix,=20refactor=20:=20=EC=96=B4?= =?UTF-8?q?=EB=93=9C=EB=AF=BC=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminController.java | 2 +- .../admin/{ => admin}/domain/Admin.java | 2 +- .../admin/{ => admin}/domain/AdminRole.java | 2 +- .../repository/AdminRepository.java | 6 +- .../service/AdminCommandService.java | 2 +- .../service/AdminFacadeService.java | 5 +- .../service/AdminQueryService.java | 2 +- .../AdminAuthController.java | 2 +- .../AdminCabinetController.java | 29 ++-- .../AdminSearchStatisticsController.java | 147 ------------------ .../admin/search/AdminSearchController.java | 57 +++++++ .../statistics/AdminStatisticsController.java | 96 ++++++++++++ .../cabinet/auth/domain/TokenProvider.java | 4 +- .../cabinet/auth/domain/TokenValidator.java | 6 +- .../auth/service/AuthFacadeService.java | 2 +- .../user/repository/UserOptionalFetcher.java | 7 +- .../user/service/UserFacadeService.java | 9 +- .../user/service/UserFacadeServiceImpl.java | 11 +- .../cabinet/user/service/UserService.java | 2 +- .../cabinet/user/service/UserServiceImpl.java | 6 +- .../auth/domain/TokenValidatorUnitTest.java | 2 +- .../ftclub/cabinet/user/domain/AdminTest.java | 4 +- .../user/repository/AdminRepositoryTest.java | 6 +- .../user/service/UserFacadeServiceTest.java | 2 +- .../cabinet/user/service/UserServiceTest.java | 6 +- .../user/service/UserServiceUnitTest.java | 6 +- .../java/org/ftclub/testutils/TestUtils.java | 12 +- 27 files changed, 221 insertions(+), 216 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/admin/{ => admin}/controller/AdminController.java (98%) rename backend/src/main/java/org/ftclub/cabinet/admin/{ => admin}/domain/Admin.java (97%) rename backend/src/main/java/org/ftclub/cabinet/admin/{ => admin}/domain/AdminRole.java (88%) rename backend/src/main/java/org/ftclub/cabinet/admin/{ => admin}/repository/AdminRepository.java (87%) rename backend/src/main/java/org/ftclub/cabinet/admin/{ => admin}/service/AdminCommandService.java (80%) rename backend/src/main/java/org/ftclub/cabinet/admin/{ => admin}/service/AdminFacadeService.java (99%) rename backend/src/main/java/org/ftclub/cabinet/admin/{ => admin}/service/AdminQueryService.java (80%) rename backend/src/main/java/org/ftclub/cabinet/admin/{controller => auth}/AdminAuthController.java (98%) rename backend/src/main/java/org/ftclub/cabinet/admin/{controller => cabinet}/AdminCabinetController.java (91%) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminSearchStatisticsController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java index cf84ef9ef..042c81bea 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.controller; +package org.ftclub.cabinet.admin.admin.controller; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/domain/Admin.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/domain/Admin.java similarity index 97% rename from backend/src/main/java/org/ftclub/cabinet/admin/domain/Admin.java rename to backend/src/main/java/org/ftclub/cabinet/admin/admin/domain/Admin.java index 749c63868..2d2122039 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/domain/Admin.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/domain/Admin.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.domain; +package org.ftclub.cabinet.admin.admin.domain; import lombok.AccessLevel; import lombok.Getter; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminRole.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/domain/AdminRole.java similarity index 88% rename from backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminRole.java rename to backend/src/main/java/org/ftclub/cabinet/admin/admin/domain/AdminRole.java index ea9facd3e..038d50174 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/domain/AdminRole.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/domain/AdminRole.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.domain; +package org.ftclub.cabinet.admin.admin.domain; /** * 어드민의 권한을 나타내는 열거형 클래스입니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/repository/AdminRepository.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/repository/AdminRepository.java similarity index 87% rename from backend/src/main/java/org/ftclub/cabinet/admin/repository/AdminRepository.java rename to backend/src/main/java/org/ftclub/cabinet/admin/admin/repository/AdminRepository.java index b7721c575..9e7fdabb3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/repository/AdminRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/repository/AdminRepository.java @@ -1,7 +1,7 @@ -package org.ftclub.cabinet.admin.repository; +package org.ftclub.cabinet.admin.admin.repository; -import org.ftclub.cabinet.admin.domain.Admin; -import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminCommandService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java similarity index 80% rename from backend/src/main/java/org/ftclub/cabinet/admin/service/AdminCommandService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java index 6b2a179d9..fbafb93af 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.service; +package org.ftclub.cabinet.admin.admin.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java index b38c4fe3a..b4523d8a1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.service; +package org.ftclub.cabinet.admin.admin.service; import static java.util.stream.Collectors.toList; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; @@ -13,6 +13,7 @@ import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -133,7 +134,7 @@ public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pag @Transactional(readOnly = true) public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, - Pageable pageable) { + Pageable pageable) { log.debug("Called getUserLentCabinetInfo {}", partialName); LocalDateTime now = LocalDateTime.now(); diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminQueryService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java similarity index 80% rename from backend/src/main/java/org/ftclub/cabinet/admin/service/AdminQueryService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java index 6e32a848a..47a72755a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.service; +package org.ftclub.cabinet.admin.admin.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminAuthController.java b/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminAuthController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java index 4c372d034..f845292ca 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminAuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.controller; +package org.ftclub.cabinet.admin.auth; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.service.AuthFacadeService; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java similarity index 91% rename from backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java index 6bcc99d9a..5e6384975 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java @@ -1,8 +1,5 @@ -package org.ftclub.cabinet.cabinet.controller; +package org.ftclub.cabinet.admin.cabinet; -import java.util.HashMap; -import java.util.Map; -import javax.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; @@ -10,21 +7,15 @@ import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; -import org.ftclub.cabinet.dto.CabinetClubStatusRequestDto; -import org.ftclub.cabinet.dto.CabinetInfoResponseDto; -import org.ftclub.cabinet.dto.CabinetPaginationDto; -import org.ftclub.cabinet.dto.CabinetStatusRequestDto; -import org.ftclub.cabinet.dto.LentHistoryPaginationDto; +import org.ftclub.cabinet.dto.*; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.lent.service.LentFacadeService; -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.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.HashMap; +import java.util.Map; /** * 관리자가 사물함을 관리할 때 사용하는 컨트롤러입니다. @@ -250,9 +241,9 @@ public CabinetPaginationDto getCabinetsByVisibleNum( @GetMapping("/{cabinetId}/lent-histories") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public LentHistoryPaginationDto getCabinetLentHistories(@Valid - @PathVariable("cabinetId") Long cabinetId, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { + @PathVariable("cabinetId") Long cabinetId, + @RequestParam("page") Integer page, + @RequestParam("size") Integer size) { log.info("Called getCabinetLentHistories: {}", cabinetId); return cabinetFacadeService.getCabinetLentHistoriesPagination(cabinetId, page, size); } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminSearchStatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminSearchStatisticsController.java deleted file mode 100644 index 3e95b7184..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/controller/AdminSearchStatisticsController.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.ftclub.cabinet.admin.controller; - -import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; - -import java.time.LocalDateTime; -import java.util.List; -import javax.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.admin.service.AdminFacadeService; -import org.ftclub.cabinet.auth.domain.AuthGuard; -import org.ftclub.cabinet.dto.BlockedUserPaginationDto; -import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; -import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; -import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; -import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; -import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.ftclub.cabinet.dto.UserCabinetPaginationDto; -import org.ftclub.cabinet.dto.UserProfilePaginationDto; -import org.springframework.data.domain.Pageable; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.format.annotation.DateTimeFormat.ISO; -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.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@Slf4j -@RestController -@RequestMapping("/v4/admin") -@RequiredArgsConstructor -public class AdminSearchStatisticsController { - - private final AdminFacadeService adminFacadeService; - - - /*---------------------------------------- Search ------------------------------------------*/ - @GetMapping("/search/cabinets-simple") - @AuthGuard(level = ADMIN_ONLY) - public CabinetSimplePaginationDto getCabinetsSimpleInfo( - @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsSimpleInfo {}", visibleNum); - return adminFacadeService.getCabinetsSimpleInfo(visibleNum); - } - - @GetMapping("/search/cabinets") - @AuthGuard(level = ADMIN_ONLY) - public CabinetInfoPaginationDto getCabinetsInfo( - @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsInfo {}", visibleNum); - return adminFacadeService.getCabinetInfo(visibleNum); - } - - @GetMapping("/search/users-simple") - @AuthGuard(level = ADMIN_ONLY) - public UserProfilePaginationDto getUsersProfile( - @RequestParam("name") String name, Pageable pageable) { - log.info("Called getUsersProfile {}", name); - return adminFacadeService.getUsersProfile(name, pageable); - } - - @GetMapping("/search/users") - @AuthGuard(level = ADMIN_ONLY) - public UserCabinetPaginationDto getCabinetsLentInfo( - @RequestParam("name") String name, Pageable pageable) { - log.info("Called getCabinetsLentInfo {}", name); - return adminFacadeService.getUserLentCabinetInfo(name, pageable); - } - - /*-------------------------------------- Statistics ----------------------------------------*/ - - /** - * 전 층의 사물함 정보를 가져옵니다. - * - * @return 전 층의 사물함 정보를 반환합니다. - */ - @GetMapping("/statistics/buildings/floors/cabinets") - @AuthGuard(level = ADMIN_ONLY) - public List getAllCabinetsInfo() { - log.info("Called getAllCabinetsInfo"); - return adminFacadeService.getAllCabinetsInfo(); - } - - /** - * 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 가져옵니다. - * - * @param startDate 입력할 기간의 시작일 - * @param endDate 입력할 기간의 종료일 - * @return 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 반환합니다. - */ - @GetMapping("/statistics/lent-histories") - @AuthGuard(level = ADMIN_ONLY) - public LentsStatisticsResponseDto getLentCountStatistics( - @RequestParam("startDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime startDate, - @RequestParam("endDate") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime endDate) { - log.info("Called getCountOnLentAndReturn startDate : {} endDate : {}", startDate, endDate); - return adminFacadeService.getLentCountStatistics(startDate, endDate); - } - - /** - * 차단당한 유저 정보를 가져옵니다. - * - * @param pageable 페이지 정보 - * @return 차단당한 유저 정보를 반환합니다. - */ - @GetMapping("/statistics/users/banned") - @AuthGuard(level = ADMIN_ONLY) - public BlockedUserPaginationDto getUsersBannedInfo(Pageable pageable) { - log.info("Called getUsersBannedInfo"); - return adminFacadeService.getAllBanUsers(pageable); - } - - /** - * 연체중인 유저 리스트를 가져옵니다. - * - * @param pageable 페이지 정보 - * @return 연체중인 유저 리스트를 반환합니다. - */ - @GetMapping("/statistics/users/overdue") - @AuthGuard(level = ADMIN_ONLY) - public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { - log.info("Called getOverdueUsers"); - return adminFacadeService.getOverdueUsers(pageable); - } - - /*----------------------------------------- Lent -------------------------------------------*/ - - @PatchMapping("/return-cabinets") - @AuthGuard(level = ADMIN_ONLY) - public void terminateLentCabinets( - @Valid @RequestBody ReturnCabinetsRequestDto returnCabinetsRequestDto) { - log.info("Called terminateLentCabinets returnCabinetsRequestDto={}", - returnCabinetsRequestDto); - adminFacadeService.endCabinetLent(returnCabinetsRequestDto.getCabinetIds()); - } - - @PatchMapping("/return-users/{userId}") - @AuthGuard(level = ADMIN_ONLY) - public void terminateLentUser(@PathVariable("userId") Long userId) { - log.info("Called terminateLentUser userId={}", userId); - adminFacadeService.endUserLent(userId); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java new file mode 100644 index 000000000..9a18f2747 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java @@ -0,0 +1,57 @@ +package org.ftclub.cabinet.admin.search; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +@Slf4j +@RestController +@RequestMapping("/v4/admin/search") +@RequiredArgsConstructor +public class AdminSearchController { + private final AdminFacadeService adminFacadeService; + + @GetMapping("/cabinets-simple") + @AuthGuard(level = ADMIN_ONLY) + public CabinetSimplePaginationDto getCabinetsSimpleInfo( + @RequestParam("visibleNum") Integer visibleNum) { + log.info("Called getCabinetsSimpleInfo {}", visibleNum); + return adminFacadeService.getCabinetsSimpleInfo(visibleNum); + } + + @GetMapping("/cabinets") + @AuthGuard(level = ADMIN_ONLY) + public CabinetInfoPaginationDto getCabinetsInfo( + @RequestParam("visibleNum") Integer visibleNum) { + log.info("Called getCabinetsInfo {}", visibleNum); + return adminFacadeService.getCabinetInfo(visibleNum); + } + + @GetMapping("/users-simple") + @AuthGuard(level = ADMIN_ONLY) + public UserProfilePaginationDto getUsersProfile( + @RequestParam("name") String name, Pageable pageable) { + log.info("Called getUsersProfile {}", name); + return adminFacadeService.getUsersProfile(name, pageable); + } + + @GetMapping("/users") + @AuthGuard(level = ADMIN_ONLY) + public UserCabinetPaginationDto getCabinetsLentInfo( + @RequestParam("name") String name, Pageable pageable) { + log.info("Called getCabinetsLentInfo {}", name); + return adminFacadeService.getUserLentCabinetInfo(name, pageable); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java new file mode 100644 index 000000000..6036a16b4 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java @@ -0,0 +1,96 @@ +package org.ftclub.cabinet.admin.statistics; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.dto.*; +import org.springframework.data.domain.Pageable; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.time.LocalDateTime; +import java.util.List; + +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +@Slf4j +@RestController +@RequestMapping("/v4/admin/statistics") +@RequiredArgsConstructor +public class AdminStatisticsController { + private final AdminFacadeService adminFacadeService; + + /** + * 전 층의 사물함 정보를 가져옵니다. + * + * @return 전 층의 사물함 정보를 반환합니다. + */ + @GetMapping("/buildings/floors/cabinets") + @AuthGuard(level = ADMIN_ONLY) + public List getAllCabinetsInfo() { + log.info("Called getAllCabinetsInfo"); + return adminFacadeService.getAllCabinetsInfo(); + } + + /** + * 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 가져옵니다. + * + * @param startDate 입력할 기간의 시작일 + * @param endDate 입력할 기간의 종료일 + * @return 현재일자 기준, 입력한 기간 동안 발생한 대여 및 반납의 횟수를 반환합니다. + */ + @GetMapping("/lent-histories") + @AuthGuard(level = ADMIN_ONLY) + public LentsStatisticsResponseDto getLentCountStatistics( + @RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate, + @RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) { + log.info("Called getCountOnLentAndReturn startDate : {} endDate : {}", startDate, endDate); + return adminFacadeService.getLentCountStatistics(startDate, endDate); + } + + /** + * 차단당한 유저 정보를 가져옵니다. + * + * @param pageable 페이지 정보 + * @return 차단당한 유저 정보를 반환합니다. + */ + @GetMapping("/users/banned") + @AuthGuard(level = ADMIN_ONLY) + public BlockedUserPaginationDto getUsersBannedInfo(Pageable pageable) { + log.info("Called getUsersBannedInfo"); + return adminFacadeService.getAllBanUsers(pageable); + } + + /** + * 연체중인 유저 리스트를 가져옵니다. + * + * @param pageable 페이지 정보 + * @return 연체중인 유저 리스트를 반환합니다. + */ + @GetMapping("/users/overdue") + @AuthGuard(level = ADMIN_ONLY) + public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { + log.info("Called getOverdueUsers"); + return adminFacadeService.getOverdueUsers(pageable); + } + + /*----------------------------------------- Lent -------------------------------------------*/ + + @PatchMapping("/return-cabinets") + @AuthGuard(level = ADMIN_ONLY) + public void terminateLentCabinets( + @Valid @RequestBody ReturnCabinetsRequestDto returnCabinetsRequestDto) { + log.info("Called terminateLentCabinets returnCabinetsRequestDto={}", + returnCabinetsRequestDto); + adminFacadeService.endCabinetLent(returnCabinetsRequestDto.getCabinetIds()); + } + + @PatchMapping("/return-users/{userId}") + @AuthGuard(level = ADMIN_ONLY) + public void terminateLentUser(@PathVariable("userId") Long userId) { + log.info("Called terminateLentUser userId={}", userId); + adminFacadeService.endUserLent(userId); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java index 6073f9d73..950763c3d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java @@ -4,8 +4,8 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.admin.domain.Admin; -import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.user.domain.User; diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java index 7471a6e2b..b1603d304 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java @@ -1,6 +1,6 @@ package org.ftclub.cabinet.auth.domain; -import static org.ftclub.cabinet.admin.domain.AdminRole.MASTER; +import static org.ftclub.cabinet.admin.admin.domain.AdminRole.MASTER; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -9,9 +9,11 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; + import java.security.Key; import java.util.Base64; import javax.servlet.http.HttpServletRequest; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.config.DomainProperties; @@ -19,7 +21,7 @@ import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.user.service.UserService; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index c595c0a9f..620cd403a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.auth.service; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.auth.domain.AuthCookieManager; import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.auth.domain.GoogleProfile; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index f78f8f30f..9f8798c13 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -3,11 +3,12 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; + import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.admin.domain.Admin; -import org.ftclub.cabinet.admin.domain.AdminRole; -import org.ftclub.cabinet.admin.repository.AdminRepository; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.repository.AdminRepository; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index d0eb58ecf..d85039dda 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.util.List; + import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.ClubUserListDto; @@ -12,7 +13,7 @@ import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; @@ -47,7 +48,7 @@ public interface UserFacadeService { */ /*기존 searchByIntraId 메서드와 동일한 역할을 합니다.*/ UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, - Integer size); + Integer size); /** * 유저 이름의 일부를 입력받아 해당 유저들의 캐비넷 정보를 반환합니다. @@ -58,7 +59,7 @@ UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer pa * @return {@link UserCabinetPaginationDto} 해당하는 유저들의 캐비넷 정보 */ UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, - Integer size); + Integer size); /** * 모든 유저의 정보를 가져옵니다. @@ -153,7 +154,7 @@ UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer p * @param expiredAt 대여 만료 날짜 */ void banUser(Long userId, LentType lentType, LocalDateTime startedAt, LocalDateTime endedAt, - LocalDateTime expiredAt); + LocalDateTime expiredAt); /** * 유저의 정지를 해제합니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java index e5bd4ecd5..028c8b39f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java @@ -6,12 +6,13 @@ import java.util.List; import java.util.stream.Collectors; import javax.transaction.Transactional; + import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; import org.ftclub.cabinet.alarm.service.AlarmCommandService; import org.ftclub.cabinet.alarm.service.AlarmQueryService; -import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetOptionalFetcher; @@ -83,7 +84,7 @@ public BlockedUserPaginationDto getAllBanUsers(Integer page, Integer size, Local @Override public UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, - Integer size) { + Integer size) { log.debug("Called getUserProfileListByPartialName: {}", name); // todo - size가 0일 때 모든 데이터를 가져오기 if (size <= 0) { @@ -99,7 +100,7 @@ public UserProfilePaginationDto getUserProfileListByPartialName(String name, Int @Override public UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, - Integer size) { + Integer size) { log.debug("Called findUserCabinetListByPartialName: {}", name); // todo - size가 0일 때 모든 데이터를 가져오기 if (size <= 0) { @@ -189,8 +190,8 @@ public void updateUserBlackholedAt(Long userId, LocalDateTime newBlackholedAt) { @Override public void banUser(Long userId, LentType lentType, LocalDateTime startedAt, - LocalDateTime endedAt, - LocalDateTime expiredAt) { + LocalDateTime endedAt, + LocalDateTime expiredAt) { log.debug("Called banUser: {}", userId); userService.banUser(userId, lentType, startedAt, endedAt, expiredAt); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java index 434c17b3a..5c177b65f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java @@ -1,6 +1,6 @@ package org.ftclub.cabinet.user.service; -import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.dto.UserBlackHoleEvent; import org.ftclub.cabinet.user.domain.User; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java index 1d5cce151..ad0df1616 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java @@ -3,9 +3,9 @@ import io.netty.util.internal.StringUtil; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.admin.domain.Admin; -import org.ftclub.cabinet.admin.domain.AdminRole; -import org.ftclub.cabinet.admin.repository.AdminRepository; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.repository.AdminRepository; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.config.CabinetProperties; diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java index 54d8aaffb..495830855 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java @@ -5,7 +5,7 @@ import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.config.MasterProperties; -import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.user.service.UserService; import org.ftclub.cabinet.utils.DateUtil; import org.ftclub.testutils.TestUtils; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminTest.java b/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminTest.java index c89e155df..06b93479f 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/domain/AdminTest.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.user.domain; -import org.ftclub.cabinet.admin.domain.Admin; -import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.exception.DomainException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminRepositoryTest.java index 8ba5935a4..6f09a9999 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminRepositoryTest.java @@ -1,8 +1,8 @@ package org.ftclub.cabinet.user.repository; -import org.ftclub.cabinet.admin.domain.Admin; -import org.ftclub.cabinet.admin.domain.AdminRole; -import org.ftclub.cabinet.admin.repository.AdminRepository; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.repository.AdminRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java index ed3a23f2e..d1a54c0b4 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserFacadeServiceTest.java @@ -1,6 +1,6 @@ package org.ftclub.cabinet.user.service; -import org.ftclub.cabinet.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java index 0f868a403..186a3e508 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceTest.java @@ -1,8 +1,8 @@ package org.ftclub.cabinet.user.service; -import org.ftclub.cabinet.admin.domain.Admin; -import org.ftclub.cabinet.admin.domain.AdminRole; -import org.ftclub.cabinet.admin.repository.AdminRepository; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.repository.AdminRepository; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java index 4cf35d331..a2f4b9822 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java @@ -1,8 +1,8 @@ package org.ftclub.cabinet.user.service; -import org.ftclub.cabinet.admin.domain.Admin; -import org.ftclub.cabinet.admin.domain.AdminRole; -import org.ftclub.cabinet.admin.repository.AdminRepository; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.repository.AdminRepository; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.DomainException; diff --git a/backend/src/test/java/org/ftclub/testutils/TestUtils.java b/backend/src/test/java/org/ftclub/testutils/TestUtils.java index f7e328804..c3d5470fa 100644 --- a/backend/src/test/java/org/ftclub/testutils/TestUtils.java +++ b/backend/src/test/java/org/ftclub/testutils/TestUtils.java @@ -4,9 +4,11 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; + import java.util.ArrayList; import java.util.List; -import org.ftclub.cabinet.admin.domain.AdminRole; + +import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.utils.DateUtil; import org.springframework.http.HttpHeaders; @@ -26,7 +28,7 @@ public class TestUtils { public static String getTestAdminToken(Key signingKey, LocalDateTime now, String emailName, - String emailDomain) { + String emailDomain) { Map claim = new HashMap<>(); claim.put("email", emailName + "@" + emailDomain); claim.put("role", AdminRole.ADMIN); @@ -38,7 +40,7 @@ public static String getTestAdminToken(Key signingKey, LocalDateTime now, String } public static String getTestMasterToken(Key signingKey, LocalDateTime now, String emailName, - String emailDomain) { + String emailDomain) { Map claim = new HashMap<>(); claim.put("email", emailName + "@" + emailDomain); claim.put("role", AdminRole.MASTER); @@ -50,7 +52,7 @@ public static String getTestMasterToken(Key signingKey, LocalDateTime now, Strin } public static String getTestUserTokenByName(Key signingKey, LocalDateTime now, - LocalDateTime blackholedAt, String name, String emailDomain) { + LocalDateTime blackholedAt, String name, String emailDomain) { Map claim = new HashMap<>(); claim.put("name", name); claim.put("email", name + "@" + emailDomain); @@ -81,7 +83,7 @@ public static Key getSigningKey(String secretKey) { } public static MockHttpServletRequestBuilder mockRequest(HttpMethod method, Cookie cookie, - String url, Object... uriVars) { + String url, Object... uriVars) { if (method.equals(HttpMethod.GET)) { return MockMvcRequestBuilders.get(url, uriVars) .cookie(cookie) From 500d567560a1f06d44a57ce7e52dbc4ab97d417b Mon Sep 17 00:00:00 2001 From: jusohn Date: Sat, 23 Dec 2023 14:48:19 +0900 Subject: [PATCH 0169/1029] =?UTF-8?q?[COMMON]=20FIX:=20config=20=EC=B6=A9?= =?UTF-8?q?=EB=8F=8C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20pull=20#1398?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 16f0fc214..689668fd3 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 16f0fc214f39facdea002e68561a809d5a9213e2 +Subproject commit 689668fd3c05aa5d591c8711d6793d46617a6a0b From 8a147a55f304a14ed06c5516e1d350c8c26ffdee Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 23 Dec 2023 14:52:59 +0900 Subject: [PATCH 0170/1029] =?UTF-8?q?fix,=20refactor=20:=20Admin=20query,?= =?UTF-8?q?=20command=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 1 - .../admin/repository/AdminRepository.java | 6 ++--- .../admin/service/AdminCommandService.java | 9 +++++++ .../admin/service/AdminQueryService.java | 8 ++++++ .../auth/service/AuthFacadeService.java | 20 +++++++++----- .../cabinet/exception/ExceptionStatus.java | 1 + .../user/newService/UserCommandService.java | 27 ++++++++++++------- .../user/newService/UserQueryService.java | 10 +++---- .../user/repository/UserOptionalFetcher.java | 18 ++++++------- .../user/repository/UserRepository.java | 17 ++++++++++-- .../user/repository/AdminRepositoryTest.java | 12 ++++----- 11 files changed, 86 insertions(+), 43 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java index 042c81bea..14f9a1669 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java @@ -132,6 +132,5 @@ public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page" public void issueLentExtension(@PathVariable("user") String username) { log.info("Called issueLentExtension"); lentExtensionService.assignLentExtension(username); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/repository/AdminRepository.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/repository/AdminRepository.java index 9e7fdabb3..1e25fe015 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/repository/AdminRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/repository/AdminRepository.java @@ -17,7 +17,7 @@ public interface AdminRepository extends JpaRepository { * @return {@link Admin} */ @Query("SELECT au FROM Admin au WHERE au.adminId = :adminUserId") - Optional findAdminUser(@Param("adminUserId") Long adminUserId); + Optional findById(@Param("adminUserId") Long adminUserId); /** * 관리자 이메일로 관리자 정보를 가져옵니다. @@ -26,7 +26,7 @@ public interface AdminRepository extends JpaRepository { * @return {@link Admin} */ @Query("SELECT au FROM Admin au WHERE au.email = :email") - Optional findAdminUserByEmail(@Param("email") String email); + Optional findByEmail(@Param("email") String email); /** * 유저의 이메일로 어드민 유저를 찾고 어드민 유저의 권한을 반환합니다. @@ -35,5 +35,5 @@ public interface AdminRepository extends JpaRepository { * @return {@link AdminRole} */ @Query("SELECT au.role FROM Admin au WHERE au.email = :email") - Optional findAdminUserRoleByEmail(@Param("email") String email); + Optional findAdminRoleByEmail(@Param("email") String email); } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java index fbafb93af..4ac716bc5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java @@ -2,6 +2,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.repository.AdminRepository; import org.springframework.stereotype.Service; @Slf4j @@ -9,4 +12,10 @@ @RequiredArgsConstructor public class AdminCommandService { + private final AdminRepository adminRepository; + + public Admin createAdminByEmail(String email) { + return adminRepository.save(Admin.of(email, AdminRole.NONE)); + } + } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java index 47a72755a..52a20b3d6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java @@ -2,11 +2,19 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.repository.AdminRepository; import org.springframework.stereotype.Service; +import java.util.Optional; + @Slf4j @Service @RequiredArgsConstructor public class AdminQueryService { + private final AdminRepository adminRepository; + public Optional findByEmail(String email) { + return adminRepository.findByEmail(email); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index 620cd403a..20b235224 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.service.AdminCommandService; +import org.ftclub.cabinet.admin.admin.service.AdminQueryService; import org.ftclub.cabinet.auth.domain.AuthCookieManager; import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.auth.domain.GoogleProfile; @@ -12,6 +14,8 @@ import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.newService.UserCommandService; +import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.stereotype.Service; import javax.servlet.http.Cookie; @@ -25,8 +29,13 @@ @RequiredArgsConstructor public class AuthFacadeService { + private final UserQueryService userQueryService; + private final UserCommandService userCommandService; + private final AdminQueryService adminQueryService; + private final AdminCommandService adminCommandService; private final FtOauthService ftOauthService; private final GoogleOauthService googleOauthService; + private final TokenProvider tokenProvider; private final AuthCookieManager authCookieManager; private final DomainProperties domainProperties; @@ -42,12 +51,8 @@ public void requestAdminLogin(HttpServletResponse res) throws IOException { public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { FtProfile profile = ftOauthService.getProfileByCode(code); - // User user = userQueryService.findUserByName(profile.getIntraName()); - // if (user.isEmpty) { - // user = userCommandService.createUser(profile); - // } - // - User user = null/*userCommandService.findUserOrCreate(profile)*/; + User user = userQueryService.findUser(profile.getIntraName()) + .orElse(userCommandService.createUserByFtProfile(profile)); String token = tokenProvider.createUserToken(user, LocalDateTime.now()); Cookie cookie = authCookieManager.cookieOf(TokenProvider.USER_TOKEN_NAME, token); authCookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); @@ -56,7 +61,8 @@ public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, Str public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { GoogleProfile profile = googleOauthService.getProfileByCode(code); - Admin admin = null/*userCommandService.createUserIfNotExists(profile)*/; + Admin admin = adminQueryService.findByEmail(profile.getEmail()) + .orElse(adminCommandService.createAdminByEmail(profile.getEmail())); String token = tokenProvider.createAdminToken(admin, LocalDateTime.now()); Cookie cookie = authCookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, token); authCookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index ea84bfaa0..392afec0a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -28,6 +28,7 @@ public enum ExceptionStatus { UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "로그인 정보가 유효하지 않습니다\n다시 로그인해주세요"), UNCHANGEABLE_CABINET(HttpStatus.BAD_REQUEST, "사물함의 상태를 변경할 수 없습니다."), LENT_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 대여중인 사물함이 있습니다"), + USER_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 존재하는 유저입니다"), INVALID_ARGUMENT(HttpStatus.BAD_REQUEST, "유효하지 않은 입력입니다"), INVALID_STATUS(HttpStatus.BAD_REQUEST, "유효하지 않은 상태변경입니다"), SHARE_CODE_TRIAL_EXCEEDED(HttpStatus.BAD_REQUEST, "초대 코드 입력 오류 초과로 입장이 제한된 상태입니다."), diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java index 17a1c2542..e1ff09c01 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java @@ -1,26 +1,33 @@ package org.ftclub.cabinet.user.newService; -import io.netty.util.internal.StringUtil; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.exception.ControllerException; +import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.UUID; - @Service @RequiredArgsConstructor @Log4j2 public class UserCommandService { - private final UserRepository userRepository; - public void createUser(User user) { - userRepository.save(user); - } + private final UserRepository userRepository; + + public User createUserByFtProfile(FtProfile profile) { + log.info("Called createUserByFtProfile. {}", profile); + if (userRepository.existsByNameAndEmail(profile.getIntraName(), profile.getEmail())) { + log.warn("이미 존재하는 유저입니다. {}", profile); + throw new ServiceException(ExceptionStatus.USER_ALREADY_EXISTED); + } + User user = User.of(profile.getIntraName(), profile.getEmail(), profile.getBlackHoledAt(), UserRole.USER); + return userRepository.save(user); + } + + public void createUser(User user) { + userRepository.save(user); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java index ed7102400..6a6537e3f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -1,7 +1,5 @@ package org.ftclub.cabinet.user.newService; -import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.exception.ExceptionStatus; @@ -12,6 +10,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Optional; + @Service @RequiredArgsConstructor @Log4j2 @@ -32,8 +33,7 @@ public Page getUsers(String partialName, Pageable pageable) { return userRepository.findPaginationByPartialName(partialName, pageable); } - public User findUser(String name) { - Optional user = userRepository.findByName(name); - return user.orElse(null); + public Optional findUser(String name) { + return userRepository.findByName(name); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index 9f8798c13..2ddd0ec10 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -1,9 +1,5 @@ package org.ftclub.cabinet.user.repository; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.admin.admin.domain.Admin; @@ -20,6 +16,10 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + @Service @RequiredArgsConstructor @Log4j2 @@ -99,7 +99,7 @@ public Page findUsersByPartialName(String name, Pageable pageable) { */ public Admin findAdminUser(Long adminUserId) { log.debug("Called findAdminUser: {}", adminUserId); - return adminRepository.findAdminUser(adminUserId).orElse(null); + return adminRepository.findById(adminUserId).orElse(null); } /** @@ -110,7 +110,7 @@ public Admin findAdminUser(Long adminUserId) { */ public Admin findAdminUserByEmail(String email) { log.debug("Called findAdminUserByEmail: {}", email); - return adminRepository.findAdminUserByEmail(email).orElse(null); + return adminRepository.findByEmail(email).orElse(null); } /** @@ -118,7 +118,7 @@ public Admin findAdminUserByEmail(String email) { */ public AdminRole findAdminUserRoleByEmail(String email) { log.debug("Called findAdminUserRoleByEmail: {}", email); - return adminRepository.findAdminUserRoleByEmail(email) + return adminRepository.findAdminRoleByEmail(email) .orElse(null); } @@ -214,7 +214,7 @@ public User getClubUser(Long userId) { */ public Admin getAdminUser(Long adminUserId) { log.debug("Called getAdminUser: {}", adminUserId); - return adminRepository.findAdminUser(adminUserId) + return adminRepository.findById(adminUserId) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); } @@ -226,7 +226,7 @@ public Admin getAdminUser(Long adminUserId) { */ public Admin getAdminUserByEmail(String adminUserEmail) { log.debug("Called getAdminUserByEmail: {}", adminUserEmail); - return adminRepository.findAdminUserByEmail(adminUserEmail) + return adminRepository.findByEmail(adminUserEmail) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index fd2b366ce..9371d9143 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -1,7 +1,5 @@ package org.ftclub.cabinet.user.repository; -import java.util.List; -import java.util.Optional; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; import org.springframework.data.domain.Page; @@ -11,6 +9,9 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + @Repository public interface UserRepository extends JpaRepository { @@ -97,4 +98,16 @@ public interface UserRepository extends JpaRepository { * @return {@Link User} 리스트 */ List findAllByDeletedAtIsNull(); + + /** + * 유저의 이름과 이메일이 일치하는 유저가 존재하는지 확인합니다. + * + * @param name 유저 이름 + * @param email 유저 이메일 + * @return 존재 여부 + */ + @Query("SELECT CASE WHEN COUNT(u) > 0 THEN true ELSE false END " + + "FROM User u " + + "WHERE u.name = :name AND u.email = :email") + boolean existsByNameAndEmail(String name, String email); } diff --git a/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminRepositoryTest.java index 6f09a9999..71d057188 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/repository/AdminRepositoryTest.java @@ -35,7 +35,7 @@ public void setUp() { @Test @DisplayName("어드민 아이디로 어드민 유저 찾기 성공 - 어드민이 존재하는 경우") public void findAdminUser_성공_어드민이_존재하는_경우() { - Optional adminUser = adminRepository.findAdminUser(adminUserId); + Optional adminUser = adminRepository.findById(adminUserId); assertTrue(adminUser.isPresent()); assertEquals(adminUserId, adminUser.get().getAdminId()); @@ -46,7 +46,7 @@ public void setUp() { @Test @DisplayName("어드민 아이디로 어드민 유저 찾기 실패 - 어드민이 존재하지 않는 경우") public void findAdminUser_실패_어드민이_존재하지_않는_경우() { - Optional adminUser = adminRepository.findAdminUser(10000L); + Optional adminUser = adminRepository.findById(10000L); assertTrue(adminUser.isEmpty()); } @@ -54,7 +54,7 @@ public void setUp() { @Test @DisplayName("어드민 이메일로 어드민 유저 찾기 성공 - 어드민이 존재하는 경우") void findAdminUserByEmail_성공_어드민_유저가_존재하는_경우() { - Optional adminUser = adminRepository.findAdminUserByEmail( + Optional adminUser = adminRepository.findByEmail( "adminTest@gmail.com"); assertTrue(adminUser.isPresent()); @@ -66,7 +66,7 @@ public void setUp() { @Test @DisplayName("어드민 이메일로 어드민 유저 찾기 실패 - 어드민이 존재하지 않는 경우") public void findAdminUserByEmail_실패_어드민이_존재하지_않는_경우() { - Optional adminUser = adminRepository.findAdminUserByEmail("test@gmail.com"); + Optional adminUser = adminRepository.findByEmail("test@gmail.com"); assertTrue(adminUser.isEmpty()); } @@ -74,7 +74,7 @@ public void setUp() { @Test @DisplayName("유저의 이메일로 어드민 유저의 권한 찾기 성공 - 어드민이 존재하는 경우") void findAdminUserRoleByEmail_성공_어드민이_존재하는_경우() { - Optional adminRole = adminRepository.findAdminUserRoleByEmail( + Optional adminRole = adminRepository.findAdminRoleByEmail( "admin1@gmail.com"); assertTrue(adminRole.isPresent()); @@ -84,7 +84,7 @@ public void setUp() { @Test @DisplayName("유저의 이메일로 어드민 유저의 권한 찾기 실패 - 어드민이 존재하지 않는 경우") public void findAdminUserRoleByEmail_실패_어드민이_존재하지_않는_경우() { - Optional adminRole = adminRepository.findAdminUserRoleByEmail( + Optional adminRole = adminRepository.findAdminRoleByEmail( "test@gmail.com"); assertFalse(adminRole.isPresent()); From 06aafcea6cc3e75bb3a421294d95d301e9b2f5ae Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 23 Dec 2023 14:53:35 +0900 Subject: [PATCH 0171/1029] =?UTF-8?q?fix=20:=20controller=20exception?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/auth/service/AuthFacadeService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index 20b235224..1d7e0a36c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -11,8 +11,8 @@ import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.dto.MasterLoginDto; -import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.newService.UserCommandService; import org.ftclub.cabinet.user.newService.UserQueryService; @@ -74,7 +74,7 @@ public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, // TODO : 서비스로 빼기 if (!masterLoginDto.getId().equals(masterProperties.getId()) || !masterLoginDto.getPassword().equals(masterProperties.getPassword())) - throw new ControllerException(ExceptionStatus.UNAUTHORIZED_ADMIN); + throw new ServiceException(ExceptionStatus.UNAUTHORIZED_ADMIN); String masterToken = tokenProvider.createMasterToken(now); Cookie cookie = authCookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, masterToken); authCookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); From 7d22d687668da27ed82cc03cd2da92df77ac3821 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 23 Dec 2023 15:24:55 +0900 Subject: [PATCH 0172/1029] =?UTF-8?q?fix,=20refactor=20:=20=EC=96=B4?= =?UTF-8?q?=EB=93=9C=EB=AF=BC=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=B3=84?= =?UTF-8?q?=EB=A1=9C=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20controller-facade=20=ED=8C=A8=ED=84=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 125 +------- .../admin/service/AdminFacadeService.java | 268 +----------------- .../cabinet/AdminCabinetFacadeService.java | 18 ++ .../admin/lent/AdminLentController.java | 35 +++ .../admin/lent/AdminLentFacadeService.java | 94 ++++++ .../admin/search/AdminSearchController.java | 11 +- .../search/AdminSearchFacadeService.java | 132 +++++++++ .../statistics/AdminStatisticsController.java | 39 +-- .../AdminStatisticsFacadeService.java | 94 ++++++ .../admin/user/AdminUserController.java | 134 +++++++++ .../user/newService/UserFacadeService.java | 2 +- 11 files changed, 533 insertions(+), 419 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java index 14f9a1669..188ce2474 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java @@ -2,135 +2,22 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.auth.domain.AuthGuard; -import org.ftclub.cabinet.auth.domain.AuthLevel; -import org.ftclub.cabinet.dto.ClubUserListDto; -import org.ftclub.cabinet.dto.LentExtensionPaginationDto; -import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.lent.service.LentFacadeService; -import org.ftclub.cabinet.user.service.LentExtensionService; -import org.ftclub.cabinet.user.service.UserFacadeService; -import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.*; - -import java.time.LocalDateTime; -import java.util.HashMap; +import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; /** * 관리자가 유저를 관리할 때 사용하는 컨트롤러입니다. */ @RestController @RequiredArgsConstructor -@RequestMapping("/v4/admin/users") +@RequestMapping("/v4/admin/admins") @Log4j2 public class AdminController { - private final UserFacadeService userFacadeService; - private final LentFacadeService lentFacadeService; - private final LentExtensionService lentExtensionService; - - /** - * 현재 유저가 차단된 상태일 때, 차단을 해제합니다. - * - * @param userId 유저 고유 아이디 - */ - @DeleteMapping("/{userId}/ban-history") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { - log.info("Called deleteBanHistoryByUserId: {}", userId); - userFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); - } - - /** - * 유저의 대여 기록을 반환합니다. - * - * @param userId 유저 고유 아이디 - * @param pageable 페이지네이션 정보 - * @return {@link LentHistoryPaginationDto} 유저의 대여 기록 - */ - @GetMapping("/{userId}/lent-histories") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentHistoryPaginationDto getLentHistoriesByUserId( - @PathVariable("userId") Long userId, Pageable pageable) { - log.info("Called getLentHistoriesByUserId: {}", userId); - return lentFacadeService.getUserLentHistories(userId, pageable); - } + private final AdminFacadeService adminFacadeService; - /** - * 유저를 어드민으로 승격시킵니다. - * - * @param email 유저 이메일 - * @return redirect:cabi.42seoul.io/admin/login - */ - @GetMapping("/admins/promote") - @AuthGuard(level = AuthLevel.MASTER_ONLY) public void promoteUserToAdmin(@RequestParam("email") String email) { - log.info("Called promoteUserToAdmin: {}", email); - userFacadeService.promoteUserToAdmin(email); - } - - /** - * 동아리 유저를 생성합니다. - * - * @param body 동아리 이름 - */ - @PostMapping("/club") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void createClubUser(@RequestBody HashMap body) { - log.info("Called createClub"); - String clubName = body.get("clubName"); - userFacadeService.createClubUser(clubName); - } - - /** - * 동아리 유저를 삭제합니다. - * - * @param clubId 동아리 고유 아이디 - */ - @DeleteMapping("/club/{clubId}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void deleteClubUser(@PathVariable("clubId") Long clubId) { - log.info("Called deleteClub"); - userFacadeService.deleteClubUser(clubId); - } - - @GetMapping("/clubs") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public ClubUserListDto findClubs(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getClubs"); - return userFacadeService.findAllClubUser(page, size); - } - - @PatchMapping("/club/{clubId}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void updateClubUser(@PathVariable("clubId") Long clubId, - @RequestBody HashMap body) { - log.info("Called updateClub"); - String clubName = body.get("clubName"); - userFacadeService.updateClubUser(clubId, clubName); - } - - @GetMapping("/lent-extensions") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentExtensionPaginationDto getAllLentExtension(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getAllLentExtension"); - return userFacadeService.getAllLentExtension(page, size); - } - - @GetMapping("/lent-extensions/active") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getAllActiveLentExtension"); - return userFacadeService.getAllActiveLentExtension(page, size); - } - - @PostMapping("/lent-extensions/{user}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void issueLentExtension(@PathVariable("user") String username) { - log.info("Called issueLentExtension"); - lentExtensionService.assignLentExtension(username); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java index b4523d8a1..fc7778756 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java @@ -1,278 +1,12 @@ package org.ftclub.cabinet.admin.admin.service; -import static java.util.stream.Collectors.toList; -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.BROKEN; -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.FULL; -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.OVERDUE; -import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; - -import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.ftclub.cabinet.cabinet.newService.CabinetCommandService; -import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; -import org.ftclub.cabinet.dto.BlockedUserPaginationDto; -import org.ftclub.cabinet.dto.CabinetDto; -import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; -import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; -import org.ftclub.cabinet.dto.CabinetInfoResponseDto; -import org.ftclub.cabinet.dto.CabinetSimpleDto; -import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; -import org.ftclub.cabinet.dto.LentDto; -import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; -import org.ftclub.cabinet.dto.UserBlockedInfoDto; -import org.ftclub.cabinet.dto.UserCabinetDto; -import org.ftclub.cabinet.dto.UserCabinetPaginationDto; -import org.ftclub.cabinet.dto.UserProfileDto; -import org.ftclub.cabinet.dto.UserProfilePaginationDto; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.service.LentCommandService; -import org.ftclub.cabinet.lent.service.LentPolicyService; -import org.ftclub.cabinet.lent.service.LentQueryService; -import org.ftclub.cabinet.lent.service.LentRedisService; -import org.ftclub.cabinet.mapper.CabinetMapper; -import org.ftclub.cabinet.mapper.LentMapper; -import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.BanType; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.newService.BanHistoryCommandService; -import org.ftclub.cabinet.user.newService.BanHistoryQueryService; -import org.ftclub.cabinet.user.newService.BanPolicyService; -import org.ftclub.cabinet.user.newService.UserQueryService; -import org.ftclub.cabinet.utils.DateUtil; -import org.ftclub.cabinet.utils.ExceptionUtil; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Slf4j @Service @RequiredArgsConstructor public class AdminFacadeService { - - private final AdminQueryService adminQueryService; - private final AdminCommandService adminCommandService; - private final CabinetQueryService cabinetQueryService; - private final CabinetCommandService cabinetCommandService; - private final UserQueryService userQueryService; - private final BanHistoryQueryService banHistoryQueryService; - private final BanHistoryCommandService banHistoryCommandService; - private final LentQueryService lentQueryService; - private final LentCommandService lentCommandService; - private final LentRedisService lentRedisService; - - private final LentPolicyService lentPolicyService; - private final BanPolicyService banPolicyService; - - private final CabinetMapper cabinetMapper; - private final UserMapper userMapper; - private final LentMapper lentMapper; - - - @Transactional(readOnly = true) - public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { - log.debug("Called getCabinetSimpleInfo {}", visibleNum); - - List cabinets = cabinetQueryService.getCabinets(visibleNum); - List result = cabinets.stream() - .map(cabinetMapper::toCabinetSimpleDto).collect(toList()); - return cabinetMapper.toCabinetSimplePaginationDto(result, (long) cabinets.size()); - } - - @Transactional(readOnly = true) - public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { - log.debug("Called getCabinetInfo {}", visibleNum); - - List cabinets = cabinetQueryService.getCabinets(visibleNum); - List cabinetIds = cabinets.stream().map(Cabinet::getCabinetId) - .collect(toList()); - List lentHistories = - lentQueryService.findCabinetsActiveLentHistories(cabinetIds); - Map> lentHistoriesByCabinetId = lentHistories.stream() - .collect(Collectors.groupingBy(LentHistory::getCabinetId)); - - List result = cabinets.stream() - .map(cabinet -> { - Long cabinetId = cabinet.getCabinetId(); - List lents = null; - if (lentHistoriesByCabinetId.containsKey(cabinetId)) { - lents = lentHistoriesByCabinetId.get(cabinetId).stream() - .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) - .collect(toList()); - } - LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); - return cabinetMapper.toCabinetInfoResponseDto(cabinet, lents, sessionExpiredAt); - }).sorted(Comparator.comparingInt(o -> o.getLocation().getFloor())) - .collect(toList()); - return cabinetMapper.toCabinetInfoPaginationDto(result, (long) cabinets.size()); - } - - @Transactional(readOnly = true) - public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pageable) { - log.debug("Called getUsersProfile {}", partialName); - - Page users = userQueryService.getUsers(partialName, pageable); - List result = users.stream() - .map(userMapper::toUserProfileDto).collect(toList()); - return userMapper.toUserProfilePaginationDto(result, users.getTotalElements()); - } - - @Transactional(readOnly = true) - public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, - Pageable pageable) { - log.debug("Called getUserLentCabinetInfo {}", partialName); - - LocalDateTime now = LocalDateTime.now(); - Page users = userQueryService.getUsers(partialName, pageable); - List userIds = users.stream().map(User::getUserId).collect(toList()); - System.out.println("userIds = " + userIds); - - List activeBanHistories = - banHistoryQueryService.findActiveBanHistories(userIds, now); - System.out.println("activeBanHistories = " + activeBanHistories); - List activeLentHistories = - lentQueryService.findUsersActiveLentHistoriesAndCabinet(userIds); - System.out.println("activeLentHistories = " + activeLentHistories); - Map> banHistoriesByUserId = activeBanHistories.stream() - .collect(Collectors.groupingBy(BanHistory::getUserId)); - System.out.println("banHistoriesByUserId = " + banHistoriesByUserId); - Map> lentHistoriesByUserId = activeLentHistories.stream() - .collect(Collectors.groupingBy(LentHistory::getUserId)); - System.out.println("lentHistoriesByUserId = " + lentHistoriesByUserId); - - List result = users.stream().map(user -> { - List banHistories = banHistoriesByUserId.get(user.getUserId()); - List lentHistories = lentHistoriesByUserId.get(user.getUserId()); - BanHistory banHistory = (Objects.nonNull(banHistories) && !banHistories.isEmpty()) - ? banHistories.get(0) : null; - Cabinet cabinet = (Objects.nonNull(lentHistories) && !lentHistories.isEmpty()) - ? lentHistories.get(0).getCabinet() : null; - UserBlockedInfoDto blockedInfoDto = userMapper.toUserBlockedInfoDto(banHistory, user); - CabinetDto cabinetDto = cabinetMapper.toCabinetDto(cabinet); - return cabinetMapper.toUserCabinetDto(blockedInfoDto, cabinetDto); - }).collect(toList()); - return cabinetMapper.toUserCabinetPaginationDto(result, users.getTotalElements()); - } - - public List getAllCabinetsInfo() { - log.debug("Called getAllCabinetsInfo"); - - List buildings = cabinetQueryService.getAllBuildings(); - List floors = cabinetQueryService.getAllFloorsByBuildings(buildings); - return floors.stream().map(floor -> { - Integer used = cabinetQueryService.countCabinets(FULL, floor); - Integer unused = cabinetQueryService.countCabinets(AVAILABLE, floor); - Integer overdue = cabinetQueryService.countCabinets(OVERDUE, floor); - Integer disabled = cabinetQueryService.countCabinets(BROKEN, floor); - Integer total = used + overdue + unused + disabled; - return cabinetMapper.toCabinetFloorStatisticsResponseDto( - floor, total, used, overdue, unused, disabled); - }).collect(Collectors.toList()); - } - - public LentsStatisticsResponseDto getLentCountStatistics( - LocalDateTime startDate, LocalDateTime endDate) { - log.debug("Called getLentCountStatistics startDate : {} endDate : {}", startDate, endDate); - - ExceptionUtil.throwIfFalse(startDate.isBefore(endDate), - new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); - int lentStartCount = lentQueryService.countLentOnDuration(startDate, endDate); - int lentEndCount = lentQueryService.countReturnOnDuration(startDate, endDate); - return cabinetMapper.toLentsStatisticsResponseDto( - startDate, endDate, lentStartCount, lentEndCount); - } - - public BlockedUserPaginationDto getAllBanUsers(Pageable pageable) { - log.debug("Called getAllBanUsers"); - - LocalDateTime now = LocalDateTime.now(); - Page banHistories = - banHistoryQueryService.findActiveBanHistories(now, pageable); - List result = banHistories.stream() - .map(b -> userMapper.toUserBlockedInfoDto(b, b.getUser())) - .collect(Collectors.toList()); - return userMapper.toBlockedUserPaginationDto(result, banHistories.getTotalElements()); - } - - public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { - log.debug("Called getOverdueUsers"); - - LocalDateTime now = LocalDateTime.now(); - List lentHistories = lentQueryService.findOverdueLentHistories(now, pageable); - List result = lentHistories.stream() - .map(lh -> { - Long overdueDays = DateUtil.calculateTwoDateDiff(now, lh.getExpiredAt()); - return cabinetMapper.toOverdueUserCabinetDto( - lh, lh.getUser(), lh.getCabinet(), overdueDays); - }).collect(Collectors.toList()); - return cabinetMapper.toOverdueUserCabinetPaginationDto(result, (long) lentHistories.size()); - } - - @Transactional - public void endUserLent(Long userId) { - log.debug("Called endUserLent: {}", userId); - - LocalDateTime now = LocalDateTime.now(); - LentHistory userLentHistory = lentQueryService.getUserActiveLentHistoryWithLock(userId); - List cabinetLentHistories = - lentQueryService.findCabinetActiveLentHistories(userLentHistory.getCabinetId()); - Cabinet cabinet = - cabinetQueryService.getCabinetsWithLock(userLentHistory.getCabinetId()); - - int userRemainCount = cabinetLentHistories.size() - 1; - cabinetCommandService.changeUserCount(cabinet, userRemainCount); - lentCommandService.endLent(userLentHistory, now); - lentRedisService.setPreviousUserName( - cabinet.getCabinetId(), userLentHistory.getUser().getName()); - - LocalDateTime endedAt = userLentHistory.getEndedAt(); - BanType banType = banPolicyService.verifyBan(endedAt, userLentHistory.getExpiredAt()); - if (!banType.equals(BanType.NONE)) { - LocalDateTime unbannedAt = banPolicyService.getUnBannedAt( - endedAt, userLentHistory.getExpiredAt()); - banHistoryCommandService.banUser(userId, endedAt, unbannedAt, banType); - } - if (cabinet.isLentType(SHARE)) { - LocalDateTime expiredAt = lentPolicyService.adjustSharCabinetExpirationDate( - userRemainCount, now, userLentHistory); - cabinetLentHistories.stream().filter(lh -> !lh.equals(userLentHistory)) - .forEach(lh -> lentCommandService.setExpiredAt(lh, expiredAt)); - } - } - - @Transactional - public void endCabinetLent(List cabinetIds) { - log.debug("Called endCabinetsLent: {}", cabinetIds); - - LocalDateTime now = LocalDateTime.now(); - List cabinets = cabinetQueryService.getCabinetsWithLock(cabinetIds); - List lentHistories = - lentQueryService.findCabinetsActiveLentHistories(cabinetIds); - Map> lentHistoriesByCabinetId = lentHistories.stream() - .collect(Collectors.groupingBy(LentHistory::getCabinetId)); - cabinets.forEach(cabinet -> { - List cabinetLentHistories = - lentHistoriesByCabinetId.get(cabinet.getCabinetId()); - cabinetLentHistories.forEach(lh -> lentCommandService.endLent(lh, now)); - cabinetCommandService.changeUserCount(cabinet, 0); - cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); - lentRedisService.setPreviousUserName( - cabinet.getCabinetId(), cabinetLentHistories.get(0).getUser().getName()); - }); - } + } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java new file mode 100644 index 000000000..c13cec88f --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java @@ -0,0 +1,18 @@ +package org.ftclub.cabinet.admin.cabinet; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.mapper.CabinetMapper; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminCabinetFacadeService { + private final CabinetQueryService cabinetQueryService; + + private final CabinetMapper cabinetMapper; + + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java new file mode 100644 index 000000000..60082335c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java @@ -0,0 +1,35 @@ +package org.ftclub.cabinet.admin.lent; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/v4/admin/lent") +@Log4j2 +public class AdminLentController { + private final AdminLentFacadeService adminLentFacadeService; + + @PatchMapping("/return-cabinets") + @AuthGuard(level = ADMIN_ONLY) + public void terminateLentCabinets( + @Valid @RequestBody ReturnCabinetsRequestDto returnCabinetsRequestDto) { + log.info("Called terminateLentCabinets returnCabinetsRequestDto={}", + returnCabinetsRequestDto); + adminLentFacadeService.endCabinetLent(returnCabinetsRequestDto.getCabinetIds()); + } + + @PatchMapping("/return-users/{userId}") + @AuthGuard(level = ADMIN_ONLY) + public void terminateLentUser(@PathVariable("userId") Long userId) { + log.info("Called terminateLentUser userId={}", userId); + adminLentFacadeService.endUserLent(userId); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java new file mode 100644 index 000000000..00425b9a3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java @@ -0,0 +1,94 @@ +package org.ftclub.cabinet.admin.lent; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.newService.CabinetCommandService; +import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentCommandService; +import org.ftclub.cabinet.lent.service.LentPolicyService; +import org.ftclub.cabinet.lent.service.LentQueryService; +import org.ftclub.cabinet.lent.service.LentRedisService; +import org.ftclub.cabinet.user.domain.BanType; +import org.ftclub.cabinet.user.newService.BanHistoryCommandService; +import org.ftclub.cabinet.user.newService.BanPolicyService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; + + +@Service +@RequiredArgsConstructor +@Log4j2 +public class AdminLentFacadeService { + private final CabinetQueryService cabinetQueryService; + private final CabinetCommandService cabinetCommandService; + private final BanHistoryCommandService banHistoryCommandService; + private final LentQueryService lentQueryService; + private final LentCommandService lentCommandService; + private final LentRedisService lentRedisService; + + private final LentPolicyService lentPolicyService; + private final BanPolicyService banPolicyService; + + @Transactional + public void endUserLent(Long userId) { + log.debug("Called endUserLent: {}", userId); + + LocalDateTime now = LocalDateTime.now(); + LentHistory userLentHistory = lentQueryService.getUserActiveLentHistoryWithLock(userId); + List cabinetLentHistories = + lentQueryService.findCabinetActiveLentHistories(userLentHistory.getCabinetId()); + Cabinet cabinet = + cabinetQueryService.getCabinetsWithLock(userLentHistory.getCabinetId()); + + int userRemainCount = cabinetLentHistories.size() - 1; + cabinetCommandService.changeUserCount(cabinet, userRemainCount); + lentCommandService.endLent(userLentHistory, now); + lentRedisService.setPreviousUserName( + cabinet.getCabinetId(), userLentHistory.getUser().getName()); + + LocalDateTime endedAt = userLentHistory.getEndedAt(); + BanType banType = banPolicyService.verifyBan(endedAt, userLentHistory.getExpiredAt()); + if (!banType.equals(BanType.NONE)) { + LocalDateTime unbannedAt = banPolicyService.getUnBannedAt( + endedAt, userLentHistory.getExpiredAt()); + banHistoryCommandService.banUser(userId, endedAt, unbannedAt, banType); + } + if (cabinet.isLentType(SHARE)) { + LocalDateTime expiredAt = lentPolicyService.adjustSharCabinetExpirationDate( + userRemainCount, now, userLentHistory); + cabinetLentHistories.stream().filter(lh -> !lh.equals(userLentHistory)) + .forEach(lh -> lentCommandService.setExpiredAt(lh, expiredAt)); + } + } + + @Transactional + public void endCabinetLent(List cabinetIds) { + log.debug("Called endCabinetsLent: {}", cabinetIds); + + LocalDateTime now = LocalDateTime.now(); + List cabinets = cabinetQueryService.getCabinetsWithLock(cabinetIds); + List lentHistories = + lentQueryService.findCabinetsActiveLentHistories(cabinetIds); + Map> lentHistoriesByCabinetId = lentHistories.stream() + .collect(Collectors.groupingBy(LentHistory::getCabinetId)); + cabinets.forEach(cabinet -> { + List cabinetLentHistories = + lentHistoriesByCabinetId.get(cabinet.getCabinetId()); + cabinetLentHistories.forEach(lh -> lentCommandService.endLent(lh, now)); + cabinetCommandService.changeUserCount(cabinet, 0); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); + lentRedisService.setPreviousUserName( + cabinet.getCabinetId(), cabinetLentHistories.get(0).getUser().getName()); + }); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java index 9a18f2747..bb3f5215c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java @@ -2,7 +2,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; @@ -21,14 +20,14 @@ @RequestMapping("/v4/admin/search") @RequiredArgsConstructor public class AdminSearchController { - private final AdminFacadeService adminFacadeService; + private final AdminSearchFacadeService adminSearchFacadeService; @GetMapping("/cabinets-simple") @AuthGuard(level = ADMIN_ONLY) public CabinetSimplePaginationDto getCabinetsSimpleInfo( @RequestParam("visibleNum") Integer visibleNum) { log.info("Called getCabinetsSimpleInfo {}", visibleNum); - return adminFacadeService.getCabinetsSimpleInfo(visibleNum); + return adminSearchFacadeService.getCabinetsSimpleInfo(visibleNum); } @GetMapping("/cabinets") @@ -36,7 +35,7 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfo( public CabinetInfoPaginationDto getCabinetsInfo( @RequestParam("visibleNum") Integer visibleNum) { log.info("Called getCabinetsInfo {}", visibleNum); - return adminFacadeService.getCabinetInfo(visibleNum); + return adminSearchFacadeService.getCabinetInfo(visibleNum); } @GetMapping("/users-simple") @@ -44,7 +43,7 @@ public CabinetInfoPaginationDto getCabinetsInfo( public UserProfilePaginationDto getUsersProfile( @RequestParam("name") String name, Pageable pageable) { log.info("Called getUsersProfile {}", name); - return adminFacadeService.getUsersProfile(name, pageable); + return adminSearchFacadeService.getUsersProfile(name, pageable); } @GetMapping("/users") @@ -52,6 +51,6 @@ public UserProfilePaginationDto getUsersProfile( public UserCabinetPaginationDto getCabinetsLentInfo( @RequestParam("name") String name, Pageable pageable) { log.info("Called getCabinetsLentInfo {}", name); - return adminFacadeService.getUserLentCabinetInfo(name, pageable); + return adminSearchFacadeService.getUserLentCabinetInfo(name, pageable); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java new file mode 100644 index 000000000..b39ad1c83 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java @@ -0,0 +1,132 @@ +package org.ftclub.cabinet.admin.search; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentQueryService; +import org.ftclub.cabinet.lent.service.LentRedisService; +import org.ftclub.cabinet.mapper.CabinetMapper; +import org.ftclub.cabinet.mapper.LentMapper; +import org.ftclub.cabinet.mapper.UserMapper; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.newService.BanHistoryQueryService; +import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class AdminSearchFacadeService { + + private final UserQueryService userQueryService; + private final CabinetQueryService cabinetQueryService; + private final BanHistoryQueryService banHistoryQueryService; + private final LentQueryService lentQueryService; + private final LentRedisService lentRedisService; + + + private final CabinetMapper cabinetMapper; + private final UserMapper userMapper; + private final LentMapper lentMapper; + + @Transactional(readOnly = true) + public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pageable) { + log.debug("Called getUsersProfile {}", partialName); + + Page users = userQueryService.getUsers(partialName, pageable); + List result = users.stream() + .map(userMapper::toUserProfileDto).collect(toList()); + return userMapper.toUserProfilePaginationDto(result, users.getTotalElements()); + } + + @Transactional(readOnly = true) + public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, + Pageable pageable) { + log.debug("Called getUserLentCabinetInfo {}", partialName); + + LocalDateTime now = LocalDateTime.now(); + Page users = userQueryService.getUsers(partialName, pageable); + List userIds = users.stream().map(User::getUserId).collect(toList()); + System.out.println("userIds = " + userIds); + + List activeBanHistories = + banHistoryQueryService.findActiveBanHistories(userIds, now); + System.out.println("activeBanHistories = " + activeBanHistories); + List activeLentHistories = + lentQueryService.findUsersActiveLentHistoriesAndCabinet(userIds); + System.out.println("activeLentHistories = " + activeLentHistories); + Map> banHistoriesByUserId = activeBanHistories.stream() + .collect(Collectors.groupingBy(BanHistory::getUserId)); + System.out.println("banHistoriesByUserId = " + banHistoriesByUserId); + Map> lentHistoriesByUserId = activeLentHistories.stream() + .collect(Collectors.groupingBy(LentHistory::getUserId)); + System.out.println("lentHistoriesByUserId = " + lentHistoriesByUserId); + + List result = users.stream().map(user -> { + List banHistories = banHistoriesByUserId.get(user.getUserId()); + List lentHistories = lentHistoriesByUserId.get(user.getUserId()); + BanHistory banHistory = (Objects.nonNull(banHistories) && !banHistories.isEmpty()) + ? banHistories.get(0) : null; + Cabinet cabinet = (Objects.nonNull(lentHistories) && !lentHistories.isEmpty()) + ? lentHistories.get(0).getCabinet() : null; + UserBlockedInfoDto blockedInfoDto = userMapper.toUserBlockedInfoDto(banHistory, user); + CabinetDto cabinetDto = cabinetMapper.toCabinetDto(cabinet); + return cabinetMapper.toUserCabinetDto(blockedInfoDto, cabinetDto); + }).collect(toList()); + return cabinetMapper.toUserCabinetPaginationDto(result, users.getTotalElements()); + } + + @Transactional(readOnly = true) + public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { + log.debug("Called getCabinetSimpleInfo {}", visibleNum); + + List cabinets = cabinetQueryService.getCabinets(visibleNum); + List result = cabinets.stream() + .map(cabinetMapper::toCabinetSimpleDto).collect(toList()); + return cabinetMapper.toCabinetSimplePaginationDto(result, (long) cabinets.size()); + } + + @Transactional(readOnly = true) + public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { + log.debug("Called getCabinetInfo {}", visibleNum); + + List cabinets = cabinetQueryService.getCabinets(visibleNum); + List cabinetIds = cabinets.stream().map(Cabinet::getCabinetId) + .collect(toList()); + List lentHistories = + lentQueryService.findCabinetsActiveLentHistories(cabinetIds); + Map> lentHistoriesByCabinetId = lentHistories.stream() + .collect(Collectors.groupingBy(LentHistory::getCabinetId)); + + List result = cabinets.stream() + .map(cabinet -> { + Long cabinetId = cabinet.getCabinetId(); + List lents = null; + if (lentHistoriesByCabinetId.containsKey(cabinetId)) { + lents = lentHistoriesByCabinetId.get(cabinetId).stream() + .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) + .collect(toList()); + } + LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); + return cabinetMapper.toCabinetInfoResponseDto(cabinet, lents, sessionExpiredAt); + }).sorted(Comparator.comparingInt(o -> o.getLocation().getFloor())) + .collect(toList()); + return cabinetMapper.toCabinetInfoPaginationDto(result, (long) cabinets.size()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java index 6036a16b4..4af989983 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java @@ -2,14 +2,18 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.BlockedUserPaginationDto; +import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; +import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; import org.springframework.data.domain.Pageable; import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; import java.time.LocalDateTime; import java.util.List; @@ -20,7 +24,7 @@ @RequestMapping("/v4/admin/statistics") @RequiredArgsConstructor public class AdminStatisticsController { - private final AdminFacadeService adminFacadeService; + private final AdminStatisticsFacadeService adminStatisticsFacadeService; /** * 전 층의 사물함 정보를 가져옵니다. @@ -31,7 +35,7 @@ public class AdminStatisticsController { @AuthGuard(level = ADMIN_ONLY) public List getAllCabinetsInfo() { log.info("Called getAllCabinetsInfo"); - return adminFacadeService.getAllCabinetsInfo(); + return adminStatisticsFacadeService.getAllCabinetsInfo(); } /** @@ -47,7 +51,7 @@ public LentsStatisticsResponseDto getLentCountStatistics( @RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate, @RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) { log.info("Called getCountOnLentAndReturn startDate : {} endDate : {}", startDate, endDate); - return adminFacadeService.getLentCountStatistics(startDate, endDate); + return adminStatisticsFacadeService.getLentCountStatistics(startDate, endDate); } /** @@ -60,7 +64,7 @@ public LentsStatisticsResponseDto getLentCountStatistics( @AuthGuard(level = ADMIN_ONLY) public BlockedUserPaginationDto getUsersBannedInfo(Pageable pageable) { log.info("Called getUsersBannedInfo"); - return adminFacadeService.getAllBanUsers(pageable); + return adminStatisticsFacadeService.getAllBanUsers(pageable); } /** @@ -73,24 +77,7 @@ public BlockedUserPaginationDto getUsersBannedInfo(Pageable pageable) { @AuthGuard(level = ADMIN_ONLY) public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { log.info("Called getOverdueUsers"); - return adminFacadeService.getOverdueUsers(pageable); + return adminStatisticsFacadeService.getOverdueUsers(pageable); } - /*----------------------------------------- Lent -------------------------------------------*/ - - @PatchMapping("/return-cabinets") - @AuthGuard(level = ADMIN_ONLY) - public void terminateLentCabinets( - @Valid @RequestBody ReturnCabinetsRequestDto returnCabinetsRequestDto) { - log.info("Called terminateLentCabinets returnCabinetsRequestDto={}", - returnCabinetsRequestDto); - adminFacadeService.endCabinetLent(returnCabinetsRequestDto.getCabinetIds()); - } - - @PatchMapping("/return-users/{userId}") - @AuthGuard(level = ADMIN_ONLY) - public void terminateLentUser(@PathVariable("userId") Long userId) { - log.info("Called terminateLentUser userId={}", userId); - adminFacadeService.endUserLent(userId); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java new file mode 100644 index 000000000..16e091647 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java @@ -0,0 +1,94 @@ +package org.ftclub.cabinet.admin.statistics; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentQueryService; +import org.ftclub.cabinet.mapper.CabinetMapper; +import org.ftclub.cabinet.mapper.UserMapper; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.newService.BanHistoryQueryService; +import org.ftclub.cabinet.utils.DateUtil; +import org.ftclub.cabinet.utils.ExceptionUtil; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.*; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminStatisticsFacadeService { + private final CabinetQueryService cabinetQueryService; + private final LentQueryService lentQueryService; + private final BanHistoryQueryService banHistoryQueryService; + + private final CabinetMapper cabinetMapper; + private final UserMapper userMapper; + + @Transactional(readOnly = true) + public List getAllCabinetsInfo() { + log.debug("Called getAllCabinetsInfo"); + + List buildings = cabinetQueryService.getAllBuildings(); + List floors = cabinetQueryService.getAllFloorsByBuildings(buildings); + return floors.stream().map(floor -> { + Integer used = cabinetQueryService.countCabinets(FULL, floor); + Integer unused = cabinetQueryService.countCabinets(AVAILABLE, floor); + Integer overdue = cabinetQueryService.countCabinets(OVERDUE, floor); + Integer disabled = cabinetQueryService.countCabinets(BROKEN, floor); + Integer total = used + overdue + unused + disabled; + return cabinetMapper.toCabinetFloorStatisticsResponseDto( + floor, total, used, overdue, unused, disabled); + }).collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public LentsStatisticsResponseDto getLentCountStatistics( + LocalDateTime startDate, LocalDateTime endDate) { + log.debug("Called getLentCountStatistics startDate : {} endDate : {}", startDate, endDate); + + ExceptionUtil.throwIfFalse(startDate.isBefore(endDate), + new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); + int lentStartCount = lentQueryService.countLentOnDuration(startDate, endDate); + int lentEndCount = lentQueryService.countReturnOnDuration(startDate, endDate); + return cabinetMapper.toLentsStatisticsResponseDto( + startDate, endDate, lentStartCount, lentEndCount); + } + + public BlockedUserPaginationDto getAllBanUsers(Pageable pageable) { + log.debug("Called getAllBanUsers"); + + LocalDateTime now = LocalDateTime.now(); + Page banHistories = + banHistoryQueryService.findActiveBanHistories(now, pageable); + List result = banHistories.stream() + .map(b -> userMapper.toUserBlockedInfoDto(b, b.getUser())) + .collect(Collectors.toList()); + return userMapper.toBlockedUserPaginationDto(result, banHistories.getTotalElements()); + } + + public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { + log.debug("Called getOverdueUsers"); + + LocalDateTime now = LocalDateTime.now(); + List lentHistories = lentQueryService.findOverdueLentHistories(now, pageable); + List result = lentHistories.stream() + .map(lh -> { + Long overdueDays = DateUtil.calculateTwoDateDiff(now, lh.getExpiredAt()); + return cabinetMapper.toOverdueUserCabinetDto( + lh, lh.getUser(), lh.getCabinet(), overdueDays); + }).collect(Collectors.toList()); + return cabinetMapper.toOverdueUserCabinetPaginationDto(result, (long) lentHistories.size()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java new file mode 100644 index 000000000..280d037f1 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java @@ -0,0 +1,134 @@ +package org.ftclub.cabinet.admin.user; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.dto.ClubUserListDto; +import org.ftclub.cabinet.dto.LentExtensionPaginationDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; +import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.user.service.LentExtensionService; +import org.ftclub.cabinet.user.service.UserFacadeService; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.HashMap; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/v4/admin/users") +@Log4j2 +public class AdminUserController { + private final UserFacadeService userFacadeService; + private final LentFacadeService lentFacadeService; + private final LentExtensionService lentExtensionService; + + /** + * 현재 유저가 차단된 상태일 때, 차단을 해제합니다. + * + * @param userId 유저 고유 아이디 + */ + @DeleteMapping("/{userId}/ban-history") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { + log.info("Called deleteBanHistoryByUserId: {}", userId); + userFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); + } + + + /** + * 유저를 어드민으로 승격시킵니다. + * + * @param email 유저 이메일 + * @return redirect:cabi.42seoul.io/admin/login + */ + @GetMapping("/admins/promote") + @AuthGuard(level = AuthLevel.MASTER_ONLY) + public void promoteUserToAdmin(@RequestParam("email") String email) { + log.info("Called promoteUserToAdmin: {}", email); + //TODO ADMIN으로 옮기기 + userFacadeService.promoteUserToAdmin(email); + } + + /** + * 동아리 유저를 생성합니다. + * + * @param body 동아리 이름 + */ + @PostMapping("/club") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void createClubUser(@RequestBody HashMap body) { + log.info("Called createClub"); + String clubName = body.get("clubName"); + userFacadeService.createClubUser(clubName); + } + + /** + * 동아리 유저를 삭제합니다. + * + * @param clubId 동아리 고유 아이디 + */ + @DeleteMapping("/club/{clubId}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void deleteClubUser(@PathVariable("clubId") Long clubId) { + log.info("Called deleteClub"); + userFacadeService.deleteClubUser(clubId); + } + + @GetMapping("/clubs") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public ClubUserListDto findClubs(@RequestParam("page") Integer page, + @RequestParam("size") Integer size) { + log.info("Called getClubs"); + return userFacadeService.findAllClubUser(page, size); + } + + @PatchMapping("/club/{clubId}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void updateClubUser(@PathVariable("clubId") Long clubId, + @RequestBody HashMap body) { + log.info("Called updateClub"); + String clubName = body.get("clubName"); + userFacadeService.updateClubUser(clubId, clubName); + } + + @GetMapping("/lent-extensions") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public LentExtensionPaginationDto getAllLentExtension(@RequestParam("page") Integer page, + @RequestParam("size") Integer size) { + log.info("Called getAllLentExtension"); + return userFacadeService.getAllLentExtension(page, size); + } + + @GetMapping("/lent-extensions/active") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page") Integer page, + @RequestParam("size") Integer size) { + log.info("Called getAllActiveLentExtension"); + return userFacadeService.getAllActiveLentExtension(page, size); + } + + /** + * 유저의 대여 기록을 반환합니다. + * + * @param userId 유저 고유 아이디 + * @param pageable 페이지네이션 정보 + * @return {@link LentHistoryPaginationDto} 유저의 대여 기록 + */ + @GetMapping("/{userId}/lent-histories") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public LentHistoryPaginationDto getLentHistoriesByUserId( + @PathVariable("userId") Long userId, Pageable pageable) { + log.info("Called getLentHistoriesByUserId: {}", userId); + return lentFacadeService.getUserLentHistories(userId, pageable); + } + + @PostMapping("/lent-extensions/{user}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void issueLentExtension(@PathVariable("user") String username) { + log.info("Called issueLentExtension"); + lentExtensionService.assignLentExtension(username); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 72179de17..20e73be4e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -56,7 +56,7 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { public void createClubUser(String clubName) { log.debug("Called createClubUser: {}", clubName); - User user = userQueryService.findUser(clubName); + User user = userQueryService.findUser(clubName).orElse(null); if (StringUtil.isNullOrEmpty(clubName)) { throw new ControllerException(ExceptionStatus.INVALID_ARGUMENT); } else if (user != null && user.getDeletedAt() == null) { From 1f36e1608a30ec90d7f73f704458576da1c22705 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 23 Dec 2023 15:51:30 +0900 Subject: [PATCH 0173/1029] =?UTF-8?q?[BE]=20admin=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/service/AdminFacadeService.java | 22 +- .../newService/CabinetCommandService.java | 22 +- .../cabinet/repository/CabinetRepository.java | 15 + .../cabinet/lent/domain/LentHistory.java | 408 +++++++++--------- .../lent/repository/LentOptionalFetcher.java | 3 +- .../lent/repository/LentRepository.java | 23 +- .../lent/service/LentCommandService.java | 10 + .../lent/service/LentQueryService.java | 3 +- .../user/repository/BanHistoryRepository.java | 7 +- 9 files changed, 279 insertions(+), 234 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java index b38c4fe3a..dc3012a50 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/service/AdminFacadeService.java @@ -16,7 +16,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.newService.CabinetCommandService; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; @@ -132,27 +131,21 @@ public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pag } @Transactional(readOnly = true) - public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, - Pageable pageable) { + public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, Pageable pageable) { log.debug("Called getUserLentCabinetInfo {}", partialName); LocalDateTime now = LocalDateTime.now(); Page users = userQueryService.getUsers(partialName, pageable); List userIds = users.stream().map(User::getUserId).collect(toList()); - System.out.println("userIds = " + userIds); List activeBanHistories = banHistoryQueryService.findActiveBanHistories(userIds, now); - System.out.println("activeBanHistories = " + activeBanHistories); List activeLentHistories = lentQueryService.findUsersActiveLentHistoriesAndCabinet(userIds); - System.out.println("activeLentHistories = " + activeLentHistories); Map> banHistoriesByUserId = activeBanHistories.stream() .collect(Collectors.groupingBy(BanHistory::getUserId)); - System.out.println("banHistoriesByUserId = " + banHistoriesByUserId); Map> lentHistoriesByUserId = activeLentHistories.stream() .collect(Collectors.groupingBy(LentHistory::getUserId)); - System.out.println("lentHistoriesByUserId = " + lentHistoriesByUserId); List result = users.stream().map(user -> { List banHistories = banHistoriesByUserId.get(user.getUserId()); @@ -214,11 +207,9 @@ public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { LocalDateTime now = LocalDateTime.now(); List lentHistories = lentQueryService.findOverdueLentHistories(now, pageable); List result = lentHistories.stream() - .map(lh -> { - Long overdueDays = DateUtil.calculateTwoDateDiff(now, lh.getExpiredAt()); - return cabinetMapper.toOverdueUserCabinetDto( - lh, lh.getUser(), lh.getCabinet(), overdueDays); - }).collect(Collectors.toList()); + .map(lh -> cabinetMapper.toOverdueUserCabinetDto(lh, lh.getUser(), lh.getCabinet(), + DateUtil.calculateTwoDateDiff(now, lh.getExpiredAt())) + ).collect(Collectors.toList()); return cabinetMapper.toOverdueUserCabinetPaginationDto(result, (long) lentHistories.size()); } @@ -267,11 +258,10 @@ public void endCabinetLent(List cabinetIds) { cabinets.forEach(cabinet -> { List cabinetLentHistories = lentHistoriesByCabinetId.get(cabinet.getCabinetId()); - cabinetLentHistories.forEach(lh -> lentCommandService.endLent(lh, now)); - cabinetCommandService.changeUserCount(cabinet, 0); - cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); lentRedisService.setPreviousUserName( cabinet.getCabinetId(), cabinetLentHistories.get(0).getUser().getName()); }); + lentCommandService.endLent(lentHistories, now); + cabinetCommandService.changeUserCount(cabinets, 0); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java index 88fa7b181..5afd3ae7f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java @@ -1,12 +1,18 @@ package org.ftclub.cabinet.cabinet.newService; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.BROKEN; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.FULL; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; import static org.ftclub.cabinet.exception.ExceptionStatus.INVALID_STATUS; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.utils.ExceptionUtil; import org.springframework.stereotype.Service; @Service @@ -21,7 +27,7 @@ public void changeStatus(Cabinet cabinet, CabinetStatus cabinetStatus) { } public void changeUserCount(Cabinet cabinet, int userCount) { - if (cabinet.isStatus(CabinetStatus.BROKEN)) { + if (cabinet.isStatus(BROKEN)) { throw new DomainException(INVALID_STATUS); } if (userCount == 0) { @@ -30,7 +36,7 @@ public void changeUserCount(Cabinet cabinet, int userCount) { cabinet.writeTitle(""); } if (userCount == cabinet.getMaxUser()) { - cabinet.specifyStatus(CabinetStatus.FULL); + cabinet.specifyStatus(FULL); } cabinetRepository.save(cabinet); } @@ -44,4 +50,16 @@ public void updateMemo(Cabinet cabinet, String memo) { cabinet.writeMemo(memo); cabinetRepository.save(cabinet); } + + public void changeUserCount(List cabinets, int userCount) { + cabinets.forEach(cabinet -> ExceptionUtil.throwIfFalse(!cabinet.isStatus(BROKEN), + new DomainException(INVALID_STATUS))); + List cabinetIds = cabinets.stream() + .map(Cabinet::getCabinetId).collect(Collectors.toList()); + if (userCount == 0) { + cabinetRepository.updateStatusAndClearTitleAndMemoByCabinetIdsIn(cabinetIds, PENDING); + } else { + cabinetRepository.updateStatusByCabinetIdsIn(cabinetIds, FULL); + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 7eb4f643a..6577a5aa7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -11,6 +11,7 @@ import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -139,4 +140,18 @@ List findAllByBuildingAndFloor( + "AND c.status IN (:status)") List findAllByBuildingAndLentTypeNotAndStatusIn(@Param("building") String building, @Param("lentType") LentType lentType, @Param("status") List status); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("UPDATE Cabinet c " + + "SET c.status = :status " + + "WHERE c.cabinetId IN (:cabinetIds)") + void updateStatusByCabinetIdsIn(@Param("cabinetIds") List cabinetIds, + @Param("status") CabinetStatus status); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("UPDATE Cabinet c " + + "SET c.status = :status, c.title = '', c.memo = '' " + + "WHERE c.cabinetId IN (:cabinetIds)") + void updateStatusAndClearTitleAndMemoByCabinetIdsIn(@Param("cabinetIds") List cabinetIds, + @Param("status") CabinetStatus status); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java index a4865e931..af43a3cae 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java @@ -37,209 +37,207 @@ @BatchSize(size = 200) public class LentHistory { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "LENT_HISTORY_ID") - private Long lentHistoryId; - - /** - * 낙관적 락을 위한 version - */ - @Version - @Getter(AccessLevel.NONE) - private final Long version = 1L; - - /** - * 대여 시작일 - */ - @Column(name = "STARTED_AT", nullable = false) - private LocalDateTime startedAt; - - /** - * 연체 시작일 - */ - @Column(name = "EXPIRED_AT") - private LocalDateTime expiredAt = null; - - /** - * 반납일 - */ - @Column(name = "ENDED_AT") - private LocalDateTime endedAt = null; - - /** - * 대여하는 유저 - */ - @Column(name = "USER_ID", nullable = false) - private Long userId; - - /** - * 대여하는 캐비넷 - */ - @Column(name = "CABINET_ID", nullable = false) - private Long cabinetId; - - @JoinColumn(name = "USER_ID", insertable = false, updatable = false) - @ManyToOne(fetch = LAZY) - private User user; - - @JoinColumn(name = "CABINET_ID", insertable = false, updatable = false) - @ManyToOne(fetch = LAZY) - private Cabinet cabinet; - - protected LentHistory(LocalDateTime startedAt, LocalDateTime expiredAt, Long userId, - Long cabinetId) { - this.startedAt = startedAt; - this.expiredAt = expiredAt; - this.userId = userId; - this.cabinetId = cabinetId; - } - - /** - * @param startedAt 대여 시작일 - * @param expiredAt 연체 시작일 - * @param userId 대여하는 user id - * @param cabinetId 대여하는 cabinet id - * @return 인자 정보를 담고있는 {@link LentHistory} - */ - public static LentHistory of(LocalDateTime startedAt, LocalDateTime expiredAt, Long userId, - Long cabinetId) { - LentHistory lentHistory = new LentHistory(startedAt, expiredAt, userId, cabinetId); - if (!lentHistory.isValid()) { - throw new DomainException(ExceptionStatus.INVALID_ARGUMENT); - } - return lentHistory; - } - - /** - * startedAt, userId, cabinetId, expiredAt 의 null 이 아닌지 확인합니다. - * - * @return 유효한 인스턴스 여부 - */ - - private boolean isValid() { - return this.startedAt != null && this.userId != null && this.cabinetId != null - && this.expiredAt != null; - } - - /** - * endedAt 보다 startedAt 이 나중이 아닌지 확인합니다. endedAt 종료시점이 null 이 아닌지 확인합니다. - * - * @param endedAt 대여 종료 날짜, 시간 - * @return - */ - private boolean isEndLentValid(LocalDateTime endedAt) { - return endedAt != null && 0 <= DateUtil.calculateTwoDateDiff(endedAt, this.startedAt); - } - - - @Override - public boolean equals(final Object other) { - if (this == other) { - return true; - } - if (!(other instanceof LentHistory)) { - return false; - } - return (this.lentHistoryId.equals(((LentHistory) other).lentHistoryId)); - } - - /** - * 대여한 아이디와 같은지 비교한다. - * - * @param cabinetId 비교하고 싶은 id - * @return boolean 같으면 true 다르면 false - */ - public boolean isCabinetIdEqual(Long cabinetId) { - return this.cabinetId.equals(cabinetId); - } - - /** - * 만료일을 변경합니다. - * - * @param expiredAt 변경하고 싶은 만료일 - */ - public void setExpiredAt(LocalDateTime expiredAt) { - log.info("setExpiredAt : {}", expiredAt); - this.expiredAt = expiredAt; - ExceptionUtil.throwIfFalse(this.isValid(), - new DomainException(ExceptionStatus.INVALID_STATUS)); - } - - /** - * 만료일이 설정 되어있는 지 확인합니다. 만료일이 {@link DateUtil}의 infinityDate와 같으면 만료일이 설정되어 있지 않다고 판단합니다. - * - * @return 설정이 되어있으면 true 아니면 false - */ - public boolean isSetExpiredAt() { - LocalDateTime expiredAt = getExpiredAt(); - if (expiredAt == null) { - return false; - } - return !expiredAt.isEqual(DateUtil.getInfinityDate()); - } - - /** - * 반납일이 설정 되어있는 지 확인합니다. 반납일이 {@link DateUtil}의 infinityDate와 같으면 만료일이 설정되어 있지 않다고 판단합니다. - * - * @return 설정이 되어있으면 ture 아니면 false - */ - public boolean isSetEndedAt() { - if (getEndedAt() == null) { - return false; - } - return !getEndedAt().isEqual(DateUtil.getInfinityDate()); - } - - - /** - * 반납일과 만료일의 차이를 계산합니다. - * - * @return endedAt - expiredAt의 값을(일 기준) - */ - public Long getDaysDiffEndedAndExpired() { - if (isSetExpiredAt() && isSetEndedAt()) { - return DateUtil.calculateTwoDateDiff(endedAt, expiredAt) + 1; - } - return null; - } - - /** - * 만료일이 지났는지 확인합니다. - * - * @return 만료일이 지났으면 true 아니면 false, 만료일이 설정되어 있지 않으면 false - */ - public Boolean isExpired(LocalDateTime now) { - if (isSetExpiredAt()) { - return DateUtil.calculateTwoDateDiffCeil(expiredAt, now) > 0; - } - return false; - } - - /** - * 만료일까지 남은 일수를 계산합니다. 만료시간이 설정되지 않았으면 null을 반환합니다. - * - * @return 만료일까지 남은 일수 (만료일 - 현재시간) (일 기준, 올림) - */ - public Long getDaysUntilExpiration(LocalDateTime now) { - if (isSetExpiredAt()) { - return DateUtil.calculateTwoDateDiffCeil(expiredAt, now); - } - return null; - } - - - /** - * 반납일을 설정합니다. - * - * @param now 설정하려고 하는 반납일 - */ - public void endLent(LocalDateTime now) { - log.info("setEndLent : {}", now); - ExceptionUtil.throwIfFalse((this.isEndLentValid(now)), - new DomainException(ExceptionStatus.INVALID_ARGUMENT)); - this.endedAt = now; - ExceptionUtil.throwIfFalse((this.isValid()), - new DomainException(ExceptionStatus.INVALID_STATUS)); - } + /** + * 낙관적 락을 위한 version + */ + @Version + @Getter(AccessLevel.NONE) + private final Long version = 1L; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "LENT_HISTORY_ID") + private Long lentHistoryId; + /** + * 대여 시작일 + */ + @Column(name = "STARTED_AT", nullable = false) + private LocalDateTime startedAt; + + /** + * 연체 시작일 + */ + @Column(name = "EXPIRED_AT") + private LocalDateTime expiredAt = null; + + /** + * 반납일 + */ + @Column(name = "ENDED_AT") + private LocalDateTime endedAt = null; + + /** + * 대여하는 유저 + */ + @Column(name = "USER_ID", nullable = false) + private Long userId; + + /** + * 대여하는 캐비넷 + */ + @Column(name = "CABINET_ID", nullable = false) + private Long cabinetId; + + @JoinColumn(name = "USER_ID", insertable = false, updatable = false) + @ManyToOne(fetch = LAZY) + private User user; + + @JoinColumn(name = "CABINET_ID", insertable = false, updatable = false) + @ManyToOne(fetch = LAZY) + private Cabinet cabinet; + + protected LentHistory(LocalDateTime startedAt, LocalDateTime expiredAt, Long userId, + Long cabinetId) { + this.startedAt = startedAt; + this.expiredAt = expiredAt; + this.userId = userId; + this.cabinetId = cabinetId; + } + + /** + * @param startedAt 대여 시작일 + * @param expiredAt 연체 시작일 + * @param userId 대여하는 user id + * @param cabinetId 대여하는 cabinet id + * @return 인자 정보를 담고있는 {@link LentHistory} + */ + public static LentHistory of(LocalDateTime startedAt, LocalDateTime expiredAt, Long userId, + Long cabinetId) { + LentHistory lentHistory = new LentHistory(startedAt, expiredAt, userId, cabinetId); + if (!lentHistory.isValid()) { + throw new DomainException(ExceptionStatus.INVALID_ARGUMENT); + } + return lentHistory; + } + + /** + * startedAt, userId, cabinetId, expiredAt 의 null 이 아닌지 확인합니다. + * + * @return 유효한 인스턴스 여부 + */ + + private boolean isValid() { + return this.startedAt != null && this.userId != null && this.cabinetId != null + && this.expiredAt != null; + } + + /** + * endedAt 보다 startedAt 이 나중이 아닌지 확인합니다. endedAt 종료시점이 null 이 아닌지 확인합니다. + * + * @param endedAt 대여 종료 날짜, 시간 + * @return + */ + public boolean isEndLentValid(LocalDateTime endedAt) { + return endedAt != null && 0 <= DateUtil.calculateTwoDateDiff(endedAt, this.startedAt); + } + + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (!(other instanceof LentHistory)) { + return false; + } + return (this.lentHistoryId.equals(((LentHistory) other).lentHistoryId)); + } + + /** + * 대여한 아이디와 같은지 비교한다. + * + * @param cabinetId 비교하고 싶은 id + * @return boolean 같으면 true 다르면 false + */ + public boolean isCabinetIdEqual(Long cabinetId) { + return this.cabinetId.equals(cabinetId); + } + + /** + * 만료일을 변경합니다. + * + * @param expiredAt 변경하고 싶은 만료일 + */ + public void setExpiredAt(LocalDateTime expiredAt) { + log.info("setExpiredAt : {}", expiredAt); + this.expiredAt = expiredAt; + ExceptionUtil.throwIfFalse(this.isValid(), + new DomainException(ExceptionStatus.INVALID_STATUS)); + } + + /** + * 만료일이 설정 되어있는 지 확인합니다. 만료일이 {@link DateUtil}의 infinityDate와 같으면 만료일이 설정되어 있지 않다고 판단합니다. + * + * @return 설정이 되어있으면 true 아니면 false + */ + public boolean isSetExpiredAt() { + LocalDateTime expiredAt = getExpiredAt(); + if (expiredAt == null) { + return false; + } + return !expiredAt.isEqual(DateUtil.getInfinityDate()); + } + + /** + * 반납일이 설정 되어있는 지 확인합니다. 반납일이 {@link DateUtil}의 infinityDate와 같으면 만료일이 설정되어 있지 않다고 판단합니다. + * + * @return 설정이 되어있으면 ture 아니면 false + */ + public boolean isSetEndedAt() { + if (getEndedAt() == null) { + return false; + } + return !getEndedAt().isEqual(DateUtil.getInfinityDate()); + } + + + /** + * 반납일과 만료일의 차이를 계산합니다. + * + * @return endedAt - expiredAt의 값을(일 기준) + */ + public Long getDaysDiffEndedAndExpired() { + if (isSetExpiredAt() && isSetEndedAt()) { + return DateUtil.calculateTwoDateDiff(endedAt, expiredAt) + 1; + } + return null; + } + + /** + * 만료일이 지났는지 확인합니다. + * + * @return 만료일이 지났으면 true 아니면 false, 만료일이 설정되어 있지 않으면 false + */ + public Boolean isExpired(LocalDateTime now) { + if (isSetExpiredAt()) { + return DateUtil.calculateTwoDateDiffCeil(expiredAt, now) > 0; + } + return false; + } + + /** + * 만료일까지 남은 일수를 계산합니다. 만료시간이 설정되지 않았으면 null을 반환합니다. + * + * @return 만료일까지 남은 일수 (만료일 - 현재시간) (일 기준, 올림) + */ + public Long getDaysUntilExpiration(LocalDateTime now) { + if (isSetExpiredAt()) { + return DateUtil.calculateTwoDateDiffCeil(expiredAt, now); + } + return null; + } + + + /** + * 반납일을 설정합니다. + * + * @param now 설정하려고 하는 반납일 + */ + public void endLent(LocalDateTime now) { + log.info("setEndLent : {}", now); + ExceptionUtil.throwIfFalse((this.isEndLentValid(now)), + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + this.endedAt = now; + ExceptionUtil.throwIfFalse((this.isValid()), + new DomainException(ExceptionStatus.INVALID_STATUS)); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index e7edf2bcf..a6a487006 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -144,7 +144,8 @@ public List findUserIdsByCabinetIdFromRedis(Long cabinetId) { public List findAllOverdueLent(LocalDateTime date, Pageable pageable) { log.debug("Called findAllOverdueLent: {}", date); - return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNull(date, pageable).toList(); + return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNullJoinUserAndCabinet(date, + pageable).toList(); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 8caf463a9..61f4c3160 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -10,6 +10,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -188,6 +189,7 @@ List findAllByCabinetIdsAfterDate(@Param("date") LocalDate date, @Query("SELECT lh " + "FROM LentHistory lh " + "LEFT JOIN FETCH lh.cabinet c " + + "LEFT JOIN FETCH c.cabinetPlace cp " + "WHERE lh.userId IN (:userIds) AND lh.endedAt IS NULL") List findByUserIdsAndEndedAtIsNullJoinCabinet( @Param("userIds") List userIds); @@ -198,12 +200,17 @@ List findByUserIdsAndEndedAtIsNullJoinCabinet( * @param date 연체의 기준 날짜/시간 * @return 연체되어 있는 {@link LentHistory}의 {@link List} */ - @Query("SELECT lh " + @Query(value = "SELECT lh " + "FROM LentHistory lh " - + "WHERE lh.expiredAt < :date AND lh.endedAt IS NULL") - Page findAllExpiredAtBeforeAndEndedAtIsNull( + + "LEFT JOIN FETCH lh.user u " + + "LEFT JOIN FETCH lh.cabinet c " + + "LEFT JOIN FETCH c.cabinetPlace cp " + + "WHERE lh.expiredAt < :date AND lh.endedAt IS NULL", + countQuery = "SELECT count(lh) FROM LentHistory lh " + + "WHERE lh.expiredAt < :date AND lh.endedAt IS NULL") + Page findAllExpiredAtBeforeAndEndedAtIsNullJoinUserAndCabinet( @Param("date") LocalDateTime date, Pageable pageable); - + @Query("SELECT lh " + "FROM LentHistory lh " + "WHERE lh.endedAt IS NULL " @@ -211,4 +218,12 @@ Page findAllExpiredAtBeforeAndEndedAtIsNull( + "IN (SELECT lh2.cabinetId " + "FROM LentHistory lh2 WHERE lh2.userId = :userId AND lh2.endedAt IS NULL)") List findAllActiveLentHistoriesByUserId(@Param("userId") Long userId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("UPDATE LentHistory lh " + + "SET lh.endedAt = :endedAt " + + "WHERE lh.userId IN (:userIds) " + + "AND lh.endedAt IS NULL") + void updateEndedAtByUserIdIn(@Param("userIds") List userIds, + @Param("endedAt") LocalDateTime endedAt); } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java index bdaa12577..0008fc955 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.lent.domain.LentHistory; @@ -42,6 +43,15 @@ public void endLent(LentHistory lentHistory, LocalDateTime now) { lentRepository.save(lentHistory); } + public void endLent(List lentHistories, LocalDateTime now) { + log.info("endLent lentHistories: {}, now: {}", lentHistories, now); + + lentHistories.forEach(lentHistory -> lentHistory.isEndLentValid(now)); + List userIds = lentHistories.stream() + .map(LentHistory::getUserId).collect(Collectors.toList()); + lentRepository.updateEndedAtByUserIdIn(userIds, now); + } + public void setExpiredAt(LentHistory lentHistory, LocalDateTime expiredAt) { log.info("setExpiredAt lentHistory: {}, expiredAt: {}", lentHistory, expiredAt); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 497bf566c..ea142c08c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -62,7 +62,8 @@ public List findAllActiveLentHistories() { } public List findOverdueLentHistories(LocalDateTime now, Pageable pageable) { - return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNull(now, pageable).stream() + return lentRepository.findAllExpiredAtBeforeAndEndedAtIsNullJoinUserAndCabinet(now, + pageable).stream() .sorted(Comparator.comparing(LentHistory::getExpiredAt)).collect(toList()); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java index e232fd48f..1d8b8bfe3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/BanHistoryRepository.java @@ -6,7 +6,6 @@ import org.ftclub.cabinet.user.domain.BanHistory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -55,10 +54,8 @@ List findByUserIdsAndUnbannedAt( * @param today 현재 날짜 * @return active {@link BanHistory} 리스트 */ - @EntityGraph(attributePaths = {"user"}) - @Query("SELECT b " - + "FROM BanHistory b " - + "WHERE b.unbannedAt > :today ") + @Query(value = "SELECT b FROM BanHistory b LEFT JOIN FETCH b.user WHERE b.unbannedAt > :today", + countQuery = "SELECT count(b) FROM BanHistory b WHERE b.unbannedAt > :today") Page findPaginationActiveBanHistoriesJoinUser(Pageable pageable, @Param("today") LocalDateTime today); From b461afa80fee96bb2e3b2e9425c9fca635381a01 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 23 Dec 2023 17:04:07 +0900 Subject: [PATCH 0174/1029] =?UTF-8?q?fix,=20refactor=20:=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=EB=81=94=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/service/AdminCommandService.java | 10 +++ .../admin/service/AdminFacadeService.java | 14 ++++- .../admin/lent/AdminLentFacadeService.java | 42 ++++++++++--- .../user/AdminLentExtensionFacadeService.java | 27 ++++++++ .../admin/user/AdminUserController.java | 62 +++++++++---------- .../admin/user/AdminUserFacadeService.java | 62 +++++++++++++++++++ .../cabinet/exception/ExceptionStatus.java | 4 +- .../lent/service/LentFacadeService.java | 40 +++--------- .../user/domain/LentExtensionPolicy.java | 9 ++- .../user/domain/LentExtensionPolicyImpl.java | 30 ++++++++- .../newService/BanHistoryCommandService.java | 15 ++++- .../newService/BanHistoryQueryService.java | 22 +++---- .../LentExtensionCommandService.java | 21 +++++++ .../user/newService/UserCommandService.java | 28 ++++++++- .../user/newService/UserFacadeService.java | 41 ++++++------ .../user/newService/UserQueryService.java | 15 +++++ .../user/repository/UserOptionalFetcher.java | 4 +- .../user/repository/UserRepository.java | 3 + .../user/service/UserServiceUnitTest.java | 6 +- 19 files changed, 340 insertions(+), 115 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java index 4ac716bc5..22e00151a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java @@ -5,6 +5,8 @@ import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.admin.admin.repository.AdminRepository; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; import org.springframework.stereotype.Service; @Slf4j @@ -15,7 +17,15 @@ public class AdminCommandService { private final AdminRepository adminRepository; public Admin createAdminByEmail(String email) { + log.info("Called createAdminByEmail: {}", email); + adminRepository.findByEmail(email).ifPresent(admin -> { + throw new ServiceException(ExceptionStatus.ADMIN_ALREADY_EXISTED); + }); return adminRepository.save(Admin.of(email, AdminRole.NONE)); } + public void changeAdminRole(Admin admin, AdminRole adminRole) { + admin.changeAdminRole(adminRole); + adminRepository.save(admin); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java index fc7778756..3648c272e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java @@ -2,11 +2,23 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor public class AdminFacadeService { - + private final AdminQueryService adminQueryService; + private final AdminCommandService adminCommandService; + + public void promoteAdminByEmail(String email) { + log.debug("Called promoteUserToAdmin: {}", email); + Admin admin = adminQueryService.findByEmail(email) + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN)); + adminCommandService.changeAdminRole(admin, AdminRole.ADMIN); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java index 86ec5f63a..183a558dd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java @@ -1,35 +1,44 @@ package org.ftclub.cabinet.admin.lent; -import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.newService.CabinetCommandService; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.LentHistoryDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentCommandService; import org.ftclub.cabinet.lent.service.LentPolicyService; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.lent.service.LentRedisService; +import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.newService.BanHistoryCommandService; import org.ftclub.cabinet.user.newService.BanPolicyService; +import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; + @Service @RequiredArgsConstructor @Log4j2 public class AdminLentFacadeService { - private final CabinetQueryService cabinetQueryService; private final CabinetCommandService cabinetCommandService; + private final UserQueryService userQueryService; private final BanHistoryCommandService banHistoryCommandService; private final LentQueryService lentQueryService; private final LentCommandService lentCommandService; @@ -38,6 +47,22 @@ public class AdminLentFacadeService { private final LentPolicyService lentPolicyService; private final BanPolicyService banPolicyService; + private final LentMapper lentMapper; + + @Transactional(readOnly = true) + public LentHistoryPaginationDto getUserLentHistories(Long userId, Pageable pageable) { + log.debug("Called getAllUserLentHistories: {}", userId); + + userQueryService.getUser(userId); + Page lentHistories = + lentQueryService.findUserActiveLentHistories(userId, pageable); + List result = lentHistories.stream() + .sorted(Comparator.comparing(LentHistory::getStartedAt)) + .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) + .collect(Collectors.toList()); + return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); + } + @Transactional public void endUserLent(Long userId) { log.debug("Called endUserLent: {}", userId); @@ -83,6 +108,9 @@ public void endCabinetLent(List cabinetIds) { cabinets.forEach(cabinet -> { List cabinetLentHistories = lentHistoriesByCabinetId.get(cabinet.getCabinetId()); + cabinetLentHistories.forEach(lh -> lentCommandService.endLent(lh, now)); + cabinetCommandService.changeUserCount(cabinet, 0); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); lentRedisService.setPreviousUserName( cabinet.getCabinetId(), cabinetLentHistories.get(0).getUser().getName()); }); diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java new file mode 100644 index 000000000..3d0ddfc1d --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java @@ -0,0 +1,27 @@ +package org.ftclub.cabinet.admin.user; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.user.domain.LentExtensionType; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.newService.LentExtensionCommandService; +import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class AdminLentExtensionFacadeService { + private final UserQueryService userQueryService; + private final LentExtensionCommandService lentExtensionCommandService; + + // TODO : 더 세부적으로 구현해야함 + public void assignLentExtension(String username) { + log.info("Called assigneLentExtension: {}", username); + LocalDateTime now = LocalDateTime.now(); + User user = userQueryService.getUserByName(username); + lentExtensionCommandService.createLentExtension(user, LentExtensionType.ALL, now); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java index 280d037f1..346dca8c3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java @@ -2,14 +2,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; +import org.ftclub.cabinet.admin.lent.AdminLentFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.dto.ClubUserListDto; -import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.lent.service.LentFacadeService; -import org.ftclub.cabinet.user.service.LentExtensionService; -import org.ftclub.cabinet.user.service.UserFacadeService; import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.*; @@ -21,9 +19,10 @@ @RequestMapping("/v4/admin/users") @Log4j2 public class AdminUserController { - private final UserFacadeService userFacadeService; - private final LentFacadeService lentFacadeService; - private final LentExtensionService lentExtensionService; + private final AdminFacadeService adminFacadeService; + private final AdminUserFacadeService adminUserFacadeService; + private final AdminLentFacadeService adminLentFacadeService; + private final AdminLentExtensionFacadeService adminLentExtensionFacadeService; /** * 현재 유저가 차단된 상태일 때, 차단을 해제합니다. @@ -34,7 +33,7 @@ public class AdminUserController { @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { log.info("Called deleteBanHistoryByUserId: {}", userId); - userFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); + adminUserFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); } @@ -48,8 +47,7 @@ public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { @AuthGuard(level = AuthLevel.MASTER_ONLY) public void promoteUserToAdmin(@RequestParam("email") String email) { log.info("Called promoteUserToAdmin: {}", email); - //TODO ADMIN으로 옮기기 - userFacadeService.promoteUserToAdmin(email); + adminFacadeService.promoteAdminByEmail(email); } /** @@ -62,7 +60,7 @@ public void promoteUserToAdmin(@RequestParam("email") String email) { public void createClubUser(@RequestBody HashMap body) { log.info("Called createClub"); String clubName = body.get("clubName"); - userFacadeService.createClubUser(clubName); + adminUserFacadeService.createClubUser(clubName); } /** @@ -74,7 +72,7 @@ public void createClubUser(@RequestBody HashMap body) { @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void deleteClubUser(@PathVariable("clubId") Long clubId) { log.info("Called deleteClub"); - userFacadeService.deleteClubUser(clubId); + adminUserFacadeService.deleteClubUser(clubId); } @GetMapping("/clubs") @@ -82,7 +80,8 @@ public void deleteClubUser(@PathVariable("clubId") Long clubId) { public ClubUserListDto findClubs(@RequestParam("page") Integer page, @RequestParam("size") Integer size) { log.info("Called getClubs"); - return userFacadeService.findAllClubUser(page, size); + Pageable pageable = Pageable.ofSize(size).withPage(page); + return adminUserFacadeService.findAllClubUsers(pageable); } @PatchMapping("/club/{clubId}") @@ -91,24 +90,25 @@ public void updateClubUser(@PathVariable("clubId") Long clubId, @RequestBody HashMap body) { log.info("Called updateClub"); String clubName = body.get("clubName"); - userFacadeService.updateClubUser(clubId, clubName); + adminUserFacadeService.updateClubUser(clubId, clubName); } - @GetMapping("/lent-extensions") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentExtensionPaginationDto getAllLentExtension(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getAllLentExtension"); - return userFacadeService.getAllLentExtension(page, size); - } - - @GetMapping("/lent-extensions/active") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getAllActiveLentExtension"); - return userFacadeService.getAllActiveLentExtension(page, size); - } + // TODO : 안 쓰는 부분인 것 확정 되면 삭제 +// @GetMapping("/lent-extensions") +// @AuthGuard(level = AuthLevel.ADMIN_ONLY) +// public LentExtensionPaginationDto getAllLentExtension(@RequestParam("page") Integer page, +// @RequestParam("size") Integer size) { +// log.info("Called getAllLentExtension"); +// return adminUserFacadeService.getAllLentExtension(page, size); +// } +// +// @GetMapping("/lent-extensions/active") +// @AuthGuard(level = AuthLevel.ADMIN_ONLY) +// public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page") Integer page, +// @RequestParam("size") Integer size) { +// log.info("Called getAllActiveLentExtension"); +// return adminUserFacadeService.getAllActiveLentExtension(page, size); +// } /** * 유저의 대여 기록을 반환합니다. @@ -122,13 +122,13 @@ public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page" public LentHistoryPaginationDto getLentHistoriesByUserId( @PathVariable("userId") Long userId, Pageable pageable) { log.info("Called getLentHistoriesByUserId: {}", userId); - return lentFacadeService.getUserLentHistories(userId, pageable); + return adminLentFacadeService.getUserLentHistories(userId, pageable); } @PostMapping("/lent-extensions/{user}") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void issueLentExtension(@PathVariable("user") String username) { log.info("Called issueLentExtension"); - lentExtensionService.assignLentExtension(username); + adminLentExtensionFacadeService.assignLentExtension(username); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java new file mode 100644 index 000000000..d20c9fb11 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java @@ -0,0 +1,62 @@ +package org.ftclub.cabinet.admin.user; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.dto.ClubUserListDto; +import org.ftclub.cabinet.dto.UserProfileDto; +import org.ftclub.cabinet.mapper.UserMapper; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.newService.BanHistoryCommandService; +import org.ftclub.cabinet.user.newService.BanHistoryQueryService; +import org.ftclub.cabinet.user.newService.UserCommandService; +import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class AdminUserFacadeService { + private final UserQueryService userQueryService; + private final UserCommandService userCommandService; + private final BanHistoryQueryService banHistoryQueryService; + private final BanHistoryCommandService banHistoryCommandService; + + private final UserMapper userMapper; + + public void deleteRecentBanHistory(Long userId, LocalDateTime now) { + log.debug("Called deleteRecentBanHistory: {}", userId); + banHistoryQueryService.findRecentActiveBanHistory(userId, now) + .ifPresent(banHistory -> { + banHistoryCommandService.deleteRecentBanHistory(banHistory, now); + }); + } + + public User createClubUser(String clubName) { + log.debug("Called createClubUser: {}", clubName); + return userCommandService.createClubUser(clubName); + } + + public void deleteClubUser(Long userId) { + log.debug("Called deleteClubUser: {}", userId); + User clubUser = userQueryService.getClubUser(userId); + userCommandService.deleteClubUserById(clubUser); + } + + public ClubUserListDto findAllClubUsers(Pageable pageable) { + log.debug("Called getClubUserList"); + Page clubUsers = userQueryService.findClubUsers(pageable); + List result = clubUsers.map(userMapper::toUserProfileDto).toList(); + return userMapper.toClubUserListDto(result, clubUsers.getTotalElements()); + } + + public void updateClubUser(Long userId, String clubName) { + log.debug("Called updateClubUser: {}", userId); + User clubUser = userQueryService.getUser(userId); + userCommandService.updateClubName(clubUser, clubName); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index 392afec0a..60912bd33 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -13,7 +13,7 @@ @Getter public enum ExceptionStatus { NOT_FOUND_USER(HttpStatus.NOT_FOUND, "유저가 존재하지 않습니다"), - NOT_FOUND_ADMIN_USER(HttpStatus.NOT_FOUND, "어드민이 존재하지 않습니다"), + NOT_FOUND_ADMIN(HttpStatus.NOT_FOUND, "어드민이 존재하지 않습니다"), NOT_FOUND_CABINET(HttpStatus.NOT_FOUND, "사물함이 존재하지 않습니다."), NOT_FOUND_LENT_HISTORY(HttpStatus.NOT_FOUND, "대여한 사물함이 존재하지 않습니다."), LENT_CLUB(HttpStatus.I_AM_A_TEAPOT, "동아리 전용 사물함입니다"), @@ -29,6 +29,8 @@ public enum ExceptionStatus { UNCHANGEABLE_CABINET(HttpStatus.BAD_REQUEST, "사물함의 상태를 변경할 수 없습니다."), LENT_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 대여중인 사물함이 있습니다"), USER_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 존재하는 유저입니다"), + ADMIN_ALREADY_EXISTED(HttpStatus.BAD_REQUEST, "이미 존재하는 어드민입니다"), + NOT_CLUB_USER(HttpStatus.BAD_REQUEST, "동아리 유저가 아닙니다"), INVALID_ARGUMENT(HttpStatus.BAD_REQUEST, "유효하지 않은 입력입니다"), INVALID_STATUS(HttpStatus.BAD_REQUEST, "유효하지 않은 상태변경입니다"), SHARE_CODE_TRIAL_EXCEEDED(HttpStatus.BAD_REQUEST, "초대 코드 입력 오류 초과로 입장이 제한된 상태입니다."), diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 6222cf1a2..2a0d2f821 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -1,13 +1,5 @@ package org.ftclub.cabinet.lent.service; -import static org.ftclub.cabinet.cabinet.domain.LentType.PRIVATE; -import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; - -import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.domain.AlarmEvent; @@ -17,13 +9,7 @@ import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.newService.CabinetCommandService; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; -import org.ftclub.cabinet.dto.ActiveLentHistoryDto; -import org.ftclub.cabinet.dto.LentDto; -import org.ftclub.cabinet.dto.LentHistoryDto; -import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.dto.MyCabinetResponseDto; -import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.dto.UserVerifyRequestDto; +import org.ftclub.cabinet.dto.*; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.LentMapper; @@ -38,10 +24,18 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.ftclub.cabinet.cabinet.domain.LentType.PRIVATE; +import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; + @Slf4j @Service @RequiredArgsConstructor @@ -64,20 +58,6 @@ public class LentFacadeService { private final CabinetMapper cabinetMapper; - @Transactional(readOnly = true) - public LentHistoryPaginationDto getUserLentHistories(Long userId, Pageable pageable) { - log.debug("Called getAllUserLentHistories: {}", userId); - - userQueryService.getUser(userId); - Page lentHistories = - lentQueryService.findUserActiveLentHistories(userId, pageable); - List result = lentHistories.stream() - .sorted(Comparator.comparing(LentHistory::getStartedAt)) - .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) - .collect(Collectors.toList()); - return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); - } - @Transactional(readOnly = true) public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, PageRequest pageable) { log.debug("Called getMyLentLog: {}", user.getName()); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensionPolicy.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensionPolicy.java index 54fcc9d9d..b7c7304ab 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensionPolicy.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensionPolicy.java @@ -1,11 +1,18 @@ package org.ftclub.cabinet.user.domain; -import java.util.List; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.lent.domain.LentHistory; +import java.time.LocalDateTime; +import java.util.List; + public interface LentExtensionPolicy { void verifyLentExtension(Cabinet cabinet, List lentHistories); + String getDefaultName(); + + int getDefaultExtensionTerm(); + + LocalDateTime getExpiry(LocalDateTime now); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensionPolicyImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensionPolicyImpl.java index bb32d7451..e442fe2db 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensionPolicyImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensionPolicyImpl.java @@ -1,21 +1,27 @@ package org.ftclub.cabinet.user.domain; -import java.time.LocalDateTime; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.LentType; +import org.ftclub.cabinet.config.CabinetProperties; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.repository.LentOptionalFetcher; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; +import java.time.temporal.TemporalAdjusters; +import java.util.List; + +// Policy가 인터페이스를 가져야 하는 이유..? (적용되는 정책은 하나일텐데) +// LentHistory의 static한 생성자로 인해서 서비스 자체에서 정책을 반영한 생성을 하기가 어려움. -> Policy를 팩토리처럼 사용하는 건? @Log4j2 @Component @RequiredArgsConstructor public class LentExtensionPolicyImpl implements LentExtensionPolicy { + private final static String DEFAULT_NAME = "lentExtension"; + private final CabinetProperties cabinetProperties; @Override public void verifyLentExtension(Cabinet cabinet, List lentHistories) { @@ -34,4 +40,22 @@ public void verifyLentExtension(Cabinet cabinet, List lentHistories throw new DomainException(ExceptionStatus.EXTENSION_LENT_DELAYED); } } + + @Override + public int getDefaultExtensionTerm() { + return cabinetProperties.getLentExtendTerm(); + } + + @Override + public String getDefaultName() { + return DEFAULT_NAME; + } + + @Override + public LocalDateTime getExpiry(LocalDateTime now) { + return now.with(TemporalAdjusters.lastDayOfMonth()) + .withHour(23) + .withMinute(59) + .withSecond(0); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java index e360c4447..1f8f97de5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java @@ -1,13 +1,15 @@ package org.ftclub.cabinet.user.newService; -import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.BanPolicy; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor @Log4j2 @@ -15,9 +17,18 @@ public class BanHistoryCommandService { private final BanHistoryRepository banHistoryRepository; + private final BanPolicy banPolicy; + public void banUser(Long userId, LocalDateTime endedAt, - LocalDateTime unBannedAt, BanType banType) { + LocalDateTime unBannedAt, BanType banType) { BanHistory banHistory = BanHistory.of(endedAt, unBannedAt, banType, userId); banHistoryRepository.save(banHistory); } + + public void deleteRecentBanHistory(BanHistory banHistory, LocalDateTime now) { + log.debug("Called deleteRecentBanHistory: {}", banHistory); + if (banPolicy.isActiveBanHistory(banHistory.getUnbannedAt(), now)) { + banHistoryRepository.delete(banHistory); + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index 71ae95602..c504ffea5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -1,7 +1,5 @@ package org.ftclub.cabinet.user.newService; -import java.time.LocalDateTime; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.user.domain.BanHistory; @@ -13,24 +11,24 @@ import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; +import java.util.Optional; @Service @RequiredArgsConstructor @Log4j2 public class BanHistoryQueryService { - private final BanHistoryRepository banHistoryRepository; + private final BanHistoryRepository banHistoryRepository; - public BanHistory findRecentActiveBanHistory(Long userId, LocalDateTime now) { - log.debug("Called findRecentActiveBanHistory: {}", userId); + public Optional findRecentActiveBanHistory(Long userId, LocalDateTime now) { + log.debug("Called findRecentActiveBanHistory: {}", userId); - List banHistories = banHistoryRepository.findByUserId(userId); - return banHistories.stream() - .filter(history -> history.getUnbannedAt().isAfter(now)) - .sorted(Comparator.comparing(BanHistory::getUnbannedAt, Comparator.reverseOrder())) - .findFirst() - .orElse(null); - } + List banHistories = banHistoryRepository.findByUserId(userId); + return banHistories.stream() + .filter(history -> history.getUnbannedAt().isAfter(now)) + .sorted(Comparator.comparing(BanHistory::getUnbannedAt, Comparator.reverseOrder())) + .findFirst(); + } public List findActiveBanHistories(Long userId, LocalDateTime date) { log.debug("Called findActiveBanHistories: {}", userId); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java index fb19dc66b..e35ed28ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java @@ -2,10 +2,31 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.config.CabinetProperties; +import org.ftclub.cabinet.user.domain.LentExtension; +import org.ftclub.cabinet.user.domain.LentExtensionPolicy; +import org.ftclub.cabinet.user.domain.LentExtensionType; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.repository.LentExtensionRepository; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor @Log4j2 public class LentExtensionCommandService { + private final LentExtensionRepository lentExtensionRepository; + + private final LentExtensionPolicy policy; + private final CabinetProperties cabinetProperties; + + public LentExtension createLentExtension(User user, LentExtensionType type, LocalDateTime now) { + log.debug("Called assignLentExtension {}", user.getName()); + LentExtension lentExtension = LentExtension.of(policy.getDefaultName(), + policy.getDefaultExtensionTerm(), + policy.getExpiry(now), + type, user.getUserId()); + return lentExtensionRepository.save(lentExtension); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java index e1ff09c01..6a19fa65e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java @@ -27,7 +27,33 @@ public User createUserByFtProfile(FtProfile profile) { return userRepository.save(user); } - public void createUser(User user) { + public User createClubUser(String clubName) { + log.info("Called createClubUser. {}", clubName); + if (userRepository.existsByNameAndEmail(clubName, clubName + "@ftclub.org")) { + log.warn("이미 존재하는 동아리입니다. {}", clubName); + throw new ServiceException(ExceptionStatus.EXISTED_CLUB_USER); + } + User user = User.of(clubName, clubName + "@ftclub.org", null, UserRole.CLUB); + return userRepository.save(user); + } + + public void updateClubName(User user, String clubName) { + log.info("Called updateClubName. {}", user); + if (!user.isUserRole(UserRole.CLUB)) + throw new ServiceException(ExceptionStatus.NOT_CLUB_USER); + user.changeName(clubName); userRepository.save(user); } + + public void deleteById(Long userId) { + log.info("Called deleteById. {}", userId); + userRepository.deleteById(userId); + } + + public void deleteClubUserById(User clubUser) { + log.info("Called deleteClubUserById. {}", clubUser); + if (!clubUser.isUserRole(UserRole.CLUB)) + throw new ServiceException(ExceptionStatus.NOT_CLUB_USER); + userRepository.delete(clubUser); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 20e73be4e..79ccba2ca 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -1,6 +1,5 @@ package org.ftclub.cabinet.user.newService; -import io.netty.util.internal.StringUtil; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; @@ -10,14 +9,13 @@ import org.ftclub.cabinet.dto.LentExtensionResponseDto; import org.ftclub.cabinet.dto.MyProfileResponseDto; import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.exception.ControllerException; -import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.user.domain.*; +import org.ftclub.cabinet.user.domain.AlarmStatus; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.LentExtension; import org.springframework.stereotype.Service; import java.time.LocalDateTime; -import java.util.UUID; @Service @RequiredArgsConstructor @@ -36,7 +34,7 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { log.debug("Called getMyProfile: {}", user.getName()); Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); - BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), LocalDateTime.now()); + BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), LocalDateTime.now()).orElse(null); LentExtension lentExtension = lentExtensionQueryService.getActiveLentExtension(user); LentExtensionResponseDto lentExtensionResponseDto = LentExtensionResponseDto.builder() .lentExtensionId(lentExtension.getLentExtensionId()) @@ -54,20 +52,21 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, lentExtensionResponseDto, alarmTypeResponseDto); } - public void createClubUser(String clubName) { - log.debug("Called createClubUser: {}", clubName); - User user = userQueryService.findUser(clubName).orElse(null); - if (StringUtil.isNullOrEmpty(clubName)) { - throw new ControllerException(ExceptionStatus.INVALID_ARGUMENT); - } else if (user != null && user.getDeletedAt() == null) { - throw new ControllerException(ExceptionStatus.EXISTED_CLUB_USER); - } else if (user != null) { - user.setDeletedAt(null); - } else { - String randomUUID = UUID.randomUUID().toString(); - User newUser = User.of(clubName, randomUUID + "@ftc.co.kr", null, UserRole.CLUB); - userCommandService.createUser(newUser); - } - } + // 동아리 유저 생성부분은 AdminUserFacadeService에서 하므로 주석처리했습니다. +// public void createClubUser(String clubName) { +// log.debug("Called createClubUser: {}", clubName); +// User user = userQueryService.findUser(clubName).orElse(null); +// if (StringUtil.isNullOrEmpty(clubName)) { +// throw new ControllerException(ExceptionStatus.INVALID_ARGUMENT); +// } else if (user != null && user.getDeletedAt() == null) { +// throw new ControllerException(ExceptionStatus.EXISTED_CLUB_USER); +// } else if (user != null) { +// user.setDeletedAt(null); +// } else { +// String randomUUID = UUID.randomUUID().toString(); +// User newUser = User.of(clubName, randomUUID + "@ftc.co.kr", null, UserRole.CLUB); +// userCommandService.save(newUser); +// } +// } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java index 6a6537e3f..25cf88b79 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -5,6 +5,7 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -25,6 +26,16 @@ public User getUser(Long userId) { return user.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); } + public User getClubUser(Long userId) { + Optional user = userRepository.findByIdAndRole(userId, UserRole.CLUB); + return user.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } + + public User getUserByName(String name) { + Optional user = userRepository.findByName(name); + return user.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_USER)); + } + public List getUsers(List userIdsInCabinet) { return userRepository.findAllByIds(userIdsInCabinet); } @@ -36,4 +47,8 @@ public Page getUsers(String partialName, Pageable pageable) { public Optional findUser(String name) { return userRepository.findByName(name); } + + public Page findClubUsers(Pageable pageable) { + return userRepository.findAllByRoleAndDeletedAtIsNull(UserRole.CLUB, pageable); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java index 2ddd0ec10..b4a6a4a45 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserOptionalFetcher.java @@ -215,7 +215,7 @@ public User getClubUser(Long userId) { public Admin getAdminUser(Long adminUserId) { log.debug("Called getAdminUser: {}", adminUserId); return adminRepository.findById(adminUserId) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN)); } /** @@ -227,7 +227,7 @@ public Admin getAdminUser(Long adminUserId) { public Admin getAdminUserByEmail(String adminUserEmail) { log.debug("Called getAdminUserByEmail: {}", adminUserEmail); return adminRepository.findByEmail(adminUserEmail) - .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN)); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index 9371d9143..206de2771 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -24,6 +24,9 @@ public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.userId = :userId AND u.deletedAt IS NULL") Optional findById(@Param("userId") Long userId); + @Query("SELECT u FROM User u WHERE u.userId = :userId AND u.role = :role AND u.deletedAt IS NULL") + Optional findByIdAndRole(Long userId, UserRole role); + /** * 유저 이름으로 유저를 찾습니다. * diff --git a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java index a2f4b9822..0668c0253 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/service/UserServiceUnitTest.java @@ -248,7 +248,7 @@ void createAdminUser() { void deleteAdminUser_실패_존재하지_않는_어드민_삭제_시도() { Long failId = -1L; given(userOptionalFetcher.getAdminUser(failId)).willThrow( - new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); + new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN)); assertThrows(ServiceException.class, () -> userService.deleteAdminUser(failId)); then(userOptionalFetcher).should(times(1)).getAdminUser(failId); @@ -278,7 +278,7 @@ void createAdminUser() { Long failId = -1L; AdminRole adminRole = AdminRole.MASTER; given(userOptionalFetcher.getAdminUser(failId)).willThrow( - new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); + new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN)); assertThrows(ServiceException.class, () -> userService.updateAdminUserRole(failId, adminRole)); @@ -324,7 +324,7 @@ void createAdminUser() { void promoteAdminByEmail_실패_존재하지_않는_어드민_승격_시도() { String failEmail = "fail@fail.com"; given(userOptionalFetcher.getAdminUserByEmail(failEmail)).willThrow( - new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN_USER)); + new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN)); assertThrows(ServiceException.class, () -> userService.promoteAdminByEmail(failEmail)); then(userOptionalFetcher).should(times(1)).getAdminUserByEmail(failEmail); From 9e10cafa6b056bd17f1da77ad52ebfc2d4eab32d Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 23 Dec 2023 17:09:16 +0900 Subject: [PATCH 0175/1029] =?UTF-8?q?fix:=20admin=20promote=20=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=8A=B8=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/admin/controller/AdminController.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java index 188ce2474..d254ef519 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java @@ -3,8 +3,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -18,6 +19,13 @@ public class AdminController { private final AdminFacadeService adminFacadeService; - public void promoteUserToAdmin(@RequestParam("email") String email) { + /** + * 유저를 관리자로 승격시킵니다. + * + * @param email 유저의 이메일 + */ + @PostMapping("/{email}/promote") + public void promoteUserToAdmin(@PathVariable String email) { + adminFacadeService.promoteAdminByEmail(email); } } From be5a5dc72bca39c951215926cd12d54b4ab35b39 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 23 Dec 2023 17:55:52 +0900 Subject: [PATCH 0176/1029] =?UTF-8?q?fix:=20orElse=20->=20orElseGet?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD,=20LocalDateTime=20?= =?UTF-8?q?=EC=A7=81/=EC=97=AD=EC=A7=81=EB=A0=AC=ED=99=94=20=EC=95=88?= =?UTF-8?q?=EB=90=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20JacksonConfig?= =?UTF-8?q?=EC=97=90=20=EC=8B=9C=EA=B0=84=20=EB=AA=A8=EB=93=88=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20LocalDateTime=EC=9D=84=20map=EC=97=90?= =?UTF-8?q?=20=EB=8B=B4=EC=95=84=20body=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EA=B3=A0=20toString=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=ED=99=98=ED=95=98=EC=97=AC=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 1 + .../cabinet/auth/domain/TokenProvider.java | 2 +- .../auth/service/AuthFacadeService.java | 4 +-- .../ftclub/cabinet/config/JacksonConfig.java | 32 +++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/config/JacksonConfig.java diff --git a/backend/build.gradle b/backend/build.gradle index a45dfc8e9..01646550c 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -89,6 +89,7 @@ dependencies { runtimeOnly 'org.mariadb.jdbc:mariadb-java-client:2.7.9' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' // test diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java index 950763c3d..ef9908b97 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java @@ -37,7 +37,7 @@ public String createUserToken(User user, LocalDateTime now) { Claims claims = Jwts.claims(); claims.put("email", user.getEmail()); claims.put("name", user.getName()); - claims.put("blackholedAt", user.getBlackholedAt()); + claims.put("blackholedAt", user.getBlackholedAt().toString()); claims.put("role", user.getRole()); return Jwts.builder() diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index 1d7e0a36c..96b220945 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -52,7 +52,7 @@ public void requestAdminLogin(HttpServletResponse res) throws IOException { public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { FtProfile profile = ftOauthService.getProfileByCode(code); User user = userQueryService.findUser(profile.getIntraName()) - .orElse(userCommandService.createUserByFtProfile(profile)); + .orElseGet(() -> userCommandService.createUserByFtProfile(profile)); String token = tokenProvider.createUserToken(user, LocalDateTime.now()); Cookie cookie = authCookieManager.cookieOf(TokenProvider.USER_TOKEN_NAME, token); authCookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); @@ -62,7 +62,7 @@ public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, Str public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { GoogleProfile profile = googleOauthService.getProfileByCode(code); Admin admin = adminQueryService.findByEmail(profile.getEmail()) - .orElse(adminCommandService.createAdminByEmail(profile.getEmail())); + .orElseGet(() -> adminCommandService.createAdminByEmail(profile.getEmail())); String token = tokenProvider.createAdminToken(admin, LocalDateTime.now()); Cookie cookie = authCookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, token); authCookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); diff --git a/backend/src/main/java/org/ftclub/cabinet/config/JacksonConfig.java b/backend/src/main/java/org/ftclub/cabinet/config/JacksonConfig.java new file mode 100644 index 000000000..1fe806ef9 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/config/JacksonConfig.java @@ -0,0 +1,32 @@ +package org.ftclub.cabinet.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.time.LocalDateTime; + +/** + * Jackson 설정 클래스입니다. + */ +@Configuration +@RequiredArgsConstructor +public class JacksonConfig { + private final ObjectMapper objectMapper; + + /** + * LocalDateTime을 직렬화/역직렬화할 수 있도록 설정합니다. + */ + @PostConstruct + public void postConstruct() { + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE); + javaTimeModule.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE); + objectMapper.registerModule(javaTimeModule); + } +} + From 0af4d8e935d0428ca3e20e78a9bd43ed7f68364a Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 23 Dec 2023 18:21:57 +0900 Subject: [PATCH 0177/1029] =?UTF-8?q?fix:=20=EA=B8=B0=EC=A1=B4=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=8A=B8=EC=99=80=20=EB=A7=9E=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20adminLentController=20=EB=9D=BC=EC=9A=B0=ED=8A=B8?= =?UTF-8?q?=20=EB=A7=9E=EA=B2=8C=EB=81=94=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/admin/lent/AdminLentController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java index 60082335c..a814c5e48 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java @@ -12,7 +12,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/v4/admin/lent") +@RequestMapping("/v4/admin") @Log4j2 public class AdminLentController { private final AdminLentFacadeService adminLentFacadeService; From 3c88765b0db0fe39654fdad9a08d92e710450ced Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 23 Dec 2023 18:29:44 +0900 Subject: [PATCH 0178/1029] =?UTF-8?q?[BE]=20DB=20=EC=A0=95=ED=95=A9?= =?UTF-8?q?=EC=84=B1=20=EC=98=A4=EB=A5=98=20=EC=8B=9C=20Exception=20throw?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B3=B5=EC=9C=A0=EC=82=AC=EB=AC=BC=ED=95=A8=20=EB=8C=80?= =?UTF-8?q?=EA=B8=B0=20=EC=A4=91=EC=97=90=20CabinetInfo=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=97=90=EC=84=9C=20=EC=95=88=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EB=8A=94=20=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/admin/lent/AdminLentController.java | 16 ++++++++++------ .../admin/search/AdminSearchFacadeService.java | 13 ++++++++++--- .../ftclub/cabinet/lent/domain/LentHistory.java | 2 +- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java index 60082335c..2e0e0f806 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java @@ -1,20 +1,24 @@ package org.ftclub.cabinet.admin.lent; +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +import javax.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; - -import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor -@RequestMapping("/v4/admin/lent") +@RequestMapping("/v4/admin") @Log4j2 public class AdminLentController { + private final AdminLentFacadeService adminLentFacadeService; @PatchMapping("/return-cabinets") diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java index ae52e9a12..ed9a54d3b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.admin.search; import static java.util.stream.Collectors.toList; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.IN_SESSION; import java.time.LocalDateTime; import java.util.Comparator; @@ -110,8 +111,7 @@ public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { log.debug("Called getCabinetInfo {}", visibleNum); List cabinets = cabinetQueryService.getCabinets(visibleNum); - List cabinetIds = cabinets.stream().map(Cabinet::getCabinetId) - .collect(toList()); + List cabinetIds = cabinets.stream().map(Cabinet::getCabinetId).collect(toList()); List lentHistories = lentQueryService.findCabinetsActiveLentHistories(cabinetIds); Map> lentHistoriesByCabinetId = lentHistories.stream() @@ -125,10 +125,17 @@ public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { lents = lentHistoriesByCabinetId.get(cabinetId).stream() .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) .collect(toList()); + } else if (cabinet.isStatus(IN_SESSION)) { + List usersInCabinet = + lentRedisService.findUsersInCabinet(cabinet.getCabinetId()); + List users = userQueryService.getUsers(usersInCabinet); + lents = users.stream().map(user -> lentMapper.toLentDto(user, null)) + .collect(toList()); } LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); return cabinetMapper.toCabinetInfoResponseDto(cabinet, lents, sessionExpiredAt); - }).sorted(Comparator.comparingInt(o -> o.getLocation().getFloor())) + }) + .sorted(Comparator.comparingInt(o -> o.getLocation().getFloor())) .collect(toList()); return cabinetMapper.toCabinetInfoPaginationDto(result, (long) cabinets.size()); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java index af43a3cae..a985d034f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java @@ -172,7 +172,7 @@ public void setExpiredAt(LocalDateTime expiredAt) { public boolean isSetExpiredAt() { LocalDateTime expiredAt = getExpiredAt(); if (expiredAt == null) { - return false; + throw new DomainException(ExceptionStatus.INTERNAL_SERVER_ERROR); } return !expiredAt.isEqual(DateUtil.getInfinityDate()); } From b089b48bfcc85e3136f2b0005c9676f4ea627269 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 23 Dec 2023 18:34:42 +0900 Subject: [PATCH 0179/1029] =?UTF-8?q?[BE]=20admin=20Transaction=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=20=EB=B6=80=EB=B6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/admin/search/AdminSearchFacadeService.java | 5 +---- .../admin/statistics/AdminStatisticsFacadeService.java | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java index ed9a54d3b..e847bc24e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java @@ -42,6 +42,7 @@ @Service @RequiredArgsConstructor @Log4j2 +@Transactional(readOnly = true) public class AdminSearchFacadeService { private final UserQueryService userQueryService; @@ -55,7 +56,6 @@ public class AdminSearchFacadeService { private final UserMapper userMapper; private final LentMapper lentMapper; - @Transactional(readOnly = true) public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pageable) { log.debug("Called getUsersProfile {}", partialName); @@ -65,7 +65,6 @@ public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pag return userMapper.toUserProfilePaginationDto(result, users.getTotalElements()); } - @Transactional(readOnly = true) public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, Pageable pageable) { log.debug("Called getUserLentCabinetInfo {}", partialName); @@ -96,7 +95,6 @@ public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, Pagea return cabinetMapper.toUserCabinetPaginationDto(result, users.getTotalElements()); } - @Transactional(readOnly = true) public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { log.debug("Called getCabinetSimpleInfo {}", visibleNum); @@ -106,7 +104,6 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { return cabinetMapper.toCabinetSimplePaginationDto(result, (long) cabinets.size()); } - @Transactional(readOnly = true) public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { log.debug("Called getCabinetInfo {}", visibleNum); diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java index 6d0bb6102..1ded6080d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java @@ -35,6 +35,7 @@ @Slf4j @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class AdminStatisticsFacadeService { private final CabinetQueryService cabinetQueryService; @@ -44,7 +45,6 @@ public class AdminStatisticsFacadeService { private final CabinetMapper cabinetMapper; private final UserMapper userMapper; - @Transactional(readOnly = true) public List getAllCabinetsInfo() { log.debug("Called getAllCabinetsInfo"); @@ -61,7 +61,6 @@ public List getAllCabinetsInfo() { }).collect(Collectors.toList()); } - @Transactional(readOnly = true) public LentsStatisticsResponseDto getLentCountStatistics( LocalDateTime startDate, LocalDateTime endDate) { log.debug("Called getLentCountStatistics startDate : {} endDate : {}", startDate, endDate); From a14ca35267cb4d1cc3dcc1c5e03534356c4bfcc7 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sun, 24 Dec 2023 02:13:57 +0900 Subject: [PATCH 0180/1029] =?UTF-8?q?[BE]=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EA=B0=95=EC=A0=9C=20=EB=B0=98=EB=82=A9=20?= =?UTF-8?q?=EB=8F=99=EC=8B=9C=EC=84=B1=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/lent/AdminLentFacadeService.java | 40 ++++++++++++------- .../cabinet/repository/CabinetRepository.java | 12 ++++++ .../lent/repository/LentRepository.java | 12 +++++- .../lent/service/LentQueryService.java | 4 ++ 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java index 183a558dd..4325433a5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java @@ -1,5 +1,12 @@ package org.ftclub.cabinet.admin.lent; +import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -23,19 +30,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; - @Service @RequiredArgsConstructor @Log4j2 public class AdminLentFacadeService { + private final CabinetQueryService cabinetQueryService; private final CabinetCommandService cabinetCommandService; private final UserQueryService userQueryService; @@ -68,13 +68,23 @@ public void endUserLent(Long userId) { log.debug("Called endUserLent: {}", userId); LocalDateTime now = LocalDateTime.now(); - LentHistory userLentHistory = lentQueryService.getUserActiveLentHistoryWithLock(userId); - List cabinetLentHistories = - lentQueryService.findCabinetActiveLentHistories(userLentHistory.getCabinetId()); - Cabinet cabinet = - cabinetQueryService.getCabinetsWithLock(userLentHistory.getCabinetId()); + List lentHistories = + lentQueryService.findUserActiveLentHistoriesInCabinet(userId); + Cabinet cabinet; + LentHistory userLentHistory; + if (!lentHistories.isEmpty()) { + cabinet = cabinetQueryService.getCabinets(lentHistories.get(0).getCabinetId()); + } else { + Long cabinetId = lentRedisService.findCabinetJoinedUser(userId); + cabinet = cabinetQueryService.getCabinets(cabinetId); + List userIds = lentRedisService.findUsersInCabinet(cabinetId); + lentHistories = lentQueryService.findUsersActiveLentHistoriesAndCabinet(userIds); + } + userLentHistory = lentHistories.stream() + .filter(lh -> lh.getUserId().equals(userId)).findFirst() + .orElseThrow(() -> new RuntimeException("사용자가 빌린 사물함이 없습니다.")); - int userRemainCount = cabinetLentHistories.size() - 1; + int userRemainCount = lentHistories.size() - 1; cabinetCommandService.changeUserCount(cabinet, userRemainCount); lentCommandService.endLent(userLentHistory, now); lentRedisService.setPreviousUserName( @@ -90,7 +100,7 @@ public void endUserLent(Long userId) { if (cabinet.isLentType(SHARE)) { LocalDateTime expiredAt = lentPolicyService.adjustSharCabinetExpirationDate( userRemainCount, now, userLentHistory); - cabinetLentHistories.stream().filter(lh -> !lh.equals(userLentHistory)) + lentHistories.stream().filter(lh -> !lh.equals(userLentHistory)) .forEach(lh -> lentCommandService.setExpiredAt(lh, expiredAt)); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 6577a5aa7..32966e697 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -63,6 +63,18 @@ public interface CabinetRepository extends JpaRepository, Cabinet + "WHERE p.location.building IN (:buildings)") List findAllFloorsByBuildings(@Param("buildings") List buildings); + /** + * cabinetId로 사물함을 조회한다.(조회 이후 업데이트를 위해 X Lock을 건다.) + * + * @param cabinetId 사물함 ID + * @return 사물함 {@link Optional} + */ + @Query("SELECT c " + + "FROM Cabinet c " + + "JOIN FETCH c.cabinetPlace p " + + "WHERE c.cabinetId = :cabinetId") + Optional findById(@Param("cabinetId") Long cabinetId); + /** * cabinetId로 사물함을 조회한다.(조회 이후 업데이트를 위해 X Lock을 건다.) * diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 61f4c3160..734c031f3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -113,6 +113,16 @@ int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDat + "WHERE lh.userId = :userId AND lh.endedAt is null") Optional findByUserIdAndEndedAtIsNullForUpdate(@Param("userId") Long userId); + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("SELECT lh " + + "FROM LentHistory lh " + + "LEFT JOIN FETCH lh.user u " + + "WHERE lh.endedAt IS NULL " + + "AND lh.cabinetId = (" + + " SELECT lh2.cabinetId FROM LentHistory lh2 " + + " WHERE lh2.userId = :userId AND lh2.endedAt IS NULL)") + List findAllByCabinetIdWithSubQuery(@Param("userId") Long userId); + /** * 사물함의 대여기록 {@link LentHistory}들을 모두 가져옵니다. {@link Pageable}이 적용되었습니다. * @@ -210,7 +220,7 @@ List findByUserIdsAndEndedAtIsNullJoinCabinet( + "WHERE lh.expiredAt < :date AND lh.endedAt IS NULL") Page findAllExpiredAtBeforeAndEndedAtIsNullJoinUserAndCabinet( @Param("date") LocalDateTime date, Pageable pageable); - + @Query("SELECT lh " + "FROM LentHistory lh " + "WHERE lh.endedAt IS NULL " diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index ea142c08c..63f1ce0bc 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -53,6 +53,10 @@ public LentHistory getUserActiveLentHistoryWithLock(Long userId) { .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_LENT_HISTORY)); } + public List findUserActiveLentHistoriesInCabinet(Long userId) { + return lentRepository.findAllByCabinetIdWithSubQuery(userId); + } + public List findUsersActiveLentHistoriesAndCabinet(List userIds) { return lentRepository.findByUserIdsAndEndedAtIsNullJoinCabinet(userIds); } From 538e119f98b9685bd8541d8c832ff1993489e49e Mon Sep 17 00:00:00 2001 From: ldw Date: Sun, 24 Dec 2023 08:37:55 +0900 Subject: [PATCH 0181/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20=EB=B3=B8?= =?UTF-8?q?=EC=9D=B8=EC=9D=98=20=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=9C=20=EC=97=B0=EC=9E=A5=EA=B6=8C=EC=9D=84=20page=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/modules.xml | 4 ---- .../newService/LentExtensionQueryService.java | 12 ++++++++-- .../user/newService/UserFacadeService.java | 22 ++++++++++++++----- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.idea/modules.xml b/.idea/modules.xml index ed9d272db..eb47657dd 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,11 +2,7 @@ - - - - diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java index 7b28c5ace..f7b76e620 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java @@ -28,12 +28,20 @@ public LentExtension getActiveLentExtension(UserSessionDto userSessionDto) { .findImminentActiveLentExtension(); } - public List getMyLentExtensionSorted(Long userId) { + public List getMyLentExtensionInLatestOrder(Long userId) { log.debug("Called getMyLentExtensionSorted: {}", userId); return lentExtensionRepository.findAll(userId) .stream() - .sorted(Comparator.comparing(LentExtension::getExpiredAt)) + .sorted(Comparator.comparing(LentExtension::getExpiredAt, Comparator.reverseOrder())) .collect(Collectors.toList()); } + + public LentExtensions getActiveLentExtensionList(long userId) { + log.debug("Called getLentExtensionList {}", userId); + + return LentExtensions.builder() + .lentExtensions(lentExtensionRepository.findAllByUserIdAndUsedAtIsNull(userId)) + .build(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index bbb988318..e1288fe45 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -6,6 +6,7 @@ import org.ftclub.cabinet.alarm.service.AlarmQueryService; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; +import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.LentExtensionResponseDto; import org.ftclub.cabinet.dto.MyProfileResponseDto; import org.ftclub.cabinet.dto.UserSessionDto; @@ -13,12 +14,11 @@ import org.ftclub.cabinet.user.domain.AlarmStatus; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.LentExtension; +import org.ftclub.cabinet.user.domain.LentExtensions; import org.springframework.stereotype.Service; import java.time.LocalDateTime; -import java.util.Comparator; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; @Service @@ -73,14 +73,26 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { // } // } - public LentExtensionPaginationDto getMyLentExtension(UserSessionDto userSessionDto) { - log.debug("Called getMyLentExtension"); + public LentExtensionPaginationDto getMyLentExtension(UserSessionDto user) { + log.debug("Called getMyLentExtension : {}", user.getName()); - List lentExtensionResponseDtos = lentExtensionQueryService.getMyLentExtensionSorted(userSessionDto.getUserId()) + List lentExtensionResponseDtos = lentExtensionQueryService.getMyLentExtensionInLatestOrder(user.getUserId()) .stream() .map(userMapper::toLentExtensionResponseDto) .collect(Collectors.toList()); return userMapper.toLentExtensionPaginationDto(lentExtensionResponseDtos, (long) lentExtensionResponseDtos.size()); } + + public LentExtensionPaginationDto getMyActiveLentExtensionPage(UserSessionDto user) { + log.debug("Called getMyActiveLentExtension : {}", user.getName()); + + LentExtensions lentExtensions = lentExtensionQueryService.getActiveLentExtensionList(user.getUserId()); + List LentExtensionResponseDtos = lentExtensions.getLentExtensions() + .stream() + .map(userMapper::toLentExtensionResponseDto) + .collect(Collectors.toList()); + + return userMapper.toLentExtensionPaginationDto(LentExtensionResponseDtos, (long) LentExtensionResponseDtos.size()); + } } From ad7e7f9ad4274a2db5e6c10ad1783b4226c0c281 Mon Sep 17 00:00:00 2001 From: ldw Date: Sun, 24 Dec 2023 10:44:39 +0900 Subject: [PATCH 0182/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20findImminentAct?= =?UTF-8?q?iveLentExtension=EC=97=90=EC=84=9C=20=EC=95=A1=ED=8B=B0?= =?UTF-8?q?=EB=B8=8C=ED=95=9C=20=EC=97=B0=EC=9E=A5=EA=B6=8C=EC=9D=B4=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=EC=A7=80=20=EA=B2=80=EC=82=AC=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/user/domain/LentExtensions.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java index 7e9b28de2..6b6c0228f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java @@ -38,6 +38,8 @@ public boolean isEmpty() { public LentExtension findImminentActiveLentExtension() { filterActiveLentExtensions(); + if (!this.hasActiveLentExtension()) + return null; sortImminentASC(); return lentExtensions.get(0); } From 02193cbdd733fb535e395de3b1e0cc6f095fa4e6 Mon Sep 17 00:00:00 2001 From: ldw Date: Sun, 24 Dec 2023 10:45:51 +0900 Subject: [PATCH 0183/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20=EC=97=B0?= =?UTF-8?q?=EC=9E=A5=EA=B6=8C=20=EC=82=AC=EC=9A=A9=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LentExtensionCommandService.java | 19 ++++++++-- .../newService/LentExtensionQueryService.java | 26 ++++++------- .../user/newService/UserFacadeService.java | 38 ++++++++++++++----- 3 files changed, 55 insertions(+), 28 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java index e35ed28ce..3835623b9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java @@ -2,15 +2,17 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.config.CabinetProperties; -import org.ftclub.cabinet.user.domain.LentExtension; -import org.ftclub.cabinet.user.domain.LentExtensionPolicy; -import org.ftclub.cabinet.user.domain.LentExtensionType; -import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.user.domain.*; import org.ftclub.cabinet.user.repository.LentExtensionRepository; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.List; @Service @RequiredArgsConstructor @@ -29,4 +31,13 @@ public LentExtension createLentExtension(User user, LentExtensionType type, Loca type, user.getUserId()); return lentExtensionRepository.save(lentExtension); } + + public void useLentExtension(LentExtension lentExtension, List lentHistories) { + log.debug("Called useLentExtension : {}", lentExtension.getLentExtensionId()); + + lentExtension.use(); + lentHistories + .forEach(lentHistory -> lentHistory.setExpiredAt( + lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java index f7b76e620..4defaee5b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java @@ -2,7 +2,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.user.domain.LentExtension; import org.ftclub.cabinet.user.domain.LentExtensions; import org.ftclub.cabinet.user.repository.LentExtensionRepository; @@ -18,17 +17,24 @@ public class LentExtensionQueryService { private final LentExtensionRepository lentExtensionRepository; - public LentExtension getActiveLentExtension(UserSessionDto userSessionDto) { - log.debug("Called getActiveLentExtension: {}", userSessionDto.getName()); + public LentExtension findActiveLentExtension(Long userId) { + log.debug("Called getActiveLentExtension: {}", userId); - List lentExtensions = lentExtensionRepository.findAll(userSessionDto.getUserId()); return LentExtensions.builder() - .lentExtensions(lentExtensions) + .lentExtensions(lentExtensionRepository.findAll(userId)) .build() .findImminentActiveLentExtension(); } - public List getMyLentExtensionInLatestOrder(Long userId) { + public LentExtensions findActiveLentExtensions(Long userId) { + log.debug("Called getLentExtensionList {}", userId); + + return LentExtensions.builder() + .lentExtensions(lentExtensionRepository.findAllByUserIdAndUsedAtIsNull(userId)) + .build(); + } + + public List findLentExtensionsInLatestOrder(Long userId) { log.debug("Called getMyLentExtensionSorted: {}", userId); return lentExtensionRepository.findAll(userId) @@ -36,12 +42,4 @@ public List getMyLentExtensionInLatestOrder(Long userId) { .sorted(Comparator.comparing(LentExtension::getExpiredAt, Comparator.reverseOrder())) .collect(Collectors.toList()); } - - public LentExtensions getActiveLentExtensionList(long userId) { - log.debug("Called getLentExtensionList {}", userId); - - return LentExtensions.builder() - .lentExtensions(lentExtensionRepository.findAllByUserIdAndUsedAtIsNull(userId)) - .build(); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index e1288fe45..03d500635 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -10,11 +10,12 @@ import org.ftclub.cabinet.dto.LentExtensionResponseDto; import org.ftclub.cabinet.dto.MyProfileResponseDto; import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.user.domain.AlarmStatus; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.LentExtension; -import org.ftclub.cabinet.user.domain.LentExtensions; +import org.ftclub.cabinet.user.domain.*; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -28,18 +29,21 @@ public class UserFacadeService { private final BanHistoryQueryService banHistoryQueryService; private final LentExtensionQueryService lentExtensionQueryService; + private final LentExtensionCommandService lentExtensionCommandService; private final CabinetQueryService cabinetQueryService; private final UserQueryService userQueryService; private final UserCommandService userCommandService; private final UserMapper userMapper; private final AlarmQueryService alarmQueryService; + private final LentQueryService lentQueryService; + private final LentExtensionPolicy lentExtensionPolicy; - public MyProfileResponseDto getMyProfile(UserSessionDto user) { + public MyProfileResponseDto getProfile(UserSessionDto user) { log.debug("Called getMyProfile: {}", user.getName()); Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), LocalDateTime.now()).orElse(null); - LentExtension lentExtension = lentExtensionQueryService.getActiveLentExtension(user); + LentExtension lentExtension = lentExtensionQueryService.findActiveLentExtension(user.getUserId()); LentExtensionResponseDto lentExtensionResponseDto = LentExtensionResponseDto.builder() .lentExtensionId(lentExtension.getLentExtensionId()) .name(lentExtension.getName()) @@ -73,20 +77,20 @@ public MyProfileResponseDto getMyProfile(UserSessionDto user) { // } // } - public LentExtensionPaginationDto getMyLentExtension(UserSessionDto user) { + public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { log.debug("Called getMyLentExtension : {}", user.getName()); - List lentExtensionResponseDtos = lentExtensionQueryService.getMyLentExtensionInLatestOrder(user.getUserId()) + List lentExtensionResponseDtos = lentExtensionQueryService.findLentExtensionsInLatestOrder(user.getUserId()) .stream() .map(userMapper::toLentExtensionResponseDto) .collect(Collectors.toList()); return userMapper.toLentExtensionPaginationDto(lentExtensionResponseDtos, (long) lentExtensionResponseDtos.size()); } - public LentExtensionPaginationDto getMyActiveLentExtensionPage(UserSessionDto user) { + public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto user) { log.debug("Called getMyActiveLentExtension : {}", user.getName()); - LentExtensions lentExtensions = lentExtensionQueryService.getActiveLentExtensionList(user.getUserId()); + LentExtensions lentExtensions = lentExtensionQueryService.findActiveLentExtensions(user.getUserId()); List LentExtensionResponseDtos = lentExtensions.getLentExtensions() .stream() .map(userMapper::toLentExtensionResponseDto) @@ -94,5 +98,19 @@ public LentExtensionPaginationDto getMyActiveLentExtensionPage(UserSessionDto us return userMapper.toLentExtensionPaginationDto(LentExtensionResponseDtos, (long) LentExtensionResponseDtos.size()); } + + public void useLentExtension(UserSessionDto user) { + log.debug("Called useLentExtension : {}", user.getName()); + + Cabinet cabinet = cabinetQueryService.getCabinets(user.getUserId()); + List activeLentHistories = lentQueryService.findCabinetActiveLentHistories(cabinet.getCabinetId()); + lentExtensionPolicy.verifyLentExtension(cabinet, activeLentHistories); + + LentExtension activeLentExtension = lentExtensionQueryService.findActiveLentExtension(user.getUserId()); + if (activeLentExtension == null) { + throw new ServiceException(ExceptionStatus.EXTENSION_NOT_FOUND); + } + lentExtensionCommandService.useLentExtension(activeLentExtension, activeLentHistories); + } } From 2df9e4fa955924c143183465e02eb318a06319ab Mon Sep 17 00:00:00 2001 From: ldw Date: Sun, 24 Dec 2023 10:53:21 +0900 Subject: [PATCH 0184/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20updateAlarmStat?= =?UTF-8?q?e=20=EA=B8=B0=EB=8A=A5=20=EC=83=88=EB=A1=9C=EC=9A=B4=20UserFaca?= =?UTF-8?q?deService=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/newService/UserFacadeService.java | 18 ++++++++++++------ config | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 03d500635..8bbb78f51 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -3,13 +3,11 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; +import org.ftclub.cabinet.alarm.service.AlarmCommandService; import org.ftclub.cabinet.alarm.service.AlarmQueryService; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; -import org.ftclub.cabinet.dto.LentExtensionPaginationDto; -import org.ftclub.cabinet.dto.LentExtensionResponseDto; -import org.ftclub.cabinet.dto.MyProfileResponseDto; -import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.dto.*; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; @@ -18,6 +16,7 @@ import org.ftclub.cabinet.user.domain.*; import org.springframework.stereotype.Service; +import javax.transaction.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -31,10 +30,9 @@ public class UserFacadeService { private final LentExtensionQueryService lentExtensionQueryService; private final LentExtensionCommandService lentExtensionCommandService; private final CabinetQueryService cabinetQueryService; - private final UserQueryService userQueryService; - private final UserCommandService userCommandService; private final UserMapper userMapper; private final AlarmQueryService alarmQueryService; + private final AlarmCommandService alarmCommandService; private final LentQueryService lentQueryService; private final LentExtensionPolicy lentExtensionPolicy; @@ -112,5 +110,13 @@ public void useLentExtension(UserSessionDto user) { } lentExtensionCommandService.useLentExtension(activeLentExtension, activeLentHistories); } + + @Transactional + public void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto) { + log.debug("Called updateAlarmState"); + + alarmCommandService.updateAlarmStatusRe(dto, alarmQueryService.findAlarmStatus( + user.getUserId())); + } } diff --git a/config b/config index 868b4f34e..689668fd3 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 868b4f34e936b36d739a6d68d280c3e3a148c118 +Subproject commit 689668fd3c05aa5d591c8711d6793d46617a6a0b From 665ef477e058690cf27c93d1682fae26b4eb415d Mon Sep 17 00:00:00 2001 From: jiwon Date: Sun, 24 Dec 2023 11:20:06 +0900 Subject: [PATCH 0185/1029] =?UTF-8?q?[BE]=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EA=B0=95=EC=A0=9C=20=EB=B0=98=EB=82=A9=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A4=91=20=EB=A0=88=EB=94=94=EC=8A=A4=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/lent/AdminLentFacadeService.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java index 4325433a5..28b4da176 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java @@ -15,6 +15,8 @@ import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentCommandService; import org.ftclub.cabinet.lent.service.LentPolicyService; @@ -70,19 +72,17 @@ public void endUserLent(Long userId) { LocalDateTime now = LocalDateTime.now(); List lentHistories = lentQueryService.findUserActiveLentHistoriesInCabinet(userId); - Cabinet cabinet; - LentHistory userLentHistory; - if (!lentHistories.isEmpty()) { - cabinet = cabinetQueryService.getCabinets(lentHistories.get(0).getCabinetId()); - } else { + if (lentHistories.isEmpty()) { Long cabinetId = lentRedisService.findCabinetJoinedUser(userId); - cabinet = cabinetQueryService.getCabinets(cabinetId); - List userIds = lentRedisService.findUsersInCabinet(cabinetId); - lentHistories = lentQueryService.findUsersActiveLentHistoriesAndCabinet(userIds); + if (cabinetId != null) { + lentRedisService.deleteUserInCabinetSession(cabinetId, userId); + } + return; } - userLentHistory = lentHistories.stream() + Cabinet cabinet = cabinetQueryService.getCabinets(lentHistories.get(0).getCabinetId()); + LentHistory userLentHistory = lentHistories.stream() .filter(lh -> lh.getUserId().equals(userId)).findFirst() - .orElseThrow(() -> new RuntimeException("사용자가 빌린 사물함이 없습니다.")); + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_LENT_HISTORY)); int userRemainCount = lentHistories.size() - 1; cabinetCommandService.changeUserCount(cabinet, userRemainCount); From 92bedc90fbb6c635b2472f969ed091705796e226 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Sun, 24 Dec 2023 19:17:29 +0900 Subject: [PATCH 0186/1029] =?UTF-8?q?[BE]=20REFACTOR:=20getCabinetsPerSect?= =?UTF-8?q?ion=20QueryService=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../newService/CabinetFacadeService.java | 64 +++++++++++++++++++ .../newService/CabinetQueryService.java | 12 +++- .../repository/CabinetComplexRepository.java | 3 +- 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index 949767675..d15470581 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -1,15 +1,25 @@ package org.ftclub.cabinet.cabinet.newService; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; + import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetPreviewDto; import org.ftclub.cabinet.dto.CabinetSimpleDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; @@ -78,6 +88,11 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { lentRedisService.getSessionExpired(cabinetId)); } + /** + * @param visibleNum + * @return + */ + public CabinetSimplePaginationDto getCabinetsSimpleInfoByVisibleNum(Integer visibleNum) { log.debug("getCabinetsSimpleInfoByVisibleNum: {}", visibleNum); @@ -93,4 +108,53 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfoByVisibleNum(Integer visi .build(); } + private String checkCabinetTitle(Cabinet cabinet, List lentHistories) { + if (cabinet.getTitle() != null && !cabinet.getTitle().isEmpty()) { + return cabinet.getTitle(); + } else if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { + return lentHistories.get(0).getUser().getName(); + } + return null; + } + + + /** + * 빌딩명과 층으로 섹션별 사물함 정보를 가져옵니다. + * + * @param building 빌딩 이름 (예: 새롬관) + * @param floor 빌딩에 있는 층 + * @return 전달인자로 받은 건물,층 에 있는 모든 섹션별 사물함 정보 + */ + @Transactional(readOnly = true) + public List getCabinetsPerSection(String building, + Integer floor) { + log.debug("getCabinetsPerSection: {}, {}", building, floor); + List activeCabinetInfos = cabinetQueryService.findActiveCabinetInfoEntities( + building, floor); + Map> cabinetLentHistories = activeCabinetInfos.stream(). + collect(groupingBy(ActiveCabinetInfoEntities::getCabinet, + mapping(ActiveCabinetInfoEntities::getLentHistory, + Collectors.toList()))); + List allCabinetsOnSection = + cabinetQueryService.findAllCabinetsByBuildingAndFloor(building, floor); + + Map> cabinetPreviewsBySection = new LinkedHashMap<>(); + allCabinetsOnSection.stream() + .sorted(Comparator.comparing(Cabinet::getVisibleNum)) + .forEach(cabinet -> { + String section = cabinet.getCabinetPlace().getLocation().getSection(); + List lentHistories = + cabinetLentHistories.getOrDefault(cabinet, Collections.emptyList()); + String title = checkCabinetTitle(cabinet, lentHistories); + cabinetPreviewsBySection.computeIfAbsent(section, k -> new ArrayList<>()) + .add(cabinetMapper.toCabinetPreviewDto(cabinet, lentHistories.size(), + title)); + }); + + return cabinetPreviewsBySection.entrySet().stream() + .map(entry -> cabinetMapper.toCabinetsPerSectionResponseDto(entry.getKey(), + entry.getValue())) + .collect(Collectors.toList()); + } + } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 159dff60f..622513cfd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -7,6 +7,7 @@ import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; +import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.springframework.data.domain.Page; @@ -20,7 +21,6 @@ public class CabinetQueryService { private final CabinetRepository cabinetRepository; - public List getAllBuildings() { return cabinetRepository.findAllBuildings(); } @@ -71,8 +71,18 @@ public Cabinet findUserActiveCabinet(Long userId) { return cabinet.orElse(null); } + public List findAllCabinetsByBuildingAndFloor(String building, Integer floor) { + return cabinetRepository.findAllByBuildingAndFloor(building, floor); + } + public List findAllBuildings() { log.debug("Called findAllBuildings"); return cabinetRepository.findAllBuildings(); } + + public List findActiveCabinetInfoEntities(String building, + Integer floor) { + log.debug("Called findActiveCabinetInfoEntities"); + return cabinetRepository.findCabinetsActiveLentHistoriesByBuildingAndFloor(building, floor); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepository.java index 078f3d965..4a9bded63 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetComplexRepository.java @@ -16,5 +16,6 @@ public interface CabinetComplexRepository { List findCabinetsActiveLentHistoriesByBuildingAndFloor( String building, Integer floor); - List findAllCabinetsByCabinetStatusAndBeforeEndedAt(CabinetStatus cabinetStatus, LocalDateTime endedAt); + List findAllCabinetsByCabinetStatusAndBeforeEndedAt(CabinetStatus cabinetStatus, + LocalDateTime endedAt); } From eeb3471fd49e60d8bba6bf68a59e82874260e4ca Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sun, 24 Dec 2023 20:05:53 +0900 Subject: [PATCH 0187/1029] =?UTF-8?q?fix=20:=20intellij=20idea=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 1 + .idea/modules.xml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.idea/misc.xml b/.idea/misc.xml index 00556c162..e0797ed03 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,6 @@ + diff --git a/.idea/modules.xml b/.idea/modules.xml index eb47657dd..5b36e82e4 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,6 +2,8 @@ + + From ae946cf125bdc198eff431ed40a73e37bacb09ad Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Sun, 24 Dec 2023 22:12:00 +0900 Subject: [PATCH 0188/1029] =?UTF-8?q?[BE]=20REFACTOR:=20CabinetFacade,=20C?= =?UTF-8?q?abinetOptionalFetcher=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../newService/CabinetCommandService.java | 28 +++ .../newService/CabinetFacadeService.java | 164 +++++++++++++++++- .../newService/CabinetQueryService.java | 21 ++- .../dto/CabinetClubStatusRequestDto.java | 2 + .../lent/service/LentQueryService.java | 5 + 5 files changed, 207 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java index 5afd3ae7f..da4df0536 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java @@ -10,6 +10,8 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.domain.Grid; +import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.utils.ExceptionUtil; @@ -62,4 +64,30 @@ public void changeUserCount(List cabinets, int userCount) { cabinetRepository.updateStatusByCabinetIdsIn(cabinetIds, FULL); } } + + public void changeCabinetStatusNote(Cabinet cabinet, String changedStatusNote) { + cabinet.writeStatusNote(changedStatusNote); + } + + public void updateGrid(Cabinet cabinet, Grid modifedGrid) { + cabinet.coordinateGrid(modifedGrid); + } + + public void updateVisibleNum(Cabinet cabinet, Integer visibleNum) { + cabinet.assignVisibleNum(visibleNum); + } + + public void updateStatus(Cabinet cabinet, CabinetStatus status) { + cabinet.specifyStatus(status); + } + + public void updateLentType(Cabinet cabinet, LentType lentType) { + cabinet.specifyLentType(lentType); + } + + public void updateClubStatus(Cabinet cabinet, String clubName, String statusNote) { + cabinet.writeTitle(clubName); + cabinet.writeStatusNote(statusNote); + cabinet.specifyLentType(LentType.CLUB); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index d15470581..e5d46444a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -2,7 +2,12 @@ import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toMap; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -13,14 +18,22 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.domain.Grid; +import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.dto.BuildingFloorsDto; +import org.ftclub.cabinet.dto.CabinetClubStatusRequestDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetPendingResponseDto; import org.ftclub.cabinet.dto.CabinetPreviewDto; import org.ftclub.cabinet.dto.CabinetSimpleDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.CabinetStatusRequestDto; import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.lent.service.LentRedisService; @@ -108,15 +121,6 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfoByVisibleNum(Integer visi .build(); } - private String checkCabinetTitle(Cabinet cabinet, List lentHistories) { - if (cabinet.getTitle() != null && !cabinet.getTitle().isEmpty()) { - return cabinet.getTitle(); - } else if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { - return lentHistories.get(0).getUser().getName(); - } - return null; - } - /** * 빌딩명과 층으로 섹션별 사물함 정보를 가져옵니다. @@ -157,4 +161,146 @@ public List getCabinetsPerSection(String building .collect(Collectors.toList()); } + private String checkCabinetTitle(Cabinet cabinet, List lentHistories) { + if (cabinet.getTitle() != null && !cabinet.getTitle().isEmpty()) { + return cabinet.getTitle(); + } else if (!lentHistories.isEmpty() && lentHistories.get(0).getUser() != null) { + return lentHistories.get(0).getUser().getName(); + } + return null; + } + + @Transactional + public CabinetPendingResponseDto getPendingCabinets(String building) { + log.debug("getPendingCabinets: {} ", building); + + final LocalDate yesterday = LocalDateTime.now().minusDays(1).toLocalDate(); + List pendingCabinets = + cabinetQueryService.findPendingCabinetsNotLentTypeAndStatus( + building, LentType.CLUB, List.of(AVAILABLE, PENDING)); + List cabinetIds = pendingCabinets.stream() + .filter(cabinet -> cabinet.isStatus(PENDING)) + .map(Cabinet::getCabinetId).collect(Collectors.toList()); + Map> cabinetFloorMap = + cabinetQueryService.findAllFloorsByBuilding(building).stream() + .collect(toMap(key -> key, value -> new ArrayList<>())); + Map> lentHistoriesMap = + lentQueryService.findAllByCabinetIdsAfterDate(yesterday, cabinetIds) + .stream().collect(groupingBy(LentHistory::getCabinetId)); + pendingCabinets.forEach(cabinet -> { + Integer floor = cabinet.getCabinetPlace().getLocation().getFloor(); + if (cabinet.isStatus(AVAILABLE)) { + cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); + } + if (cabinet.isStatus(PENDING)) { + LocalDateTime latestEndedAt = lentHistoriesMap.get(cabinet.getCabinetId()).stream() + .map(LentHistory::getEndedAt) + .max(LocalDateTime::compareTo).orElse(null); + if (latestEndedAt != null && latestEndedAt.toLocalDate().isEqual(yesterday)) { + cabinetFloorMap.get(floor) + .add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); + } + } + }); + return cabinetMapper.toCabinetPendingResponseDto(cabinetFloorMap); + } + /*--------------------------------------------CUD--------------------------------------------*/ + + /** + * Admin에서 사용되는, 사물함의 상태 노트 변경 + * + * @param cabinetId 변경할 cabinet ID + * @param statusNote 변경할 상태 메모 + */ + @Transactional + public void updateCabinetStatusNote(Long cabinetId, String statusNote) { + log.debug("updateCabinetStatusNote: {}, {}", cabinetId, statusNote); + Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); + cabinetCommandService.changeCabinetStatusNote(cabinet, statusNote); + } + + + /** + * 사물함의 제목을 변경합니다 + * + * @param cabinetId 변경할 사물함 ID + * @param title 변경할 사물함 제목 + */ + @Transactional + public void updateCabinetTitle(Long cabinetId, String title) { + log.debug("updateCabinetTitle: {}, {}", cabinetId, title); + Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); + cabinetCommandService.updateTitle(cabinet, title); + } + + @Transactional + public void updateCabinetGrid(Long cabinetId, Integer row, Integer col) { + log.debug("updateCabinetGrid: {}, {}, {}", cabinetId, row, col); + Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); + cabinetCommandService.updateGrid(cabinet, Grid.of(row, col)); + } + + /** + * 사물함의 번호를 변경합니다. + * + * @param cabinetId 변경할 사물함 ID + * @param visibleNum 변경할 visibleNum + */ + @Transactional + public void updateCabinetVisibleNum(Long cabinetId, Integer visibleNum) { + log.debug("updateCabinetVisibleNum: {}, {}", cabinetId, visibleNum); + Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); + cabinetCommandService.updateVisibleNum(cabinet, visibleNum); + } + + /** + * @param cabinetStatusRequestDto 변경할 사물함들의 ID, 상태 Bundle + */ + @Transactional + public void updateCabinetBundleStatus(CabinetStatusRequestDto cabinetStatusRequestDto) { + log.debug("updateCabinetBundleStatus: {}", cabinetStatusRequestDto.getCabinetIds()); + + CabinetStatus status = cabinetStatusRequestDto.getStatus(); + LentType lentType = cabinetStatusRequestDto.getLentType(); + + List cabinetsWithLock = cabinetQueryService.getCabinetsWithLock( + cabinetStatusRequestDto.getCabinetIds()); + + for (Cabinet cabinet : cabinetsWithLock) { + if (status != null) { + cabinetCommandService.updateStatus(cabinet, cabinetStatusRequestDto.getStatus()); + } + if (lentType != null) { + cabinetCommandService.updateLentType(cabinet, + cabinetStatusRequestDto.getLentType()); + } + } + } + + /** + * 사물함에 동아리 유저를 대여 시킵니다. {inheritDoc} + * + * @param dto 변경하려는 동아리 정보 dto + */ + @Transactional + public void updateClub(CabinetClubStatusRequestDto dto) { + log.debug("updateClub: {}", dto); + + Cabinet cabinet = cabinetQueryService.getUserActiveCabinetWithLock(dto.getCabinetId()); + + Cabinet activeCabinetByUserId = cabinetQueryService.findActiveCabinetByUserId( + dto.getUserId()); + if (activeCabinetByUserId != null) { + throw new ServiceException(ExceptionStatus.LENT_ALREADY_EXISTED); + } + + String clubName = ""; + if (dto.getUserId() != null) { + clubName = userQueryService.getUser(dto.getUserId()).getName(); + } + + cabinetCommandService.updateClubStatus(cabinet, clubName, dto.getStatusNote()); + } + + } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 622513cfd..ecab773a0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -6,6 +6,7 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; +import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.exception.ExceptionStatus; @@ -42,6 +43,10 @@ public Cabinet getCabinets(Long cabinetId) { return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } + public List getCabinets(List cabinetIds) { + return cabinetRepository.findAllById(cabinetIds); + } + public Cabinet getCabinetsWithLock(Long cabinetId) { Optional cabinet = cabinetRepository.findByIdWithLock(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); @@ -75,14 +80,22 @@ public List findAllCabinetsByBuildingAndFloor(String building, Integer return cabinetRepository.findAllByBuildingAndFloor(building, floor); } - public List findAllBuildings() { - log.debug("Called findAllBuildings"); - return cabinetRepository.findAllBuildings(); - } public List findActiveCabinetInfoEntities(String building, Integer floor) { log.debug("Called findActiveCabinetInfoEntities"); return cabinetRepository.findCabinetsActiveLentHistoriesByBuildingAndFloor(building, floor); } + + public List findPendingCabinetsNotLentTypeAndStatus( + String building, LentType lentType, List cabinetStatuses) { + log.debug("Called findPendingCabinetsNotLentTypeAndStatus"); + return cabinetRepository.findAllByBuildingAndLentTypeNotAndStatusIn(building, lentType, + cabinetStatuses); + } + + public Cabinet findActiveCabinetByUserId(Long userId) { + log.debug("Called findActiveLentCabinetByUserId: {}", userId); + return cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId).orElse(null); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetClubStatusRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetClubStatusRequestDto.java index 7a5bcde14..92848ff63 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetClubStatusRequestDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CabinetClubStatusRequestDto.java @@ -3,8 +3,10 @@ import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; @AllArgsConstructor +@ToString @Getter public class CabinetClubStatusRequestDto { diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 63f1ce0bc..74c7db9de 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.toList; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; @@ -70,4 +71,8 @@ public List findOverdueLentHistories(LocalDateTime now, Pageable pa pageable).stream() .sorted(Comparator.comparing(LentHistory::getExpiredAt)).collect(toList()); } + + public List findAllByCabinetIdsAfterDate(LocalDate date, List cabinetIds) { + return lentRepository.findAllByCabinetIdsAfterDate(date, cabinetIds); + } } From 0d635c7da2eba1040738320bba6068ac16c80fa4 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Sun, 24 Dec 2023 22:12:25 +0900 Subject: [PATCH 0189/1029] =?UTF-8?q?[BE]=20REFACTOR:=20repository=20get?= =?UTF-8?q?=20find=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/statistics/AdminStatisticsFacadeService.java | 4 ++-- .../cabinet/cabinet/newService/CabinetFacadeService.java | 4 ++-- .../cabinet/cabinet/newService/CabinetQueryService.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java index 1ded6080d..50d1c9e6d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java @@ -48,8 +48,8 @@ public class AdminStatisticsFacadeService { public List getAllCabinetsInfo() { log.debug("Called getAllCabinetsInfo"); - List buildings = cabinetQueryService.getAllBuildings(); - List floors = cabinetQueryService.getAllFloorsByBuildings(buildings); + List buildings = cabinetQueryService.findAllBuildings(); + List floors = cabinetQueryService.findAllFloorsByBuildings(buildings); return floors.stream().map(floor -> { Integer used = cabinetQueryService.countCabinets(FULL, floor); Integer unused = cabinetQueryService.countCabinets(AVAILABLE, floor); diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index e5d46444a..4a80d6d26 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -67,10 +67,10 @@ public class CabinetFacadeService { @Transactional(readOnly = true) public List getBuildingFloorsResponse() { log.debug("getBuildingFloorsResponse"); - List allBuildings = cabinetQueryService.getAllBuildings(); + List allBuildings = cabinetQueryService.findAllBuildings(); return allBuildings.stream() .map(building -> cabinetMapper.toBuildingFloorsDto(building, - cabinetQueryService.getAllFloorsByBuilding(building)) + cabinetQueryService.findAllFloorsByBuilding(building)) ) .collect(Collectors.toList()); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index ecab773a0..52ab40a4c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -22,11 +22,11 @@ public class CabinetQueryService { private final CabinetRepository cabinetRepository; - public List getAllBuildings() { + public List findAllBuildings() { return cabinetRepository.findAllBuildings(); } - public List getAllFloorsByBuilding(String building) { + public List findAllFloorsByBuilding(String building) { return cabinetRepository.findAllFloorsByBuilding(building); } @@ -34,7 +34,7 @@ public int countCabinets(CabinetStatus status, Integer floor) { return cabinetRepository.countByStatusAndFloor(status, floor); } - public List getAllFloorsByBuildings(List buildings) { + public List findAllFloorsByBuildings(List buildings) { return cabinetRepository.findAllFloorsByBuildings(buildings); } From 2a9ec6000b3fbe46db511f4d81f194e48ee954b4 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Sun, 24 Dec 2023 22:15:47 +0900 Subject: [PATCH 0190/1029] =?UTF-8?q?[BE]=20REFACTOR:=20admin=20getAllCabi?= =?UTF-8?q?netsInfo=20-=20get=EC=9D=B4=20=EC=95=84=EB=8B=88=EB=A9=B4=20?= =?UTF-8?q?=ED=84=B0=EC=A7=80=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/statistics/AdminStatisticsFacadeService.java | 2 +- .../cabinet/cabinet/newService/CabinetQueryService.java | 5 +++++ .../ftclub/cabinet/cabinet/repository/CabinetRepository.java | 4 ++++ .../java/org/ftclub/cabinet/exception/ExceptionStatus.java | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java index 50d1c9e6d..9492c8b65 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java @@ -48,7 +48,7 @@ public class AdminStatisticsFacadeService { public List getAllCabinetsInfo() { log.debug("Called getAllCabinetsInfo"); - List buildings = cabinetQueryService.findAllBuildings(); + List buildings = cabinetQueryService.getAllBuildings(); List floors = cabinetQueryService.findAllFloorsByBuildings(buildings); return floors.stream().map(floor -> { Integer used = cabinetQueryService.countCabinets(FULL, floor); diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 52ab40a4c..8056e8103 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -26,6 +26,11 @@ public List findAllBuildings() { return cabinetRepository.findAllBuildings(); } + public List getAllBuildings() { + return cabinetRepository.getAllBuildings() + .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_BUILDING)); + } + public List findAllFloorsByBuilding(String building) { return cabinetRepository.findAllFloorsByBuilding(building); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 32966e697..bce8da998 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -28,6 +28,10 @@ public interface CabinetRepository extends JpaRepository, Cabinet + "FROM CabinetPlace p ") List findAllBuildings(); + @Query("SELECT DISTINCT p.location.building " + + "FROM CabinetPlace p ") + Optional> getAllBuildings(); + /** * 빌딩의 모든 층을 조회한다. * diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index 60912bd33..369d4db48 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -58,7 +58,7 @@ public enum ExceptionStatus { SLACK_ID_NOT_FOUND(HttpStatus.NOT_FOUND, "슬랙 아이디를 찾을 수 없습니다."), NOT_FOUND_ALARM(HttpStatus.BAD_REQUEST, "알람이 존재하지 않습니다"), INVALID_LENT_TYPE(HttpStatus.BAD_REQUEST, "사물함의 대여 타입이 유효하지 않습니다."), - ; + NOT_FOUND_BUILDING(HttpStatus.NOT_FOUND, "빌딩이 존재하지 않습니다."); final private int statusCode; final private String message; From a684809683dd52de302517e63e6fc0313f37e46b Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Sun, 24 Dec 2023 22:22:16 +0900 Subject: [PATCH 0191/1029] =?UTF-8?q?[BE]=20REFACTOR:=20get,=20find=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=20&=20debug=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/lent/AdminLentFacadeService.java | 4 +- .../search/AdminSearchFacadeService.java | 4 +- .../newService/CabinetFacadeService.java | 12 ++-- .../newService/CabinetQueryService.java | 44 ++++++++----- .../lent/newService/LentFacadeService2.java | 13 ++-- .../lent/service/LentFacadeService.java | 39 +++++++----- .../user/newService/UserFacadeService.java | 62 ++++++++++++------- 7 files changed, 107 insertions(+), 71 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java index 28b4da176..de5fae86e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java @@ -79,7 +79,7 @@ public void endUserLent(Long userId) { } return; } - Cabinet cabinet = cabinetQueryService.getCabinets(lentHistories.get(0).getCabinetId()); + Cabinet cabinet = cabinetQueryService.findCabinets(lentHistories.get(0).getCabinetId()); LentHistory userLentHistory = lentHistories.stream() .filter(lh -> lh.getUserId().equals(userId)).findFirst() .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_LENT_HISTORY)); @@ -110,7 +110,7 @@ public void endCabinetLent(List cabinetIds) { log.debug("Called endCabinetsLent: {}", cabinetIds); LocalDateTime now = LocalDateTime.now(); - List cabinets = cabinetQueryService.getCabinetsWithLock(cabinetIds); + List cabinets = cabinetQueryService.findCabinetsWithLock(cabinetIds); List lentHistories = lentQueryService.findCabinetsActiveLentHistories(cabinetIds); Map> lentHistoriesByCabinetId = lentHistories.stream() diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java index e847bc24e..eceb96301 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java @@ -98,7 +98,7 @@ public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, Pagea public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { log.debug("Called getCabinetSimpleInfo {}", visibleNum); - List cabinets = cabinetQueryService.getCabinets(visibleNum); + List cabinets = cabinetQueryService.findCabinets(visibleNum); List result = cabinets.stream() .map(cabinetMapper::toCabinetSimpleDto).collect(toList()); return cabinetMapper.toCabinetSimplePaginationDto(result, (long) cabinets.size()); @@ -107,7 +107,7 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { log.debug("Called getCabinetInfo {}", visibleNum); - List cabinets = cabinetQueryService.getCabinets(visibleNum); + List cabinets = cabinetQueryService.findCabinets(visibleNum); List cabinetIds = cabinets.stream().map(Cabinet::getCabinetId).collect(toList()); List lentHistories = lentQueryService.findCabinetsActiveLentHistories(cabinetIds); diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index 4a80d6d26..63f323260 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -109,7 +109,7 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { public CabinetSimplePaginationDto getCabinetsSimpleInfoByVisibleNum(Integer visibleNum) { log.debug("getCabinetsSimpleInfoByVisibleNum: {}", visibleNum); - List cabinets = cabinetQueryService.getCabinets(visibleNum); + List cabinets = cabinetQueryService.findCabinets(visibleNum); List cabinetSimpleDtos = cabinets.stream() .map(cabinetMapper::toCabinetSimpleDto) @@ -215,7 +215,7 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { @Transactional public void updateCabinetStatusNote(Long cabinetId, String statusNote) { log.debug("updateCabinetStatusNote: {}, {}", cabinetId, statusNote); - Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); + Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); cabinetCommandService.changeCabinetStatusNote(cabinet, statusNote); } @@ -229,14 +229,14 @@ public void updateCabinetStatusNote(Long cabinetId, String statusNote) { @Transactional public void updateCabinetTitle(Long cabinetId, String title) { log.debug("updateCabinetTitle: {}, {}", cabinetId, title); - Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); + Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); cabinetCommandService.updateTitle(cabinet, title); } @Transactional public void updateCabinetGrid(Long cabinetId, Integer row, Integer col) { log.debug("updateCabinetGrid: {}, {}, {}", cabinetId, row, col); - Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); + Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); cabinetCommandService.updateGrid(cabinet, Grid.of(row, col)); } @@ -249,7 +249,7 @@ public void updateCabinetGrid(Long cabinetId, Integer row, Integer col) { @Transactional public void updateCabinetVisibleNum(Long cabinetId, Integer visibleNum) { log.debug("updateCabinetVisibleNum: {}, {}", cabinetId, visibleNum); - Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); + Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); cabinetCommandService.updateVisibleNum(cabinet, visibleNum); } @@ -263,7 +263,7 @@ public void updateCabinetBundleStatus(CabinetStatusRequestDto cabinetStatusReque CabinetStatus status = cabinetStatusRequestDto.getStatus(); LentType lentType = cabinetStatusRequestDto.getLentType(); - List cabinetsWithLock = cabinetQueryService.getCabinetsWithLock( + List cabinetsWithLock = cabinetQueryService.findCabinetsWithLock( cabinetStatusRequestDto.getCabinetIds()); for (Cabinet cabinet : cabinetsWithLock) { diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index 8056e8103..cd2eeaafd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -22,66 +22,80 @@ public class CabinetQueryService { private final CabinetRepository cabinetRepository; - public List findAllBuildings() { - return cabinetRepository.findAllBuildings(); + public int countCabinets(CabinetStatus status, Integer floor) { + log.debug("Called countCabinets {} {}", status, floor); + return cabinetRepository.countByStatusAndFloor(status, floor); } + /*------------------------------------------ GET -------------------------------------------*/ + + public List getAllBuildings() { + log.debug("Called getAllBuildings"); return cabinetRepository.getAllBuildings() .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_BUILDING)); } - public List findAllFloorsByBuilding(String building) { - return cabinetRepository.findAllFloorsByBuilding(building); + /*------------------------------------------ FIND -------------------------------------------*/ + + public List findAllBuildings() { + log.debug("Called findAllBuildings"); + return cabinetRepository.findAllBuildings(); } - public int countCabinets(CabinetStatus status, Integer floor) { - return cabinetRepository.countByStatusAndFloor(status, floor); + public List findAllFloorsByBuilding(String building) { + log.debug("Called findAllFloorsByBuilding {}", building); + return cabinetRepository.findAllFloorsByBuilding(building); } public List findAllFloorsByBuildings(List buildings) { + log.debug("Called findAllFloorsByBuildings"); return cabinetRepository.findAllFloorsByBuildings(buildings); } - public Cabinet getCabinets(Long cabinetId) { + public Cabinet findCabinets(Long cabinetId) { + log.debug("Called findCabinets: {}", cabinetId); Optional cabinet = cabinetRepository.findById(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } - public List getCabinets(List cabinetIds) { - return cabinetRepository.findAllById(cabinetIds); - } - - public Cabinet getCabinetsWithLock(Long cabinetId) { + public Cabinet findCabinetsWithLock(Long cabinetId) { + log.debug("Called findCabinetsWithLock: {}", cabinetId); Optional cabinet = cabinetRepository.findByIdWithLock(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } - public List getCabinets(Integer visibleNum) { + public List findCabinets(Integer visibleNum) { + log.debug("Called findCabinets: {}", visibleNum); return cabinetRepository.findAllByVisibleNum(visibleNum); } - public Page getCabinets(Integer visibleNum, PageRequest pageable) { + public Page findCabinets(Integer visibleNum, PageRequest pageable) { + log.debug("Called findCabinets: {}", visibleNum); return cabinetRepository.findPaginationByVisibleNum(visibleNum, pageable); } - public List getCabinetsWithLock(List cabinetIds) { + public List findCabinetsWithLock(List cabinetIds) { + log.debug("Called findCabinetsWithLock: {}", cabinetIds); return cabinetRepository.findAllByIdsWithLock(cabinetIds); } public Cabinet getUserActiveCabinetWithLock(Long userId) { + log.debug("Called getUserActiveCabinetWithLock: {}", userId); Optional cabinet = cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNullWithLock(userId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } public Cabinet findUserActiveCabinet(Long userId) { + log.debug("Called findUserActiveCabinet: {}", userId); Optional cabinet = cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId); return cabinet.orElse(null); } public List findAllCabinetsByBuildingAndFloor(String building, Integer floor) { + log.debug("Called findAllCabinetsByBuildingAndFloor"); return cabinetRepository.findAllByBuildingAndFloor(building, floor); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java index 81be2750e..77bc1ef26 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java @@ -1,5 +1,7 @@ package org.ftclub.cabinet.lent.newService; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; @@ -16,9 +18,6 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.stream.Collectors; - @Slf4j @Service @RequiredArgsConstructor @@ -32,7 +31,8 @@ public class LentFacadeService2 { private final LentMapper lentMapper; - public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, Integer size) { + public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, + Integer size) { log.debug("Called getAllUserLentHistories: {}", userId); userQueryService.getUser(userId); @@ -40,7 +40,8 @@ public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer pag size = Integer.MAX_VALUE; } PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); - Page lentHistories = lentQueryService.findUserActiveLentHistories(userId, pageable); + Page lentHistories = lentQueryService.findUserActiveLentHistories(userId, + pageable); List result = lentHistories.stream() .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) .collect(Collectors.toList()); @@ -49,7 +50,7 @@ public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer pag public List getLentDtoList(Long cabinetId) { log.debug("Called getLentDtoList: {}", cabinetId); - cabinetQueryService.getCabinets(cabinetId); + cabinetQueryService.findCabinets(cabinetId); // cabinetOptionalFetcher.getCabinet(cabinetId); // List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 2a0d2f821..bde2e4dcf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -1,5 +1,13 @@ package org.ftclub.cabinet.lent.service; +import static org.ftclub.cabinet.cabinet.domain.LentType.PRIVATE; +import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.domain.AlarmEvent; @@ -9,7 +17,13 @@ import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.newService.CabinetCommandService; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.ActiveLentHistoryDto; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentHistoryDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; +import org.ftclub.cabinet.dto.MyCabinetResponseDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.dto.UserVerifyRequestDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.LentMapper; @@ -27,15 +41,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static org.ftclub.cabinet.cabinet.domain.LentType.PRIVATE; -import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; - @Slf4j @Service @RequiredArgsConstructor @@ -88,7 +93,7 @@ public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { } List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); List userList = userQueryService.getUsers(usersInCabinet); - userActiveCabinet = cabinetQueryService.getCabinets(cabinetId); + userActiveCabinet = cabinetQueryService.findCabinets(cabinetId); lentDtoList = userList.stream() .map(u -> lentMapper.toLentDto(u, null)).collect(Collectors.toList()); } else { @@ -128,7 +133,7 @@ public void startLentCabinet(Long userId, Long cabinetId) { LocalDateTime now = LocalDateTime.now(); User user = userQueryService.getUser(userId); - Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.findCabinetsWithLock(cabinetId); int lentCount = lentQueryService.countUserActiveLent(userId); List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); int userCount = lentQueryService.countCabinetUser(cabinetId); @@ -151,7 +156,7 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) log.info("Called startLentShareCabinet: {}, {}, {}", userId, cabinetId, shareCode); LocalDateTime now = LocalDateTime.now(); - Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.findCabinetsWithLock(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); lentPolicyService.verifyCabinetLentCount( cabinet.getLentType(), cabinet.getMaxUser(), userCount); @@ -194,7 +199,7 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { LocalDateTime now = LocalDateTime.now(); // TODO : ClubUser 추가 이후 userId로 ClubUser 검증 로직 필요(Policy) - Cabinet cabinet = cabinetQueryService.getCabinets(cabinetId); + Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); lentPolicyService.verifyCabinetLentCount( @@ -216,7 +221,7 @@ public void endUserLent(Long userId, String memo) { List cabinetLentHistories = lentQueryService.findCabinetActiveLentHistories(userLentHistory.getCabinetId()); Cabinet cabinet = - cabinetQueryService.getCabinetsWithLock(userLentHistory.getCabinetId()); + cabinetQueryService.findCabinetsWithLock(userLentHistory.getCabinetId()); int userRemainCount = cabinetLentHistories.size() - 1; cabinetCommandService.changeUserCount(cabinet, userRemainCount); @@ -257,7 +262,7 @@ public void cancelShareCabinetLent(Long userId, Long cabinetId) { lentRedisService.deleteUserInCabinetSession(cabinetId, userId); if (lentRedisService.isCabinetSessionEmpty(cabinetId)) { - Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.findCabinetsWithLock(cabinetId); cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); } } @@ -266,7 +271,7 @@ public void cancelShareCabinetLent(Long userId, Long cabinetId) { public void shareCabinetSessionExpired(Long cabinetId) { log.debug("Called shareCabinetSessionExpired: {}", cabinetId); - Cabinet cabinet = cabinetQueryService.getCabinetsWithLock(cabinetId); + Cabinet cabinet = cabinetQueryService.findCabinetsWithLock(cabinetId); List usersInCabinetSession = lentRedisService.getUsersInCabinetSession(cabinetId); if (lentPolicyService.verifyUserCountOnShareCabinet(usersInCabinetSession.size())) { LocalDateTime now = LocalDateTime.now(); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 8bbb78f51..6da572a53 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -1,5 +1,9 @@ package org.ftclub.cabinet.user.newService; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; @@ -7,20 +11,23 @@ import org.ftclub.cabinet.alarm.service.AlarmQueryService; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.LentExtensionPaginationDto; +import org.ftclub.cabinet.dto.LentExtensionResponseDto; +import org.ftclub.cabinet.dto.MyProfileResponseDto; +import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; +import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.user.domain.*; +import org.ftclub.cabinet.user.domain.AlarmStatus; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.LentExtension; +import org.ftclub.cabinet.user.domain.LentExtensionPolicy; +import org.ftclub.cabinet.user.domain.LentExtensions; import org.springframework.stereotype.Service; -import javax.transaction.Transactional; -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor @Log4j2 @@ -40,8 +47,10 @@ public MyProfileResponseDto getProfile(UserSessionDto user) { log.debug("Called getMyProfile: {}", user.getName()); Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); - BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), LocalDateTime.now()).orElse(null); - LentExtension lentExtension = lentExtensionQueryService.findActiveLentExtension(user.getUserId()); + BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), + LocalDateTime.now()).orElse(null); + LentExtension lentExtension = lentExtensionQueryService.findActiveLentExtension( + user.getUserId()); LentExtensionResponseDto lentExtensionResponseDto = LentExtensionResponseDto.builder() .lentExtensionId(lentExtension.getLentExtensionId()) .name(lentExtension.getName()) @@ -55,7 +64,8 @@ public MyProfileResponseDto getProfile(UserSessionDto user) { .alarmStatus(alarmStatus) .build(); - return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, lentExtensionResponseDto, alarmTypeResponseDto); + return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, + lentExtensionResponseDto, alarmTypeResponseDto); } // 동아리 유저 생성부분은 AdminUserFacadeService에서 하므로 주석처리했습니다. @@ -75,36 +85,42 @@ public MyProfileResponseDto getProfile(UserSessionDto user) { // } // } - public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { - log.debug("Called getMyLentExtension : {}", user.getName()); + public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { + log.debug("Called getMyLentExtension : {}", user.getName()); - List lentExtensionResponseDtos = lentExtensionQueryService.findLentExtensionsInLatestOrder(user.getUserId()) - .stream() - .map(userMapper::toLentExtensionResponseDto) - .collect(Collectors.toList()); - return userMapper.toLentExtensionPaginationDto(lentExtensionResponseDtos, (long) lentExtensionResponseDtos.size()); - } + List lentExtensionResponseDtos = lentExtensionQueryService.findLentExtensionsInLatestOrder( + user.getUserId()) + .stream() + .map(userMapper::toLentExtensionResponseDto) + .collect(Collectors.toList()); + return userMapper.toLentExtensionPaginationDto(lentExtensionResponseDtos, + (long) lentExtensionResponseDtos.size()); + } public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto user) { log.debug("Called getMyActiveLentExtension : {}", user.getName()); - LentExtensions lentExtensions = lentExtensionQueryService.findActiveLentExtensions(user.getUserId()); + LentExtensions lentExtensions = lentExtensionQueryService.findActiveLentExtensions( + user.getUserId()); List LentExtensionResponseDtos = lentExtensions.getLentExtensions() .stream() .map(userMapper::toLentExtensionResponseDto) .collect(Collectors.toList()); - return userMapper.toLentExtensionPaginationDto(LentExtensionResponseDtos, (long) LentExtensionResponseDtos.size()); + return userMapper.toLentExtensionPaginationDto(LentExtensionResponseDtos, + (long) LentExtensionResponseDtos.size()); } public void useLentExtension(UserSessionDto user) { log.debug("Called useLentExtension : {}", user.getName()); - Cabinet cabinet = cabinetQueryService.getCabinets(user.getUserId()); - List activeLentHistories = lentQueryService.findCabinetActiveLentHistories(cabinet.getCabinetId()); + Cabinet cabinet = cabinetQueryService.findCabinets(user.getUserId()); + List activeLentHistories = lentQueryService.findCabinetActiveLentHistories( + cabinet.getCabinetId()); lentExtensionPolicy.verifyLentExtension(cabinet, activeLentHistories); - LentExtension activeLentExtension = lentExtensionQueryService.findActiveLentExtension(user.getUserId()); + LentExtension activeLentExtension = lentExtensionQueryService.findActiveLentExtension( + user.getUserId()); if (activeLentExtension == null) { throw new ServiceException(ExceptionStatus.EXTENSION_NOT_FOUND); } From 9df41595112fb236411a1f0a70b6c5a661c302b3 Mon Sep 17 00:00:00 2001 From: jiwon Date: Mon, 25 Dec 2023 11:37:31 +0900 Subject: [PATCH 0192/1029] =?UTF-8?q?[BE]=20Lent=EC=97=90=20AuthGuard=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20Pagination=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lent/controller/LentController.java | 19 +++-- .../lent/newService/LentFacadeService2.java | 70 ------------------- .../lent/service/LentFacadeService.java | 4 +- 3 files changed, 16 insertions(+), 77 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java index 513b68b2b..902510088 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java @@ -3,6 +3,8 @@ import javax.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.dto.CabinetInfoRequestDto; import org.ftclub.cabinet.dto.LentEndMemoDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; @@ -11,7 +13,7 @@ import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.domain.UserSession; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -31,6 +33,7 @@ public class LentController { private final LentFacadeService lentFacadeService; @PostMapping("/cabinets/{cabinetId}") + @AuthGuard(level = AuthLevel.USER_ONLY) public void startLentCabinet( @UserSession UserSessionDto user, @PathVariable Long cabinetId) { @@ -39,6 +42,7 @@ public void startLentCabinet( } @PostMapping("/cabinets/share/{cabinetId}") + @AuthGuard(level = AuthLevel.USER_ONLY) public void startLentShareCabinet( @UserSession UserSessionDto user, @PathVariable Long cabinetId, @@ -49,6 +53,7 @@ public void startLentShareCabinet( } @PatchMapping("/cabinets/share/cancel/{cabinetId}") + @AuthGuard(level = AuthLevel.USER_OR_ADMIN) public void cancelLentShareCabinet( @UserSession UserSessionDto user, @PathVariable Long cabinetId) { @@ -57,6 +62,7 @@ public void cancelLentShareCabinet( } @PatchMapping("/return") + @AuthGuard(level = AuthLevel.USER_ONLY) public void endLent( @UserSession UserSessionDto userSessionDto) { log.info("Called endLent user: {}", userSessionDto); @@ -64,6 +70,7 @@ public void endLent( } @PatchMapping("/return-memo") + @AuthGuard(level = AuthLevel.USER_ONLY) public void endLentWithMemo( @UserSession UserSessionDto userSessionDto, @Valid @RequestBody LentEndMemoDto lentEndMemoDto) { @@ -73,6 +80,7 @@ public void endLentWithMemo( } @PatchMapping("/me/cabinet") + @AuthGuard(level = AuthLevel.USER_ONLY) public void updateCabinetInfo( @UserSession UserSessionDto user, @RequestBody CabinetInfoRequestDto cabinetInfoRequestDto) { @@ -83,6 +91,7 @@ public void updateCabinetInfo( } @GetMapping("/me") + @AuthGuard(level = AuthLevel.USER_ONLY) public ResponseEntity getMyLentInfo( @UserSession UserSessionDto user) { log.info("Called getMyLentInfo user: {}", user); @@ -94,10 +103,10 @@ public ResponseEntity getMyLentInfo( } @GetMapping("/me/histories") + @AuthGuard(level = AuthLevel.USER_ONLY) public LentHistoryPaginationDto getMyLentLog( - @UserSession UserSessionDto user, - @RequestBody @Valid PageRequest pageRequest) { - log.info("Called getMyLentLog user: {}, pageable: {}", user, pageRequest); - return lentFacadeService.getMyLentLog(user, pageRequest); + @UserSession UserSessionDto user, @Valid Pageable pageable) { + log.info("Called getMyLentLog user: {}, pageable: {}", user, pageable); + return lentFacadeService.getMyLentLog(user, pageable); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java b/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java deleted file mode 100644 index 77bc1ef26..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/lent/newService/LentFacadeService2.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.ftclub.cabinet.lent.newService; - -import java.util.List; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; -import org.ftclub.cabinet.dto.LentDto; -import org.ftclub.cabinet.dto.LentHistoryDto; -import org.ftclub.cabinet.dto.LentHistoryPaginationDto; -import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.service.LentCommandService; -import org.ftclub.cabinet.lent.service.LentQueryService; -import org.ftclub.cabinet.mapper.LentMapper; -import org.ftclub.cabinet.user.newService.UserQueryService; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -@RequiredArgsConstructor -public class LentFacadeService2 { - - private final LentQueryService lentQueryService; - private final LentCommandService lentCommandService; - private final UserQueryService userQueryService; - private final CabinetQueryService cabinetQueryService; - - private final LentMapper lentMapper; - - - public LentHistoryPaginationDto getAllUserLentHistories(Long userId, Integer page, - Integer size) { - log.debug("Called getAllUserLentHistories: {}", userId); - userQueryService.getUser(userId); - - if (size <= 0) { - size = Integer.MAX_VALUE; - } - PageRequest pageable = PageRequest.of(page, size, Sort.by("startedAt")); - Page lentHistories = lentQueryService.findUserActiveLentHistories(userId, - pageable); - List result = lentHistories.stream() - .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) - .collect(Collectors.toList()); - return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); - } - - public List getLentDtoList(Long cabinetId) { - log.debug("Called getLentDtoList: {}", cabinetId); - cabinetQueryService.findCabinets(cabinetId); - -// cabinetOptionalFetcher.getCabinet(cabinetId); -// List lentHistories = lentOptionalFetcher.findAllActiveLentByCabinetId( -// cabinetId); -// return lentHistories.stream().map( -// e -> lentMapper.toLentDto(e.getUser(), e)).collect(Collectors.toList()); -// return lentHistories.stream() -// .map(e -> new LentDto( -// e.getUserId(), -// e.getUser().getName(), -// e.getLentHistoryId(), -// e.getStartedAt(), -// e.getExpiredAt())) -// .collect(Collectors.toList()); - return null; - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index bde2e4dcf..bb1ca4b82 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -37,7 +37,7 @@ import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -64,7 +64,7 @@ public class LentFacadeService { @Transactional(readOnly = true) - public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, PageRequest pageable) { + public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, Pageable pageable) { log.debug("Called getMyLentLog: {}", user.getName()); Page lentHistories = From d7845e3113680e8a72045e07a4522aa3d2084bda Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Tue, 26 Dec 2023 16:25:04 +0900 Subject: [PATCH 0193/1029] =?UTF-8?q?fix=20:=20=EC=A4=91=EB=B3=B5=EB=90=9C?= =?UTF-8?q?=20=EB=9D=BC=EC=9A=B0=ED=8A=B8=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20adminContoller#promoteAdmin=EC=97=90=20MASTER=5FONLY=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/admin/controller/AdminController.java | 3 +++ .../cabinet/admin/user/AdminUserController.java | 14 -------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java index d254ef519..942775c19 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java @@ -3,6 +3,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -24,6 +26,7 @@ public class AdminController { * * @param email 유저의 이메일 */ + @AuthGuard(level = AuthLevel.MASTER_ONLY) @PostMapping("/{email}/promote") public void promoteUserToAdmin(@PathVariable String email) { adminFacadeService.promoteAdminByEmail(email); diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java index 346dca8c3..fd63dd941 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java @@ -36,20 +36,6 @@ public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { adminUserFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); } - - /** - * 유저를 어드민으로 승격시킵니다. - * - * @param email 유저 이메일 - * @return redirect:cabi.42seoul.io/admin/login - */ - @GetMapping("/admins/promote") - @AuthGuard(level = AuthLevel.MASTER_ONLY) - public void promoteUserToAdmin(@RequestParam("email") String email) { - log.info("Called promoteUserToAdmin: {}", email); - adminFacadeService.promoteAdminByEmail(email); - } - /** * 동아리 유저를 생성합니다. * From 13817ae15137e2a0323e71927d3df7db8b0ae887 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Tue, 26 Dec 2023 16:48:28 +0900 Subject: [PATCH 0194/1029] =?UTF-8?q?[BE]=20FIX:=20User=20blackholedAt=20t?= =?UTF-8?q?oString=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/modules.xml | 1 - .../org/ftclub/cabinet/auth/domain/TokenProvider.java | 7 +++---- .../main/java/org/ftclub/cabinet/user/domain/User.java | 9 +++++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.idea/modules.xml b/.idea/modules.xml index 5b36e82e4..a18f0e478 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -5,7 +5,6 @@ - diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java index ef9908b97..6a76dc4d7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java @@ -3,6 +3,8 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.sql.Timestamp; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.admin.admin.domain.AdminRole; @@ -11,9 +13,6 @@ import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Component; -import java.sql.Timestamp; -import java.time.LocalDateTime; - /** * API 제공자에 따라 JWT 토큰을 생성하는 클래스입니다. */ @@ -37,7 +36,7 @@ public String createUserToken(User user, LocalDateTime now) { Claims claims = Jwts.claims(); claims.put("email", user.getEmail()); claims.put("name", user.getName()); - claims.put("blackholedAt", user.getBlackholedAt().toString()); + claims.put("blackholedAt", user.getBlackholedAtString()); claims.put("role", user.getRole()); return Jwts.builder() diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index db32b0e45..ddf13c11c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -16,7 +16,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.ToString; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; @@ -26,7 +25,6 @@ @Table(name = "USER") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -@ToString(exclude = {"alarmStatus"}) @Log4j2 public class User { @@ -107,4 +105,11 @@ public void changeName(String name) { ExceptionUtil.throwIfFalse(this.isValid(), new DomainException(ExceptionStatus.INVALID_ARGUMENT)); } + + public String getBlackholedAtString() { + if (blackholedAt == null) { + return null; + } + return blackholedAt.toString(); + } } From 72f2b19802b7c7437e2d668672978e7265e07bff Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Tue, 26 Dec 2023 17:00:05 +0900 Subject: [PATCH 0195/1029] [BE] FIX: map -> forEach --- .../cabinet/cabinet/newService/CabinetFacadeService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index 63f323260..48b6c0952 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -85,13 +85,13 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { List cabinetActiveLentHistories = lentQueryService.findCabinetActiveLentHistories( cabinetId); - cabinetActiveLentHistories.stream().map(lentHistory -> lentDtos.add( + cabinetActiveLentHistories.forEach(lentHistory -> lentDtos.add( lentMapper.toLentDto(lentHistory.getUser(), lentHistory))); if (cabinetActiveLentHistories.isEmpty()) { List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); List users = userQueryService.getUsers(usersInCabinet); - users.stream().map(user -> lentDtos.add( + users.forEach(user -> lentDtos.add( LentDto.builder().userId(user.getUserId()).name(user.getName()).build() )); } From 3930403e773ae44a324085218bfd6d94b78ad433 Mon Sep 17 00:00:00 2001 From: moonseonghui Date: Wed, 27 Dec 2023 12:56:08 +0900 Subject: [PATCH 0196/1029] =?UTF-8?q?[FE]=20REFACTOR:=20colorInfo=EC=97=90?= =?UTF-8?q?customColor=ED=95=A9=EC=B9=98=EA=B3=A0=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20themeColorCard=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD#1484?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/customColors.ts | 12 ------------ .../Card/ThemeColorCard/ThemeColorCard.tsx | 6 ++++-- .../Card/ThemeColorCard}/colorInfo.ts | 13 +++++++++++++ 3 files changed, 17 insertions(+), 14 deletions(-) delete mode 100644 frontend/src/assets/data/customColors.ts rename frontend/src/{assets/data => components/Card/ThemeColorCard}/colorInfo.ts (77%) diff --git a/frontend/src/assets/data/customColors.ts b/frontend/src/assets/data/customColors.ts deleted file mode 100644 index 1c617b74b..000000000 --- a/frontend/src/assets/data/customColors.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const customColors = [ - "#FF4589", - "#FF8B5B", - "#FFC74C", - "#00cec9", - "#00C2AB", - "#74b9ff", - "#0984e3", - "#0D4C92", - "#a29bfe", - "#9747FF", -]; diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx index 9e13feacd..30358665b 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -6,8 +6,10 @@ import { ContentInfoStyled, } from "@/components/Card/CardStyles"; import ColorPicker from "@/components/Card/ThemeColorCard/ColorPicker"; -import { themeColorData } from "@/assets/data/colorInfo"; -import { customColors } from "@/assets/data/customColors"; +import { + customColors, + themeColorData, +} from "@/components/Card/ThemeColorCard/colorInfo"; interface ThemeColorProps { showColorPicker: boolean; diff --git a/frontend/src/assets/data/colorInfo.ts b/frontend/src/components/Card/ThemeColorCard/colorInfo.ts similarity index 77% rename from frontend/src/assets/data/colorInfo.ts rename to frontend/src/components/Card/ThemeColorCard/colorInfo.ts index 7631d2434..66b284607 100644 --- a/frontend/src/assets/data/colorInfo.ts +++ b/frontend/src/components/Card/ThemeColorCard/colorInfo.ts @@ -27,3 +27,16 @@ export const themeColorData: ColorData[] = [ getColor: (props) => props.mineColor, }, ]; + +export const customColors = [ + "#FF4589", + "#FF8B5B", + "#FFC74C", + "#00cec9", + "#00C2AB", + "#74b9ff", + "#0984e3", + "#0D4C92", + "#a29bfe", + "#9747FF", +]; From d41d4d79a67c32bba8415d28261b7da21c9bc5e1 Mon Sep 17 00:00:00 2001 From: ldw Date: Wed, 27 Dec 2023 13:22:48 +0900 Subject: [PATCH 0197/1029] [BE] REFACTOR : resolve conflicts --- .idea/modules.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.idea/modules.xml b/.idea/modules.xml index eb47657dd..4a42ffd66 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,9 @@ + + From 26725382ac7d103c823fad2daf0562b9eaa8777e Mon Sep 17 00:00:00 2001 From: ldw Date: Wed, 27 Dec 2023 13:37:43 +0900 Subject: [PATCH 0198/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20getProfile=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EC=97=90=EC=84=9C=20mapper=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20UserMapper=EC=97=90=20toAlaramTypeRespo?= =?UTF-8?q?nseDto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/mapper/UserMapper.java | 3 ++ .../LentExtensionCommandService.java | 8 ++--- .../user/newService/UserFacadeService.java | 29 ++----------------- 3 files changed, 7 insertions(+), 33 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java index c92f763ab..cc9d7e27b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java @@ -14,6 +14,7 @@ import org.ftclub.cabinet.dto.UserProfileDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.user.domain.AlarmStatus; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.LentExtension; import org.ftclub.cabinet.user.domain.User; @@ -55,4 +56,6 @@ UserProfilePaginationDto toUserProfilePaginationDto(List result, LentExtensionPaginationDto toLentExtensionPaginationDto(List result, Long totalLength); + + AlarmTypeResponseDto toAlarmTypeResponseDto(AlarmStatus alarmStatus); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java index 3835623b9..304a26282 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java @@ -2,10 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.config.CabinetProperties; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.user.domain.*; import org.ftclub.cabinet.user.repository.LentExtensionRepository; @@ -36,8 +33,7 @@ public void useLentExtension(LentExtension lentExtension, List lent log.debug("Called useLentExtension : {}", lentExtension.getLentExtensionId()); lentExtension.use(); - lentHistories - .forEach(lentHistory -> lentHistory.setExpiredAt( - lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); + lentHistories.forEach(lentHistory -> + lentHistory.setExpiredAt(lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 6da572a53..9e399cd38 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -51,40 +51,15 @@ public MyProfileResponseDto getProfile(UserSessionDto user) { LocalDateTime.now()).orElse(null); LentExtension lentExtension = lentExtensionQueryService.findActiveLentExtension( user.getUserId()); - LentExtensionResponseDto lentExtensionResponseDto = LentExtensionResponseDto.builder() - .lentExtensionId(lentExtension.getLentExtensionId()) - .name(lentExtension.getName()) - .extensionPeriod(lentExtension.getExtensionPeriod()) - .expiredAt(lentExtension.getExpiredAt().toString()) - .lentExtensionType(lentExtension.getLentExtensionType()) - .build(); + LentExtensionResponseDto lentExtensionResponseDto = userMapper.toLentExtensionResponseDto(lentExtension); AlarmStatus alarmStatus = alarmQueryService.findAlarmStatus(user.getUserId()); - AlarmTypeResponseDto alarmTypeResponseDto = AlarmTypeResponseDto.builder() - .alarmStatus(alarmStatus) - .build(); + AlarmTypeResponseDto alarmTypeResponseDto = userMapper.toAlarmTypeResponseDto(alarmStatus); return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, lentExtensionResponseDto, alarmTypeResponseDto); } - // 동아리 유저 생성부분은 AdminUserFacadeService에서 하므로 주석처리했습니다. -// public void createClubUser(String clubName) { -// log.debug("Called createClubUser: {}", clubName); -// User user = userQueryService.findUser(clubName).orElse(null); -// if (StringUtil.isNullOrEmpty(clubName)) { -// throw new ControllerException(ExceptionStatus.INVALID_ARGUMENT); -// } else if (user != null && user.getDeletedAt() == null) { -// throw new ControllerException(ExceptionStatus.EXISTED_CLUB_USER); -// } else if (user != null) { -// user.setDeletedAt(null); -// } else { -// String randomUUID = UUID.randomUUID().toString(); -// User newUser = User.of(clubName, randomUUID + "@ftc.co.kr", null, UserRole.CLUB); -// userCommandService.save(newUser); -// } -// } - public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { log.debug("Called getMyLentExtension : {}", user.getName()); From d5a18ea759889395b71064850c0318e0817ebdcb Mon Sep 17 00:00:00 2001 From: Elineely Date: Wed, 27 Dec 2023 15:19:17 +0900 Subject: [PATCH 0199/1029] [BE] DOCS:add_my_name(surlee)#1491 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index fbc740746..180b1d3f1 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,10 @@ | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | | --------------------------------------------- | ---------------------------------------------- | -------------------------------------- | --------------------------------------------- | --------------------------------------------- | -------------------------------------- | +| [ 🐶 surlee](https://github.com/Elineely) | []() | []() | []() | []() | []() | +|-------------------------------------------| ---------------------------------------------- | -------------------------------------- | --------------------------------------------- | --------------------------------------------- | -------------------------------------- | + + | | | --------------------------------------------------------------------------------------------------------------------------------------------------------- | From f639507683052db15dc1ea97c3240da58cd0a095 Mon Sep 17 00:00:00 2001 From: jiwon Date: Wed, 27 Dec 2023 15:51:40 +0900 Subject: [PATCH 0200/1029] =?UTF-8?q?[BE]=20Logging=20=EC=95=A0=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=EB=A1=9C=EA=B9=85=20=EB=8C=80=EC=B2=B4,?= =?UTF-8?q?=20AdminCabinet=20=EB=A1=9C=EC=A7=81=20=EB=B0=98=EC=98=81=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EC=96=B4=EC=9E=88=EB=8A=94=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/modules.xml | 1 + .../admin/controller/AdminController.java | 5 +- .../admin/service/AdminCommandService.java | 6 +- .../admin/service/AdminFacadeService.java | 7 +- .../admin/service/AdminQueryService.java | 9 +- .../admin/auth/AdminAuthController.java | 26 ++-- .../admin/cabinet/AdminCabinetController.java | 117 ++++++------------ .../cabinet/AdminCabinetFacadeService.java | 4 - .../admin/lent/AdminLentController.java | 7 +- .../admin/lent/AdminLentFacadeService.java | 11 +- .../admin/search/AdminSearchController.java | 13 +- .../search/AdminSearchFacadeService.java | 13 +- .../statistics/AdminStatisticsController.java | 18 ++- .../AdminStatisticsFacadeService.java | 13 +- .../user/AdminLentExtensionFacadeService.java | 10 +- .../admin/user/AdminUserController.java | 35 +++--- .../admin/user/AdminUserFacadeService.java | 16 +-- .../cabinet/controller/CabinetController.java | 8 +- .../newService/CabinetCommandService.java | 3 + .../newService/CabinetFacadeService.java | 63 +++++++--- .../newService/CabinetQueryService.java | 34 +++-- .../lent/controller/LentController.java | 14 +-- .../lent/repository/LentOptionalFetcher.java | 2 +- .../cabinet/lent/repository/LentRedis.java | 34 +---- .../lent/repository/LentRepository.java | 7 +- .../lent/service/LentCommandService.java | 17 +-- .../lent/service/LentFacadeService.java | 25 +--- .../lent/service/LentPolicyService.java | 21 +--- .../lent/service/LentQueryService.java | 8 ++ .../lent/service/LentRedisService.java | 37 +----- .../org/ftclub/cabinet/log/LogAspect.java | 74 +++++++++++ .../java/org/ftclub/cabinet/log/LogLevel.java | 6 + .../java/org/ftclub/cabinet/log/Logging.java | 13 ++ .../user/controller/UserController.java | 9 +- .../newService/AlarmStatusQueryService.java | 5 +- .../newService/BanHistoryCommandService.java | 11 +- .../newService/BanHistoryQueryService.java | 22 ++-- .../user/newService/BanPolicyService.java | 9 +- .../LentExtensionCommandService.java | 22 ++-- .../newService/LentExtensionQueryService.java | 60 +++++---- .../user/newService/UserCommandService.java | 21 ++-- .../user/newService/UserFacadeService.java | 18 +-- .../user/newService/UserQueryService.java | 10 +- .../lent/repository/LentRepositoryTest.java | 8 +- 44 files changed, 391 insertions(+), 481 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/log/LogAspect.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/log/LogLevel.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/log/Logging.java diff --git a/.idea/modules.xml b/.idea/modules.xml index a18f0e478..5b36e82e4 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -5,6 +5,7 @@ + diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java index 942775c19..abf74cc1b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java @@ -1,10 +1,11 @@ package org.ftclub.cabinet.admin.admin.controller; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -16,7 +17,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/v4/admin/admins") -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class AdminController { private final AdminFacadeService adminFacadeService; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java index 22e00151a..2017d7d26 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java @@ -1,23 +1,23 @@ package org.ftclub.cabinet.admin.admin.service; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.admin.admin.repository.AdminRepository; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class AdminCommandService { private final AdminRepository adminRepository; public Admin createAdminByEmail(String email) { - log.info("Called createAdminByEmail: {}", email); adminRepository.findByEmail(email).ifPresent(admin -> { throw new ServiceException(ExceptionStatus.ADMIN_ALREADY_EXISTED); }); diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java index 3648c272e..8b2012f85 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java @@ -1,22 +1,23 @@ package org.ftclub.cabinet.admin.admin.service; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class AdminFacadeService { + private final AdminQueryService adminQueryService; private final AdminCommandService adminCommandService; public void promoteAdminByEmail(String email) { - log.debug("Called promoteUserToAdmin: {}", email); Admin admin = adminQueryService.findByEmail(email) .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_ADMIN)); adminCommandService.changeAdminRole(admin, AdminRole.ADMIN); diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java index 52a20b3d6..e4d098498 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminQueryService.java @@ -1,17 +1,18 @@ package org.ftclub.cabinet.admin.admin.service; +import java.util.Optional; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.admin.admin.repository.AdminRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.springframework.stereotype.Service; -import java.util.Optional; - -@Slf4j @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class AdminQueryService { + private final AdminRepository adminRepository; public Optional findByEmail(String email) { diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java b/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java index f845292ca..710803e93 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java @@ -1,15 +1,20 @@ package org.ftclub.cabinet.admin.auth; -import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.auth.service.AuthFacadeService; -import org.ftclub.cabinet.dto.MasterLoginDto; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.LocalDateTime; import java.util.concurrent.ExecutionException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.auth.service.AuthFacadeService; +import org.ftclub.cabinet.dto.MasterLoginDto; +import org.ftclub.cabinet.log.Logging; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; /** * 관리자 인증을 수행하는 컨트롤러 클래스입니다. @@ -17,6 +22,7 @@ @RestController @RequestMapping("/v4/admin/auth") @RequiredArgsConstructor +@Logging public class AdminAuthController { private final AuthFacadeService authFacadeService; @@ -41,8 +47,8 @@ public void login(HttpServletResponse res) throws IOException { */ @PostMapping("/login") public void masterLogin(HttpServletRequest req, - HttpServletResponse res, - @RequestBody MasterLoginDto masterLoginDto) { + HttpServletResponse res, + @RequestBody MasterLoginDto masterLoginDto) { authFacadeService.masterLogin(masterLoginDto, req, res, LocalDateTime.now()); } @@ -57,7 +63,7 @@ public void masterLogin(HttpServletRequest req, @GetMapping("/login/callback") public void loginCallback(@RequestParam String code, HttpServletRequest req, - HttpServletResponse res) throws IOException, ExecutionException, InterruptedException { + HttpServletResponse res) throws IOException, ExecutionException, InterruptedException { authFacadeService.handleAdminLogin(req, res, code); } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java index 5e6384975..7fdd643b5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java @@ -1,21 +1,30 @@ package org.ftclub.cabinet.admin.cabinet; +import java.util.HashMap; +import java.util.Map; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.cabinet.newService.CabinetFacadeService; +import org.ftclub.cabinet.dto.CabinetClubStatusRequestDto; +import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetPaginationDto; +import org.ftclub.cabinet.dto.CabinetStatusRequestDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.lent.service.LentFacadeService; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import java.util.HashMap; -import java.util.Map; +import org.ftclub.cabinet.log.Logging; +import org.springframework.data.domain.Pageable; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; /** * 관리자가 사물함을 관리할 때 사용하는 컨트롤러입니다. @@ -23,7 +32,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/v4/admin/cabinets") -@Log4j2 +@Logging public class AdminCabinetController { private final CabinetFacadeService cabinetFacadeService; @@ -40,7 +49,6 @@ public class AdminCabinetController { @AuthGuard(level = AuthLevel.ADMIN_ONLY) public CabinetInfoResponseDto getCabinetInfo( @PathVariable("cabinetId") Long cabinetId) { - log.info("Called getCabinetInfo: {}", cabinetId); return cabinetFacadeService.getCabinetInfo(cabinetId); } @@ -56,9 +64,7 @@ public CabinetInfoResponseDto getCabinetInfo( public void updateCabinetStatusNote( @PathVariable("cabinetId") Long cabinetId, @RequestBody HashMap body) { - log.info("Called updateCabinetStatusNote: {}", cabinetId); - if (body == null || body.isEmpty() || !body.containsKey( - "statusNote")) { + if (body == null || body.isEmpty() || !body.containsKey("statusNote")) { throw new ControllerException(ExceptionStatus.INCORRECT_ARGUMENT); } cabinetFacadeService.updateCabinetStatusNote(cabinetId, body.get("statusNote")); @@ -76,7 +82,6 @@ public void updateCabinetStatusNote( public void updateCabinetTitle( @PathVariable("cabinetId") Long cabinetId, @RequestBody HashMap body) { - log.info("Called updateCabinetTitle: {}", cabinetId); if (body == null || body.isEmpty() || !body.containsKey("title")) { throw new ControllerException(ExceptionStatus.INCORRECT_ARGUMENT); } @@ -87,7 +92,6 @@ public void updateCabinetTitle( @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void updateCabinetBundleStatus( @Valid @RequestBody CabinetStatusRequestDto cabinetStatusRequestDto) { - log.info("Called updateCabinetBundleStatus: {}", cabinetStatusRequestDto.getCabinetIds()); cabinetFacadeService.updateCabinetBundleStatus(cabinetStatusRequestDto); } @@ -95,8 +99,7 @@ public void updateCabinetBundleStatus( @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void updateCabinetClubStatus( @Valid @RequestBody CabinetClubStatusRequestDto cabinetClubStatusRequestDto) { - log.info("Called updateCabinetClubStatus: {}", cabinetClubStatusRequestDto.getCabinetId()); - cabinetFacadeService.updateCabinetClubStatus(cabinetClubStatusRequestDto); + cabinetFacadeService.updateClub(cabinetClubStatusRequestDto); lentFacadeService.startLentClubCabinet(cabinetClubStatusRequestDto.getUserId(), cabinetClubStatusRequestDto.getCabinetId()); } @@ -114,7 +117,6 @@ public void updateCabinetClubStatus( public void updateCabinetGrid( @PathVariable("cabinetId") Long cabinetId, @RequestBody Map body) { - log.info("Called updateCabinetGrid: {}", cabinetId); if (body == null || body.isEmpty() || !body.containsKey("row") || !body.containsKey("col")) { throw new ControllerException(ExceptionStatus.INCORRECT_ARGUMENT); @@ -134,118 +136,69 @@ public void updateCabinetGrid( public void updateCabinetVisibleNum( @PathVariable("cabinetId") Long cabinetId, @RequestBody HashMap body) { - log.info("Called updateCabinetVisibleNum: {}", cabinetId); - if (body == null || body.isEmpty() || !body.containsKey( - "visibleNum")) { + if (body == null || body.isEmpty() || !body.containsKey("visibleNum")) { throw new ControllerException(ExceptionStatus.INCORRECT_ARGUMENT); } cabinetFacadeService.updateCabinetVisibleNum(cabinetId, body.get("visibleNum")); } -// /** -// * 사물함들의 상태를 일괄 변경합니다. -// * -// * @param updateCabinetsRequestDto 변경할 사물함 아이디 리스트 dto -// * @param status 변경할 사물함 상태 -// * @throws ControllerException 인자가 null이거나 빈 값일 경우 발생시킵니다. -// */ -// @PatchMapping("/status/{status}") -// @AuthGuard(level = AuthLevel.ADMIN_ONLY) -// public void updateCabinetBundleStatus( -// @Valid @RequestBody UpdateCabinetsRequestDto updateCabinetsRequestDto, -// @PathVariable("status") CabinetStatus status) { -// log.debug("Called updateCabinetBundleStatus: {}", status); -// cabinetFacadeService.updateCabinetBundleStatus(updateCabinetsRequestDto, status); -// } - -// /** -// * 사물함들의 대여 타입을 일괄 변경합니다. -// * -// * @param body { 변경할 사물함 아이디 리스트 } -// * @param lentType 변경할 사물함 대여 타입 -// * @throws ControllerException 인자가 null이거나 빈 값일 경우 발생시킵니다. -// */ -// @PatchMapping("/lent-types/{lentType}") -// @AuthGuard(level = AuthLevel.ADMIN_ONLY) -// public void updateCabinetBundleLentType( -// @RequestBody HashMap> body, -// @PathVariable("lentType") LentType lentType) { -// if (body == null || body.isEmpty() || !body.containsKey("cabinetIds") || lentType == null) { -// throw new ControllerException(ExceptionStatus.INCORRECT_ARGUMENT); -// } -// cabinetFacadeService.updateCabinetBundleLentType(body.get("cabinetIds"), lentType); -// } - /** * 사물함 대여 타입에 따른 사물함의 정보를 페이지네이션으로 가져옵니다. * * @param lentType 사물함 대여 타입 - * @param page 페이지 - * @param size 한 페이지에 있는 정보의 수 + * @param pageable 페이지네이션 정보 * @return 사물함 정보 페이지네이션 */ @GetMapping("/lent-types/{lentType}") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public CabinetPaginationDto getCabinetsByLentType( @PathVariable("lentType") LentType lentType, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getCabinetsByLentType: {}", lentType); - return cabinetFacadeService.getCabinetPaginationByLentType(lentType, page, size); + @Valid Pageable pageable) { + return cabinetFacadeService.getCabinetPaginationByLentType(lentType, pageable); } /** * 사물함 상태에 따른 사물함의 정보를 페이지네이션으로 가져옵니다. * - * @param status 사물함 상태 - * @param page 페이지 - * @param size 한 페이지에 있는 정보의 수 + * @param status 사물함 상태 + * @param pageable 페이지네이션 정보 * @return 사물함 정보 페이지네이션 */ @GetMapping("/status/{status}") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public CabinetPaginationDto getCabinetsByStatus( @PathVariable("status") CabinetStatus status, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getCabinetsByStatus: {}", status); - return cabinetFacadeService.getCabinetPaginationByStatus(status, page, size); + @Valid Pageable pageable) { + return cabinetFacadeService.getCabinetPaginationByStatus(status, pageable); } /** * 사물함 표시 번호에 따른 사물함의 정보를 페이지네이션으로 가져옵니다. * * @param visibleNum 사물함 표시 번호 - * @param page 페이지 - * @param size 한 페이지에 있는 정보의 수 + * @param pageable 페이지네이션 정보 * @return 사물함 정보 페이지네이션 */ @GetMapping("/visible-num/{visibleNum}") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public CabinetPaginationDto getCabinetsByVisibleNum( @PathVariable("visibleNum") Integer visibleNum, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getCabinetsByVisibleNum: {}", visibleNum); - return cabinetFacadeService.getCabinetPaginationByVisibleNum(visibleNum, page, size); + @Valid Pageable pageable) { + return cabinetFacadeService.getCabinetPaginationByVisibleNum(visibleNum, pageable); } /** * 사물함의 대여 기록을 페이지네이션으로 가져옵니다. * * @param cabinetId 사물함 아이디 - * @param page 페이지 - * @param size 한 페이지에 있는 정보의 수 + * @param pageable 페이지네이션 정보 * @return 대여 기록 페이지네이션 */ @GetMapping("/{cabinetId}/lent-histories") @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public LentHistoryPaginationDto getCabinetLentHistories(@Valid - @PathVariable("cabinetId") Long cabinetId, - @RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getCabinetLentHistories: {}", cabinetId); - return cabinetFacadeService.getCabinetLentHistoriesPagination(cabinetId, page, size); + public LentHistoryPaginationDto getCabinetLentHistories( + @Valid @PathVariable("cabinetId") Long cabinetId, @Valid Pageable pageable) { + return cabinetFacadeService.getLentHistoryPagination(cabinetId, pageable); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java index 1c613cdf1..26401238f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java @@ -1,12 +1,10 @@ package org.ftclub.cabinet.admin.cabinet; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; import org.ftclub.cabinet.mapper.CabinetMapper; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor public class AdminCabinetFacadeService { @@ -14,6 +12,4 @@ public class AdminCabinetFacadeService { private final CabinetQueryService cabinetQueryService; private final CabinetMapper cabinetMapper; - - } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java index 2e0e0f806..e350add89 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java @@ -4,9 +4,9 @@ import javax.validation.Valid; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; +import org.ftclub.cabinet.log.Logging; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -16,7 +16,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/v4/admin") -@Log4j2 +@Logging public class AdminLentController { private final AdminLentFacadeService adminLentFacadeService; @@ -25,15 +25,12 @@ public class AdminLentController { @AuthGuard(level = ADMIN_ONLY) public void terminateLentCabinets( @Valid @RequestBody ReturnCabinetsRequestDto returnCabinetsRequestDto) { - log.info("Called terminateLentCabinets returnCabinetsRequestDto={}", - returnCabinetsRequestDto); adminLentFacadeService.endCabinetLent(returnCabinetsRequestDto.getCabinetIds()); } @PatchMapping("/return-users/{userId}") @AuthGuard(level = ADMIN_ONLY) public void terminateLentUser(@PathVariable("userId") Long userId) { - log.info("Called terminateLentUser userId={}", userId); adminLentFacadeService.endUserLent(userId); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java index de5fae86e..421c8a7da 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java @@ -8,7 +8,6 @@ import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.newService.CabinetCommandService; @@ -22,6 +21,8 @@ import org.ftclub.cabinet.lent.service.LentPolicyService; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.lent.service.LentRedisService; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.newService.BanHistoryCommandService; @@ -35,7 +36,7 @@ @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class AdminLentFacadeService { private final CabinetQueryService cabinetQueryService; @@ -53,8 +54,6 @@ public class AdminLentFacadeService { @Transactional(readOnly = true) public LentHistoryPaginationDto getUserLentHistories(Long userId, Pageable pageable) { - log.debug("Called getAllUserLentHistories: {}", userId); - userQueryService.getUser(userId); Page lentHistories = lentQueryService.findUserActiveLentHistories(userId, pageable); @@ -67,8 +66,6 @@ public LentHistoryPaginationDto getUserLentHistories(Long userId, Pageable pagea @Transactional public void endUserLent(Long userId) { - log.debug("Called endUserLent: {}", userId); - LocalDateTime now = LocalDateTime.now(); List lentHistories = lentQueryService.findUserActiveLentHistoriesInCabinet(userId); @@ -107,8 +104,6 @@ public void endUserLent(Long userId) { @Transactional public void endCabinetLent(List cabinetIds) { - log.debug("Called endCabinetsLent: {}", cabinetIds); - LocalDateTime now = LocalDateTime.now(); List cabinets = cabinetQueryService.findCabinetsWithLock(cabinetIds); List lentHistories = diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java index bb3f5215c..3e1cfdbc4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java @@ -1,32 +1,32 @@ package org.ftclub.cabinet.admin.search; +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; import org.ftclub.cabinet.dto.UserCabinetPaginationDto; import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.ftclub.cabinet.log.Logging; import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; - -@Slf4j @RestController @RequestMapping("/v4/admin/search") @RequiredArgsConstructor +@Logging public class AdminSearchController { + private final AdminSearchFacadeService adminSearchFacadeService; @GetMapping("/cabinets-simple") @AuthGuard(level = ADMIN_ONLY) public CabinetSimplePaginationDto getCabinetsSimpleInfo( @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsSimpleInfo {}", visibleNum); return adminSearchFacadeService.getCabinetsSimpleInfo(visibleNum); } @@ -34,7 +34,6 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfo( @AuthGuard(level = ADMIN_ONLY) public CabinetInfoPaginationDto getCabinetsInfo( @RequestParam("visibleNum") Integer visibleNum) { - log.info("Called getCabinetsInfo {}", visibleNum); return adminSearchFacadeService.getCabinetInfo(visibleNum); } @@ -42,7 +41,6 @@ public CabinetInfoPaginationDto getCabinetsInfo( @AuthGuard(level = ADMIN_ONLY) public UserProfilePaginationDto getUsersProfile( @RequestParam("name") String name, Pageable pageable) { - log.info("Called getUsersProfile {}", name); return adminSearchFacadeService.getUsersProfile(name, pageable); } @@ -50,7 +48,6 @@ public UserProfilePaginationDto getUsersProfile( @AuthGuard(level = ADMIN_ONLY) public UserCabinetPaginationDto getCabinetsLentInfo( @RequestParam("name") String name, Pageable pageable) { - log.info("Called getCabinetsLentInfo {}", name); return adminSearchFacadeService.getUserLentCabinetInfo(name, pageable); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java index eceb96301..f136758a8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java @@ -10,7 +10,6 @@ import java.util.Objects; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; import org.ftclub.cabinet.dto.CabinetDto; @@ -27,6 +26,8 @@ import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.lent.service.LentRedisService; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.mapper.UserMapper; @@ -41,7 +42,7 @@ @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) @Transactional(readOnly = true) public class AdminSearchFacadeService { @@ -57,8 +58,6 @@ public class AdminSearchFacadeService { private final LentMapper lentMapper; public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pageable) { - log.debug("Called getUsersProfile {}", partialName); - Page users = userQueryService.getUsers(partialName, pageable); List result = users.stream() .map(userMapper::toUserProfileDto).collect(toList()); @@ -66,8 +65,6 @@ public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pag } public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, Pageable pageable) { - log.debug("Called getUserLentCabinetInfo {}", partialName); - LocalDateTime now = LocalDateTime.now(); Page users = userQueryService.getUsers(partialName, pageable); List userIds = users.stream().map(User::getUserId).collect(toList()); @@ -96,8 +93,6 @@ public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, Pagea } public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { - log.debug("Called getCabinetSimpleInfo {}", visibleNum); - List cabinets = cabinetQueryService.findCabinets(visibleNum); List result = cabinets.stream() .map(cabinetMapper::toCabinetSimpleDto).collect(toList()); @@ -105,8 +100,6 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfo(Integer visibleNum) { } public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { - log.debug("Called getCabinetInfo {}", visibleNum); - List cabinets = cabinetQueryService.findCabinets(visibleNum); List cabinetIds = cabinets.stream().map(Cabinet::getCabinetId).collect(toList()); List lentHistories = diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java index 4af989983..e18d08ab8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java @@ -1,12 +1,16 @@ package org.ftclub.cabinet.admin.statistics; +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; import org.ftclub.cabinet.dto.LentsStatisticsResponseDto; import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; +import org.ftclub.cabinet.log.Logging; import org.springframework.data.domain.Pageable; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.GetMapping; @@ -14,16 +18,12 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.time.LocalDateTime; -import java.util.List; - -import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; - -@Slf4j @RestController @RequestMapping("/v4/admin/statistics") @RequiredArgsConstructor +@Logging public class AdminStatisticsController { + private final AdminStatisticsFacadeService adminStatisticsFacadeService; /** @@ -34,7 +34,6 @@ public class AdminStatisticsController { @GetMapping("/buildings/floors/cabinets") @AuthGuard(level = ADMIN_ONLY) public List getAllCabinetsInfo() { - log.info("Called getAllCabinetsInfo"); return adminStatisticsFacadeService.getAllCabinetsInfo(); } @@ -50,7 +49,6 @@ public List getAllCabinetsInfo() { public LentsStatisticsResponseDto getLentCountStatistics( @RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate, @RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) { - log.info("Called getCountOnLentAndReturn startDate : {} endDate : {}", startDate, endDate); return adminStatisticsFacadeService.getLentCountStatistics(startDate, endDate); } @@ -63,7 +61,6 @@ public LentsStatisticsResponseDto getLentCountStatistics( @GetMapping("/users/banned") @AuthGuard(level = ADMIN_ONLY) public BlockedUserPaginationDto getUsersBannedInfo(Pageable pageable) { - log.info("Called getUsersBannedInfo"); return adminStatisticsFacadeService.getAllBanUsers(pageable); } @@ -76,7 +73,6 @@ public BlockedUserPaginationDto getUsersBannedInfo(Pageable pageable) { @GetMapping("/users/overdue") @AuthGuard(level = ADMIN_ONLY) public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { - log.info("Called getOverdueUsers"); return adminStatisticsFacadeService.getOverdueUsers(pageable); } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java index 9492c8b65..3a5e693a4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; @@ -21,6 +20,8 @@ import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.BanHistory; @@ -32,10 +33,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) +@Logging(level = LogLevel.DEBUG) public class AdminStatisticsFacadeService { private final CabinetQueryService cabinetQueryService; @@ -46,8 +47,6 @@ public class AdminStatisticsFacadeService { private final UserMapper userMapper; public List getAllCabinetsInfo() { - log.debug("Called getAllCabinetsInfo"); - List buildings = cabinetQueryService.getAllBuildings(); List floors = cabinetQueryService.findAllFloorsByBuildings(buildings); return floors.stream().map(floor -> { @@ -63,8 +62,6 @@ public List getAllCabinetsInfo() { public LentsStatisticsResponseDto getLentCountStatistics( LocalDateTime startDate, LocalDateTime endDate) { - log.debug("Called getLentCountStatistics startDate : {} endDate : {}", startDate, endDate); - ExceptionUtil.throwIfFalse(startDate.isBefore(endDate), new ServiceException(ExceptionStatus.INVALID_ARGUMENT)); int lentStartCount = lentQueryService.countLentOnDuration(startDate, endDate); @@ -74,8 +71,6 @@ public LentsStatisticsResponseDto getLentCountStatistics( } public BlockedUserPaginationDto getAllBanUsers(Pageable pageable) { - log.debug("Called getAllBanUsers"); - LocalDateTime now = LocalDateTime.now(); Page banHistories = banHistoryQueryService.findActiveBanHistories(now, pageable); @@ -86,8 +81,6 @@ public BlockedUserPaginationDto getAllBanUsers(Pageable pageable) { } public OverdueUserCabinetPaginationDto getOverdueUsers(Pageable pageable) { - log.debug("Called getOverdueUsers"); - LocalDateTime now = LocalDateTime.now(); List lentHistories = lentQueryService.findOverdueLentHistories(now, pageable); List result = lentHistories.stream() diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java index 3d0ddfc1d..fa98e27c9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java @@ -1,25 +1,25 @@ package org.ftclub.cabinet.admin.user; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.LentExtensionType; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.newService.LentExtensionCommandService; import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; - @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class AdminLentExtensionFacadeService { + private final UserQueryService userQueryService; private final LentExtensionCommandService lentExtensionCommandService; // TODO : 더 세부적으로 구현해야함 public void assignLentExtension(String username) { - log.info("Called assigneLentExtension: {}", username); LocalDateTime now = LocalDateTime.now(); User user = userQueryService.getUserByName(username); lentExtensionCommandService.createLentExtension(user, LentExtensionType.ALL, now); diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java index fd63dd941..731b62a28 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java @@ -1,25 +1,31 @@ package org.ftclub.cabinet.admin.user; +import java.time.LocalDateTime; +import java.util.HashMap; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; import org.ftclub.cabinet.admin.lent.AdminLentFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.dto.ClubUserListDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; +import org.ftclub.cabinet.log.Logging; import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.*; - -import java.time.LocalDateTime; -import java.util.HashMap; +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; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor @RequestMapping("/v4/admin/users") -@Log4j2 +@Logging public class AdminUserController { - private final AdminFacadeService adminFacadeService; + private final AdminUserFacadeService adminUserFacadeService; private final AdminLentFacadeService adminLentFacadeService; private final AdminLentExtensionFacadeService adminLentExtensionFacadeService; @@ -32,7 +38,6 @@ public class AdminUserController { @DeleteMapping("/{userId}/ban-history") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { - log.info("Called deleteBanHistoryByUserId: {}", userId); adminUserFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); } @@ -44,7 +49,6 @@ public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { @PostMapping("/club") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void createClubUser(@RequestBody HashMap body) { - log.info("Called createClub"); String clubName = body.get("clubName"); adminUserFacadeService.createClubUser(clubName); } @@ -57,24 +61,19 @@ public void createClubUser(@RequestBody HashMap body) { @DeleteMapping("/club/{clubId}") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void deleteClubUser(@PathVariable("clubId") Long clubId) { - log.info("Called deleteClub"); adminUserFacadeService.deleteClubUser(clubId); } @GetMapping("/clubs") @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public ClubUserListDto findClubs(@RequestParam("page") Integer page, - @RequestParam("size") Integer size) { - log.info("Called getClubs"); - Pageable pageable = Pageable.ofSize(size).withPage(page); + public ClubUserListDto findClubs(@Valid Pageable pageable) { return adminUserFacadeService.findAllClubUsers(pageable); } @PatchMapping("/club/{clubId}") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void updateClubUser(@PathVariable("clubId") Long clubId, - @RequestBody HashMap body) { - log.info("Called updateClub"); + @RequestBody HashMap body) { String clubName = body.get("clubName"); adminUserFacadeService.updateClubUser(clubId, clubName); } @@ -107,14 +106,12 @@ public void updateClubUser(@PathVariable("clubId") Long clubId, @AuthGuard(level = AuthLevel.ADMIN_ONLY) public LentHistoryPaginationDto getLentHistoriesByUserId( @PathVariable("userId") Long userId, Pageable pageable) { - log.info("Called getLentHistoriesByUserId: {}", userId); return adminLentFacadeService.getUserLentHistories(userId, pageable); } @PostMapping("/lent-extensions/{user}") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void issueLentExtension(@PathVariable("user") String username) { - log.info("Called issueLentExtension"); adminLentExtensionFacadeService.assignLentExtension(username); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java index d20c9fb11..0cfa183e9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java @@ -1,9 +1,12 @@ package org.ftclub.cabinet.admin.user; +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.dto.ClubUserListDto; import org.ftclub.cabinet.dto.UserProfileDto; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.newService.BanHistoryCommandService; @@ -14,13 +17,11 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; - @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class AdminUserFacadeService { + private final UserQueryService userQueryService; private final UserCommandService userCommandService; private final BanHistoryQueryService banHistoryQueryService; @@ -29,7 +30,6 @@ public class AdminUserFacadeService { private final UserMapper userMapper; public void deleteRecentBanHistory(Long userId, LocalDateTime now) { - log.debug("Called deleteRecentBanHistory: {}", userId); banHistoryQueryService.findRecentActiveBanHistory(userId, now) .ifPresent(banHistory -> { banHistoryCommandService.deleteRecentBanHistory(banHistory, now); @@ -37,25 +37,21 @@ public void deleteRecentBanHistory(Long userId, LocalDateTime now) { } public User createClubUser(String clubName) { - log.debug("Called createClubUser: {}", clubName); return userCommandService.createClubUser(clubName); } public void deleteClubUser(Long userId) { - log.debug("Called deleteClubUser: {}", userId); User clubUser = userQueryService.getClubUser(userId); userCommandService.deleteClubUserById(clubUser); } public ClubUserListDto findAllClubUsers(Pageable pageable) { - log.debug("Called getClubUserList"); Page clubUsers = userQueryService.findClubUsers(pageable); List result = clubUsers.map(userMapper::toUserProfileDto).toList(); return userMapper.toClubUserListDto(result, clubUsers.getTotalElements()); } public void updateClubUser(Long userId, String clubName) { - log.debug("Called updateClubUser: {}", userId); User clubUser = userQueryService.getUser(userId); userCommandService.updateClubName(clubUser, clubName); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java index 72cbc0d61..774de34d0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java @@ -2,7 +2,6 @@ import java.util.List; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; @@ -11,6 +10,7 @@ import org.ftclub.cabinet.dto.CabinetPendingResponseDto; import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; import org.ftclub.cabinet.exception.ControllerException; +import org.ftclub.cabinet.log.Logging; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -22,7 +22,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/v4/cabinets") -@Log4j2 +@Logging public class CabinetController { private final CabinetFacadeService cabinetFacadeService; @@ -35,7 +35,6 @@ public class CabinetController { @GetMapping("/buildings/floors") @AuthGuard(level = AuthLevel.USER_OR_ADMIN) public List getBuildingFloorsResponse() { - log.info("Called getBuildingFloorsResponse"); return cabinetFacadeService.getBuildingFloorsResponse(); } @@ -52,7 +51,6 @@ public List getBuildingFloorsResponse() { public List getCabinetsPerSection( @PathVariable("building") String building, @PathVariable("floor") Integer floor) { - log.info("Called getCabinetsPerSection {} {}", building, floor); return cabinetFacadeService.getCabinetsPerSection(building, floor); } @@ -67,7 +65,6 @@ public List getCabinetsPerSection( @AuthGuard(level = AuthLevel.USER_OR_ADMIN) public CabinetInfoResponseDto getCabinetInfo( @PathVariable("cabinetId") Long cabinetId) { - log.info("Called getCabinetInfo {}", cabinetId); return cabinetFacadeService.getCabinetInfo(cabinetId); } @@ -79,7 +76,6 @@ public CabinetInfoResponseDto getCabinetInfo( @GetMapping("/buildings/{building}/pending") @AuthGuard(level = AuthLevel.USER_OR_ADMIN) public CabinetPendingResponseDto getPendingCabinets(@PathVariable("building") String building) { - log.info("Called getPendingCabinets"); return cabinetFacadeService.getPendingCabinets(building); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java index da4df0536..068da98cc 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetCommandService.java @@ -14,11 +14,14 @@ import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.repository.CabinetRepository; import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.utils.ExceptionUtil; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class CabinetCommandService { private final CabinetRepository cabinetRepository; diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index 48b6c0952..65aa47278 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -16,7 +16,6 @@ import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.Grid; @@ -24,7 +23,9 @@ import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetClubStatusRequestDto; +import org.ftclub.cabinet.dto.CabinetDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetPaginationDto; import org.ftclub.cabinet.dto.CabinetPendingResponseDto; import org.ftclub.cabinet.dto.CabinetPreviewDto; import org.ftclub.cabinet.dto.CabinetSimpleDto; @@ -32,21 +33,27 @@ import org.ftclub.cabinet.dto.CabinetStatusRequestDto; import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.LentHistoryDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.lent.service.LentRedisService; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.newService.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Log4j2 @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class CabinetFacadeService { private final CabinetCommandService cabinetCommandService; @@ -66,7 +73,6 @@ public class CabinetFacadeService { */ @Transactional(readOnly = true) public List getBuildingFloorsResponse() { - log.debug("getBuildingFloorsResponse"); List allBuildings = cabinetQueryService.findAllBuildings(); return allBuildings.stream() .map(building -> cabinetMapper.toBuildingFloorsDto(building, @@ -79,7 +85,6 @@ public List getBuildingFloorsResponse() { * {@inheritDoc} 사물함 id로 사물함 정보를 가져옵니다. active 대여기록이 없는경우, IN_SESSION 상태의 사물함인지 확인합니다. */ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { - log.debug("getCabinetInfo: {}", cabinetId); List lentDtos = new ArrayList<>(); List cabinetActiveLentHistories = lentQueryService.findCabinetActiveLentHistories( @@ -107,8 +112,6 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { */ public CabinetSimplePaginationDto getCabinetsSimpleInfoByVisibleNum(Integer visibleNum) { - log.debug("getCabinetsSimpleInfoByVisibleNum: {}", visibleNum); - List cabinets = cabinetQueryService.findCabinets(visibleNum); List cabinetSimpleDtos = cabinets.stream() @@ -132,7 +135,6 @@ public CabinetSimplePaginationDto getCabinetsSimpleInfoByVisibleNum(Integer visi @Transactional(readOnly = true) public List getCabinetsPerSection(String building, Integer floor) { - log.debug("getCabinetsPerSection: {}, {}", building, floor); List activeCabinetInfos = cabinetQueryService.findActiveCabinetInfoEntities( building, floor); Map> cabinetLentHistories = activeCabinetInfos.stream(). @@ -172,8 +174,6 @@ private String checkCabinetTitle(Cabinet cabinet, List lentHistorie @Transactional public CabinetPendingResponseDto getPendingCabinets(String building) { - log.debug("getPendingCabinets: {} ", building); - final LocalDate yesterday = LocalDateTime.now().minusDays(1).toLocalDate(); List pendingCabinets = cabinetQueryService.findPendingCabinetsNotLentTypeAndStatus( @@ -204,6 +204,41 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { }); return cabinetMapper.toCabinetPendingResponseDto(cabinetFloorMap); } + + public CabinetPaginationDto getCabinetPaginationByLentType(LentType lentType, + Pageable pageable) { + Page cabinets = cabinetQueryService.findAllByLentType(lentType, pageable); + List result = cabinets.stream() + .map(cabinetMapper::toCabinetDto).collect(Collectors.toList()); + return cabinetMapper.toCabinetPaginationDtoList(result, cabinets.getTotalElements()); + } + + public CabinetPaginationDto getCabinetPaginationByStatus(CabinetStatus status, + Pageable pageable) { + Page cabinets = cabinetQueryService.findAllByStatus(status, pageable); + List result = cabinets.stream() + .map(cabinetMapper::toCabinetDto).collect(Collectors.toList()); + return cabinetMapper.toCabinetPaginationDtoList(result, cabinets.getTotalElements()); + } + + public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, + Pageable pageable) { + Page cabinets = cabinetQueryService.findAllByVisibleNum(visibleNum, pageable); + List result = cabinets.stream() + .map(cabinetMapper::toCabinetDto).collect(Collectors.toList()); + return cabinetMapper.toCabinetPaginationDtoList(result, cabinets.getTotalElements()); + } + + public LentHistoryPaginationDto getLentHistoryPagination(Long cabinetId, Pageable pageable) { + Page lentHistories = lentQueryService.findAllWithUserAndCabinetByCabinetId( + cabinetId, pageable); + List result = lentHistories.stream() + .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) + .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) + .collect(Collectors.toList()); + return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); + } + /*--------------------------------------------CUD--------------------------------------------*/ /** @@ -214,7 +249,6 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { */ @Transactional public void updateCabinetStatusNote(Long cabinetId, String statusNote) { - log.debug("updateCabinetStatusNote: {}, {}", cabinetId, statusNote); Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); cabinetCommandService.changeCabinetStatusNote(cabinet, statusNote); } @@ -228,14 +262,12 @@ public void updateCabinetStatusNote(Long cabinetId, String statusNote) { */ @Transactional public void updateCabinetTitle(Long cabinetId, String title) { - log.debug("updateCabinetTitle: {}, {}", cabinetId, title); Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); cabinetCommandService.updateTitle(cabinet, title); } @Transactional public void updateCabinetGrid(Long cabinetId, Integer row, Integer col) { - log.debug("updateCabinetGrid: {}, {}, {}", cabinetId, row, col); Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); cabinetCommandService.updateGrid(cabinet, Grid.of(row, col)); } @@ -248,7 +280,6 @@ public void updateCabinetGrid(Long cabinetId, Integer row, Integer col) { */ @Transactional public void updateCabinetVisibleNum(Long cabinetId, Integer visibleNum) { - log.debug("updateCabinetVisibleNum: {}, {}", cabinetId, visibleNum); Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); cabinetCommandService.updateVisibleNum(cabinet, visibleNum); } @@ -258,8 +289,6 @@ public void updateCabinetVisibleNum(Long cabinetId, Integer visibleNum) { */ @Transactional public void updateCabinetBundleStatus(CabinetStatusRequestDto cabinetStatusRequestDto) { - log.debug("updateCabinetBundleStatus: {}", cabinetStatusRequestDto.getCabinetIds()); - CabinetStatus status = cabinetStatusRequestDto.getStatus(); LentType lentType = cabinetStatusRequestDto.getLentType(); @@ -284,8 +313,6 @@ public void updateCabinetBundleStatus(CabinetStatusRequestDto cabinetStatusReque */ @Transactional public void updateClub(CabinetClubStatusRequestDto dto) { - log.debug("updateClub: {}", dto); - Cabinet cabinet = cabinetQueryService.getUserActiveCabinetWithLock(dto.getCabinetId()); Cabinet activeCabinetByUserId = cabinetQueryService.findActiveCabinetByUserId( @@ -301,6 +328,4 @@ public void updateClub(CabinetClubStatusRequestDto dto) { cabinetCommandService.updateClubStatus(cabinet, clubName, dto.getStatusNote()); } - - } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java index cd2eeaafd..fd5be2c8f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetQueryService.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; @@ -11,19 +10,21 @@ import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -@Log4j2 @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class CabinetQueryService { private final CabinetRepository cabinetRepository; public int countCabinets(CabinetStatus status, Integer floor) { - log.debug("Called countCabinets {} {}", status, floor); return cabinetRepository.countByStatusAndFloor(status, floor); } @@ -31,7 +32,6 @@ public int countCabinets(CabinetStatus status, Integer floor) { public List getAllBuildings() { - log.debug("Called getAllBuildings"); return cabinetRepository.getAllBuildings() .orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_BUILDING)); } @@ -39,82 +39,80 @@ public List getAllBuildings() { /*------------------------------------------ FIND -------------------------------------------*/ public List findAllBuildings() { - log.debug("Called findAllBuildings"); return cabinetRepository.findAllBuildings(); } public List findAllFloorsByBuilding(String building) { - log.debug("Called findAllFloorsByBuilding {}", building); return cabinetRepository.findAllFloorsByBuilding(building); } public List findAllFloorsByBuildings(List buildings) { - log.debug("Called findAllFloorsByBuildings"); return cabinetRepository.findAllFloorsByBuildings(buildings); } public Cabinet findCabinets(Long cabinetId) { - log.debug("Called findCabinets: {}", cabinetId); Optional cabinet = cabinetRepository.findById(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } public Cabinet findCabinetsWithLock(Long cabinetId) { - log.debug("Called findCabinetsWithLock: {}", cabinetId); Optional cabinet = cabinetRepository.findByIdWithLock(cabinetId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } public List findCabinets(Integer visibleNum) { - log.debug("Called findCabinets: {}", visibleNum); return cabinetRepository.findAllByVisibleNum(visibleNum); } public Page findCabinets(Integer visibleNum, PageRequest pageable) { - log.debug("Called findCabinets: {}", visibleNum); return cabinetRepository.findPaginationByVisibleNum(visibleNum, pageable); } public List findCabinetsWithLock(List cabinetIds) { - log.debug("Called findCabinetsWithLock: {}", cabinetIds); return cabinetRepository.findAllByIdsWithLock(cabinetIds); } public Cabinet getUserActiveCabinetWithLock(Long userId) { - log.debug("Called getUserActiveCabinetWithLock: {}", userId); Optional cabinet = cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNullWithLock(userId); return cabinet.orElseThrow(() -> new ServiceException(ExceptionStatus.NOT_FOUND_CABINET)); } public Cabinet findUserActiveCabinet(Long userId) { - log.debug("Called findUserActiveCabinet: {}", userId); Optional cabinet = cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId); return cabinet.orElse(null); } public List findAllCabinetsByBuildingAndFloor(String building, Integer floor) { - log.debug("Called findAllCabinetsByBuildingAndFloor"); return cabinetRepository.findAllByBuildingAndFloor(building, floor); } public List findActiveCabinetInfoEntities(String building, Integer floor) { - log.debug("Called findActiveCabinetInfoEntities"); return cabinetRepository.findCabinetsActiveLentHistoriesByBuildingAndFloor(building, floor); } public List findPendingCabinetsNotLentTypeAndStatus( String building, LentType lentType, List cabinetStatuses) { - log.debug("Called findPendingCabinetsNotLentTypeAndStatus"); return cabinetRepository.findAllByBuildingAndLentTypeNotAndStatusIn(building, lentType, cabinetStatuses); } public Cabinet findActiveCabinetByUserId(Long userId) { - log.debug("Called findActiveLentCabinetByUserId: {}", userId); return cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId).orElse(null); } + + public Page findAllByLentType(LentType lentType, Pageable pageable) { + return cabinetRepository.findPaginationByLentType(lentType, pageable); + } + + public Page findAllByStatus(CabinetStatus status, Pageable pageable) { + return cabinetRepository.findPaginationByStatus(status, pageable); + } + + public Page findAllByVisibleNum(Integer visibleNum, Pageable pageable) { + return cabinetRepository.findPaginationByVisibleNum(visibleNum, pageable); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java index 902510088..96dc98442 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/controller/LentController.java @@ -2,7 +2,6 @@ import javax.validation.Valid; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.dto.CabinetInfoRequestDto; @@ -12,6 +11,7 @@ import org.ftclub.cabinet.dto.ShareCodeDto; import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.UserSession; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -27,7 +27,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/v4/lent") -@Log4j2 +@Logging public class LentController { private final LentFacadeService lentFacadeService; @@ -37,7 +37,6 @@ public class LentController { public void startLentCabinet( @UserSession UserSessionDto user, @PathVariable Long cabinetId) { - log.info("Called startLentCabinet user: {}, cabinetId: {}", user, cabinetId); lentFacadeService.startLentCabinet(user.getUserId(), cabinetId); } @@ -47,7 +46,6 @@ public void startLentShareCabinet( @UserSession UserSessionDto user, @PathVariable Long cabinetId, @Valid @RequestBody ShareCodeDto shareCodeDto) { - log.info("Called startLentShareCabinet user: {}, cabinetId: {}", user, cabinetId); lentFacadeService.startLentShareCabinet(user.getUserId(), cabinetId, shareCodeDto.getShareCode()); } @@ -57,7 +55,6 @@ public void startLentShareCabinet( public void cancelLentShareCabinet( @UserSession UserSessionDto user, @PathVariable Long cabinetId) { - log.info("Called cancelLentShareCabinet user: {}, cabinetId: {}", user, cabinetId); lentFacadeService.cancelShareCabinetLent(user.getUserId(), cabinetId); } @@ -65,7 +62,6 @@ public void cancelLentShareCabinet( @AuthGuard(level = AuthLevel.USER_ONLY) public void endLent( @UserSession UserSessionDto userSessionDto) { - log.info("Called endLent user: {}", userSessionDto); lentFacadeService.endUserLent(userSessionDto.getUserId(), null); } @@ -74,8 +70,6 @@ public void endLent( public void endLentWithMemo( @UserSession UserSessionDto userSessionDto, @Valid @RequestBody LentEndMemoDto lentEndMemoDto) { - log.info("Called endLentWithMemo user: {}, lentEndMemoDto: {}", - userSessionDto, lentEndMemoDto); lentFacadeService.endUserLent(userSessionDto.getUserId(), lentEndMemoDto.getCabinetMemo()); } @@ -84,8 +78,6 @@ public void endLentWithMemo( public void updateCabinetInfo( @UserSession UserSessionDto user, @RequestBody CabinetInfoRequestDto cabinetInfoRequestDto) { - log.info("Called updateCabinetInfo user: {}, cabinetInfoRequestDto: {}", user, - cabinetInfoRequestDto); lentFacadeService.updateLentCabinetInfo(user.getUserId(), cabinetInfoRequestDto.getTitle(), cabinetInfoRequestDto.getMemo()); } @@ -94,7 +86,6 @@ public void updateCabinetInfo( @AuthGuard(level = AuthLevel.USER_ONLY) public ResponseEntity getMyLentInfo( @UserSession UserSessionDto user) { - log.info("Called getMyLentInfo user: {}", user); MyCabinetResponseDto myCabinetResponseDto = lentFacadeService.getMyLentInfo(user); if (myCabinetResponseDto == null) { return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); @@ -106,7 +97,6 @@ public ResponseEntity getMyLentInfo( @AuthGuard(level = AuthLevel.USER_ONLY) public LentHistoryPaginationDto getMyLentLog( @UserSession UserSessionDto user, @Valid Pageable pageable) { - log.info("Called getMyLentLog user: {}, pageable: {}", user, pageable); return lentFacadeService.getMyLentLog(user, pageable); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java index a6a487006..30346a4e6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentOptionalFetcher.java @@ -37,7 +37,7 @@ public List findAllActiveLentByCabinetId(Long cabinetId) { public Page findPaginationByCabinetId(Long cabinetId, PageRequest pageable) { log.debug("Called findPaginationByCabinetId: {}", cabinetId); - return lentRepository.findPaginationByCabinetId(cabinetId, pageable); + return lentRepository.findPaginationByCabinetIdJoinCabinetAndUser(cabinetId, pageable); } public Page findPaginationByUserId(Long userId, PageRequest pageable) { diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java index 1d8302818..09a844240 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java @@ -8,10 +8,11 @@ import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.config.CabinetProperties; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; @@ -19,7 +20,7 @@ import org.springframework.stereotype.Component; @Component -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class LentRedis { private static final String MAX_SHARE_CODE_TRY = "3"; @@ -53,8 +54,6 @@ public LentRedis(RedisTemplate valueHashRedisTemplate, * @param shareCode : 초대코드 */ public void attemptJoinCabinet(String cabinetId, String userId, String shareCode) { - log.debug("called saveUserInRedis: {}, {}, {}", cabinetId, userId, shareCode); - String savedCode = shadowKeyTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX); if (Objects.equals(savedCode, shareCode)) { shareCabinetTemplate.put(cabinetId, userId, USER_ENTERED); @@ -70,41 +69,29 @@ public void attemptJoinCabinet(String cabinetId, String userId, String shareCode } public boolean isUserInCabinet(String cabinetId, String userId) { - log.debug("called isUserInRedis: {}, {}", cabinetId, userId); - return shareCabinetTemplate.hasKey(cabinetId, userId); } public Long countUserInCabinet(String cabinetId) { - log.debug("called getSizeOfUsersInSession: {}", cabinetId); - Collection joinUsers = shareCabinetTemplate.entries(cabinetId).values(); return joinUsers.parallelStream() .filter(value -> Objects.nonNull(value) && value.equals(USER_ENTERED)).count(); } public String getAttemptCountInCabinet(String cabinetId, String userId) { - log.debug("called getPwTrialCountInRedis: {}, {}", cabinetId, userId); - return shareCabinetTemplate.get(cabinetId, userId); } public boolean isExistShadowKey(String cabinetId) { - log.debug("called isShadowKey: {}", cabinetId); - Boolean isExist = shadowKeyTemplate.hasKey(cabinetId + SHADOW_KEY_SUFFIX); return Objects.nonNull(isExist) && isExist; } public String getShareCode(String cabinetId) { - log.debug("called getShareCode: {}", cabinetId); - return shadowKeyTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX); } public String setShadowKey(String cabinetId) { - log.debug("called setShadowKey: {}", cabinetId); - Random rand = new Random(); String shareCode = Integer.toString(1000 + rand.nextInt(9000)); String shadowKey = cabinetId + SHADOW_KEY_SUFFIX; @@ -114,39 +101,27 @@ public String setShadowKey(String cabinetId) { } public void deleteShadowKey(String cabinetId) { - log.debug("called deleteShadowKey: {}", cabinetId); - shadowKeyTemplate.delete(cabinetId + SHADOW_KEY_SUFFIX); } public void deleteUserInCabinet(String cabinetId, String userId) { - log.debug("called deleteUserInRedis: {}, {}", cabinetId, userId); - shareCabinetTemplate.delete(cabinetId, userId); userCabinetTemplate.getOperations().delete(userId + VALUE_KEY_SUFFIX); } public void deleteCabinet(String cabinetId) { - log.debug("called deleteCabinetIdInRedis: {}", cabinetId); - shareCabinetTemplate.getOperations().delete(cabinetId); } public void deleteUser(Long userId) { - log.debug("called deleteUserIdInRedis: {}", userId); - userCabinetTemplate.getOperations().delete(userId + VALUE_KEY_SUFFIX); } public String findCabinetByUser(String userId) { - log.debug("Called findCabinetIdByUserIdInRedis: {}", userId); - return userCabinetTemplate.get(userId + VALUE_KEY_SUFFIX); } public List getAllUserInCabinet(String cabinetId) { - log.debug("Called getUserIdsByCabinetIdInRedis: {}", cabinetId); - Map entries = shareCabinetTemplate.entries(cabinetId); return entries.entrySet().stream() .filter(entry -> entry.getValue().equals(USER_ENTERED)) @@ -154,7 +129,6 @@ public List getAllUserInCabinet(String cabinetId) { } public LocalDateTime getCabinetExpiredAt(String cabinetId) { - log.debug("Called getSessionExpiredAtInRedis: {}", cabinetId); if (this.isExistShadowKey(cabinetId)) { String shadowKey = cabinetId + SHADOW_KEY_SUFFIX; long expire = shadowKeyTemplate.getExpire(shadowKey, TimeUnit.SECONDS).longValue(); @@ -166,12 +140,10 @@ public LocalDateTime getCabinetExpiredAt(String cabinetId) { /*---------------------------------------- Caching -----------------------------------------*/ public void setPreviousUserName(String cabinetId, String userName) { - log.debug("Called setPreviousUser: {}, {}", cabinetId, userName); previousUserTemplate.set(cabinetId + PREVIOUS_USER_SUFFIX, userName); } public String getPreviousUserName(String cabinetId) { - log.debug("Called getPreviousUser: {}", cabinetId); return previousUserTemplate.get(cabinetId + PREVIOUS_USER_SUFFIX); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 734c031f3..6f1e03c9a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -130,7 +130,12 @@ int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDat * @param pageable pagination 정보 * @return {@link LentHistory}의 {@link Page} */ - Page findPaginationByCabinetId( + @Query("SELECT lh " + + "FROM LentHistory lh " + + "LEFT JOIN FETCH lh.user u " + + "LEFT JOIN FETCH lh.cabinet c " + + "WHERE lh.cabinetId = :cabinetId ") + Page findPaginationByCabinetIdJoinCabinetAndUser( @Param("cabinetId") Long cabinetId, Pageable pageable); /** diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java index 0008fc955..018040743 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java @@ -4,32 +4,27 @@ import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class LentCommandService { private final LentRepository lentRepository; public void startLent(Long userId, Long cabinetId, LocalDateTime now, LocalDateTime expiredAt) { - log.info("startLent userId: {}, cabinetId: {}, now: {}, expiredAt: {}", - userId, cabinetId, now, expiredAt); - LentHistory lentHistory = LentHistory.of(now, expiredAt, userId, cabinetId); lentRepository.save(lentHistory); } public void startLent(List userIds, Long cabinetId, LocalDateTime now, LocalDateTime expiredAt) { - log.info("startLent userIds: {}, cabinetId: {}, now: {}, expiredAt: {}", - userIds, cabinetId, now, expiredAt); - userIds.forEach(userId -> { LentHistory lentHistory = LentHistory.of(now, expiredAt, userId, cabinetId); lentRepository.save(lentHistory); @@ -37,15 +32,11 @@ public void startLent(List userIds, Long cabinetId, LocalDateTime now, } public void endLent(LentHistory lentHistory, LocalDateTime now) { - log.info("endLent lentHistory: {}, now: {}", lentHistory, now); - lentHistory.endLent(now); lentRepository.save(lentHistory); } public void endLent(List lentHistories, LocalDateTime now) { - log.info("endLent lentHistories: {}, now: {}", lentHistories, now); - lentHistories.forEach(lentHistory -> lentHistory.isEndLentValid(now)); List userIds = lentHistories.stream() .map(LentHistory::getUserId).collect(Collectors.toList()); @@ -53,8 +44,6 @@ public void endLent(List lentHistories, LocalDateTime now) { } public void setExpiredAt(LentHistory lentHistory, LocalDateTime expiredAt) { - log.info("setExpiredAt lentHistory: {}, expiredAt: {}", lentHistory, expiredAt); - lentHistory.setExpiredAt(expiredAt); lentRepository.save(lentHistory); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index bb1ca4b82..b3c91ba4e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -9,7 +9,6 @@ import java.util.Objects; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -25,6 +24,8 @@ import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.dto.UserVerifyRequestDto; import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.user.domain.BanHistory; @@ -41,9 +42,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Slf4j @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class LentFacadeService { private final LentRedisService lentRedisService; @@ -65,8 +66,6 @@ public class LentFacadeService { @Transactional(readOnly = true) public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, Pageable pageable) { - log.debug("Called getMyLentLog: {}", user.getName()); - Page lentHistories = lentQueryService.findUserActiveLentHistories(user.getUserId(), pageable); List result = lentHistories.stream() @@ -81,8 +80,6 @@ public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, Pageable pagea @Transactional(readOnly = true) public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { - log.debug("Called getMyLentInfo: {}", user.getName()); - Cabinet userActiveCabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); Long cabinetId; List lentDtoList; @@ -112,8 +109,6 @@ public MyCabinetResponseDto getMyLentInfo(@UserSession UserSessionDto user) { @Transactional(readOnly = true) public List getAllActiveLentHistories() { - log.debug("Called getAllActiveLentHistories"); - LocalDateTime now = LocalDateTime.now(); List lentHistories = lentQueryService.findAllActiveLentHistories(); return lentHistories.stream() @@ -129,8 +124,6 @@ public List getAllActiveLentHistories() { @Transactional public void startLentCabinet(Long userId, Long cabinetId) { - log.debug("Called startLentCabinet: {}, {}", userId, cabinetId); - LocalDateTime now = LocalDateTime.now(); User user = userQueryService.getUser(userId); Cabinet cabinet = cabinetQueryService.findCabinetsWithLock(cabinetId); @@ -153,8 +146,6 @@ public void startLentCabinet(Long userId, Long cabinetId) { @Transactional public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) { - log.info("Called startLentShareCabinet: {}, {}, {}", userId, cabinetId, shareCode); - LocalDateTime now = LocalDateTime.now(); Cabinet cabinet = cabinetQueryService.findCabinetsWithLock(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); @@ -195,8 +186,6 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) @Transactional public void startLentClubCabinet(Long userId, Long cabinetId) { - log.debug("Called startLentClubCabinet: {}, {}", userId, cabinetId); - LocalDateTime now = LocalDateTime.now(); // TODO : ClubUser 추가 이후 userId로 ClubUser 검증 로직 필요(Policy) Cabinet cabinet = cabinetQueryService.findCabinets(cabinetId); @@ -214,8 +203,6 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { @Transactional public void endUserLent(Long userId, String memo) { - log.debug("Called endLentCabinet: {}", userId); - LocalDateTime now = LocalDateTime.now(); LentHistory userLentHistory = lentQueryService.getUserActiveLentHistoryWithLock(userId); List cabinetLentHistories = @@ -249,8 +236,6 @@ public void endUserLent(Long userId, String memo) { @Transactional public void updateLentCabinetInfo(Long userId, String title, String memo) { - log.debug("Called updateLentCabinetInfo: {}, {}, {}", userId, title, memo); - Cabinet cabinet = cabinetQueryService.getUserActiveCabinetWithLock(userId); cabinetCommandService.updateTitle(cabinet, title); cabinetCommandService.updateMemo(cabinet, memo); @@ -258,8 +243,6 @@ public void updateLentCabinetInfo(Long userId, String title, String memo) { @Transactional public void cancelShareCabinetLent(Long userId, Long cabinetId) { - log.debug("Called cancelShareCabinetLent: {}, {}", userId, cabinetId); - lentRedisService.deleteUserInCabinetSession(cabinetId, userId); if (lentRedisService.isCabinetSessionEmpty(cabinetId)) { Cabinet cabinet = cabinetQueryService.findCabinetsWithLock(cabinetId); @@ -269,8 +252,6 @@ public void cancelShareCabinetLent(Long userId, Long cabinetId) { @Transactional public void shareCabinetSessionExpired(Long cabinetId) { - log.debug("Called shareCabinetSessionExpired: {}", cabinetId); - Cabinet cabinet = cabinetQueryService.findCabinetsWithLock(cabinetId); List usersInCabinetSession = lentRedisService.getUsersInCabinetSession(cabinetId); if (lentPolicyService.verifyUserCountOnShareCabinet(usersInCabinetSession.size())) { diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java index 67b071740..bfa6c395b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java @@ -4,7 +4,6 @@ import java.time.format.DateTimeFormatter; import java.util.Objects; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.config.CabinetProperties; @@ -15,15 +14,17 @@ import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.domain.LentPolicyStatus; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.utils.DateUtil; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class LentPolicyService { private final CabinetProperties cabinetProperties; @@ -70,8 +71,6 @@ private void handlePolicyStatus(LentPolicyStatus status, LocalDateTime unbannedA } public void verifyUserForLent(UserVerifyRequestDto requestDto) { - log.debug("Called verifyUser"); - LocalDateTime now = LocalDateTime.now(); LentPolicyStatus status = LentPolicyStatus.FINE; if (!requestDto.getUserRole().equals(UserRole.USER)) { @@ -104,8 +103,6 @@ public void verifyUserForLent(UserVerifyRequestDto requestDto) { } public void verifyCabinetForLent(CabinetStatus cabinetStatus, LentType lentType) { - log.debug("Called verifyCabinet"); - LentPolicyStatus status = LentPolicyStatus.FINE; if (lentType.equals(LentType.CLUB)) { status = LentPolicyStatus.LENT_CLUB; @@ -124,16 +121,12 @@ public void verifyCabinetForLent(CabinetStatus cabinetStatus, LentType lentType) } public void verifyCabinetType(LentType cabinetLentType, LentType lentType) { - log.debug("Called verifyCabinetType"); - if (!cabinetLentType.equals(lentType)) { throw new ServiceException(ExceptionStatus.INVALID_LENT_TYPE); } } public void verifyCabinetLentCount(LentType lentType, int maxUserCount, int lentCount) { - log.debug("Called verifyCabinetLentCount"); - int maxLentCount = 1; if (lentType.equals(LentType.SHARE)) { maxLentCount = cabinetProperties.getShareMaxUserCount().intValue(); @@ -148,8 +141,6 @@ public void verifyCabinetLentCount(LentType lentType, int maxUserCount, int lent public LocalDateTime generateExpirationDate(LocalDateTime now, LentType lentType, int lentUserCount) { - log.debug("Called generateExpirationDate"); - if (!DateUtil.isSameDay(now)) { throw new ServiceException(ExceptionStatus.INVALID_ARGUMENT); } @@ -169,8 +160,6 @@ public LocalDateTime generateExpirationDate(LocalDateTime now, LentType lentType public LocalDateTime adjustSharCabinetExpirationDate(int userCount, LocalDateTime now, LentHistory lentHistory) { - log.debug("Called adjustSharCabinetExpirationDate"); - double daysUntilExpiration = lentHistory.getDaysUntilExpiration(now) * -1; double secondsUntilExpiration = daysUntilExpiration * 24 * 60 * 60; long secondsRemaining = Math.round(secondsUntilExpiration * userCount / (userCount + 1)); @@ -178,16 +167,12 @@ public LocalDateTime adjustSharCabinetExpirationDate(int userCount, LocalDateTim } public boolean verifyUserCountOnShareCabinet(int userCount) { - log.debug("Called verifyUserCountOnShareCabinet"); - long minUserCount = cabinetProperties.getShareMinUserCount(); long maxUserCount = cabinetProperties.getShareMaxUserCount(); return minUserCount <= userCount && userCount <= maxUserCount; } public void verifyAttemptCountOnShareCabinet(Long attemptCount) { - log.debug("Called verifyAttemptCountOnShareCabinet"); - LentPolicyStatus status = LentPolicyStatus.FINE; Long shareMaxAttemptCount = cabinetProperties.getShareMaxAttemptCount(); if (Objects.nonNull(attemptCount) && attemptCount >= shareMaxAttemptCount) { diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 74c7db9de..b39b9213c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -11,12 +11,15 @@ import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class LentQueryService { private final LentRepository lentRepository; @@ -75,4 +78,9 @@ public List findOverdueLentHistories(LocalDateTime now, Pageable pa public List findAllByCabinetIdsAfterDate(LocalDate date, List cabinetIds) { return lentRepository.findAllByCabinetIdsAfterDate(date, cabinetIds); } + + public Page findAllWithUserAndCabinetByCabinetId(Long cabinetId, + Pageable pageable) { + return lentRepository.findPaginationByCabinetIdJoinCabinetAndUser(cabinetId, pageable); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java index 5dae08228..5bca6346f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentRedisService.java @@ -6,18 +6,19 @@ import java.util.Objects; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.config.CabinetProperties; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentRedis; import org.ftclub.cabinet.lent.repository.LentRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class LentRedisService { private final LentRedis lentRedis; @@ -25,16 +26,12 @@ public class LentRedisService { private final CabinetProperties cabinetProperties; public List findUsersInCabinet(Long cabinetId) { - log.debug("findUsersInCabinet: {}", cabinetId); - List userIdList = lentRedis.getAllUserInCabinet( cabinetId.toString()); return userIdList.stream().map(Long::valueOf).collect(Collectors.toList()); } public Long findCabinetJoinedUser(Long userId) { - log.debug("findCabinetJoinedUser: {}", userId); - String cabinetId = lentRedis.findCabinetByUser(userId.toString()); if (Objects.isNull(cabinetId)) { return null; @@ -43,32 +40,22 @@ public Long findCabinetJoinedUser(Long userId) { } public String getShareCode(Long cabinetId) { - log.debug("getShareCode: {}", cabinetId); - return lentRedis.getShareCode(cabinetId.toString()); } public LocalDateTime getSessionExpired(Long cabinetId) { - log.debug("getSessionExpired: {}", cabinetId); - return lentRedis.getCabinetExpiredAt(cabinetId.toString()); } public String createCabinetSession(Long cabinetId) { - log.debug("createCabinetSession: {}", cabinetId); - return lentRedis.setShadowKey(cabinetId.toString()); } public boolean isInCabinetSession(Long cabinetId) { - log.debug("isInCabinetSession: {}", cabinetId); - return lentRedis.isExistShadowKey(cabinetId.toString()); } public Long getAttemptCountOnShareCabinet(Long cabinetId, Long userId) { - log.debug("getAttemptCountOnShareCabinet: {}, {}", cabinetId, userId); - String attemptCount = lentRedis.getAttemptCountInCabinet(cabinetId.toString(), userId.toString()); if (Objects.isNull(attemptCount)) { @@ -78,34 +65,24 @@ public Long getAttemptCountOnShareCabinet(Long cabinetId, Long userId) { } public void joinCabinetSession(Long cabinetId, Long userId, String shareCode) { - log.debug("joinCabinetSession: {}, {}, {}", cabinetId, userId, shareCode); - lentRedis.attemptJoinCabinet(cabinetId.toString(), userId.toString(), shareCode); } public boolean isCabinetSessionEmpty(Long cabinetId) { - log.debug("isCabinetSessionEmpty: {}", cabinetId); - return lentRedis.countUserInCabinet(cabinetId.toString()) == 0; } public boolean isCabinetSessionFull(Long cabinetId) { - log.debug("isCabinetSessionFull: {}", cabinetId); - Long userCount = lentRedis.countUserInCabinet(cabinetId.toString()); return Objects.equals(userCount, cabinetProperties.getShareMaxUserCount()); } public List getUsersInCabinetSession(Long cabinetId) { - log.debug("getUsersInCabinetSession: {}", cabinetId); - List userIdList = lentRedis.getAllUserInCabinet(cabinetId.toString()); return userIdList.stream().map(Long::valueOf).collect(Collectors.toList()); } public void deleteUserInCabinetSession(Long cabinetId, Long userId) { - log.debug("deleteUserInCabinetSession: {}, {}", cabinetId, userId); - String cabinetIdString = cabinetId.toString(); String userIdString = userId.toString(); if (!lentRedis.isUserInCabinet(cabinetIdString, userIdString)) { @@ -118,8 +95,6 @@ public void deleteUserInCabinetSession(Long cabinetId, Long userId) { } public void confirmCabinetSession(Long cabinetId, List userIdList) { - log.debug("confirmCabinetSession: {}, {}", cabinetId, userIdList); - String cabinetIdString = cabinetId.toString(); userIdList.stream().map(Object::toString) .forEach(userId -> lentRedis.deleteUserInCabinet(cabinetIdString, userId)); @@ -127,8 +102,6 @@ public void confirmCabinetSession(Long cabinetId, List userIdList) { } public void clearCabinetSession(Long cabinetId) { - log.debug("clearCabinetSession: {}", cabinetId); - String cabinetIdString = cabinetId.toString(); List userList = lentRedis.getAllUserInCabinet(cabinetIdString); lentRedis.deleteShadowKey(cabinetIdString); @@ -137,8 +110,6 @@ public void clearCabinetSession(Long cabinetId) { } public String getPreviousUserName(Long cabinetId) { - log.debug("getPreviousUserName: {}", cabinetId); - String previousUserName = lentRedis.getPreviousUserName(cabinetId.toString()); if (Objects.isNull(previousUserName)) { List cabinetLentHistories = @@ -155,8 +126,6 @@ public String getPreviousUserName(Long cabinetId) { } public void setPreviousUserName(Long cabinetId, String userName) { - log.debug("setPreviousUserName: {}, {}", cabinetId, userName); - lentRedis.setPreviousUserName(cabinetId.toString(), userName); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/log/LogAspect.java b/backend/src/main/java/org/ftclub/cabinet/log/LogAspect.java new file mode 100644 index 000000000..5e3646882 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/log/LogAspect.java @@ -0,0 +1,74 @@ +package org.ftclub.cabinet.log; + +import java.lang.reflect.Method; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.stereotype.Component; + +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class LogAspect { + + private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); + + + @Before(value = "@within(logging)", argNames = "joinPoint,logging") + public void classLogAdvice(JoinPoint joinPoint, Logging logging) { + printLog(joinPoint, logging); + } + + @Before(value = "@annotation(logging)", argNames = "joinPoint,logging") + public void methodLogAdvice(JoinPoint joinPoint, Logging logging) { + printLog(joinPoint, logging); + } + + private void printLog(JoinPoint joinPoint, Logging logging) { + if (logging == null) { + return; + } + LogLevel level = logging.level(); + + String[] classPath = joinPoint.getSignature().getDeclaringType().getName().split("\\."); + String callerClass = classPath[classPath.length - 1]; + Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); + String methodName = method.getName(); + Object[] args = joinPoint.getArgs(); + String[] parameterNames = discoverer.getParameterNames(method); + + StringBuilder sb = new StringBuilder(); + sb.append(callerClass).append(" - "); + sb.append("Called ").append(methodName).append(" "); + if (Objects.nonNull(parameterNames)) { + for (int i = 0; i < args.length; i++) { + sb.append(parameterNames[i]).append(": ").append(args[i]).append(", "); + } + } + sb.delete(sb.length() - 2, sb.length() - 1); + switch (level) { + case TRACE: + log.trace(sb.toString()); + break; + case DEBUG: + log.debug(sb.toString()); + break; + case WARN: + log.warn(sb.toString()); + break; + case ERROR: + log.error(sb.toString()); + break; + default: + log.info(sb.toString()); + break; + } + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/log/LogLevel.java b/backend/src/main/java/org/ftclub/cabinet/log/LogLevel.java new file mode 100644 index 000000000..9d7f368d2 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/log/LogLevel.java @@ -0,0 +1,6 @@ +package org.ftclub.cabinet.log; + +public enum LogLevel { + + TRACE, DEBUG, INFO, WARN, ERROR +} diff --git a/backend/src/main/java/org/ftclub/cabinet/log/Logging.java b/backend/src/main/java/org/ftclub/cabinet/log/Logging.java new file mode 100644 index 000000000..7246b8c9f --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/log/Logging.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.log; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Logging { + + LogLevel level() default LogLevel.INFO; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java index 0ea765011..5675ce5bf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java @@ -1,13 +1,13 @@ package org.ftclub.cabinet.user.controller; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.dto.LentExtensionPaginationDto; import org.ftclub.cabinet.dto.MyProfileResponseDto; import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.UserSession; import org.ftclub.cabinet.user.service.UserFacadeService; import org.springframework.web.bind.annotation.GetMapping; @@ -22,7 +22,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/v4/users") -@Log4j2 +@Logging public class UserController { private final UserFacadeService userFacadeService; @@ -36,7 +36,6 @@ public class UserController { @GetMapping("/me") @AuthGuard(level = AuthLevel.USER_ONLY) public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSessionDto) { - log.info("Called getMyProfile: {}", userSessionDto.getName()); return userFacadeService.getMyProfile(userSessionDto); } @@ -50,7 +49,6 @@ public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSession @AuthGuard(level = AuthLevel.USER_ONLY) public LentExtensionPaginationDto getMyLentExtension( @UserSession UserSessionDto userSessionDto) { - log.info("Called getMyLentExtension: {}", userSessionDto.getName()); return userFacadeService.getMyLentExtension(userSessionDto); } @@ -64,7 +62,6 @@ public LentExtensionPaginationDto getMyLentExtension( @AuthGuard(level = AuthLevel.USER_ONLY) public LentExtensionPaginationDto getMyActiveLentExtension( @UserSession UserSessionDto userSessionDto) { - log.info("Called getMyActiveLentExtension: {}", userSessionDto.getName()); return userFacadeService.getMyActiveLentExtensionPage(userSessionDto); } @@ -77,7 +74,6 @@ public LentExtensionPaginationDto getMyActiveLentExtension( @AuthGuard(level = AuthLevel.USER_ONLY) public void useLentExtension( @UserSession UserSessionDto userSessionDto) { - log.info("Called useLentExtension"); userFacadeService.useLentExtension(userSessionDto); } @@ -86,7 +82,6 @@ public void useLentExtension( public void updateMyProfile( @UserSession UserSessionDto userSessionDto, @RequestBody UpdateAlarmRequestDto updateAlarmRequestDto) { - log.info("Called updateMyProfile"); userFacadeService.updateAlarmState(userSessionDto, updateAlarmRequestDto); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java index 1a59147fa..5eb674ff3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java @@ -1,16 +1,17 @@ package org.ftclub.cabinet.user.newService; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.repository.AlarmStatusRepository; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.AlarmStatus; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) public class AlarmStatusQueryService { private final AlarmStatusRepository alarmStatusRepository; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java index 1f8f97de5..d3ea29404 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java @@ -1,18 +1,18 @@ package org.ftclub.cabinet.user.newService; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.BanPolicy; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; - @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class BanHistoryCommandService { private final BanHistoryRepository banHistoryRepository; @@ -20,13 +20,12 @@ public class BanHistoryCommandService { private final BanPolicy banPolicy; public void banUser(Long userId, LocalDateTime endedAt, - LocalDateTime unBannedAt, BanType banType) { + LocalDateTime unBannedAt, BanType banType) { BanHistory banHistory = BanHistory.of(endedAt, unBannedAt, banType, userId); banHistoryRepository.save(banHistory); } public void deleteRecentBanHistory(BanHistory banHistory, LocalDateTime now) { - log.debug("Called deleteRecentBanHistory: {}", banHistory); if (banPolicy.isActiveBanHistory(banHistory.getUnbannedAt(), now)) { banHistoryRepository.delete(banHistory); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java index c504ffea5..a52805c38 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java @@ -1,28 +1,26 @@ package org.ftclub.cabinet.user.newService; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.repository.BanHistoryRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; - @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class BanHistoryQueryService { private final BanHistoryRepository banHistoryRepository; public Optional findRecentActiveBanHistory(Long userId, LocalDateTime now) { - log.debug("Called findRecentActiveBanHistory: {}", userId); - List banHistories = banHistoryRepository.findByUserId(userId); return banHistories.stream() .filter(history -> history.getUnbannedAt().isAfter(now)) @@ -31,20 +29,14 @@ public Optional findRecentActiveBanHistory(Long userId, LocalDateTim } public List findActiveBanHistories(Long userId, LocalDateTime date) { - log.debug("Called findActiveBanHistories: {}", userId); - return banHistoryRepository.findByUserIdAndUnbannedAt(userId, date); } public List findActiveBanHistories(List userIds, LocalDateTime date) { - log.debug("Called findActiveBanHistories: {}", userIds); - return banHistoryRepository.findByUserIdsAndUnbannedAt(userIds, date); } public Page findActiveBanHistories(LocalDateTime now, Pageable pageable) { - log.debug("Called findActiveBanHistories: {}", now); - return banHistoryRepository.findPaginationActiveBanHistoriesJoinUser(pageable, now); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java index 30c520b02..c6d17c7d2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java @@ -1,18 +1,17 @@ package org.ftclub.cabinet.user.newService; import java.time.LocalDateTime; -import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.utils.DateUtil; import org.springframework.stereotype.Service; -@Slf4j @Service +@Logging(level = LogLevel.DEBUG) public class BanPolicyService { public BanType verifyBan(LocalDateTime endedAt, LocalDateTime expiredAt) { - log.debug("Called verifyBan"); - if (expiredAt.isBefore(endedAt)) { return BanType.ALL; } @@ -20,8 +19,6 @@ public BanType verifyBan(LocalDateTime endedAt, LocalDateTime expiredAt) { } public LocalDateTime getUnBannedAt(LocalDateTime endedAt, LocalDateTime expiredAt) { - log.debug("Called getBanDate"); - long recentBanDays = DateUtil.calculateTwoDateDiffCeil(expiredAt, endedAt); double squaredBanDays = Math.pow(recentBanDays, 2); return endedAt.plusDays((long) squaredBanDays); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java index 304a26282..02d096126 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java @@ -1,27 +1,30 @@ package org.ftclub.cabinet.user.newService; +import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.config.CabinetProperties; import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.user.domain.*; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.user.domain.LentExtension; +import org.ftclub.cabinet.user.domain.LentExtensionPolicy; +import org.ftclub.cabinet.user.domain.LentExtensionType; +import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.repository.LentExtensionRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; - @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class LentExtensionCommandService { + private final LentExtensionRepository lentExtensionRepository; private final LentExtensionPolicy policy; private final CabinetProperties cabinetProperties; public LentExtension createLentExtension(User user, LentExtensionType type, LocalDateTime now) { - log.debug("Called assignLentExtension {}", user.getName()); LentExtension lentExtension = LentExtension.of(policy.getDefaultName(), policy.getDefaultExtensionTerm(), policy.getExpiry(now), @@ -30,10 +33,9 @@ public LentExtension createLentExtension(User user, LentExtensionType type, Loca } public void useLentExtension(LentExtension lentExtension, List lentHistories) { - log.debug("Called useLentExtension : {}", lentExtension.getLentExtensionId()); - lentExtension.use(); lentHistories.forEach(lentHistory -> - lentHistory.setExpiredAt(lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); + lentHistory.setExpiredAt( + lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java index 4defaee5b..bc64ae2e5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java @@ -1,45 +1,41 @@ package org.ftclub.cabinet.user.newService; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.LentExtension; import org.ftclub.cabinet.user.domain.LentExtensions; import org.ftclub.cabinet.user.repository.LentExtensionRepository; import org.springframework.stereotype.Service; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class LentExtensionQueryService { - private final LentExtensionRepository lentExtensionRepository; - public LentExtension findActiveLentExtension(Long userId) { - log.debug("Called getActiveLentExtension: {}", userId); - - return LentExtensions.builder() - .lentExtensions(lentExtensionRepository.findAll(userId)) - .build() - .findImminentActiveLentExtension(); - } - - public LentExtensions findActiveLentExtensions(Long userId) { - log.debug("Called getLentExtensionList {}", userId); - - return LentExtensions.builder() - .lentExtensions(lentExtensionRepository.findAllByUserIdAndUsedAtIsNull(userId)) - .build(); - } - - public List findLentExtensionsInLatestOrder(Long userId) { - log.debug("Called getMyLentExtensionSorted: {}", userId); - - return lentExtensionRepository.findAll(userId) - .stream() - .sorted(Comparator.comparing(LentExtension::getExpiredAt, Comparator.reverseOrder())) - .collect(Collectors.toList()); - } + private final LentExtensionRepository lentExtensionRepository; + + public LentExtension findActiveLentExtension(Long userId) { + return LentExtensions.builder() + .lentExtensions(lentExtensionRepository.findAll(userId)) + .build() + .findImminentActiveLentExtension(); + } + + public LentExtensions findActiveLentExtensions(Long userId) { + return LentExtensions.builder() + .lentExtensions(lentExtensionRepository.findAllByUserIdAndUsedAtIsNull(userId)) + .build(); + } + + public List findLentExtensionsInLatestOrder(Long userId) { + return lentExtensionRepository.findAll(userId) + .stream() + .sorted(Comparator.comparing(LentExtension::getExpiredAt, + Comparator.reverseOrder())) + .collect(Collectors.toList()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java index 6a19fa65e..969cdebe7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java @@ -1,10 +1,11 @@ package org.ftclub.cabinet.user.newService; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.user.repository.UserRepository; @@ -12,25 +13,22 @@ @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class UserCommandService { private final UserRepository userRepository; public User createUserByFtProfile(FtProfile profile) { - log.info("Called createUserByFtProfile. {}", profile); if (userRepository.existsByNameAndEmail(profile.getIntraName(), profile.getEmail())) { - log.warn("이미 존재하는 유저입니다. {}", profile); throw new ServiceException(ExceptionStatus.USER_ALREADY_EXISTED); } - User user = User.of(profile.getIntraName(), profile.getEmail(), profile.getBlackHoledAt(), UserRole.USER); + User user = User.of(profile.getIntraName(), profile.getEmail(), profile.getBlackHoledAt(), + UserRole.USER); return userRepository.save(user); } public User createClubUser(String clubName) { - log.info("Called createClubUser. {}", clubName); if (userRepository.existsByNameAndEmail(clubName, clubName + "@ftclub.org")) { - log.warn("이미 존재하는 동아리입니다. {}", clubName); throw new ServiceException(ExceptionStatus.EXISTED_CLUB_USER); } User user = User.of(clubName, clubName + "@ftclub.org", null, UserRole.CLUB); @@ -38,22 +36,21 @@ public User createClubUser(String clubName) { } public void updateClubName(User user, String clubName) { - log.info("Called updateClubName. {}", user); - if (!user.isUserRole(UserRole.CLUB)) + if (!user.isUserRole(UserRole.CLUB)) { throw new ServiceException(ExceptionStatus.NOT_CLUB_USER); + } user.changeName(clubName); userRepository.save(user); } public void deleteById(Long userId) { - log.info("Called deleteById. {}", userId); userRepository.deleteById(userId); } public void deleteClubUserById(User clubUser) { - log.info("Called deleteClubUserById. {}", clubUser); - if (!clubUser.isUserRole(UserRole.CLUB)) + if (!clubUser.isUserRole(UserRole.CLUB)) { throw new ServiceException(ExceptionStatus.NOT_CLUB_USER); + } userRepository.delete(clubUser); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java index 9e399cd38..eaeef0e2b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java @@ -5,7 +5,6 @@ import java.util.stream.Collectors; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; import org.ftclub.cabinet.alarm.service.AlarmCommandService; import org.ftclub.cabinet.alarm.service.AlarmQueryService; @@ -20,6 +19,8 @@ import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.AlarmStatus; import org.ftclub.cabinet.user.domain.BanHistory; @@ -30,7 +31,7 @@ @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class UserFacadeService { private final BanHistoryQueryService banHistoryQueryService; @@ -44,14 +45,13 @@ public class UserFacadeService { private final LentExtensionPolicy lentExtensionPolicy; public MyProfileResponseDto getProfile(UserSessionDto user) { - log.debug("Called getMyProfile: {}", user.getName()); - Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), LocalDateTime.now()).orElse(null); LentExtension lentExtension = lentExtensionQueryService.findActiveLentExtension( user.getUserId()); - LentExtensionResponseDto lentExtensionResponseDto = userMapper.toLentExtensionResponseDto(lentExtension); + LentExtensionResponseDto lentExtensionResponseDto = userMapper.toLentExtensionResponseDto( + lentExtension); AlarmStatus alarmStatus = alarmQueryService.findAlarmStatus(user.getUserId()); AlarmTypeResponseDto alarmTypeResponseDto = userMapper.toAlarmTypeResponseDto(alarmStatus); @@ -61,8 +61,6 @@ public MyProfileResponseDto getProfile(UserSessionDto user) { } public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { - log.debug("Called getMyLentExtension : {}", user.getName()); - List lentExtensionResponseDtos = lentExtensionQueryService.findLentExtensionsInLatestOrder( user.getUserId()) .stream() @@ -73,8 +71,6 @@ public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { } public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto user) { - log.debug("Called getMyActiveLentExtension : {}", user.getName()); - LentExtensions lentExtensions = lentExtensionQueryService.findActiveLentExtensions( user.getUserId()); List LentExtensionResponseDtos = lentExtensions.getLentExtensions() @@ -87,8 +83,6 @@ public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto use } public void useLentExtension(UserSessionDto user) { - log.debug("Called useLentExtension : {}", user.getName()); - Cabinet cabinet = cabinetQueryService.findCabinets(user.getUserId()); List activeLentHistories = lentQueryService.findCabinetActiveLentHistories( cabinet.getCabinetId()); @@ -104,8 +98,6 @@ public void useLentExtension(UserSessionDto user) { @Transactional public void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto) { - log.debug("Called updateAlarmState"); - alarmCommandService.updateAlarmStatusRe(dto, alarmQueryService.findAlarmStatus( user.getUserId())); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java index 25cf88b79..7584a3e71 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -1,9 +1,12 @@ package org.ftclub.cabinet.user.newService; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.user.repository.UserRepository; @@ -11,12 +14,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Optional; - @Service @RequiredArgsConstructor -@Log4j2 +@Logging(level = LogLevel.DEBUG) public class UserQueryService { private final UserRepository userRepository; diff --git a/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java index 655f959c9..78f839934 100644 --- a/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java @@ -77,13 +77,14 @@ void findByUserId() { void findByCabinetId() { // 빌린 기록이 없는 cabinet id long cabinetId = 1L; - List lentHistories = lentRepository.findPaginationByCabinetId(cabinetId, + List lentHistories = lentRepository.findPaginationByCabinetIdJoinCabinetAndUser( + cabinetId, PageRequest.of(0, 1)).toList(); assertTrue(lentHistories.isEmpty()); // 빌린 기록이 6개 있는 cabinet id cabinetId = 3L; - lentHistories = lentRepository.findPaginationByCabinetId(cabinetId, + lentHistories = lentRepository.findPaginationByCabinetIdJoinCabinetAndUser(cabinetId, PageRequest.of(0, 20)).toList(); assertEquals(6, lentHistories.size()); } @@ -124,7 +125,8 @@ void countCabinetAllLent() { void findAllActiveLentByCabinetId() { // 빌리고 있는 기록이 3개 있는 cabinet id long cabinetId = 4L; - List lentHistories = lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); + List lentHistories = lentRepository.findAllByCabinetIdAndEndedAtIsNull( + cabinetId); assertEquals(3, lentHistories.size()); cabinetId = 2L; lentHistories = lentRepository.findAllByCabinetIdAndEndedAtIsNull(cabinetId); From 9eae02e2d5b4ec2b57b4eb3724e7fcd65266c823 Mon Sep 17 00:00:00 2001 From: ldw Date: Wed, 27 Dec 2023 16:32:59 +0900 Subject: [PATCH 0201/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20userMapper?= =?UTF-8?q?=EC=9D=98=20nullValueMappingStrategy=EB=A5=BC=20RETURN=5FNULL?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/ftclub/cabinet/mapper/UserMapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java index cc9d7e27b..551d8e11d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/UserMapper.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.mapper; import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; +import static org.mapstruct.NullValueMappingStrategy.RETURN_NULL; import java.util.List; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; @@ -24,7 +25,7 @@ //@NullableMapper @Mapper(componentModel = "spring", - nullValueMappingStrategy = RETURN_DEFAULT, + nullValueMappingStrategy = RETURN_NULL, nullValueMapMappingStrategy = RETURN_DEFAULT, nullValueIterableMappingStrategy = RETURN_DEFAULT) @Component From b5d208c83c047e54a253531bb4e1199e078aa7a7 Mon Sep 17 00:00:00 2001 From: ldw Date: Wed, 27 Dec 2023 16:35:56 +0900 Subject: [PATCH 0202/1029] =?UTF-8?q?[BE]=20REFACTOR=20:=20UserFacadeServi?= =?UTF-8?q?ce=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20UserCon?= =?UTF-8?q?troller=EC=97=90=20=EC=97=B0=EA=B2=B0=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/lent/AdminLentFacadeService.java | 6 +- .../search/AdminSearchFacadeService.java | 4 +- .../AdminStatisticsFacadeService.java | 2 +- .../user/AdminLentExtensionFacadeService.java | 4 +- .../admin/user/AdminUserFacadeService.java | 8 +- .../alarm/handler/AlarmEventHandler.java | 4 +- .../cabinet/auth/domain/TokenValidator.java | 2 +- .../auth/service/AuthFacadeService.java | 4 +- .../newService/CabinetFacadeService.java | 2 +- .../lent/service/LentFacadeService.java | 8 +- .../occupiedtime/OccupiedTimeController.java | 9 - .../user/controller/UserController.java | 6 +- .../cabinet/user/domain/LentExtensions.java | 8 +- .../cabinet/user/domain/UserAspect.java | 2 +- .../user/newService/UserFacadeService.java | 113 ------- .../LentExtensionService.java | 2 +- .../LentExtensionServiceImpl.java | 15 +- .../user/oldService/UserFacadeService.java | 205 ++++++++++++ .../UserFacadeServiceImpl.java | 2 +- .../{service => oldService}/UserService.java | 2 +- .../UserServiceImpl.java | 2 +- .../AlarmStatusQueryService.java | 2 +- .../BanHistoryCommandService.java | 2 +- .../BanHistoryQueryService.java | 2 +- .../BanPolicyService.java | 2 +- .../LentExtensionCommandService.java | 2 +- .../LentExtensionQueryService.java | 5 +- .../UserCommandService.java | 2 +- .../user/service/UserFacadeService.java | 300 ++++++------------ .../UserQueryService.java | 2 +- .../blackhole/manager/BlackholeManager.java | 2 +- .../leave/absence/LeaveAbsenceManager.java | 2 +- .../utils/scheduler/SystemScheduler.java | 2 +- 33 files changed, 362 insertions(+), 373 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java rename backend/src/main/java/org/ftclub/cabinet/user/{service => oldService}/LentExtensionService.java (91%) rename backend/src/main/java/org/ftclub/cabinet/user/{service => oldService}/LentExtensionServiceImpl.java (94%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/user/oldService/UserFacadeService.java rename backend/src/main/java/org/ftclub/cabinet/user/{service => oldService}/UserFacadeServiceImpl.java (99%) rename backend/src/main/java/org/ftclub/cabinet/user/{service => oldService}/UserService.java (98%) rename backend/src/main/java/org/ftclub/cabinet/user/{service => oldService}/UserServiceImpl.java (99%) rename backend/src/main/java/org/ftclub/cabinet/user/{newService => service}/AlarmStatusQueryService.java (93%) rename backend/src/main/java/org/ftclub/cabinet/user/{newService => service}/BanHistoryCommandService.java (96%) rename backend/src/main/java/org/ftclub/cabinet/user/{newService => service}/BanHistoryQueryService.java (97%) rename backend/src/main/java/org/ftclub/cabinet/user/{newService => service}/BanPolicyService.java (94%) rename backend/src/main/java/org/ftclub/cabinet/user/{newService => service}/LentExtensionCommandService.java (96%) rename backend/src/main/java/org/ftclub/cabinet/user/{newService => service}/LentExtensionQueryService.java (92%) rename backend/src/main/java/org/ftclub/cabinet/user/{newService => service}/UserCommandService.java (97%) rename backend/src/main/java/org/ftclub/cabinet/user/{newService => service}/UserQueryService.java (97%) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java index de5fae86e..d2db8e8a7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java @@ -24,9 +24,9 @@ import org.ftclub.cabinet.lent.service.LentRedisService; import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.user.domain.BanType; -import org.ftclub.cabinet.user.newService.BanHistoryCommandService; -import org.ftclub.cabinet.user.newService.BanPolicyService; -import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.user.service.BanHistoryCommandService; +import org.ftclub.cabinet.user.service.BanPolicyService; +import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java index eceb96301..4801f1f13 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java @@ -32,8 +32,8 @@ import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.newService.BanHistoryQueryService; -import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.user.service.BanHistoryQueryService; +import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java index 9492c8b65..eb2fdcff5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java @@ -24,7 +24,7 @@ import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.newService.BanHistoryQueryService; +import org.ftclub.cabinet.user.service.BanHistoryQueryService; import org.ftclub.cabinet.utils.DateUtil; import org.ftclub.cabinet.utils.ExceptionUtil; import org.springframework.data.domain.Page; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java index 3d0ddfc1d..bf99b179e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java @@ -4,8 +4,8 @@ import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.user.domain.LentExtensionType; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.newService.LentExtensionCommandService; -import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.user.service.LentExtensionCommandService; +import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.stereotype.Service; import java.time.LocalDateTime; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java index d20c9fb11..640f00393 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java @@ -6,10 +6,10 @@ import org.ftclub.cabinet.dto.UserProfileDto; import org.ftclub.cabinet.mapper.UserMapper; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.newService.BanHistoryCommandService; -import org.ftclub.cabinet.user.newService.BanHistoryQueryService; -import org.ftclub.cabinet.user.newService.UserCommandService; -import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.user.service.BanHistoryCommandService; +import org.ftclub.cabinet.user.service.BanHistoryQueryService; +import org.ftclub.cabinet.user.service.UserCommandService; +import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index 19475f6cd..08846fdaa 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -7,8 +7,8 @@ import org.ftclub.cabinet.alarm.domain.TransactionalAlarmEvent; import org.ftclub.cabinet.user.domain.AlarmStatus; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.newService.AlarmStatusQueryService; -import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.user.service.AlarmStatusQueryService; +import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java index b1603d304..146f9c7ec 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java @@ -22,7 +22,7 @@ import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.admin.admin.domain.AdminRole; -import org.ftclub.cabinet.user.service.UserService; +import org.ftclub.cabinet.user.oldService.UserService; import org.springframework.stereotype.Component; /** diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index 96b220945..2d70d0afe 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -14,8 +14,8 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.newService.UserCommandService; -import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.user.service.UserCommandService; +import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.stereotype.Service; import javax.servlet.http.Cookie; diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java index 48b6c0952..0f133af3a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/newService/CabinetFacadeService.java @@ -40,7 +40,7 @@ import org.ftclub.cabinet.mapper.CabinetMapper; import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index bb1ca4b82..436d72d63 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -31,10 +31,10 @@ import org.ftclub.cabinet.user.domain.BanType; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.domain.UserSession; -import org.ftclub.cabinet.user.newService.BanHistoryCommandService; -import org.ftclub.cabinet.user.newService.BanHistoryQueryService; -import org.ftclub.cabinet.user.newService.BanPolicyService; -import org.ftclub.cabinet.user.newService.UserQueryService; +import org.ftclub.cabinet.user.service.BanHistoryCommandService; +import org.ftclub.cabinet.user.service.BanHistoryQueryService; +import org.ftclub.cabinet.user.service.BanPolicyService; +import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java index a1a077e62..a836eab17 100644 --- a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java +++ b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeController.java @@ -1,14 +1,5 @@ package org.ftclub.cabinet.occupiedtime; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.dto.UserMonthDataDto; -import org.ftclub.cabinet.user.service.LentExtensionService; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - /** * FOR TEST ONLY */ diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java index 0ea765011..b515f2f07 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java @@ -37,7 +37,7 @@ public class UserController { @AuthGuard(level = AuthLevel.USER_ONLY) public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSessionDto) { log.info("Called getMyProfile: {}", userSessionDto.getName()); - return userFacadeService.getMyProfile(userSessionDto); + return userFacadeService.getProfile(userSessionDto); } /** @@ -51,7 +51,7 @@ public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSession public LentExtensionPaginationDto getMyLentExtension( @UserSession UserSessionDto userSessionDto) { log.info("Called getMyLentExtension: {}", userSessionDto.getName()); - return userFacadeService.getMyLentExtension(userSessionDto); + return userFacadeService.getLentExtensions(userSessionDto); } /** @@ -65,7 +65,7 @@ public LentExtensionPaginationDto getMyLentExtension( public LentExtensionPaginationDto getMyActiveLentExtension( @UserSession UserSessionDto userSessionDto) { log.info("Called getMyActiveLentExtension: {}", userSessionDto.getName()); - return userFacadeService.getMyActiveLentExtensionPage(userSessionDto); + return userFacadeService.getActiveLentExtensionsPage(userSessionDto); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java index 6b6c0228f..326602b70 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java @@ -9,6 +9,7 @@ import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; +import java.util.Optional; @Getter @ToString @@ -36,12 +37,11 @@ public boolean isEmpty() { return lentExtensions == null || lentExtensions.isEmpty(); } - public LentExtension findImminentActiveLentExtension() { + public Optional findImminentActiveLentExtension() { filterActiveLentExtensions(); - if (!this.hasActiveLentExtension()) - return null; sortImminentASC(); - return lentExtensions.get(0); + return Optional.ofNullable(!lentExtensions.isEmpty() ? + lentExtensions.get(0) : null); } public List getActiveLentExtensions() { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java index 49cd9cdd6..3d33eb509 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java @@ -12,7 +12,7 @@ import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.user.repository.UserOptionalFetcher; -import org.ftclub.cabinet.user.service.UserService; +import org.ftclub.cabinet.user.oldService.UserService; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java deleted file mode 100644 index 9e399cd38..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserFacadeService.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.ftclub.cabinet.user.newService; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; -import javax.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; -import org.ftclub.cabinet.alarm.service.AlarmCommandService; -import org.ftclub.cabinet.alarm.service.AlarmQueryService; -import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; -import org.ftclub.cabinet.dto.LentExtensionPaginationDto; -import org.ftclub.cabinet.dto.LentExtensionResponseDto; -import org.ftclub.cabinet.dto.MyProfileResponseDto; -import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; -import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; -import org.ftclub.cabinet.lent.domain.LentHistory; -import org.ftclub.cabinet.lent.service.LentQueryService; -import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.user.domain.AlarmStatus; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.LentExtension; -import org.ftclub.cabinet.user.domain.LentExtensionPolicy; -import org.ftclub.cabinet.user.domain.LentExtensions; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Log4j2 -public class UserFacadeService { - - private final BanHistoryQueryService banHistoryQueryService; - private final LentExtensionQueryService lentExtensionQueryService; - private final LentExtensionCommandService lentExtensionCommandService; - private final CabinetQueryService cabinetQueryService; - private final UserMapper userMapper; - private final AlarmQueryService alarmQueryService; - private final AlarmCommandService alarmCommandService; - private final LentQueryService lentQueryService; - private final LentExtensionPolicy lentExtensionPolicy; - - public MyProfileResponseDto getProfile(UserSessionDto user) { - log.debug("Called getMyProfile: {}", user.getName()); - - Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); - BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), - LocalDateTime.now()).orElse(null); - LentExtension lentExtension = lentExtensionQueryService.findActiveLentExtension( - user.getUserId()); - LentExtensionResponseDto lentExtensionResponseDto = userMapper.toLentExtensionResponseDto(lentExtension); - - AlarmStatus alarmStatus = alarmQueryService.findAlarmStatus(user.getUserId()); - AlarmTypeResponseDto alarmTypeResponseDto = userMapper.toAlarmTypeResponseDto(alarmStatus); - - return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, - lentExtensionResponseDto, alarmTypeResponseDto); - } - - public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { - log.debug("Called getMyLentExtension : {}", user.getName()); - - List lentExtensionResponseDtos = lentExtensionQueryService.findLentExtensionsInLatestOrder( - user.getUserId()) - .stream() - .map(userMapper::toLentExtensionResponseDto) - .collect(Collectors.toList()); - return userMapper.toLentExtensionPaginationDto(lentExtensionResponseDtos, - (long) lentExtensionResponseDtos.size()); - } - - public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto user) { - log.debug("Called getMyActiveLentExtension : {}", user.getName()); - - LentExtensions lentExtensions = lentExtensionQueryService.findActiveLentExtensions( - user.getUserId()); - List LentExtensionResponseDtos = lentExtensions.getLentExtensions() - .stream() - .map(userMapper::toLentExtensionResponseDto) - .collect(Collectors.toList()); - - return userMapper.toLentExtensionPaginationDto(LentExtensionResponseDtos, - (long) LentExtensionResponseDtos.size()); - } - - public void useLentExtension(UserSessionDto user) { - log.debug("Called useLentExtension : {}", user.getName()); - - Cabinet cabinet = cabinetQueryService.findCabinets(user.getUserId()); - List activeLentHistories = lentQueryService.findCabinetActiveLentHistories( - cabinet.getCabinetId()); - lentExtensionPolicy.verifyLentExtension(cabinet, activeLentHistories); - - LentExtension activeLentExtension = lentExtensionQueryService.findActiveLentExtension( - user.getUserId()); - if (activeLentExtension == null) { - throw new ServiceException(ExceptionStatus.EXTENSION_NOT_FOUND); - } - lentExtensionCommandService.useLentExtension(activeLentExtension, activeLentHistories); - } - - @Transactional - public void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto) { - log.debug("Called updateAlarmState"); - - alarmCommandService.updateAlarmStatusRe(dto, alarmQueryService.findAlarmStatus( - user.getUserId())); - } -} - diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionService.java b/backend/src/main/java/org/ftclub/cabinet/user/oldService/LentExtensionService.java similarity index 91% rename from backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionService.java rename to backend/src/main/java/org/ftclub/cabinet/user/oldService/LentExtensionService.java index 1d557c2d7..c2d741f32 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/oldService/LentExtensionService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.service; +package org.ftclub.cabinet.user.oldService; import org.ftclub.cabinet.dto.LentExtensionResponseDto; import org.ftclub.cabinet.dto.UserSessionDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/oldService/LentExtensionServiceImpl.java similarity index 94% rename from backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java rename to backend/src/main/java/org/ftclub/cabinet/user/oldService/LentExtensionServiceImpl.java index 60df9114e..0f8727666 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/oldService/LentExtensionServiceImpl.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.service; +package org.ftclub.cabinet.user.oldService; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -93,16 +93,12 @@ public List getActiveLentExtensionList( @Override public LentExtensionResponseDto getActiveLentExtension(UserSessionDto userSessionDto) { - LentExtensionResponseDto lentExtensionResponseDto = null; List activeLentExtensionsByUserId = lentExtensionOptionalFetcher.findAllByUserId( userSessionDto.getUserId()); LentExtensions lentExtensions = LentExtensions.builder() .lentExtensions(activeLentExtensionsByUserId).build(); - if (lentExtensions.hasActiveLentExtension()) { - lentExtensionResponseDto = userMapper.toLentExtensionResponseDto( - lentExtensions.findImminentActiveLentExtension()); - } - return lentExtensionResponseDto; + return userMapper.toLentExtensionResponseDto( + lentExtensions.findImminentActiveLentExtension().orElse(null)); } @Override @@ -123,12 +119,13 @@ public void useLentExtension(Long userId, String username) { cabinet.getCabinetId()); lentExtensionPolicy.verifyLentExtension(cabinet, activeLentHistories); - LentExtension lentExtension = lentExtensions.findImminentActiveLentExtension(); + LentExtension lentExtension = lentExtensions.findImminentActiveLentExtension().orElse(null); + if (lentExtension == null) + return ; lentExtension.use(); // 연장 activeLentHistories .forEach(lentHistory -> lentHistory.setExpiredAt( lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); } - } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/oldService/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/oldService/UserFacadeService.java new file mode 100644 index 000000000..256bd61ef --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/user/oldService/UserFacadeService.java @@ -0,0 +1,205 @@ +package org.ftclub.cabinet.user.oldService; + +import java.time.LocalDateTime; +import java.util.List; + +import org.ftclub.cabinet.cabinet.domain.LentType; +import org.ftclub.cabinet.dto.BlockedUserPaginationDto; +import org.ftclub.cabinet.dto.ClubUserListDto; +import org.ftclub.cabinet.dto.LentExtensionPaginationDto; +import org.ftclub.cabinet.dto.MyProfileResponseDto; +import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserRole; + +public interface UserFacadeService { + + /** + * 현재 로그인한 유저의 프로필을 반환합니다. 대여한 사물함 아이디 정보가 포합됩니다. + * + * @param user 로그인한 유저의 정보 + * @return {@link MyProfileResponseDto} 현재 로그인한 유저의 정보 + */ + MyProfileResponseDto getMyProfile(UserSessionDto user); + + /** + * 모든 정지 유저를 반환합니다. + * + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @param now 현재 시간 + * @return {@link BlockedUserPaginationDto} 모든 정지 유저 + */ + /* 기존 searchByBanUser와 동일한 역할을 합니다. */ + BlockedUserPaginationDto getAllBanUsers(Integer page, Integer size, LocalDateTime now); + + /** + * 유저 이름의 일부를 입력받아 해당하는 유저들의 프로필을 받아옵니다. + * + * @param name 유저 이름의 일부 + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @return {@link UserProfilePaginationDto} 해당하는 유저들의 프로필 + */ + /*기존 searchByIntraId 메서드와 동일한 역할을 합니다.*/ + UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, + Integer size); + + /** + * 유저 이름의 일부를 입력받아 해당 유저들의 캐비넷 정보를 반환합니다. + * + * @param name 유저 이름의 일부 + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @return {@link UserCabinetPaginationDto} 해당하는 유저들의 캐비넷 정보 + */ + UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, + Integer size); + + /** + * 모든 유저의 정보를 가져옵니다. + * + * @return 모든 유저의 정보를 가져옵니다. + */ + List getAllUsers(); + + /** + * 유저가 존재하는지 확인합니다. + * + * @param name 유저 이름 + * @return 유저가 존재하면 true, 아니면 false + */ + boolean checkUserExists(String name); + + /** + * 유저를 생성합니다. + * + * @param name 유저 이름 + * @param email 유저 이메일 + * @param blackholedAt 유저 블랙홀 날짜 + * @param role 유저 역할 + */ + void createUser(String name, String email, LocalDateTime blackholedAt, UserRole role); + + /** + * @param clubName 동아리 유저 이름 + */ + void createClubUser(String clubName); + + /** + * 관리자가 존재하는지 확인합니다. + * + * @param email 관리자 이메일 + * @return 관리자가 존재하면 true, 아니면 false + */ + boolean checkAdminUserExists(String email); + + /** + * 관리자를 생성합니다. + * + * @param email 관리자 이메일 + */ + void createAdminUser(String email); + + /** + * 유저를 삭제합니다. + * + * @param userId 유저 고유 아이디 + * @param deletedAt 유저 삭제 날짜 + */ + void deleteUser(Long userId, LocalDateTime deletedAt); + + /** + * 관리자를 삭제합니다. + * + * @param adminUserId 관리자 고유 아이디 + */ + void deleteAdminUser(Long adminUserId); + + /** + * 유저의 권한을 변경합니다. + * + * @param adminUserId 관리자 고유 아이디 + * @param role 관리자 권한 + */ + void updateAdminUserRole(Long adminUserId, AdminRole role); + + /** + * 유저를 어드민으로 승격시킵니다. + * + * @param email 유저 이메일 + */ + void promoteUserToAdmin(String email); + + /** + * 유저의 블랙홀 시간을 변경합니다. + * + * @param userId 유저 고유 아이디 + * @param newBlackholedAt 새로운 유저 블랙홀 시간 + */ + void updateUserBlackholedAt(Long userId, LocalDateTime newBlackholedAt); + + /** + * 유저를 정지시킵니다. + * + * @param userId 유저 고유 아이디 + * @param lentType 현재 대여 타입 + * @param startedAt 대여 시작 날짜 + * @param endedAt 대여 종료 날짜 + * @param expiredAt 대여 만료 날짜 + */ + void banUser(Long userId, LentType lentType, LocalDateTime startedAt, LocalDateTime endedAt, + LocalDateTime expiredAt); + + /** + * 유저의 정지를 해제합니다. + * + * @param userId 유저 고유 아이디 + * @param today 현재 날짜 + */ + void deleteRecentBanHistory(Long userId, LocalDateTime today); + + /** + * 연체 중인 유저 리스트를 반환합니다. + * + * @param page 페이지 번호 + * @param size 페이지 당 길이 + */ + OverdueUserCabinetPaginationDto getOverdueUserList(Integer page, Integer size); + + /** + * 동아리 유저 리스트DTO를 반환합니다. + * + * @param page 페이지 번호 + * @param size 페이지 당 길이 + * @return + */ + ClubUserListDto findAllClubUser(Integer page, Integer size); + + + /** + * 동아리 유저를 삭제합니다. + * + * @param clubId 동아리 고유 아이디 + */ + void deleteClubUser(Long clubId); + + void updateClubUser(Long clubId, String clubName); + + LentExtensionPaginationDto getAllLentExtension(Integer page, Integer size); + + LentExtensionPaginationDto getAllActiveLentExtension(Integer page, Integer size); + + LentExtensionPaginationDto getMyLentExtension(UserSessionDto userSessionDto); + + LentExtensionPaginationDto getMyActiveLentExtensionPage(UserSessionDto userSessionDto); + + void useLentExtension(UserSessionDto userSessionDto); + + void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/oldService/UserFacadeServiceImpl.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java rename to backend/src/main/java/org/ftclub/cabinet/user/oldService/UserFacadeServiceImpl.java index 028c8b39f..514477960 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/oldService/UserFacadeServiceImpl.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.service; +package org.ftclub.cabinet.user.oldService; import java.time.LocalDateTime; import java.util.ArrayList; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java b/backend/src/main/java/org/ftclub/cabinet/user/oldService/UserService.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java rename to backend/src/main/java/org/ftclub/cabinet/user/oldService/UserService.java index 5c177b65f..e2574bd4a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/oldService/UserService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.service; +package org.ftclub.cabinet.user.oldService; import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.cabinet.domain.LentType; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/oldService/UserServiceImpl.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java rename to backend/src/main/java/org/ftclub/cabinet/user/oldService/UserServiceImpl.java index ad0df1616..12fbaf1b2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/oldService/UserServiceImpl.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.service; +package org.ftclub.cabinet.user.oldService; import io.netty.util.internal.StringUtil; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/AlarmStatusQueryService.java similarity index 93% rename from backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java rename to backend/src/main/java/org/ftclub/cabinet/user/service/AlarmStatusQueryService.java index 1a59147fa..ed33753cb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/AlarmStatusQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/AlarmStatusQueryService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.newService; +package org.ftclub.cabinet.user.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java similarity index 96% rename from backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java rename to backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java index 1f8f97de5..55ba430e3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.newService; +package org.ftclub.cabinet.user.service; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryQueryService.java similarity index 97% rename from backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java rename to backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryQueryService.java index c504ffea5..a859f9ecf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanHistoryQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryQueryService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.newService; +package org.ftclub.cabinet.user.service; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/BanPolicyService.java similarity index 94% rename from backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java rename to backend/src/main/java/org/ftclub/cabinet/user/service/BanPolicyService.java index 30c520b02..c6281a0fe 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/BanPolicyService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/BanPolicyService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.newService; +package org.ftclub.cabinet.user.service; import java.time.LocalDateTime; import lombok.extern.slf4j.Slf4j; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java similarity index 96% rename from backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java rename to backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java index 304a26282..46a52419b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.newService; +package org.ftclub.cabinet.user.service; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java similarity index 92% rename from backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java rename to backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java index 4defaee5b..7eccaee89 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.newService; +package org.ftclub.cabinet.user.service; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -23,7 +23,8 @@ public LentExtension findActiveLentExtension(Long userId) { return LentExtensions.builder() .lentExtensions(lentExtensionRepository.findAll(userId)) .build() - .findImminentActiveLentExtension(); + .findImminentActiveLentExtension() + .orElse(null); } public LentExtensions findActiveLentExtensions(Long userId) { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java similarity index 97% rename from backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java rename to backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java index 6a19fa65e..53fb320a6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.newService; +package org.ftclub.cabinet.user.service; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index d85039dda..cb10194cc 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -2,204 +2,112 @@ import java.time.LocalDateTime; import java.util.List; - -import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.dto.BlockedUserPaginationDto; -import org.ftclub.cabinet.dto.ClubUserListDto; +import java.util.stream.Collectors; +import javax.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; +import org.ftclub.cabinet.alarm.service.AlarmCommandService; +import org.ftclub.cabinet.alarm.service.AlarmQueryService; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; import org.ftclub.cabinet.dto.LentExtensionPaginationDto; +import org.ftclub.cabinet.dto.LentExtensionResponseDto; import org.ftclub.cabinet.dto.MyProfileResponseDto; -import org.ftclub.cabinet.dto.OverdueUserCabinetPaginationDto; import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; -import org.ftclub.cabinet.dto.UserCabinetPaginationDto; -import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.ftclub.cabinet.dto.UserSessionDto; -import org.ftclub.cabinet.admin.admin.domain.AdminRole; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; - -public interface UserFacadeService { - - /** - * 현재 로그인한 유저의 프로필을 반환합니다. 대여한 사물함 아이디 정보가 포합됩니다. - * - * @param user 로그인한 유저의 정보 - * @return {@link MyProfileResponseDto} 현재 로그인한 유저의 정보 - */ - MyProfileResponseDto getMyProfile(UserSessionDto user); - - /** - * 모든 정지 유저를 반환합니다. - * - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @param now 현재 시간 - * @return {@link BlockedUserPaginationDto} 모든 정지 유저 - */ - /* 기존 searchByBanUser와 동일한 역할을 합니다. */ - BlockedUserPaginationDto getAllBanUsers(Integer page, Integer size, LocalDateTime now); - - /** - * 유저 이름의 일부를 입력받아 해당하는 유저들의 프로필을 받아옵니다. - * - * @param name 유저 이름의 일부 - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return {@link UserProfilePaginationDto} 해당하는 유저들의 프로필 - */ - /*기존 searchByIntraId 메서드와 동일한 역할을 합니다.*/ - UserProfilePaginationDto getUserProfileListByPartialName(String name, Integer page, - Integer size); - - /** - * 유저 이름의 일부를 입력받아 해당 유저들의 캐비넷 정보를 반환합니다. - * - * @param name 유저 이름의 일부 - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return {@link UserCabinetPaginationDto} 해당하는 유저들의 캐비넷 정보 - */ - UserCabinetPaginationDto findUserCabinetListByPartialName(String name, Integer page, - Integer size); - - /** - * 모든 유저의 정보를 가져옵니다. - * - * @return 모든 유저의 정보를 가져옵니다. - */ - List getAllUsers(); - - /** - * 유저가 존재하는지 확인합니다. - * - * @param name 유저 이름 - * @return 유저가 존재하면 true, 아니면 false - */ - boolean checkUserExists(String name); - - /** - * 유저를 생성합니다. - * - * @param name 유저 이름 - * @param email 유저 이메일 - * @param blackholedAt 유저 블랙홀 날짜 - * @param role 유저 역할 - */ - void createUser(String name, String email, LocalDateTime blackholedAt, UserRole role); - - /** - * @param clubName 동아리 유저 이름 - */ - void createClubUser(String clubName); - - /** - * 관리자가 존재하는지 확인합니다. - * - * @param email 관리자 이메일 - * @return 관리자가 존재하면 true, 아니면 false - */ - boolean checkAdminUserExists(String email); - - /** - * 관리자를 생성합니다. - * - * @param email 관리자 이메일 - */ - void createAdminUser(String email); - - /** - * 유저를 삭제합니다. - * - * @param userId 유저 고유 아이디 - * @param deletedAt 유저 삭제 날짜 - */ - void deleteUser(Long userId, LocalDateTime deletedAt); - - /** - * 관리자를 삭제합니다. - * - * @param adminUserId 관리자 고유 아이디 - */ - void deleteAdminUser(Long adminUserId); - - /** - * 유저의 권한을 변경합니다. - * - * @param adminUserId 관리자 고유 아이디 - * @param role 관리자 권한 - */ - void updateAdminUserRole(Long adminUserId, AdminRole role); - - /** - * 유저를 어드민으로 승격시킵니다. - * - * @param email 유저 이메일 - */ - void promoteUserToAdmin(String email); - - /** - * 유저의 블랙홀 시간을 변경합니다. - * - * @param userId 유저 고유 아이디 - * @param newBlackholedAt 새로운 유저 블랙홀 시간 - */ - void updateUserBlackholedAt(Long userId, LocalDateTime newBlackholedAt); - - /** - * 유저를 정지시킵니다. - * - * @param userId 유저 고유 아이디 - * @param lentType 현재 대여 타입 - * @param startedAt 대여 시작 날짜 - * @param endedAt 대여 종료 날짜 - * @param expiredAt 대여 만료 날짜 - */ - void banUser(Long userId, LentType lentType, LocalDateTime startedAt, LocalDateTime endedAt, - LocalDateTime expiredAt); - - /** - * 유저의 정지를 해제합니다. - * - * @param userId 유저 고유 아이디 - * @param today 현재 날짜 - */ - void deleteRecentBanHistory(Long userId, LocalDateTime today); - - /** - * 연체 중인 유저 리스트를 반환합니다. - * - * @param page 페이지 번호 - * @param size 페이지 당 길이 - */ - OverdueUserCabinetPaginationDto getOverdueUserList(Integer page, Integer size); - - /** - * 동아리 유저 리스트DTO를 반환합니다. - * - * @param page 페이지 번호 - * @param size 페이지 당 길이 - * @return - */ - ClubUserListDto findAllClubUser(Integer page, Integer size); - - - /** - * 동아리 유저를 삭제합니다. - * - * @param clubId 동아리 고유 아이디 - */ - void deleteClubUser(Long clubId); - - void updateClubUser(Long clubId, String clubName); - - LentExtensionPaginationDto getAllLentExtension(Integer page, Integer size); - - LentExtensionPaginationDto getAllActiveLentExtension(Integer page, Integer size); - - LentExtensionPaginationDto getMyLentExtension(UserSessionDto userSessionDto); - - LentExtensionPaginationDto getMyActiveLentExtensionPage(UserSessionDto userSessionDto); - - void useLentExtension(UserSessionDto userSessionDto); - - void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto); +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.exception.ServiceException; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentQueryService; +import org.ftclub.cabinet.mapper.UserMapper; +import org.ftclub.cabinet.user.domain.AlarmStatus; +import org.ftclub.cabinet.user.domain.BanHistory; +import org.ftclub.cabinet.user.domain.LentExtension; +import org.ftclub.cabinet.user.domain.LentExtensionPolicy; +import org.ftclub.cabinet.user.domain.LentExtensions; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Log4j2 +public class UserFacadeService { + + private final BanHistoryQueryService banHistoryQueryService; + private final LentExtensionQueryService lentExtensionQueryService; + private final LentExtensionCommandService lentExtensionCommandService; + private final CabinetQueryService cabinetQueryService; + private final UserMapper userMapper; + private final AlarmQueryService alarmQueryService; + private final AlarmCommandService alarmCommandService; + private final LentQueryService lentQueryService; + private final LentExtensionPolicy lentExtensionPolicy; + + public MyProfileResponseDto getProfile(UserSessionDto user) { + log.debug("Called getMyProfile: {}", user.getName()); + + Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); + BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), + LocalDateTime.now()).orElse(null); + LentExtension lentExtension = lentExtensionQueryService.findActiveLentExtension( + user.getUserId()); + LentExtensionResponseDto lentExtensionResponseDto = userMapper.toLentExtensionResponseDto(lentExtension); + + AlarmStatus alarmStatus = alarmQueryService.findAlarmStatus(user.getUserId()); + AlarmTypeResponseDto alarmTypeResponseDto = userMapper.toAlarmTypeResponseDto(alarmStatus); + + return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, + lentExtensionResponseDto, alarmTypeResponseDto); + } + + public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { + log.debug("Called getMyLentExtension : {}", user.getName()); + + List lentExtensionResponseDtos = lentExtensionQueryService.findLentExtensionsInLatestOrder( + user.getUserId()) + .stream() + .map(userMapper::toLentExtensionResponseDto) + .collect(Collectors.toList()); + return userMapper.toLentExtensionPaginationDto(lentExtensionResponseDtos, + (long) lentExtensionResponseDtos.size()); + } + + public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto user) { + log.debug("Called getMyActiveLentExtension : {}", user.getName()); + + LentExtensions lentExtensions = lentExtensionQueryService.findActiveLentExtensions( + user.getUserId()); + List LentExtensionResponseDtos = lentExtensions.getLentExtensions() + .stream() + .map(userMapper::toLentExtensionResponseDto) + .collect(Collectors.toList()); + + return userMapper.toLentExtensionPaginationDto(LentExtensionResponseDtos, + (long) LentExtensionResponseDtos.size()); + } + + public void useLentExtension(UserSessionDto user) { + log.debug("Called useLentExtension : {}", user.getName()); + + Cabinet cabinet = cabinetQueryService.findCabinets(user.getUserId()); + List activeLentHistories = lentQueryService.findCabinetActiveLentHistories( + cabinet.getCabinetId()); + lentExtensionPolicy.verifyLentExtension(cabinet, activeLentHistories); + + LentExtension activeLentExtension = lentExtensionQueryService.findActiveLentExtension( + user.getUserId()); + if (activeLentExtension == null) { + throw new ServiceException(ExceptionStatus.EXTENSION_NOT_FOUND); + } + lentExtensionCommandService.useLentExtension(activeLentExtension, activeLentHistories); + } + + @Transactional + public void updateAlarmState(UserSessionDto user, UpdateAlarmRequestDto dto) { + log.debug("Called updateAlarmState"); + + alarmCommandService.updateAlarmStatusRe(dto, alarmQueryService.findAlarmStatus( + user.getUserId())); + } } + diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java similarity index 97% rename from backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java rename to backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java index 25cf88b79..fd5a34d19 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.user.newService; +package org.ftclub.cabinet.user.service; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java index e57243639..7dd95d89a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java @@ -11,7 +11,7 @@ import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.exception.UtilException; import org.ftclub.cabinet.lent.service.LentFacadeService; -import org.ftclub.cabinet.user.service.UserService; +import org.ftclub.cabinet.user.oldService.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.client.HttpClientErrorException; diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java index ef0093fba..182f307e8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java @@ -7,7 +7,7 @@ import org.ftclub.cabinet.auth.service.ApplicationTokenManager; import org.ftclub.cabinet.auth.service.FtOauthService; import org.ftclub.cabinet.lent.service.LentFacadeService; -import org.ftclub.cabinet.user.service.UserService; +import org.ftclub.cabinet.user.oldService.UserService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.client.HttpClientErrorException; diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 295cb3887..066c5b642 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -6,7 +6,7 @@ import org.ftclub.cabinet.dto.UserBlackHoleEvent; import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; -import org.ftclub.cabinet.user.service.UserService; +import org.ftclub.cabinet.user.oldService.UserService; import org.ftclub.cabinet.utils.blackhole.manager.BlackholeManager; import org.ftclub.cabinet.utils.leave.absence.LeaveAbsenceManager; import org.ftclub.cabinet.utils.overdue.manager.OverdueManager; From a31feefe887061021c7f85aa4d1b1418426f2d5c Mon Sep 17 00:00:00 2001 From: chyo1 Date: Wed, 27 Dec 2023 17:01:19 +0900 Subject: [PATCH 0203/1029] =?UTF-8?q?[COMMON]=20README=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 180b1d3f1..0afa6fc62 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Cabi - + [![GitHub Stars](https://img.shields.io/github/stars/innovationacademy-kr/42cabi?style=for-the-badge)](https://github.com/innovationacademy-kr/42cabi/stargazers) [![GitHub Stars](https://img.shields.io/github/issues/innovationacademy-kr/42cabi?style=for-the-badge)](https://github.com/innovationacademy-kr/42cabi/issues) [![Current Version](https://img.shields.io/badge/version-4.0.0-black?style=for-the-badge)](https://github.com/IgorAntun/node-chat) [![GitHub License](https://img.shields.io/github/license/innovationacademy-kr/42cabi?style=for-the-badge)](https://github.com/IgorAntun/node-chat/issues) @@ -12,6 +12,7 @@ - [💬 프로젝트 소개](#-프로젝트-소개) - [🛠 기술 스택](#-기술-스택) - [🧑‍💻 프로젝트 멤버](#-프로젝트-멤버) +
@@ -33,7 +34,7 @@ - 클러스터 별 캐비닛 배치도를 바탕으로 실제 위치에 따른 캐비닛 대여 현황을 확인할 수 있습니다. - 클러스터에 방문할 필요 없이 간편하게 캐비닛 대여 및 반납이 가능합니다. - 캐비닛마다 `READ` / `UPDATE` 가능한 메모장을 제공합니다. - - 공유 캐비닛의 경우, 캐비닛 사용자들끼리만 공유 가능한 메모장을 제공합니다. + - 공유 캐비닛의 경우, 캐비닛 사용자들끼리만 공유 가능한 메모장을 제공합니다. ### 기술적 도전 @@ -68,7 +69,7 @@ ## 🛠 기술 스택

- + @@ -155,26 +156,25 @@
| [🐇dongglee](https://github.com/leedonggyu1848) | [🍑 eunbikim](https://github.com/eunbi9n) | [🥔 gyuwlee](https://github.com/gyutato) | [🐬huchoi](https://github.com/hunjin-choi) | [👻 hybae](https://github.com/HyeonsikBae) | -| ----------------------------------------------- | ----------------------------------------- | ---------------------------------------- | ------------------------------------------ | ------------------------------------------ | +|-------------------------------------------------|-------------------------------------------|------------------------------------------|--------------------------------------------|--------------------------------------------| | [🍒 hyoon](https://github.com/kamg2218) | [🍏 hyospark](https://github.com/kyoshong) | [🙉 inshin](https://github.com/42inshin) | [🧑‍✈️ jaesjeon](https://github.com/Oris482) | [🐶 jiwchoi](https://github.com/jiwon-choi) | -| --------------------------------------- | ------------------------------------------ | ---------------------------------------- | ----------------------------------------- | ------------------------------------------- | +|-----------------------------------------|--------------------------------------------|------------------------------------------|----------------------------------------------|---------------------------------------------| | [🐯 joopark](https://github.com/joohongpark) | [🚀sanan](https://github.com/Ssuamje) | [🐻 seuan](https://github.com/aseungbo) | [🤑seycho](https://github.com/SeyoungCho) | [😺 sichoi](https://github.com/sichoi42) | -| -------------------------------------------- | ------------------------------------- | --------------------------------------- | ----------------------------------------- | ---------------------------------------- | +|----------------------------------------------|---------------------------------------|-----------------------------------------|-------------------------------------------|------------------------------------------| | [🍎 skim](https://github.com/subin195-09) | [🍪 spark](https://github.com/Hyunja27) | [✏️yooh](https://github.com/oyhoyhk) | [🪀 yoyoo](https://github.com/Yoowatney) | [🎒 yubchoi](https://github.com/yubinquitous) | -| ----------------------------------------- | --------------------------------------- | ------------------------------------ | ---------------------------------------- | --------------------------------------------- | +|-------------------------------------------|-----------------------------------------|--------------------------------------|------------------------------------------|-----------------------------------------------| | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | -| --------------------------------------------- | ---------------------------------------------- | -------------------------------------- | --------------------------------------------- | --------------------------------------------- | -------------------------------------- | - -| [ 🐶 surlee](https://github.com/Elineely) | []() | []() | []() | []() | []() | -|-------------------------------------------| ---------------------------------------------- | -------------------------------------- | --------------------------------------------- | --------------------------------------------- | -------------------------------------- | +|-----------------------------------------------|------------------------------------------------|---------------------------------------|----------------------------------------------|-----------------------------------------------|----------------------------------------| +| [ 🐶 surlee](https://github.com/Elineely) | [hyowchoi](https://github.com/chyo1/) | []() | []() | []() | []() | +|-------------------------------------------|---------------------------------------|------|------|------|------| | | -| --------------------------------------------------------------------------------------------------------------------------------------------------------- | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------|

From 3773e70ed77935cede63747265d99bcaabcbfffe Mon Sep 17 00:00:00 2001 From: jiwon Date: Wed, 27 Dec 2023 17:03:14 +0900 Subject: [PATCH 0204/1029] =?UTF-8?q?[COMMON]=20=ED=9A=A8=EC=9B=90?= =?UTF-8?q?=EB=8B=98=20=EC=9D=B4=EB=AA=A8=ED=8B=B0=EC=BD=98=20=EB=8C=80?= =?UTF-8?q?=EC=8B=A0=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/modules.xml | 1 + README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.idea/modules.xml b/.idea/modules.xml index 3ba732021..094b3411f 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -4,6 +4,7 @@ + diff --git a/README.md b/README.md index 0afa6fc62..dc1579cd1 100644 --- a/README.md +++ b/README.md @@ -170,8 +170,8 @@ | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | |-----------------------------------------------|------------------------------------------------|---------------------------------------|----------------------------------------------|-----------------------------------------------|----------------------------------------| -| [ 🐶 surlee](https://github.com/Elineely) | [hyowchoi](https://github.com/chyo1/) | []() | []() | []() | []() | -|-------------------------------------------|---------------------------------------|------|------|------|------| +| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | []() | []() | []() | []() | +|-------------------------------------------|-------------------------------------------|------|------|------|------| | | |-----------------------------------------------------------------------------------------------------------------------------------------------------------| From a21748bda3f30b7efb5ab459bec842d4790b50ca Mon Sep 17 00:00:00 2001 From: jiwon Date: Wed, 27 Dec 2023 17:06:12 +0900 Subject: [PATCH 0205/1029] =?UTF-8?q?=EC=BB=A4=EB=B0=8B=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From c7b3915170b15e93075fe471e9feb268f5774817 Mon Sep 17 00:00:00 2001 From: jiwon Date: Wed, 27 Dec 2023 17:28:23 +0900 Subject: [PATCH 0206/1029] =?UTF-8?q?git=20commit=20=EC=B7=A8=EC=86=8C=20&?= =?UTF-8?q?=20PR=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=ED=95=B4=EB=B3=B4=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc1579cd1..695a60a45 100644 --- a/README.md +++ b/README.md @@ -170,8 +170,8 @@ | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | |-----------------------------------------------|------------------------------------------------|---------------------------------------|----------------------------------------------|-----------------------------------------------|----------------------------------------| -| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | []() | []() | []() | []() | -|-------------------------------------------|-------------------------------------------|------|------|------|------| +| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 ]() | []() | []() | []() | []() | +|-------------------------------------------|----------|------|------|------|------| | | |-----------------------------------------------------------------------------------------------------------------------------------------------------------| From 67af7fdf3c8768652b9a61ab351ec0950f206cfd Mon Sep 17 00:00:00 2001 From: chyo1 Date: Wed, 27 Dec 2023 17:44:40 +0900 Subject: [PATCH 0207/1029] =?UTF-8?q?[COMMON]=20README=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 695a60a45..dc1579cd1 100644 --- a/README.md +++ b/README.md @@ -170,8 +170,8 @@ | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | |-----------------------------------------------|------------------------------------------------|---------------------------------------|----------------------------------------------|-----------------------------------------------|----------------------------------------| -| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 ]() | []() | []() | []() | []() | -|-------------------------------------------|----------|------|------|------|------| +| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | []() | []() | []() | []() | +|-------------------------------------------|-------------------------------------------|------|------|------|------| | | |-----------------------------------------------------------------------------------------------------------------------------------------------------------| From 7472d06b4860837397a1516acf34381a6e08236a Mon Sep 17 00:00:00 2001 From: sohyun95277 Date: Wed, 27 Dec 2023 20:46:50 +0900 Subject: [PATCH 0208/1029] =?UTF-8?q?DOCS:=20sohyupar=20=EA=B3=84=EC=A0=95?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc1579cd1..187cbb618 100644 --- a/README.md +++ b/README.md @@ -170,8 +170,8 @@ | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | |-----------------------------------------------|------------------------------------------------|---------------------------------------|----------------------------------------------|-----------------------------------------------|----------------------------------------| -| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | []() | []() | []() | []() | -|-------------------------------------------|-------------------------------------------|------|------|------|------| +| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [👽 sohyupar](https://github.com/saewoo1) | []() | []() | []() | +|-------------------------------------------|-------------------------------------------|-----------------|------|------|------| | | |-----------------------------------------------------------------------------------------------------------------------------------------------------------| From 40a4193806220431497b839b3ef9be4159a56898 Mon Sep 17 00:00:00 2001 From: saewoo1 Date: Wed, 27 Dec 2023 21:07:48 +0900 Subject: [PATCH 0209/1029] =?UTF-8?q?[BE]=20DOCS:=20sohyupar=20=EA=B3=84?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80#1494?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 컨벤션에 맞춘 commit 다시 수행 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 187cbb618..77138294e 100644 --- a/README.md +++ b/README.md @@ -170,8 +170,8 @@ | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | |-----------------------------------------------|------------------------------------------------|---------------------------------------|----------------------------------------------|-----------------------------------------------|----------------------------------------| -| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [👽 sohyupar](https://github.com/saewoo1) | []() | []() | []() | -|-------------------------------------------|-------------------------------------------|-----------------|------|------|------| +| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | []() | []() | []() | +|-------------------------------------------|-------------------------------------------|--------------------------------------------|------|------|------| | | |-----------------------------------------------------------------------------------------------------------------------------------------------------------| From c54459fdd9761ea6c35b722d6568c1b142156e5d Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 28 Dec 2023 08:44:40 +0900 Subject: [PATCH 0210/1029] =?UTF-8?q?[BE]=20Redis=20Config=20=EC=95=88?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [BE] AdminUser 패키지 정리 [BE] AdminStatistics 패키지 정리 [BE] AdminSearch 패키지 정리 [BE] AdminLent 패키지 정리 [BE] AdminCabinet 패키지 정리 [BE] UserController의 UserFacade 리팩토링 코드로 변경 --- .../admin/auth/{ => controller}/AdminAuthController.java | 2 +- .../cabinet/{ => controller}/AdminCabinetController.java | 2 +- .../cabinet/{ => service}/AdminCabinetFacadeService.java | 2 +- .../admin/lent/{ => controller}/AdminLentController.java | 3 ++- .../admin/lent/{ => service}/AdminLentFacadeService.java | 2 +- .../search/{ => controller}/AdminSearchController.java | 3 ++- .../search/{ => service}/AdminSearchFacadeService.java | 2 +- .../{ => controller}/AdminStatisticsController.java | 3 ++- .../{ => service}/AdminStatisticsFacadeService.java | 2 +- .../admin/user/{ => controller}/AdminUserController.java | 6 ++++-- .../{ => service}/AdminLentExtensionFacadeService.java | 2 +- .../admin/user/{ => service}/AdminUserFacadeService.java | 2 +- .../cabinet/cabinet/controller/CabinetController.java | 2 +- .../org/ftclub/cabinet/config/RedisRepositoryConfig.java | 7 +++---- .../ftclub/cabinet/user/controller/UserController.java | 8 ++++---- 15 files changed, 26 insertions(+), 22 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/admin/auth/{ => controller}/AdminAuthController.java (98%) rename backend/src/main/java/org/ftclub/cabinet/admin/cabinet/{ => controller}/AdminCabinetController.java (99%) rename backend/src/main/java/org/ftclub/cabinet/admin/cabinet/{ => service}/AdminCabinetFacadeService.java (88%) rename backend/src/main/java/org/ftclub/cabinet/admin/lent/{ => controller}/AdminLentController.java (91%) rename backend/src/main/java/org/ftclub/cabinet/admin/lent/{ => service}/AdminLentFacadeService.java (99%) rename backend/src/main/java/org/ftclub/cabinet/admin/search/{ => controller}/AdminSearchController.java (93%) rename backend/src/main/java/org/ftclub/cabinet/admin/search/{ => service}/AdminSearchFacadeService.java (99%) rename backend/src/main/java/org/ftclub/cabinet/admin/statistics/{ => controller}/AdminStatisticsController.java (95%) rename backend/src/main/java/org/ftclub/cabinet/admin/statistics/{ => service}/AdminStatisticsFacadeService.java (98%) rename backend/src/main/java/org/ftclub/cabinet/admin/user/{ => controller}/AdminUserController.java (94%) rename backend/src/main/java/org/ftclub/cabinet/admin/user/{ => service}/AdminLentExtensionFacadeService.java (95%) rename backend/src/main/java/org/ftclub/cabinet/admin/user/{ => service}/AdminUserFacadeService.java (97%) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java b/backend/src/main/java/org/ftclub/cabinet/admin/auth/controller/AdminAuthController.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/auth/controller/AdminAuthController.java index 710803e93..f68c4bd3c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/auth/controller/AdminAuthController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.auth; +package org.ftclub.cabinet.admin.auth.controller; import java.io.IOException; import java.time.LocalDateTime; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java index 7fdd643b5..f6f4671ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.cabinet; +package org.ftclub.cabinet.admin.cabinet.controller; import java.util.HashMap; import java.util.Map; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/service/AdminCabinetFacadeService.java similarity index 88% rename from backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/cabinet/service/AdminCabinetFacadeService.java index 26401238f..858b0d030 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/service/AdminCabinetFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.cabinet; +package org.ftclub.cabinet.admin.cabinet.service; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/controller/AdminLentController.java similarity index 91% rename from backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/lent/controller/AdminLentController.java index e350add89..da7391e6d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/controller/AdminLentController.java @@ -1,9 +1,10 @@ -package org.ftclub.cabinet.admin.lent; +package org.ftclub.cabinet.admin.lent.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; import javax.validation.Valid; import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.lent.service.AdminLentFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; import org.ftclub.cabinet.log.Logging; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java index 421c8a7da..391bdcd59 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.lent; +package org.ftclub.cabinet.admin.lent.service; import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/controller/AdminSearchController.java similarity index 93% rename from backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/search/controller/AdminSearchController.java index 3e1cfdbc4..a778acfc5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/controller/AdminSearchController.java @@ -1,8 +1,9 @@ -package org.ftclub.cabinet.admin.search; +package org.ftclub.cabinet.admin.search.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.search.service.AdminSearchFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java index f136758a8..32dbd6e13 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.search; +package org.ftclub.cabinet.admin.search.service; import static java.util.stream.Collectors.toList; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.IN_SESSION; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminStatisticsController.java similarity index 95% rename from backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminStatisticsController.java index e18d08ab8..dea400d9f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminStatisticsController.java @@ -1,10 +1,11 @@ -package org.ftclub.cabinet.admin.statistics; +package org.ftclub.cabinet.admin.statistics.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.statistics.service.AdminStatisticsFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java index 3a5e693a4..5560c7eae 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.statistics; +package org.ftclub.cabinet.admin.statistics.service; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.BROKEN; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java similarity index 94% rename from backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java index 731b62a28..014ab50de 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java @@ -1,10 +1,12 @@ -package org.ftclub.cabinet.admin.user; +package org.ftclub.cabinet.admin.user.controller; import java.time.LocalDateTime; import java.util.HashMap; import javax.validation.Valid; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.admin.lent.AdminLentFacadeService; +import org.ftclub.cabinet.admin.lent.service.AdminLentFacadeService; +import org.ftclub.cabinet.admin.user.service.AdminLentExtensionFacadeService; +import org.ftclub.cabinet.admin.user.service.AdminUserFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.dto.ClubUserListDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java similarity index 95% rename from backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java index fa98e27c9..6bd24f5f5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.user; +package org.ftclub.cabinet.admin.user.service; import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java similarity index 97% rename from backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java index 0cfa183e9..5eeb5dcab 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.user; +package org.ftclub.cabinet.admin.user.service; import java.time.LocalDateTime; import java.util.List; diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java index 774de34d0..b1c7934a6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; -import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; +import org.ftclub.cabinet.cabinet.newService.CabinetFacadeService; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetPendingResponseDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/config/RedisRepositoryConfig.java b/backend/src/main/java/org/ftclub/cabinet/config/RedisRepositoryConfig.java index 0a860cf27..4e837bed9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/RedisRepositoryConfig.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/RedisRepositoryConfig.java @@ -18,8 +18,6 @@ @EnableRedisRepositories // Redis Repository 활성화 public class RedisRepositoryConfig { - private final String PATTERN = "__keyevent@*__:expired"; - @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") @@ -28,6 +26,7 @@ public class RedisRepositoryConfig { @Bean public RedisMessageListenerContainer redisMessageListenerContainer( RedisConnectionFactory redisConnectionFactory) { + RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); return redisMessageListenerContainer; @@ -47,9 +46,9 @@ public RedisConnectionFactory redisConnectionFactory() { @Bean public RedisTemplate redisTemplate() { RedisTemplate redisTemplate = new RedisTemplate<>(); -// GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); - StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); redisTemplate.setConnectionFactory(redisConnectionFactory()); + StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); + redisTemplate.setKeySerializer(stringRedisSerializer); redisTemplate.setValueSerializer(stringRedisSerializer); redisTemplate.setHashKeySerializer(stringRedisSerializer); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java index 5675ce5bf..18f7667a6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java @@ -9,7 +9,7 @@ import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.UserSession; -import org.ftclub.cabinet.user.service.UserFacadeService; +import org.ftclub.cabinet.user.newService.UserFacadeService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -36,7 +36,7 @@ public class UserController { @GetMapping("/me") @AuthGuard(level = AuthLevel.USER_ONLY) public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSessionDto) { - return userFacadeService.getMyProfile(userSessionDto); + return userFacadeService.getProfile(userSessionDto); } /** @@ -49,7 +49,7 @@ public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSession @AuthGuard(level = AuthLevel.USER_ONLY) public LentExtensionPaginationDto getMyLentExtension( @UserSession UserSessionDto userSessionDto) { - return userFacadeService.getMyLentExtension(userSessionDto); + return userFacadeService.getLentExtensions(userSessionDto); } /** @@ -62,7 +62,7 @@ public LentExtensionPaginationDto getMyLentExtension( @AuthGuard(level = AuthLevel.USER_ONLY) public LentExtensionPaginationDto getMyActiveLentExtension( @UserSession UserSessionDto userSessionDto) { - return userFacadeService.getMyActiveLentExtensionPage(userSessionDto); + return userFacadeService.getActiveLentExtensionsPage(userSessionDto); } /** From bc8c8dc1031efbefdf3704e177ab219c7e63b881 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 28 Dec 2023 08:44:40 +0900 Subject: [PATCH 0211/1029] =?UTF-8?q?[BE]=20Redis=20Config=20=EC=95=88?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [BE] AdminUser 패키지 정리 [BE] AdminStatistics 패키지 정리 [BE] AdminSearch 패키지 정리 [BE] AdminLent 패키지 정리 [BE] AdminCabinet 패키지 정리 [BE] UserController의 UserFacade 리팩토링 코드로 변경 --- .../admin/auth/{ => controller}/AdminAuthController.java | 2 +- .../cabinet/{ => controller}/AdminCabinetController.java | 2 +- .../cabinet/{ => service}/AdminCabinetFacadeService.java | 2 +- .../admin/lent/{ => controller}/AdminLentController.java | 3 ++- .../admin/lent/{ => service}/AdminLentFacadeService.java | 2 +- .../search/{ => controller}/AdminSearchController.java | 3 ++- .../search/{ => service}/AdminSearchFacadeService.java | 2 +- .../{ => controller}/AdminStatisticsController.java | 3 ++- .../{ => service}/AdminStatisticsFacadeService.java | 2 +- .../admin/user/{ => controller}/AdminUserController.java | 6 ++++-- .../{ => service}/AdminLentExtensionFacadeService.java | 2 +- .../admin/user/{ => service}/AdminUserFacadeService.java | 2 +- .../cabinet/cabinet/controller/CabinetController.java | 2 +- .../org/ftclub/cabinet/config/RedisRepositoryConfig.java | 7 +++---- .../ftclub/cabinet/user/controller/UserController.java | 8 ++++---- 15 files changed, 26 insertions(+), 22 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/admin/auth/{ => controller}/AdminAuthController.java (98%) rename backend/src/main/java/org/ftclub/cabinet/admin/cabinet/{ => controller}/AdminCabinetController.java (99%) rename backend/src/main/java/org/ftclub/cabinet/admin/cabinet/{ => service}/AdminCabinetFacadeService.java (88%) rename backend/src/main/java/org/ftclub/cabinet/admin/lent/{ => controller}/AdminLentController.java (91%) rename backend/src/main/java/org/ftclub/cabinet/admin/lent/{ => service}/AdminLentFacadeService.java (99%) rename backend/src/main/java/org/ftclub/cabinet/admin/search/{ => controller}/AdminSearchController.java (93%) rename backend/src/main/java/org/ftclub/cabinet/admin/search/{ => service}/AdminSearchFacadeService.java (99%) rename backend/src/main/java/org/ftclub/cabinet/admin/statistics/{ => controller}/AdminStatisticsController.java (95%) rename backend/src/main/java/org/ftclub/cabinet/admin/statistics/{ => service}/AdminStatisticsFacadeService.java (98%) rename backend/src/main/java/org/ftclub/cabinet/admin/user/{ => controller}/AdminUserController.java (94%) rename backend/src/main/java/org/ftclub/cabinet/admin/user/{ => service}/AdminLentExtensionFacadeService.java (95%) rename backend/src/main/java/org/ftclub/cabinet/admin/user/{ => service}/AdminUserFacadeService.java (97%) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java b/backend/src/main/java/org/ftclub/cabinet/admin/auth/controller/AdminAuthController.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/auth/controller/AdminAuthController.java index 710803e93..f68c4bd3c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/auth/AdminAuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/auth/controller/AdminAuthController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.auth; +package org.ftclub.cabinet.admin.auth.controller; import java.io.IOException; import java.time.LocalDateTime; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java index 7fdd643b5..f6f4671ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.cabinet; +package org.ftclub.cabinet.admin.cabinet.controller; import java.util.HashMap; import java.util.Map; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/service/AdminCabinetFacadeService.java similarity index 88% rename from backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/cabinet/service/AdminCabinetFacadeService.java index 26401238f..858b0d030 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/AdminCabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/service/AdminCabinetFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.cabinet; +package org.ftclub.cabinet.admin.cabinet.service; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.newService.CabinetQueryService; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/controller/AdminLentController.java similarity index 91% rename from backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/lent/controller/AdminLentController.java index e350add89..da7391e6d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/controller/AdminLentController.java @@ -1,9 +1,10 @@ -package org.ftclub.cabinet.admin.lent; +package org.ftclub.cabinet.admin.lent.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; import javax.validation.Valid; import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.lent.service.AdminLentFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.ReturnCabinetsRequestDto; import org.ftclub.cabinet.log.Logging; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java index 421c8a7da..391bdcd59 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.lent; +package org.ftclub.cabinet.admin.lent.service; import static org.ftclub.cabinet.cabinet.domain.LentType.SHARE; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/controller/AdminSearchController.java similarity index 93% rename from backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/search/controller/AdminSearchController.java index 3e1cfdbc4..a778acfc5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/controller/AdminSearchController.java @@ -1,8 +1,9 @@ -package org.ftclub.cabinet.admin.search; +package org.ftclub.cabinet.admin.search.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.search.service.AdminSearchFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java similarity index 99% rename from backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java index f136758a8..32dbd6e13 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/AdminSearchFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.search; +package org.ftclub.cabinet.admin.search.service; import static java.util.stream.Collectors.toList; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.IN_SESSION; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminStatisticsController.java similarity index 95% rename from backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminStatisticsController.java index e18d08ab8..dea400d9f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/controller/AdminStatisticsController.java @@ -1,10 +1,11 @@ -package org.ftclub.cabinet.admin.statistics; +package org.ftclub.cabinet.admin.statistics.controller; import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.statistics.service.AdminStatisticsFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.dto.BlockedUserPaginationDto; import org.ftclub.cabinet.dto.CabinetFloorStatisticsResponseDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java index 3a5e693a4..5560c7eae 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/statistics/AdminStatisticsFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/statistics/service/AdminStatisticsFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.statistics; +package org.ftclub.cabinet.admin.statistics.service; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.AVAILABLE; import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.BROKEN; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java similarity index 94% rename from backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java rename to backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java index 731b62a28..014ab50de 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java @@ -1,10 +1,12 @@ -package org.ftclub.cabinet.admin.user; +package org.ftclub.cabinet.admin.user.controller; import java.time.LocalDateTime; import java.util.HashMap; import javax.validation.Valid; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.admin.lent.AdminLentFacadeService; +import org.ftclub.cabinet.admin.lent.service.AdminLentFacadeService; +import org.ftclub.cabinet.admin.user.service.AdminLentExtensionFacadeService; +import org.ftclub.cabinet.admin.user.service.AdminUserFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.dto.ClubUserListDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java similarity index 95% rename from backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java index fa98e27c9..6bd24f5f5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminLentExtensionFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.user; +package org.ftclub.cabinet.admin.user.service; import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java similarity index 97% rename from backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java rename to backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java index 0cfa183e9..5eeb5dcab 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/AdminUserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.admin.user; +package org.ftclub.cabinet.admin.user.service; import java.time.LocalDateTime; import java.util.List; diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java index 774de34d0..b1c7934a6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; -import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; +import org.ftclub.cabinet.cabinet.newService.CabinetFacadeService; import org.ftclub.cabinet.dto.BuildingFloorsDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetPendingResponseDto; diff --git a/backend/src/main/java/org/ftclub/cabinet/config/RedisRepositoryConfig.java b/backend/src/main/java/org/ftclub/cabinet/config/RedisRepositoryConfig.java index 0a860cf27..4e837bed9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/config/RedisRepositoryConfig.java +++ b/backend/src/main/java/org/ftclub/cabinet/config/RedisRepositoryConfig.java @@ -18,8 +18,6 @@ @EnableRedisRepositories // Redis Repository 활성화 public class RedisRepositoryConfig { - private final String PATTERN = "__keyevent@*__:expired"; - @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") @@ -28,6 +26,7 @@ public class RedisRepositoryConfig { @Bean public RedisMessageListenerContainer redisMessageListenerContainer( RedisConnectionFactory redisConnectionFactory) { + RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); return redisMessageListenerContainer; @@ -47,9 +46,9 @@ public RedisConnectionFactory redisConnectionFactory() { @Bean public RedisTemplate redisTemplate() { RedisTemplate redisTemplate = new RedisTemplate<>(); -// GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); - StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); redisTemplate.setConnectionFactory(redisConnectionFactory()); + StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); + redisTemplate.setKeySerializer(stringRedisSerializer); redisTemplate.setValueSerializer(stringRedisSerializer); redisTemplate.setHashKeySerializer(stringRedisSerializer); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java index 5675ce5bf..18f7667a6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java @@ -9,7 +9,7 @@ import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.UserSession; -import org.ftclub.cabinet.user.service.UserFacadeService; +import org.ftclub.cabinet.user.newService.UserFacadeService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -36,7 +36,7 @@ public class UserController { @GetMapping("/me") @AuthGuard(level = AuthLevel.USER_ONLY) public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSessionDto) { - return userFacadeService.getMyProfile(userSessionDto); + return userFacadeService.getProfile(userSessionDto); } /** @@ -49,7 +49,7 @@ public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSession @AuthGuard(level = AuthLevel.USER_ONLY) public LentExtensionPaginationDto getMyLentExtension( @UserSession UserSessionDto userSessionDto) { - return userFacadeService.getMyLentExtension(userSessionDto); + return userFacadeService.getLentExtensions(userSessionDto); } /** @@ -62,7 +62,7 @@ public LentExtensionPaginationDto getMyLentExtension( @AuthGuard(level = AuthLevel.USER_ONLY) public LentExtensionPaginationDto getMyActiveLentExtension( @UserSession UserSessionDto userSessionDto) { - return userFacadeService.getMyActiveLentExtensionPage(userSessionDto); + return userFacadeService.getActiveLentExtensionsPage(userSessionDto); } /** From 1e1ccdcedf318eaf6d092db7a1768bb8adf9612f Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Fri, 29 Dec 2023 14:08:35 +0900 Subject: [PATCH 0212/1029] =?UTF-8?q?HOTFIX=20:=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=20=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/auth/domain/TokenValidator.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java index 5c44c7d4b..6458c4e87 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java @@ -1,7 +1,5 @@ package org.ftclub.cabinet.auth.domain; -import static org.ftclub.cabinet.user.domain.AdminRole.MASTER; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -9,9 +7,6 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; -import java.security.Key; -import java.util.Base64; -import javax.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.config.DomainProperties; @@ -23,6 +18,12 @@ import org.ftclub.cabinet.user.service.UserService; import org.springframework.stereotype.Component; +import javax.servlet.http.HttpServletRequest; +import java.security.Key; +import java.util.Base64; + +import static org.ftclub.cabinet.user.domain.AdminRole.MASTER; + /** * 토큰의 유효성을 검사하는 클래스입니다. *

@@ -146,7 +147,7 @@ public boolean isTokenAuthenticatable(String token, AuthLevel authLevel) */ private boolean isAdminEmail(String email) { // TODO : 이메일 검증 로직 수정 : 현재는 도메인만 검증하고 있어서 뚫릴 가능성이 있을듯, 추후 검토 필요 - return email.endsWith(masterProperties.getDomain()) - || email.endsWith(domainProperties.getAdminEmailDomain()); + AdminRole adminUserRole = userService.getAdminUserRole(email); + return adminUserRole.equals(MASTER) || adminUserRole.equals(AdminRole.ADMIN); } } From 716649b7813152dc03ed338888bf9bd9bcb461da Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Fri, 29 Dec 2023 14:31:09 +0900 Subject: [PATCH 0213/1029] =?UTF-8?q?HOTFIX=20:=20adminEmail=EB=A1=9C=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=ED=95=98=EB=8D=98=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/auth/domain/TokenValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java index 6458c4e87..aa9741a99 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java @@ -128,7 +128,7 @@ public boolean isTokenAuthenticatable(String token, AuthLevel authLevel) case USER_OR_ADMIN: return true; case USER_ONLY: - return !isAdminEmail(email); + return userService.checkUserExists(email); case ADMIN_ONLY: return isAdminEmail(email); case MASTER_ONLY: From 657725d239713ebaafd7c47c2e3c57dca832c297 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Fri, 29 Dec 2023 14:34:23 +0900 Subject: [PATCH 0214/1029] =?UTF-8?q?HOTFIX=20:=20adminEmail=EB=A1=9C=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=ED=95=98=EB=8D=98=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/auth/domain/TokenValidator.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java index 5c44c7d4b..aa9741a99 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java @@ -1,7 +1,5 @@ package org.ftclub.cabinet.auth.domain; -import static org.ftclub.cabinet.user.domain.AdminRole.MASTER; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -9,9 +7,6 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; -import java.security.Key; -import java.util.Base64; -import javax.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.config.DomainProperties; @@ -23,6 +18,12 @@ import org.ftclub.cabinet.user.service.UserService; import org.springframework.stereotype.Component; +import javax.servlet.http.HttpServletRequest; +import java.security.Key; +import java.util.Base64; + +import static org.ftclub.cabinet.user.domain.AdminRole.MASTER; + /** * 토큰의 유효성을 검사하는 클래스입니다. *

@@ -127,7 +128,7 @@ public boolean isTokenAuthenticatable(String token, AuthLevel authLevel) case USER_OR_ADMIN: return true; case USER_ONLY: - return !isAdminEmail(email); + return userService.checkUserExists(email); case ADMIN_ONLY: return isAdminEmail(email); case MASTER_ONLY: @@ -146,7 +147,7 @@ public boolean isTokenAuthenticatable(String token, AuthLevel authLevel) */ private boolean isAdminEmail(String email) { // TODO : 이메일 검증 로직 수정 : 현재는 도메인만 검증하고 있어서 뚫릴 가능성이 있을듯, 추후 검토 필요 - return email.endsWith(masterProperties.getDomain()) - || email.endsWith(domainProperties.getAdminEmailDomain()); + AdminRole adminUserRole = userService.getAdminUserRole(email); + return adminUserRole.equals(MASTER) || adminUserRole.equals(AdminRole.ADMIN); } } From 2cbeb3cecea37f78e34678206d1cdef51e34dba1 Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Fri, 29 Dec 2023 14:43:39 +0900 Subject: [PATCH 0215/1029] =?UTF-8?q?HOTFIX=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EA=B0=80=EC=9E=85=EC=8B=9C=20=ED=95=B4=EB=8B=B9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20throw?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/auth/domain/TokenValidator.java | 2 +- .../java/org/ftclub/cabinet/auth/service/AuthService.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java index aa9741a99..7f9eb549f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java @@ -148,6 +148,6 @@ public boolean isTokenAuthenticatable(String token, AuthLevel authLevel) private boolean isAdminEmail(String email) { // TODO : 이메일 검증 로직 수정 : 현재는 도메인만 검증하고 있어서 뚫릴 가능성이 있을듯, 추후 검토 필요 AdminRole adminUserRole = userService.getAdminUserRole(email); - return adminUserRole.equals(MASTER) || adminUserRole.equals(AdminRole.ADMIN); + return adminUserRole != null && (adminUserRole.equals(MASTER) || adminUserRole.equals(AdminRole.ADMIN)); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthService.java index c5e0dd532..b057f713b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthService.java @@ -1,8 +1,5 @@ package org.ftclub.cabinet.auth.service; -import static org.ftclub.cabinet.user.domain.UserRole.USER; - -import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.config.DomainProperties; @@ -14,6 +11,10 @@ import org.ftclub.cabinet.utils.DateUtil; import org.springframework.stereotype.Service; +import java.util.Map; + +import static org.ftclub.cabinet.user.domain.UserRole.USER; + /** * Cabi 자체의 인증 서비스입니다. */ @@ -55,6 +56,7 @@ public void addUserIfNotExistsByClaims(Map claims) { DateUtil.stringToDate(blackHoledAtObject.toString()), USER); } } + throw new ServiceException(ExceptionStatus.INVALID_ARGUMENT); } } From 5524b7fd14e2a78721040d797831982f48413a60 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 29 Dec 2023 15:20:02 +0900 Subject: [PATCH 0216/1029] [BE] FIX: production alarm off --- .../org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index 9c6161ec1..f53c9849b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -28,9 +28,9 @@ public class AlarmEventHandler { @TransactionalEventListener public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactionalAlarmEvent) { - if (!alarmProperties.getIsProduction()) { - return; - } +// if (!alarmProperties.getIsProduction()) { +// return; +// } log.info("handleAlarmEventWithTransactional = {}", transactionalAlarmEvent); if (!(transactionalAlarmEvent instanceof TransactionalAlarmEvent)) { return; From 9dee26b050725c2b1f1f12463904fd2cdfe4b091 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 29 Dec 2023 15:59:28 +0900 Subject: [PATCH 0217/1029] [BE] FIX: production alarm off --- .../org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index f53c9849b..113df83ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -42,9 +42,9 @@ public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactio @EventListener public void handleAlarmEvent(AlarmEvent alarmEvent) { log.info("handleAlarmEvent = {}", alarmEvent); - if (!alarmProperties.getIsProduction()) { - return; - } +// if (!alarmProperties.getIsProduction()) { +// return; +// } eventProceed(alarmEvent); } From 128cc8ddf6b399338d3f6ed05e748fdf9cda98af Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Fri, 29 Dec 2023 17:25:43 +0900 Subject: [PATCH 0218/1029] =?UTF-8?q?FIX,=20REFACTOR=20:=20Auth=EC=97=90?= =?UTF-8?q?=EC=84=9C=20admin=EC=9D=98=20role=EB=A1=9C=20=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=ED=95=98=EA=B3=A0,=20user=EC=9D=98=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=9C=A0=EB=AC=B4?= =?UTF-8?q?=EB=A1=9C=20=ED=8C=90=EB=8B=A8=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EA=B5=AC=EC=B2=B4=EC=A0=81?= =?UTF-8?q?=EC=9D=B4=EC=97=88=EB=8D=98=20FtOauth=20->=20UserOauth,=20Googl?= =?UTF-8?q?eOauth=20->=20AdminOauth=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/auth/domain/AuthAspect.java | 22 +++- .../ftclub/cabinet/auth/domain/FtProfile.java | 4 +- ...uthService.java => AdminOauthService.java} | 7 +- .../auth/service/ApplicationTokenManager.java | 4 +- .../auth/service/AuthFacadeService.java | 13 +- .../{domain => service}/TokenProvider.java | 7 +- .../{domain => service}/TokenValidator.java | 113 +++++++----------- ...authService.java => UserOauthService.java} | 15 ++- .../lent/repository/LentRepository.java | 26 ++-- .../ftclub/cabinet/log/AdminApiLogAspect.java | 4 +- .../cabinet/log/AllRequestLogInterceptor.java | 2 +- .../cabinet/user/domain/UserAspect.java | 2 +- .../user/newService/UserQueryService.java | 9 +- .../blackhole/manager/BlackholeManager.java | 9 +- .../leave/absence/LeaveAbsenceManager.java | 6 +- .../auth/domain/AuthAspectUnitTest.java | 7 +- .../auth/domain/TokenProviderUnitTest.java | 14 ++- .../auth/domain/TokenValidatorUnitTest.java | 3 +- .../AuthFacadeServiceImplUnitTest.java | 1 - .../AdminSearchStatisticsControllerTest.java | 2 +- 20 files changed, 135 insertions(+), 135 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/auth/service/{GoogleOauthService.java => AdminOauthService.java} (93%) rename backend/src/main/java/org/ftclub/cabinet/auth/{domain => service}/TokenProvider.java (98%) rename backend/src/main/java/org/ftclub/cabinet/auth/{domain => service}/TokenValidator.java (58%) rename backend/src/main/java/org/ftclub/cabinet/auth/service/{FtOauthService.java => UserOauthService.java} (90%) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java index 05b8df062..be25fc00e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java @@ -5,9 +5,11 @@ import lombok.extern.log4j.Log4j2; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; +import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; +import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -26,10 +28,9 @@ @RequiredArgsConstructor public class AuthAspect { + private static final String BEARER = "Bearer "; private final TokenValidator tokenValidator; - private final AuthCookieManager authCookieManager; - private final JwtProperties jwtProperties; /** @@ -50,6 +51,7 @@ public void AuthToken(AuthGuard authGuard) throws JsonProcessingException { .getResponse(); String mainTokenName = jwtProperties.getMainTokenName(); String adminTokenName = jwtProperties.getAdminTokenName(); + String token = extractToken(request); /** * {@link AuthGuard}의 레벨에 따라서 토큰의 유무와 유효성을 검사합니다. @@ -61,29 +63,37 @@ public void AuthToken(AuthGuard authGuard) throws JsonProcessingException { */ switch (authGuard.level()) { case ADMIN_ONLY: - if (!tokenValidator.isValidRequestWithLevel(request, ADMIN_ONLY)) { + if (!tokenValidator.isValidTokenWithLevel(token, ADMIN_ONLY)) { authCookieManager.deleteCookie(response, adminTokenName); throw new ControllerException(ExceptionStatus.UNAUTHORIZED_ADMIN); } break; case USER_ONLY: - if (!tokenValidator.isValidRequestWithLevel(request, USER_ONLY)) { + if (!tokenValidator.isValidTokenWithLevel(token, USER_ONLY)) { authCookieManager.deleteCookie(response, mainTokenName); throw new ControllerException(ExceptionStatus.UNAUTHORIZED_USER); } break; case USER_OR_ADMIN: - if (!tokenValidator.isValidRequestWithLevel(request, USER_OR_ADMIN)) { + if (!tokenValidator.isValidTokenWithLevel(token, USER_OR_ADMIN)) { authCookieManager.deleteCookie(response, mainTokenName); authCookieManager.deleteCookie(response, adminTokenName); throw new ControllerException(ExceptionStatus.UNAUTHORIZED); } break; case MASTER_ONLY: - if (!tokenValidator.isValidRequestWithLevel(request, MASTER_ONLY)) { + if (!tokenValidator.isValidTokenWithLevel(token, MASTER_ONLY)) { authCookieManager.deleteCookie(response, adminTokenName); throw new ControllerException(ExceptionStatus.UNAUTHORIZED_ADMIN); } } } + + private String extractToken(HttpServletRequest request) { + String header = request.getHeader(HttpHeaders.AUTHORIZATION); + if (header == null || !header.startsWith(BEARER)) { + return null; + } + return header.substring(BEARER.length()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java index f18161f55..125f6312c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/FtProfile.java @@ -3,14 +3,14 @@ import lombok.Builder; import lombok.Getter; import lombok.ToString; -import org.ftclub.cabinet.auth.service.FtOauthService; +import org.ftclub.cabinet.auth.service.UserOauthService; import java.time.LocalDateTime; /** * 42 OAuth 로그인을 통해 서비스에서 사용하는 프로필 정보를 담는 클래스입니다. *

- * 정보에 변경이 생겨야 한다면 {@link FtOauthService}#convertJsonStringToProfile 메서드를 수정하세요. + * 정보에 변경이 생겨야 한다면 {@link UserOauthService}#convertJsonStringToProfile 메서드를 수정하세요. */ @Builder @Getter diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/GoogleOauthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AdminOauthService.java similarity index 93% rename from backend/src/main/java/org/ftclub/cabinet/auth/service/GoogleOauthService.java rename to backend/src/main/java/org/ftclub/cabinet/auth/service/AdminOauthService.java index 8775e696f..46c90f7d2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/GoogleOauthService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AdminOauthService.java @@ -23,7 +23,7 @@ @Service @Log4j2 @RequiredArgsConstructor -public class GoogleOauthService { +public class AdminOauthService { @Qualifier(OauthConfig.GOOGLE_OAUTH_20_SERVICE) private final OAuth20Service googleOAuth20Service; private final ObjectMapper objectMapper; @@ -48,12 +48,12 @@ public void requestLogin(HttpServletResponse res) throws IOException { * @throws InterruptedException 비동기 처리시 스레드 종료를 위한 예외 */ public GoogleProfile getProfileByCode(String code) throws IOException, ExecutionException, InterruptedException { - OAuthRequest oAuthRequest = new OAuthRequest(Verb.GET, googleOAuth20Service.getAuthorizationUrl()); OAuth2AccessToken accessToken = googleOAuth20Service.getAccessToken(code); + OAuthRequest oAuthRequest = new OAuthRequest(Verb.GET, "https://www.googleapis.com/oauth2/v2/userinfo"); googleOAuth20Service.signRequest(accessToken, oAuthRequest); try { Response response = googleOAuth20Service.execute(oAuthRequest); - return null; + return convertJsonStringToProfile(response.getBody()); } catch (Exception e) { if (e instanceof IOException) log.error("42 API 서버에서 프로필 정보를 가져오는데 실패했습니다." @@ -61,6 +61,7 @@ public GoogleProfile getProfileByCode(String code) throws IOException, Execution if (e instanceof ExecutionException || e instanceof InterruptedException) log.error("42 API 서버에서 프로필 정보를 비동기적으로 가져오는데 실패했습니다." + "code: {}, message: {}", code, e.getMessage()); + e.printStackTrace(); throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/ApplicationTokenManager.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/ApplicationTokenManager.java index f4d7e45ee..6285181d6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/ApplicationTokenManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/ApplicationTokenManager.java @@ -17,7 +17,7 @@ public class ApplicationTokenManager { private static final int MAX_RETRY = 3; private static String FT_ACCESS_TOKEN; - private final FtOauthService ftOauthService; + private final UserOauthService userOauthService; @PostConstruct private void init() { @@ -32,7 +32,7 @@ public void refreshFtAccessToken() { int tryCount = 0; while (++tryCount <= MAX_RETRY) { try { - FT_ACCESS_TOKEN = ftOauthService.issueAccessTokenByCredentialsGrant().getAccessToken(); + FT_ACCESS_TOKEN = userOauthService.issueAccessTokenByCredentialsGrant().getAccessToken(); break; } catch (Exception e) { log.error("42 OAuth 액세스 토큰을 발급하는 데에 실패했습니다. 현재 시도 횟수 : {}, {}", tryCount, e.getMessage()); diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index 96b220945..a5e748d11 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -7,7 +7,6 @@ import org.ftclub.cabinet.auth.domain.AuthCookieManager; import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.auth.domain.GoogleProfile; -import org.ftclub.cabinet.auth.domain.TokenProvider; import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.dto.MasterLoginDto; @@ -33,8 +32,8 @@ public class AuthFacadeService { private final UserCommandService userCommandService; private final AdminQueryService adminQueryService; private final AdminCommandService adminCommandService; - private final FtOauthService ftOauthService; - private final GoogleOauthService googleOauthService; + private final UserOauthService userOauthService; + private final AdminOauthService adminOauthService; private final TokenProvider tokenProvider; private final AuthCookieManager authCookieManager; @@ -42,15 +41,15 @@ public class AuthFacadeService { private final MasterProperties masterProperties; public void requestUserLogin(HttpServletResponse res) throws IOException { - ftOauthService.requestLogin(res); + userOauthService.requestLogin(res); } public void requestAdminLogin(HttpServletResponse res) throws IOException { - googleOauthService.requestLogin(res); + adminOauthService.requestLogin(res); } public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { - FtProfile profile = ftOauthService.getProfileByCode(code); + FtProfile profile = userOauthService.getProfileByCode(code); User user = userQueryService.findUser(profile.getIntraName()) .orElseGet(() -> userCommandService.createUserByFtProfile(profile)); String token = tokenProvider.createUserToken(user, LocalDateTime.now()); @@ -60,7 +59,7 @@ public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, Str } public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { - GoogleProfile profile = googleOauthService.getProfileByCode(code); + GoogleProfile profile = adminOauthService.getProfileByCode(code); Admin admin = adminQueryService.findByEmail(profile.getEmail()) .orElseGet(() -> adminCommandService.createAdminByEmail(profile.getEmail())); String token = tokenProvider.createAdminToken(admin, LocalDateTime.now()); diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java rename to backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java index 6a76dc4d7..71bc2842c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenProvider.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java @@ -1,10 +1,8 @@ -package org.ftclub.cabinet.auth.domain; +package org.ftclub.cabinet.auth.service; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import java.sql.Timestamp; -import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.admin.admin.domain.AdminRole; @@ -13,6 +11,9 @@ import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Component; +import java.sql.Timestamp; +import java.time.LocalDateTime; + /** * API 제공자에 따라 JWT 토큰을 생성하는 클래스입니다. */ diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenValidator.java similarity index 58% rename from backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java rename to backend/src/main/java/org/ftclub/cabinet/auth/service/TokenValidator.java index 3279ac1d7..7dc844642 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenValidator.java @@ -1,6 +1,4 @@ -package org.ftclub.cabinet.auth.domain; - -import static org.ftclub.cabinet.admin.admin.domain.AdminRole.MASTER; +package org.ftclub.cabinet.auth.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -9,27 +7,22 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; - -import java.security.Key; -import java.util.Base64; -import javax.servlet.http.HttpServletRequest; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ftclub.cabinet.config.DomainProperties; +import org.ftclub.cabinet.admin.admin.domain.Admin; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.admin.admin.service.AdminQueryService; +import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.config.JwtProperties; -import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.admin.admin.domain.AdminRole; -import org.ftclub.cabinet.user.service.UserService; +import org.ftclub.cabinet.user.newService.UserQueryService; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; import java.security.Key; import java.util.Base64; -import static org.ftclub.cabinet.user.domain.AdminRole.MASTER; +import static org.ftclub.cabinet.admin.admin.domain.AdminRole.MASTER; /** * 토큰의 유효성을 검사하는 클래스입니다. @@ -41,34 +34,39 @@ @Slf4j public class TokenValidator { - private final MasterProperties masterProperties; - private final DomainProperties domainProperties; private final JwtProperties jwtProperties; - private final UserService userService; + + private final UserQueryService userQueryService; + private final AdminQueryService adminQueryService; /** * 토큰의 유효성을 검사합니다. - *
- * 매 요청시 헤더에 Bearer 토큰으로 인증을 시도하기 때문에, - *
- * 헤더에 bearer 방식으로 유효하게 토큰이 전달되었는지 검사합니다. - *

- * USER_ONLY의 경우 검증하지 않습니다. * - * @param req {@link HttpServletRequest} + * @param token 검사할 토큰 + * @param authLevel 검사할 토큰의 레벨 * @return 정상적인 방식의 토큰 요청인지, 유효한 토큰인지 여부 */ - public Boolean isValidRequestWithLevel(HttpServletRequest req, AuthLevel authLevel) + public boolean isValidTokenWithLevel(String token, AuthLevel authLevel) throws JsonProcessingException { - String authHeader = req.getHeader("Authorization"); - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - return false; + String email = getPayloadJson(token).get("email").asText(); + if (email == null) { + throw new DomainException(ExceptionStatus.INVALID_ARGUMENT); } - String token = authHeader.substring(7); - if (!isTokenValid(token, jwtProperties.getSigningKey())) { + if (!isTokenValid(token, jwtProperties.getSigningKey())) return false; + + switch (authLevel) { + case USER_OR_ADMIN: + return isUser(email) || isAdmin(email); + case USER_ONLY: + return isUser(email); + case ADMIN_ONLY: + return isAdmin(email); + case MASTER_ONLY: + return isMaster(email); + default: + throw new DomainException(ExceptionStatus.INVALID_STATUS); } - return isTokenAuthenticatable(token, authLevel); } /** @@ -81,7 +79,7 @@ public Boolean isValidRequestWithLevel(HttpServletRequest req, AuthLevel authLev * @param token 검사할 토큰 * @return 토큰이 만료되거나 유효한지 아닌지 여부 */ - public Boolean isTokenValid(String token, Key key) { + public boolean isTokenValid(String token, Key key) { try { Jwts.parserBuilder().setSigningKey(key).build() .parseClaimsJws(token); @@ -115,46 +113,27 @@ public JsonNode getPayloadJson(final String token) throws JsonProcessingExceptio return objectMapper.readTree(new String(decoder.decode(payloadJWT))); } - /** - * 해당 토큰의 페이로드 정보가 인증 단계에 알맞는지 확인합니다. - *

- * MASTER의 경우 현재 정적으로 관리하므로 이메일만 검증합니다. - *

- * - * @param token 토큰 - * @param authLevel 인증 단계 - * @return 페이로드 정보가 실제 DB와 일치하면 true를 반환합니다. - */ - public boolean isTokenAuthenticatable(String token, AuthLevel authLevel) - throws JsonProcessingException { - String email = getPayloadJson(token).get("email").asText(); - if (email == null) { - throw new DomainException(ExceptionStatus.INVALID_ARGUMENT); - } - switch (authLevel) { - case USER_OR_ADMIN: - return true; - case USER_ONLY: - return userService.checkUserExists(email); - case ADMIN_ONLY: - return isAdminEmail(email); - case MASTER_ONLY: - AdminRole role = userService.getAdminUserRole(email); - return role != null && role.equals(MASTER); - default: - throw new DomainException(ExceptionStatus.INVALID_STATUS); - } - } - /** * 해당 이메일이 관리자 이메일인지 확인합니다. * * @param email 관리자 이메일 * @return 관리자 이메일이면 true를 반환합니다. */ - private boolean isAdminEmail(String email) { - // TODO : 이메일 검증 로직 수정 : 현재는 도메인만 검증하고 있어서 뚫릴 가능성이 있을듯, 추후 검토 필요 - AdminRole adminUserRole = userService.getAdminUserRole(email); - return adminUserRole != null && (adminUserRole.equals(MASTER) || adminUserRole.equals(AdminRole.ADMIN)); + private boolean isAdmin(String email) { + AdminRole role = adminQueryService.findByEmail(email) + .map(Admin::getRole) + .orElse(null); + return role != null && (role.equals(AdminRole.ADMIN) || role.equals(MASTER)); + } + + private boolean isMaster(String email) { + AdminRole role = adminQueryService.findByEmail(email) + .map(Admin::getRole) + .orElse(null); + return role != null && role.equals(MASTER); + } + + private boolean isUser(String email) { + return userQueryService.findUserByEmail(email).isPresent(); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/UserOauthService.java similarity index 90% rename from backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java rename to backend/src/main/java/org/ftclub/cabinet/auth/service/UserOauthService.java index c83e3ee74..c70f3f32b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/FtOauthService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/UserOauthService.java @@ -30,7 +30,7 @@ @Service @RequiredArgsConstructor @Log4j2 -public class FtOauthService { +public class UserOauthService { private static final int CURSUS_INDEX = 1; @Qualifier(OauthConfig.FT_OAUTH_20_SERVICE) @@ -67,7 +67,6 @@ public FtProfile getProfileByIntraName(String accessToken, String intraName) thr * @throws IOException HTTP 통신에서 일어나는 입출력 예외 * @throws ExecutionException 비동기 처리시 스레드에서 발생한 오류 처리 예외 * @throws InterruptedException 비동기 처리시 스레드 종료를 위한 예외 - * @see 위 예외에 대한 정보 */ public FtProfile getProfileByCode(String code) throws IOException, ExecutionException, InterruptedException { OAuth2AccessToken accessToken = ftOAuth20Service.getAccessToken(code); @@ -90,19 +89,19 @@ public FtProfile getProfileByCode(String code) throws IOException, ExecutionExce /** * String 형태의 JSON 데이터를 {@link FtProfile}로 변환합니다. * - * @param jsonString String 형태의 JSON 데이터 + * @param jsonNode JSON 데이터 * @return 유저 프로필 정보 {@link FtProfile} * @throws JsonProcessingException JSON 파싱 예외 * @see 42 API에서 제공하는 Profile Json에 대한 정보 */ - private FtProfile convertJsonStringToProfile(JsonNode rootNode) throws JsonProcessingException { - String intraName = rootNode.get("login").asText(); - String email = rootNode.get("email").asText(); + private FtProfile convertJsonStringToProfile(JsonNode jsonNode) throws JsonProcessingException { + String intraName = jsonNode.get("login").asText(); + String email = jsonNode.get("email").asText(); if (intraName == null || email == null) throw new ServiceException(ExceptionStatus.INCORRECT_ARGUMENT); - LocalDateTime blackHoledAt = determineBlackHoledAt(rootNode); - FtRole role = determineFtRole(rootNode, blackHoledAt); + LocalDateTime blackHoledAt = determineBlackHoledAt(jsonNode); + FtRole role = determineFtRole(jsonNode, blackHoledAt); return FtProfile.builder() .intraName(intraName) diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 6f1e03c9a..4af88db09 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -1,10 +1,5 @@ package org.ftclub.cabinet.lent.repository; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import javax.persistence.LockModeType; import org.ftclub.cabinet.lent.domain.LentHistory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -15,6 +10,12 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import javax.persistence.LockModeType; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + /** * {@link LentHistory}를 가져오기 위한 repository */ @@ -69,13 +70,13 @@ public interface LentRepository extends JpaRepository { + "FROM LentHistory lh " + "WHERE lh.startedAt < :endDate AND lh.startedAt >= :startDate") int countLentFromStartDateToEndDate(@Param("startDate") LocalDateTime startDate, - @Param("endDate") LocalDateTime endDate); + @Param("endDate") LocalDateTime endDate); @Query("SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.endedAt < :endDate AND lh.endedAt >= :startDate") int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDate, - @Param("endDate") LocalDateTime endDate); + @Param("endDate") LocalDateTime endDate); /** * 사물함을 기준으로 아직 반납하지 않은 {@link LentHistory}중 하나를 가져옵니다. @@ -130,11 +131,14 @@ int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDat * @param pageable pagination 정보 * @return {@link LentHistory}의 {@link Page} */ - @Query("SELECT lh " + @Query(value = "SELECT lh " + "FROM LentHistory lh " + "LEFT JOIN FETCH lh.user u " + "LEFT JOIN FETCH lh.cabinet c " - + "WHERE lh.cabinetId = :cabinetId ") + + "WHERE lh.cabinetId = :cabinetId ", + countQuery = "SELECT count(lh) " + + "FROM LentHistory lh " + + "WHERE lh.cabinetId = :cabinetId ") Page findPaginationByCabinetIdJoinCabinetAndUser( @Param("cabinetId") Long cabinetId, Pageable pageable); @@ -199,7 +203,7 @@ List findAllByCabinetIdInAndEndedAtIsNullJoinUser( + "WHERE lh.cabinetId IN :cabinetIds " + "AND DATE(lh.endedAt) >= DATE(:date)") List findAllByCabinetIdsAfterDate(@Param("date") LocalDate date, - @Param("cabinetIds") List cabinetIds); + @Param("cabinetIds") List cabinetIds); @Query("SELECT lh " + "FROM LentHistory lh " @@ -240,5 +244,5 @@ Page findAllExpiredAtBeforeAndEndedAtIsNullJoinUserAndCabinet( + "WHERE lh.userId IN (:userIds) " + "AND lh.endedAt IS NULL") void updateEndedAtByUserIdIn(@Param("userIds") List userIds, - @Param("endedAt") LocalDateTime endedAt); + @Param("endedAt") LocalDateTime endedAt); } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java b/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java index da54a0a84..30786b41f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java +++ b/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java @@ -8,12 +8,12 @@ import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; -import org.ftclub.cabinet.auth.domain.AuthCookieManager; import org.ftclub.cabinet.alarm.discord.DiscordAlarmMessage; import org.ftclub.cabinet.alarm.discord.DiscordWebHookMessenger; +import org.ftclub.cabinet.auth.domain.AuthCookieManager; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; -import org.ftclub.cabinet.auth.domain.TokenValidator; +import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.JwtProperties; import org.springframework.context.annotation.Profile; import org.springframework.core.DefaultParameterNameDiscoverer; diff --git a/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java b/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java index 6ac21af12..560dd2bd3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java +++ b/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.AuthCookieManager; -import org.ftclub.cabinet.auth.domain.TokenValidator; +import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.JwtProperties; import org.slf4j.MDC; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java index 49cd9cdd6..113a9f760 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java @@ -6,7 +6,7 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.ftclub.cabinet.auth.domain.AuthCookieManager; -import org.ftclub.cabinet.auth.domain.TokenValidator; +import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.exception.ControllerException; diff --git a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java index 7584a3e71..958dcb4d1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/newService/UserQueryService.java @@ -1,7 +1,5 @@ package org.ftclub.cabinet.user.newService; -import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; @@ -14,6 +12,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Optional; + @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) @@ -48,6 +49,10 @@ public Optional findUser(String name) { return userRepository.findByName(name); } + public Optional findUserByEmail(String email) { + return userRepository.findByEmail(email); + } + public Page findClubUsers(Pageable pageable) { return userRepository.findAllByRoleAndDeletedAtIsNull(UserRole.CLUB, pageable); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java index e57243639..c0222033c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java @@ -1,11 +1,10 @@ package org.ftclub.cabinet.utils.blackhole.manager; -import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.auth.service.ApplicationTokenManager; -import org.ftclub.cabinet.auth.service.FtOauthService; +import org.ftclub.cabinet.auth.service.UserOauthService; import org.ftclub.cabinet.dto.UserBlackHoleEvent; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; @@ -16,12 +15,14 @@ import org.springframework.stereotype.Component; import org.springframework.web.client.HttpClientErrorException; +import java.time.LocalDateTime; + @Component @RequiredArgsConstructor @Log4j2 public class BlackholeManager { - private final FtOauthService ftOauthService; + private final UserOauthService userOauthService; private final ApplicationTokenManager tokenManager; private final LentFacadeService lentFacadeService; private final UserService userService; @@ -47,7 +48,7 @@ private void terminateInvalidUser(UserBlackHoleEvent userBlackHoleEvent, LocalDa public void handleBlackHole(UserBlackHoleEvent dto) { LocalDateTime now = LocalDateTime.now(); try { - FtProfile recentProfile = ftOauthService.getProfileByIntraName(tokenManager.getFtAccessToken(), dto.getName()); + FtProfile recentProfile = userOauthService.getProfileByIntraName(tokenManager.getFtAccessToken(), dto.getName()); if (!recentProfile.getRole().isInCursus()) { terminateInvalidUser(dto, now); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java index ef0093fba..01ed8e6f1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/leave/absence/LeaveAbsenceManager.java @@ -5,7 +5,7 @@ import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.auth.domain.FtRole; import org.ftclub.cabinet.auth.service.ApplicationTokenManager; -import org.ftclub.cabinet.auth.service.FtOauthService; +import org.ftclub.cabinet.auth.service.UserOauthService; import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.service.UserService; import org.springframework.http.HttpStatus; @@ -19,7 +19,7 @@ @Log4j2 public class LeaveAbsenceManager { - private final FtOauthService ftOauthService; + private final UserOauthService userOauthService; private final ApplicationTokenManager tokenManager; private final LentFacadeService lentFacadeService; private final UserService userService; @@ -27,7 +27,7 @@ public class LeaveAbsenceManager { public void handleLeaveAbsence(Long userId, String name) { log.info("called handleLeaveAbsence {} {}", userId, name); try { - FtProfile ftProfile = ftOauthService.getProfileByIntraName(tokenManager.getFtAccessToken(), name); + FtProfile ftProfile = userOauthService.getProfileByIntraName(tokenManager.getFtAccessToken(), name); if (ftProfile.getRole().equals(FtRole.INACTIVE)) { lentFacadeService.endUserLent(userId, null); } diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthAspectUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthAspectUnitTest.java index 6fedb0b00..cc9de7176 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthAspectUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthAspectUnitTest.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.auth.domain; import com.fasterxml.jackson.core.JsonProcessingException; +import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.exception.ControllerException; import org.ftclub.cabinet.exception.ExceptionStatus; @@ -25,20 +26,18 @@ @ExtendWith(MockitoExtension.class) class AuthAspectUnitTest { + private final MockHttpServletRequest request = new MockHttpServletRequest(); + private final MockHttpServletResponse response = new MockHttpServletResponse(); @Mock TokenValidator tokenValidator = mock(TokenValidator.class); - @Mock JwtProperties jwtProperties = mock(JwtProperties.class); - @Mock AuthCookieManager authCookieManager = mock(AuthCookieManager.class); @Mock private AuthGuard authGuard = mock(AuthGuard.class); @InjectMocks private AuthAspect authAspect; - private final MockHttpServletRequest request = new MockHttpServletRequest(); - private final MockHttpServletResponse response = new MockHttpServletResponse(); @BeforeEach void setUp() { diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenProviderUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenProviderUnitTest.java index 933823414..a3b47e686 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenProviderUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenProviderUnitTest.java @@ -1,13 +1,8 @@ package org.ftclub.cabinet.auth.domain; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.BDDMockito.given; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import java.time.LocalDateTime; -import java.util.Map; +import org.ftclub.cabinet.auth.service.TokenProvider; import org.ftclub.cabinet.config.FtApiProperties; import org.ftclub.cabinet.config.GoogleApiProperties; import org.ftclub.cabinet.config.JwtProperties; @@ -25,6 +20,13 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.time.LocalDateTime; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; + @ExtendWith(MockitoExtension.class) public class TokenProviderUnitTest { diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java index 495830855..06ac2563d 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/domain/TokenValidatorUnitTest.java @@ -2,10 +2,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import org.ftclub.cabinet.admin.admin.domain.AdminRole; +import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.config.MasterProperties; -import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.user.service.UserService; import org.ftclub.cabinet.utils.DateUtil; import org.ftclub.testutils.TestUtils; diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImplUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImplUnitTest.java index f4dfe4cf8..4aa2c4635 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImplUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImplUnitTest.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.ftclub.cabinet.auth.domain.AuthCookieManager; -import org.ftclub.cabinet.auth.domain.TokenProvider; import org.ftclub.cabinet.config.ApiProperties; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.dto.MasterLoginDto; diff --git a/backend/src/test/java/org/ftclub/cabinet/user/controller/AdminSearchStatisticsControllerTest.java b/backend/src/test/java/org/ftclub/cabinet/user/controller/AdminSearchStatisticsControllerTest.java index 676d9e2c8..84fcdd67e 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/controller/AdminSearchStatisticsControllerTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/controller/AdminSearchStatisticsControllerTest.java @@ -1,6 +1,6 @@ package org.ftclub.cabinet.user.controller; -import org.ftclub.cabinet.auth.domain.TokenValidator; +import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.testutils.TestUtils; import org.junit.jupiter.api.BeforeEach; From 2d8947b4d63ae76f42b05babc97871aa85fe29e1 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 29 Dec 2023 17:28:49 +0900 Subject: [PATCH 0219/1029] =?UTF-8?q?[BE]=20FIX:=20=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=EB=A1=9C=20expiredAt=20=EB=A6=AC=ED=84=B4=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95(=EB=A6=AC=ED=8E=99=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=EB=B8=8C=EB=A0=8C=EC=B9=98=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EB=90=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/lent/service/LentServiceImpl.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java index 032782328..777918685 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; - import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.domain.AlarmEvent; @@ -74,7 +73,8 @@ public void startLentCabinet(Long userId, Long cabinetId) { lentPolicy.applyExpirationDate(lentHistory, expiredAt); lentRepository.save(lentHistory); eventPublisher.publishEvent(AlarmEvent.of(userId, - new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), cabinet.getVisibleNum(), expiredAt))); + new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), + cabinet.getVisibleNum(), expiredAt))); } @Override @@ -103,15 +103,19 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) if (Objects.equals(lentRedis.getSizeOfUsersInSession(cabinetId.toString()), cabinetProperties.getShareMaxUserCount())) { cabinet.specifyStatus(CabinetStatus.FULL); - saveLentHistories(now, cabinetId); + LocalDateTime expiredAt = saveLentHistories(now, cabinetId); // cabinetId에 대한 shadowKey, valueKey 삭제 lentRedis.deleteShadowKey(cabinetId); ArrayList userIds = lentRedis.getUserIdsByCabinetIdInRedis( cabinetId.toString()); for (String id : userIds) { lentRedis.deleteUserIdInRedis(Long.valueOf(id)); + eventPublisher.publishEvent(AlarmEvent.of(Long.valueOf(id), + new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), + cabinet.getVisibleNum(), expiredAt))); } lentRedis.deleteCabinetIdInRedis(cabinetId.toString()); + } } @@ -155,7 +159,8 @@ public void endLentCabinet(Long userId) { e.setExpiredAt(expiredAt); }); } - lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), lentHistory.getUser().getName()); + lentRedis.setPreviousUser(cabinet.getCabinetId().toString(), + lentHistory.getUser().getName()); } @Override @@ -210,7 +215,8 @@ private LentHistory returnCabinetByUserId(Long userId) { LentHistory lentHistory = lentOptionalFetcher.getActiveLentHistoryWithUserIdForUpdate( userId); Cabinet cabinet = cabinetOptionalFetcher.getCabinetForUpdate(lentHistory.getCabinetId()); - int activeLentCount = lentRepository.countByCabinetIdAndEndedAtIsNull(lentHistory.getCabinetId()); + int activeLentCount = lentRepository.countByCabinetIdAndEndedAtIsNull( + lentHistory.getCabinetId()); lentHistory.endLent(LocalDateTime.now()); cabinet.specifyStatusByUserCount(activeLentCount - 1); // policy로 빠질만한 부분인듯? if (activeLentCount - 1 == 0) { @@ -271,7 +277,7 @@ public List getAllActiveLentHistories() { .collect(Collectors.toList()); } - public void saveLentHistories(LocalDateTime now, Long cabinetId) { + private LocalDateTime saveLentHistories(LocalDateTime now, Long cabinetId) { ArrayList userIdList = lentRedis.getUserIdsByCabinetIdInRedis( cabinetId.toString()); LocalDateTime expiredAt = lentPolicy.generateSharedCabinetExpirationDate(now, @@ -283,6 +289,7 @@ public void saveLentHistories(LocalDateTime now, Long cabinetId) { lentPolicy.applyExpirationDate(lentHistory, expiredAt); lentRepository.save(lentHistory); }); + return expiredAt; } } From d47dc242f4d29c0f373395c3dc9f81e497042529 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 29 Dec 2023 17:41:36 +0900 Subject: [PATCH 0220/1029] =?UTF-8?q?[BE]=20FIX:=20isProduction=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/alarm/handler/AlarmEventHandler.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index 113df83ce..9c6161ec1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -28,9 +28,9 @@ public class AlarmEventHandler { @TransactionalEventListener public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactionalAlarmEvent) { -// if (!alarmProperties.getIsProduction()) { -// return; -// } + if (!alarmProperties.getIsProduction()) { + return; + } log.info("handleAlarmEventWithTransactional = {}", transactionalAlarmEvent); if (!(transactionalAlarmEvent instanceof TransactionalAlarmEvent)) { return; @@ -42,9 +42,9 @@ public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactio @EventListener public void handleAlarmEvent(AlarmEvent alarmEvent) { log.info("handleAlarmEvent = {}", alarmEvent); -// if (!alarmProperties.getIsProduction()) { -// return; -// } + if (!alarmProperties.getIsProduction()) { + return; + } eventProceed(alarmEvent); } From cd9900e000b42609a564e8a4f1a9c3051f588471 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 29 Dec 2023 17:47:48 +0900 Subject: [PATCH 0221/1029] [BE] FIX: config fix --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 689668fd3..fab56a92d 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 689668fd3c05aa5d591c8711d6793d46617a6a0b +Subproject commit fab56a92dc7394efdd29e22b2b788f9ad9d82f7d From 8787936bd7951239b3460e5bcbd457a93f1905dd Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Fri, 29 Dec 2023 18:09:09 +0900 Subject: [PATCH 0222/1029] =?UTF-8?q?FIX,=20REFACTOR=20:=20Master=EB=8F=84?= =?UTF-8?q?=20Admin=EA=B3=BC=20=EB=8F=99=EC=9D=BC=ED=95=9C=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=EC=9D=84=20?= =?UTF-8?q?=EA=B0=96=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/AuthFacadeService.java | 4 +++- .../cabinet/auth/service/TokenProvider.java | 18 ------------------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index a5e748d11..db16f2b62 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -74,7 +74,9 @@ public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, if (!masterLoginDto.getId().equals(masterProperties.getId()) || !masterLoginDto.getPassword().equals(masterProperties.getPassword())) throw new ServiceException(ExceptionStatus.UNAUTHORIZED_ADMIN); - String masterToken = tokenProvider.createMasterToken(now); + Admin master = adminQueryService.findByEmail(masterProperties.getEmail()) + .orElseThrow(() -> new ServiceException(ExceptionStatus.UNAUTHORIZED_ADMIN)); + String masterToken = tokenProvider.createAdminToken(master, now); Cookie cookie = authCookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, masterToken); authCookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java index 71bc2842c..a1b9be2ae 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java @@ -5,7 +5,6 @@ import io.jsonwebtoken.SignatureAlgorithm; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.admin.admin.domain.Admin; -import org.ftclub.cabinet.admin.admin.domain.AdminRole; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.config.MasterProperties; import org.ftclub.cabinet.user.domain.User; @@ -64,21 +63,4 @@ public String createAdminToken(Admin admin, LocalDateTime now) { .setExpiration(Timestamp.valueOf(now.plusDays(jwtProperties.getExpiryDays()))) .compact(); } - - /** - * JWT 토큰을 생성합니다. - * - * @param now 현재 시각 - * @return JWT 토큰 - */ - public String createMasterToken(LocalDateTime now) { - Claims claims = Jwts.claims(); - claims.put("email", masterProperties.getEmail()); - claims.put("role", AdminRole.MASTER); - return Jwts.builder() - .setClaims(claims) - .signWith(jwtProperties.getSigningKey(), SignatureAlgorithm.HS256) - .setExpiration(Timestamp.valueOf(now.plusDays(jwtProperties.getExpiryDays()))) - .compact(); - } } From 5961125ac8bab679745e543750ccb4da583d071a Mon Sep 17 00:00:00 2001 From: Ssuamje Date: Sat, 30 Dec 2023 11:53:51 +0900 Subject: [PATCH 0223/1029] FIX, RENAME : AuthCookieManager -> CookieManager --- .../cabinet/auth/domain/AuthAspect.java | 12 +++++----- ...hCookieManager.java => CookieManager.java} | 2 +- .../auth/service/AuthFacadeService.java | 24 +++++++++---------- .../ftclub/cabinet/log/AdminApiLogAspect.java | 6 ++--- .../cabinet/log/AllRequestLogInterceptor.java | 6 ++--- .../cabinet/user/domain/UserAspect.java | 6 ++--- .../auth/domain/AuthAspectUnitTest.java | 10 ++++---- ...itTest.java => CookieManagerUnitTest.java} | 14 +++++------ .../AuthFacadeServiceImplUnitTest.java | 18 +++++++------- 9 files changed, 49 insertions(+), 49 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/auth/domain/{AuthCookieManager.java => CookieManager.java} (98%) rename backend/src/test/java/org/ftclub/cabinet/auth/domain/{AuthCookieManagerUnitTest.java => CookieManagerUnitTest.java} (87%) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java index be25fc00e..481c76bc4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthAspect.java @@ -30,7 +30,7 @@ public class AuthAspect { private static final String BEARER = "Bearer "; private final TokenValidator tokenValidator; - private final AuthCookieManager authCookieManager; + private final CookieManager cookieManager; private final JwtProperties jwtProperties; /** @@ -64,26 +64,26 @@ public void AuthToken(AuthGuard authGuard) throws JsonProcessingException { switch (authGuard.level()) { case ADMIN_ONLY: if (!tokenValidator.isValidTokenWithLevel(token, ADMIN_ONLY)) { - authCookieManager.deleteCookie(response, adminTokenName); + cookieManager.deleteCookie(response, adminTokenName); throw new ControllerException(ExceptionStatus.UNAUTHORIZED_ADMIN); } break; case USER_ONLY: if (!tokenValidator.isValidTokenWithLevel(token, USER_ONLY)) { - authCookieManager.deleteCookie(response, mainTokenName); + cookieManager.deleteCookie(response, mainTokenName); throw new ControllerException(ExceptionStatus.UNAUTHORIZED_USER); } break; case USER_OR_ADMIN: if (!tokenValidator.isValidTokenWithLevel(token, USER_OR_ADMIN)) { - authCookieManager.deleteCookie(response, mainTokenName); - authCookieManager.deleteCookie(response, adminTokenName); + cookieManager.deleteCookie(response, mainTokenName); + cookieManager.deleteCookie(response, adminTokenName); throw new ControllerException(ExceptionStatus.UNAUTHORIZED); } break; case MASTER_ONLY: if (!tokenValidator.isValidTokenWithLevel(token, MASTER_ONLY)) { - authCookieManager.deleteCookie(response, adminTokenName); + cookieManager.deleteCookie(response, adminTokenName); throw new ControllerException(ExceptionStatus.UNAUTHORIZED_ADMIN); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthCookieManager.java b/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java similarity index 98% rename from backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthCookieManager.java rename to backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java index 9330a27c9..d467c9e1d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/domain/AuthCookieManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/domain/CookieManager.java @@ -14,7 +14,7 @@ */ @Component @RequiredArgsConstructor -public class AuthCookieManager { +public class CookieManager { private final DomainProperties domainProperties; private final JwtProperties jwtProperties; diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index db16f2b62..989b022f0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -4,7 +4,7 @@ import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.admin.admin.service.AdminCommandService; import org.ftclub.cabinet.admin.admin.service.AdminQueryService; -import org.ftclub.cabinet.auth.domain.AuthCookieManager; +import org.ftclub.cabinet.auth.domain.CookieManager; import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.auth.domain.GoogleProfile; import org.ftclub.cabinet.config.DomainProperties; @@ -36,7 +36,7 @@ public class AuthFacadeService { private final AdminOauthService adminOauthService; private final TokenProvider tokenProvider; - private final AuthCookieManager authCookieManager; + private final CookieManager cookieManager; private final DomainProperties domainProperties; private final MasterProperties masterProperties; @@ -53,8 +53,8 @@ public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, Str User user = userQueryService.findUser(profile.getIntraName()) .orElseGet(() -> userCommandService.createUserByFtProfile(profile)); String token = tokenProvider.createUserToken(user, LocalDateTime.now()); - Cookie cookie = authCookieManager.cookieOf(TokenProvider.USER_TOKEN_NAME, token); - authCookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + Cookie cookie = cookieManager.cookieOf(TokenProvider.USER_TOKEN_NAME, token); + cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); res.sendRedirect(domainProperties.getFeHost() + "/home"); } @@ -63,8 +63,8 @@ public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, St Admin admin = adminQueryService.findByEmail(profile.getEmail()) .orElseGet(() -> adminCommandService.createAdminByEmail(profile.getEmail())); String token = tokenProvider.createAdminToken(admin, LocalDateTime.now()); - Cookie cookie = authCookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, token); - authCookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + Cookie cookie = cookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, token); + cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); res.sendRedirect(domainProperties.getFeHost() + "/admin/home"); } @@ -77,17 +77,17 @@ public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, Admin master = adminQueryService.findByEmail(masterProperties.getEmail()) .orElseThrow(() -> new ServiceException(ExceptionStatus.UNAUTHORIZED_ADMIN)); String masterToken = tokenProvider.createAdminToken(master, now); - Cookie cookie = authCookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, masterToken); - authCookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + Cookie cookie = cookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, masterToken); + cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); } public void userLogout(HttpServletResponse res) { - Cookie userCookie = authCookieManager.cookieOf(TokenProvider.USER_TOKEN_NAME, ""); - authCookieManager.setCookieToClient(res, userCookie, "/", res.getHeader("host")); + Cookie userCookie = cookieManager.cookieOf(TokenProvider.USER_TOKEN_NAME, ""); + cookieManager.setCookieToClient(res, userCookie, "/", res.getHeader("host")); } public void adminLogout(HttpServletResponse res) { - Cookie adminCookie = authCookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, ""); - authCookieManager.setCookieToClient(res, adminCookie, "/", res.getHeader("host")); + Cookie adminCookie = cookieManager.cookieOf(TokenProvider.ADMIN_TOKEN_NAME, ""); + cookieManager.setCookieToClient(res, adminCookie, "/", res.getHeader("host")); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java b/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java index 30786b41f..d5f217aca 100644 --- a/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java +++ b/backend/src/main/java/org/ftclub/cabinet/log/AdminApiLogAspect.java @@ -10,9 +10,9 @@ import org.aspectj.lang.reflect.MethodSignature; import org.ftclub.cabinet.alarm.discord.DiscordAlarmMessage; import org.ftclub.cabinet.alarm.discord.DiscordWebHookMessenger; -import org.ftclub.cabinet.auth.domain.AuthCookieManager; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.auth.domain.CookieManager; import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.JwtProperties; import org.springframework.context.annotation.Profile; @@ -41,7 +41,7 @@ public class AdminApiLogAspect { private final static String ADMIN_CUD_POINTCUT = "@annotation(authGuard) && !@annotation(org.springframework.web.bind.annotation.GetMapping))"; private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); - private final AuthCookieManager authCookieManager; + private final CookieManager cookieManager; private final TokenValidator tokenValidator; private final JwtProperties jwtProperties; private final LogParser logParser; @@ -99,7 +99,7 @@ private void sendLogMessage(JoinPoint joinPoint, String responseString) throws J .getRequest(); String name = tokenValidator.getPayloadJson( - authCookieManager.getCookieValue(request, jwtProperties.getAdminTokenName())) + cookieManager.getCookieValue(request, jwtProperties.getAdminTokenName())) .get("email").asText(); Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); String className = joinPoint.getTarget().getClass().getName(); diff --git a/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java b/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java index 560dd2bd3..aba7cce48 100644 --- a/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java +++ b/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.ftclub.cabinet.auth.domain.AuthCookieManager; +import org.ftclub.cabinet.auth.domain.CookieManager; import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.JwtProperties; import org.slf4j.MDC; @@ -27,7 +27,7 @@ public class AllRequestLogInterceptor implements HandlerInterceptor { private static final List IP_HEADERS = Arrays.asList("X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"); private final TokenValidator tokenValidator; - private final AuthCookieManager authCookieManager; + private final CookieManager cookieManager; private final JwtProperties jwtProperties; @Override @@ -67,7 +67,7 @@ private String getUserId(HttpServletRequest request) { String ret = null; try { ret = tokenValidator.getPayloadJson( - authCookieManager.getCookieValue(request, jwtProperties.getMainTokenName())) + cookieManager.getCookieValue(request, jwtProperties.getMainTokenName())) .get("name") .asText(); } catch (Exception ignore) { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java index 113a9f760..49c7a94c0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserAspect.java @@ -5,7 +5,7 @@ import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.ftclub.cabinet.auth.domain.AuthCookieManager; +import org.ftclub.cabinet.auth.domain.CookieManager; import org.ftclub.cabinet.auth.service.TokenValidator; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.dto.UserSessionDto; @@ -28,7 +28,7 @@ public class UserAspect { //private final UserMapper ... //private final UserService ... //컨트롤러가 아니므로 Facade를 주입받지는 않지만, 서비스와 매퍼를 주입받아서 UserSessionDto를 생성해 줌. - private final AuthCookieManager authCookieManager; + private final CookieManager cookieManager; private final TokenValidator tokenValidator; private final JwtProperties jwtProperties; private final UserOptionalFetcher userOptionalFetcher; @@ -52,7 +52,7 @@ public Object setUserSessionDto(ProceedingJoinPoint joinPoint) public UserSessionDto getUserSessionDtoByRequest(HttpServletRequest req) throws JsonProcessingException { String name = tokenValidator.getPayloadJson( - authCookieManager.getCookieValue(req, jwtProperties.getMainTokenName())).get("name") + cookieManager.getCookieValue(req, jwtProperties.getMainTokenName())).get("name") .asText(); User user = userOptionalFetcher.getUserByName(name); //ToDo: name을 기준으로 service에게 정보를 받고, 매핑한다. diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthAspectUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthAspectUnitTest.java index cc9de7176..d226d715e 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthAspectUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthAspectUnitTest.java @@ -33,7 +33,7 @@ class AuthAspectUnitTest { @Mock JwtProperties jwtProperties = mock(JwtProperties.class); @Mock - AuthCookieManager authCookieManager = mock(AuthCookieManager.class); + CookieManager cookieManager = mock(CookieManager.class); @Mock private AuthGuard authGuard = mock(AuthGuard.class); @InjectMocks @@ -65,7 +65,7 @@ void setUp() { exception = assertThrows(ControllerException.class, () -> authAspect.AuthToken(authGuard)); assertEquals(ExceptionStatus.UNAUTHORIZED_ADMIN, exception.getStatus()); - then(authCookieManager).should().deleteCookie(response, jwtProperties.getAdminTokenName()); + then(cookieManager).should().deleteCookie(response, jwtProperties.getAdminTokenName()); } @Test @@ -86,7 +86,7 @@ void setUp() { exception = assertThrows(ControllerException.class, () -> authAspect.AuthToken(authGuard)); assertEquals(ExceptionStatus.UNAUTHORIZED_USER, exception.getStatus()); - then(authCookieManager).should().deleteCookie(response, jwtProperties.getMainTokenName()); + then(cookieManager).should().deleteCookie(response, jwtProperties.getMainTokenName()); } @Test @@ -107,7 +107,7 @@ void setUp() { exception = assertThrows(ControllerException.class, () -> authAspect.AuthToken(authGuard)); assertEquals(ExceptionStatus.UNAUTHORIZED, exception.getStatus()); - then(authCookieManager).should().deleteCookie(response, jwtProperties.getMainTokenName()); - then(authCookieManager).should().deleteCookie(response, jwtProperties.getAdminTokenName()); + then(cookieManager).should().deleteCookie(response, jwtProperties.getMainTokenName()); + then(cookieManager).should().deleteCookie(response, jwtProperties.getAdminTokenName()); } } \ No newline at end of file diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthCookieManagerUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/domain/CookieManagerUnitTest.java similarity index 87% rename from backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthCookieManagerUnitTest.java rename to backend/src/test/java/org/ftclub/cabinet/auth/domain/CookieManagerUnitTest.java index a5fc147b8..febc2d950 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/domain/AuthCookieManagerUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/domain/CookieManagerUnitTest.java @@ -18,7 +18,7 @@ import static org.mockito.Mockito.mock; @ExtendWith(MockitoExtension.class) -public class AuthCookieManagerUnitTest { +public class CookieManagerUnitTest { @Mock DomainProperties domainProperties = mock(DomainProperties.class); @@ -27,7 +27,7 @@ public class AuthCookieManagerUnitTest { JwtProperties jwtProperties = mock(JwtProperties.class); @InjectMocks - AuthCookieManager authCookieManager; + CookieManager cookieManager; MockHttpServletRequest request = new MockHttpServletRequest(); @@ -39,7 +39,7 @@ public class AuthCookieManagerUnitTest { Cookie expect = new Cookie("name", "value"); request.setCookies(expect); - String result = authCookieManager.getCookieValue(request, "name"); + String result = cookieManager.getCookieValue(request, "name"); assertEquals(expect.getValue(), result); } @@ -50,7 +50,7 @@ public class AuthCookieManagerUnitTest { Cookie expect = new Cookie("name", "value"); request.setCookies(expect); - String result = authCookieManager.getCookieValue(request, "name2"); + String result = cookieManager.getCookieValue(request, "name2"); assertNull(result); } @@ -64,7 +64,7 @@ public class AuthCookieManagerUnitTest { String path = "/"; Cookie cookie = new Cookie("name", "value"); - authCookieManager.setCookieToClient(response, cookie, path, serverName); + cookieManager.setCookieToClient(response, cookie, path, serverName); assertEquals(60 * 60 * 24 * jwtProperties.getExpiryDays(), cookie.getMaxAge()); assertEquals(path, cookie.getPath()); @@ -82,7 +82,7 @@ public class AuthCookieManagerUnitTest { String path = "/"; Cookie cookie = new Cookie("name", "value"); - authCookieManager.setCookieToClient(response, cookie, path, serverName); + cookieManager.setCookieToClient(response, cookie, path, serverName); assertEquals(60 * 60 * 24 * jwtProperties.getExpiryDays(), cookie.getMaxAge()); assertEquals(path, cookie.getPath()); @@ -93,7 +93,7 @@ public class AuthCookieManagerUnitTest { @Test @DisplayName("성공: 쿠키 지우기") void 성공_deleteCookie() { - authCookieManager.deleteCookie(response, "name"); + cookieManager.deleteCookie(response, "name"); Cookie cookie = response.getCookie("name"); assertEquals(0, cookie.getMaxAge()); diff --git a/backend/src/test/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImplUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImplUnitTest.java index 4aa2c4635..24e43c122 100644 --- a/backend/src/test/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImplUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/auth/service/AuthFacadeServiceImplUnitTest.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.auth.service; import com.fasterxml.jackson.databind.JsonNode; -import org.ftclub.cabinet.auth.domain.AuthCookieManager; +import org.ftclub.cabinet.auth.domain.CookieManager; import org.ftclub.cabinet.config.ApiProperties; import org.ftclub.cabinet.config.JwtProperties; import org.ftclub.cabinet.dto.MasterLoginDto; @@ -37,7 +37,7 @@ class AuthFacadeServiceImplUnitTest { @Mock TokenProvider tokenProvider = mock(TokenProvider.class); @Mock - AuthCookieManager authCookieManager = mock(AuthCookieManager.class); + CookieManager cookieManager = mock(CookieManager.class); @Mock AuthService authService = mock(AuthService.class); @Mock @@ -68,13 +68,13 @@ class AuthFacadeServiceImplUnitTest { given(tokenProvider.makeClaimsByProviderProfile(apiProperties.getProviderName(), profileJsonNode)).willReturn(claims); given(tokenProvider.createToken(claims, timeTokenCreated)).willReturn("accessToken"); given(tokenProvider.getTokenNameByProvider(apiProperties.getProviderName())).willReturn("tokenName"); - given(authCookieManager.cookieOf("tokenName", "accessToken")).willReturn(new Cookie("tokenName", "accessToken")); - Cookie mustPutCookie = authCookieManager.cookieOf("tokenName", "accessToken"); + given(cookieManager.cookieOf("tokenName", "accessToken")).willReturn(new Cookie("tokenName", "accessToken")); + Cookie mustPutCookie = cookieManager.cookieOf("tokenName", "accessToken"); authFacadeService.handleLogin("code", request, response, apiProperties, timeTokenCreated); then(authService).should().addUserIfNotExistsByClaims(claims); - then(authCookieManager).should().setCookieToClient(response, mustPutCookie, "/", request.getServerName()); + then(cookieManager).should().setCookieToClient(response, mustPutCookie, "/", request.getServerName()); } @Test @@ -85,12 +85,12 @@ class AuthFacadeServiceImplUnitTest { given(authService.validateMasterLogin(masterLoginDto)).willReturn(true); given(tokenProvider.createMasterToken(now)).willReturn("masterToken"); given(jwtProperties.getAdminTokenName()).willReturn("adminTokenName"); - given(authCookieManager.cookieOf(jwtProperties.getAdminTokenName(), "masterToken")).willReturn(new Cookie("adminTokenName", "masterToken")); - Cookie mustPutCookie = authCookieManager.cookieOf(jwtProperties.getAdminTokenName(), "masterToken"); + given(cookieManager.cookieOf(jwtProperties.getAdminTokenName(), "masterToken")).willReturn(new Cookie("adminTokenName", "masterToken")); + Cookie mustPutCookie = cookieManager.cookieOf(jwtProperties.getAdminTokenName(), "masterToken"); authFacadeService.masterLogin(masterLoginDto, request, response, now); - then(authCookieManager).should().setCookieToClient(response, mustPutCookie, "/", request.getServerName()); + then(cookieManager).should().setCookieToClient(response, mustPutCookie, "/", request.getServerName()); } @Test @@ -113,6 +113,6 @@ class AuthFacadeServiceImplUnitTest { authFacadeService.logout(response, apiProperties); - then(authCookieManager).should().deleteCookie(response, "tokenName"); + then(cookieManager).should().deleteCookie(response, "tokenName"); } } \ No newline at end of file From e2e1ded8da72da16d2f981f5ccf4a038a316fc69 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Sat, 30 Dec 2023 18:20:25 +0900 Subject: [PATCH 0224/1029] [BE] FEAT: discord logging webhook Co-authored-by: leedonggyu1848 --- .gitignore | 4 ++ backend/.gitignore | 5 +- backend/build.gradle | 1 + .../org/ftclub/cabinet/TestController.java | 24 +++++++ backend/src/main/resources/log4j2-dev.yml | 54 -------------- backend/src/main/resources/log4j2-local.yml | 71 ------------------- backend/src/main/resources/log4j2-prod.yml | 54 -------------- 7 files changed, 33 insertions(+), 180 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/TestController.java delete mode 100644 backend/src/main/resources/log4j2-dev.yml delete mode 100644 backend/src/main/resources/log4j2-local.yml delete mode 100644 backend/src/main/resources/log4j2-prod.yml diff --git a/.gitignore b/.gitignore index aeb52b511..99c94d053 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ lerna-debug.log* *.launch .settings/ *.sublime-workspace +*.imi +.idea/* # IDE - VSCode .vscode/* @@ -37,6 +39,8 @@ lerna-debug.log* # IDE - WebStrom **/*.idea +**/*.iml +misc.xml #env .env diff --git a/backend/.gitignore b/backend/.gitignore index 99080bafc..35313373f 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -43,4 +43,7 @@ application*.yml **/static/docs ### Firebase ### -*firebase*.json \ No newline at end of file +*firebase*.json + +### Logging ### +log4j2-*.xml \ No newline at end of file diff --git a/backend/build.gradle b/backend/build.gradle index 720b555b5..b66ee0ade 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -51,6 +51,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-log4j2' + runtimeOnly 'org.apache.logging.log4j:log4j-spring-boot' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml' implementation 'org.modelmapper:modelmapper:3.1.1' implementation 'org.springframework.boot:spring-boot-starter-mail' diff --git a/backend/src/main/java/org/ftclub/cabinet/TestController.java b/backend/src/main/java/org/ftclub/cabinet/TestController.java new file mode 100644 index 000000000..33aad807e --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/TestController.java @@ -0,0 +1,24 @@ +package org.ftclub.cabinet; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Log4j2 +@RequiredArgsConstructor +@RequestMapping("/test") +public class TestController { + + @GetMapping("/logs") + public void testLogs() { + log.trace("trace log"); + log.debug("debug log"); + log.info("info log"); + log.warn("warn log"); + log.error("error log"); + log.fatal("fatal log"); + } +} diff --git a/backend/src/main/resources/log4j2-dev.yml b/backend/src/main/resources/log4j2-dev.yml deleted file mode 100644 index 22a7a437b..000000000 --- a/backend/src/main/resources/log4j2-dev.yml +++ /dev/null @@ -1,54 +0,0 @@ -Configuration: - status: info - - Properties: - property: - name: pattern - value: "%style{%d{yyyy-MM-dd HH:mm:ss}}{white} %highlight{%-5level }[%style{%t}{bright,blue}] %style{%X}{normal, white} %style{%C{1}}{bright,yellow}: %msg%n%throwable" - - Appenders: - Console: - name: console - target: SYSTEM_OUT - PatternLayout: - pattern: ${pattern} - - RollingFile: - name: rollingFile - # 파일 저장 위치를 어디로? - fileName: logs/42cabi.log - filePattern: logs/42cabi-%d{yyyy-MM-dd}-%i.log.gz - PatternLayout: - pattern: ${pattern} - Policies: - # 1일 단위로 로그를 압축해서 저장 - TimeBasedTriggeringPolicy: - interval: 1 - modulate: true - DefaultRolloverStrategy: - Delete: - basePath: logs - ifFileName: - glob: "42cabi-*.log.gz" - # 30일 이상된 로그는 삭제 - ifLastModified: - age: "30d" - max: 30 - - Loggers: - logger: - - - name: org.springframework - level: info - additivity: false - AppenderRef: - - ref: console - - ref: rollingFile - - - - name: org.ftclub.cabinet - level: info - additivity: false - AppenderRef: - - ref: console - - ref: rollingFile diff --git a/backend/src/main/resources/log4j2-local.yml b/backend/src/main/resources/log4j2-local.yml deleted file mode 100644 index 36fba56ff..000000000 --- a/backend/src/main/resources/log4j2-local.yml +++ /dev/null @@ -1,71 +0,0 @@ -Configuration: - status: INFO - - Properties: - property: - name: pattern - value: "%style{%d{yyyy-MM-dd HH:mm:ss}}{white} %highlight{%-5level }[%style{%t}{bright,blue}] %style{%X}{normal, white} %style{%C{1}}{bright,yellow}: %msg%n%throwable" - - Appenders: - Console: - name: console - target: SYSTEM_OUT - PatternLayout: - pattern: ${pattern} - # RollingFile: - # name: rollingFile - # # 파일 저장 위치를 어디로? - # fileName: logs/42cabi.log - # filePattern: logs/42cabi-%d{yyyy-MM-dd}-%i.log.gz - # PatternLayout: - # pattern: ${pattern} - # Policies: - # # 1일 단위로 로그를 압축해서 저장 - # TimeBasedTriggeringPolicy: - # interval: 1 - # modulate: true - # DefaultRolloverStrategy: - # Delete: - # basePath: logs - # ifFileName: - # glob: "42cabi-*.log.gz" - # # 30일 이상된 로그는 삭제 - # ifLastModified: - # age: "30d" - # max: 30 - - Loggers: - logger: - - - name: org.springframework - # local 환경에서는 debug, production 환경에서는 info - level: info - additivity: false - AppenderRef: - - ref: console - # - ref: rollingFile - - - name: org.ftclub.cabinet - level: debug - additivity: false - AppenderRef: - - ref: console - # - ref: rollingFile - - # SQL문 확인을 위한 설정 (시작) - # sql을 봐야한다면 해당 부분 주석 해제하면 됨. - # trace로 해놓는 이유는 JDBC 파라미터를 보기 위함 - - - name: org.hibernate.type - level: debug - additivity: false - AppenderRef: - ref: console - - - - name: org.hibernate.SQL - level: debug - additivity: false - AppenderRef: - ref: console - # SQL문 확인을 위한 설정 (끝) \ No newline at end of file diff --git a/backend/src/main/resources/log4j2-prod.yml b/backend/src/main/resources/log4j2-prod.yml deleted file mode 100644 index 22a7a437b..000000000 --- a/backend/src/main/resources/log4j2-prod.yml +++ /dev/null @@ -1,54 +0,0 @@ -Configuration: - status: info - - Properties: - property: - name: pattern - value: "%style{%d{yyyy-MM-dd HH:mm:ss}}{white} %highlight{%-5level }[%style{%t}{bright,blue}] %style{%X}{normal, white} %style{%C{1}}{bright,yellow}: %msg%n%throwable" - - Appenders: - Console: - name: console - target: SYSTEM_OUT - PatternLayout: - pattern: ${pattern} - - RollingFile: - name: rollingFile - # 파일 저장 위치를 어디로? - fileName: logs/42cabi.log - filePattern: logs/42cabi-%d{yyyy-MM-dd}-%i.log.gz - PatternLayout: - pattern: ${pattern} - Policies: - # 1일 단위로 로그를 압축해서 저장 - TimeBasedTriggeringPolicy: - interval: 1 - modulate: true - DefaultRolloverStrategy: - Delete: - basePath: logs - ifFileName: - glob: "42cabi-*.log.gz" - # 30일 이상된 로그는 삭제 - ifLastModified: - age: "30d" - max: 30 - - Loggers: - logger: - - - name: org.springframework - level: info - additivity: false - AppenderRef: - - ref: console - - ref: rollingFile - - - - name: org.ftclub.cabinet - level: info - additivity: false - AppenderRef: - - ref: console - - ref: rollingFile From 72b921000a8183c6684462c193405928fe352392 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Sat, 30 Dec 2023 18:30:03 +0900 Subject: [PATCH 0225/1029] [BE] FIX: update submodule Co-authored-by: leedonggyu1848 --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 689668fd3..284163cb5 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 689668fd3c05aa5d591c8711d6793d46617a6a0b +Subproject commit 284163cb54436c543b902ce2a5d7748fc469e001 From 4b46e8c97f0a51ddc29cfa7b2db5ec75c849fd07 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Sat, 30 Dec 2023 18:40:37 +0900 Subject: [PATCH 0226/1029] =?UTF-8?q?[BE]=20FIX:=20log4j2.xml=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20config=20->=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8?= =?UTF-8?q?=20=EB=A3=A8=ED=8A=B8=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: leedonggyu1848 --- backend/.gitignore | 3 -- backend/src/main/resources/log4j2-dev.xml | 49 +++++++++++++++++++++ backend/src/main/resources/log4j2-local.xml | 32 ++++++++++++++ backend/src/main/resources/log4j2-prod.xml | 48 ++++++++++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/resources/log4j2-dev.xml create mode 100644 backend/src/main/resources/log4j2-local.xml create mode 100644 backend/src/main/resources/log4j2-prod.xml diff --git a/backend/.gitignore b/backend/.gitignore index 35313373f..462f68714 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -44,6 +44,3 @@ application*.yml ### Firebase ### *firebase*.json - -### Logging ### -log4j2-*.xml \ No newline at end of file diff --git a/backend/src/main/resources/log4j2-dev.xml b/backend/src/main/resources/log4j2-dev.xml new file mode 100644 index 000000000..c700bae94 --- /dev/null +++ b/backend/src/main/resources/log4j2-dev.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + application/json + + + {"content": "Profile: dev | %encode{%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level: %m%n}{JSON}"} + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/log4j2-local.xml b/backend/src/main/resources/log4j2-local.xml new file mode 100644 index 000000000..e28b0701e --- /dev/null +++ b/backend/src/main/resources/log4j2-local.xml @@ -0,0 +1,32 @@ + + + + %style{%d{yyyy-MM-dd HH:mm:ss}}{white} %highlight{%-5level }[%style{%t}{bright,blue}] %style{%X}{normal, white} %style{%C{1}}{bright,yellow}: %msg%n%throwable + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/log4j2-prod.xml b/backend/src/main/resources/log4j2-prod.xml new file mode 100644 index 000000000..614eab665 --- /dev/null +++ b/backend/src/main/resources/log4j2-prod.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + application/json + + + {"content": "Profile: Main | %encode{%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level: %m%n}{JSON}"} + + + + + + + + + + + + + + + + + + + + From 931707c88d00846fa72350c71903a91eac8890c0 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Sat, 30 Dec 2023 18:43:51 +0900 Subject: [PATCH 0227/1029] =?UTF-8?q?[BE]=20FIX:=20log4j2.xml=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20config=20->=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8?= =?UTF-8?q?=20=EB=A3=A8=ED=8A=B8=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: leedonggyu1848 --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 284163cb5..cd35d5cd5 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 284163cb54436c543b902ce2a5d7748fc469e001 +Subproject commit cd35d5cd56b674fbf2be59150eb6a6c5cff20af7 From 609493d9f1f4804b3de999425fb93253121f4639 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Sat, 30 Dec 2023 18:44:39 +0900 Subject: [PATCH 0228/1029] =?UTF-8?q?[BE]=20FIX:=20main=EB=A7=8C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=EC=9D=84=20=EB=B0=9C=EC=86=A1=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: leedonggyu1848 --- backend/src/main/resources/log4j2-dev.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/backend/src/main/resources/log4j2-dev.xml b/backend/src/main/resources/log4j2-dev.xml index c700bae94..88fd17746 100644 --- a/backend/src/main/resources/log4j2-dev.xml +++ b/backend/src/main/resources/log4j2-dev.xml @@ -21,29 +21,17 @@ - - - application/json - - - {"content": "Profile: dev | %encode{%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level: %m%n}{JSON}"} - - - - - - From 138768b33b9237e876c1c7bf96cf9a28af386527 Mon Sep 17 00:00:00 2001 From: space Date: Mon, 1 Jan 2024 15:26:00 +0900 Subject: [PATCH 0229/1029] =?UTF-8?q?[BE]=20REFACTOR:=20Alarm=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20-=20ToString=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java | 2 ++ .../alarm/domain/ExtensionExpirationImminentAlarm.java | 4 +++- .../ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java | 2 ++ .../org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java | 2 ++ .../cabinet/alarm/domain/LentExpirationImminentAlarm.java | 2 ++ .../org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java | 2 ++ 6 files changed, 13 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java index 2fb611bfc..73239bd64 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/AnnouncementAlarm.java @@ -2,12 +2,14 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; /** * 공지사항 알람 */ @Getter @AllArgsConstructor +@ToString public class AnnouncementAlarm implements Alarm, TransactionalAlarmEvent { private final String announcementContent; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java index de42d5357..16540d5dd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionExpirationImminentAlarm.java @@ -3,12 +3,14 @@ import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; /** * 연장 만료 임박 알람 */ -@AllArgsConstructor @Getter +@ToString +@AllArgsConstructor public class ExtensionExpirationImminentAlarm implements Alarm, TransactionalAlarmEvent { private final String extensionName; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java index 39fde0bf3..a8bb74dae 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/ExtensionIssuanceAlarm.java @@ -3,11 +3,13 @@ import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; /** * 연장권 발급 알람 */ @AllArgsConstructor +@ToString @Getter public class ExtensionIssuanceAlarm implements Alarm, TransactionalAlarmEvent { diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java index 6c1822e2e..241880a89 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java @@ -2,11 +2,13 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; /** * 대여 만료 알람 */ @Getter +@ToString @AllArgsConstructor public class LentExpirationAlarm implements Alarm, TransactionalAlarmEvent { diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index cd3d006a4..6983092ef 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -2,11 +2,13 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; /** * 대여 만료 임박 알람 */ @Getter +@ToString @AllArgsConstructor public class LentExpirationImminentAlarm implements Alarm, TransactionalAlarmEvent { diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java index de0fae956..70f18582f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentSuccessAlarm.java @@ -3,12 +3,14 @@ import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; import org.ftclub.cabinet.cabinet.domain.Location; /** * 대여 성공 알람 */ @Getter +@ToString @AllArgsConstructor public class LentSuccessAlarm implements Alarm, TransactionalAlarmEvent { From ee70d71898c3eac931c92986753f240fbfa4acdd Mon Sep 17 00:00:00 2001 From: space Date: Mon, 1 Jan 2024 15:26:25 +0900 Subject: [PATCH 0230/1029] =?UTF-8?q?[BE]=20FIX:=20MailDto=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=A7=80=EC=A0=95=EC=9E=90=20=EC=B6=94=EA=B0=80=20&?= =?UTF-8?q?=20toString=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/ftclub/cabinet/alarm/dto/MailDto.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/MailDto.java b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/MailDto.java index 352bb6eb6..9424c25d3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/dto/MailDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/dto/MailDto.java @@ -2,13 +2,15 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; import org.thymeleaf.context.Context; @Getter @AllArgsConstructor +@ToString public class MailDto { - String subject; - String template; - Context context; + private String subject; + private String template; + private Context context; } From 23dff483ece367f4a057594ce73b6bbeeba70ef2 Mon Sep 17 00:00:00 2001 From: space Date: Mon, 1 Jan 2024 16:04:13 +0900 Subject: [PATCH 0231/1029] =?UTF-8?q?[BE]=20FIX:=20=EC=95=8C=EB=9E=8C?= =?UTF-8?q?=ED=98=95=ED=83=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/domain/LentExpirationImminentAlarm.java | 8 ++++++++ .../ftclub/cabinet/alarm/handler/EmailAlarmSender.java | 9 ++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index 6983092ef..1f702e349 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -1,5 +1,7 @@ package org.ftclub.cabinet.alarm.domain; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @@ -13,4 +15,10 @@ public class LentExpirationImminentAlarm implements Alarm, TransactionalAlarmEvent { private final Long daysAfterFromExpireDate; + + public String getExpirationDate() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime expireDate = now.plusDays(daysAfterFromExpireDate * -1); + return expireDate.format(DateTimeFormatter.ofPattern("YYYY년 MM월 DD일")); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java index b3cbfbe69..f0b0463af 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java @@ -36,8 +36,7 @@ public void send(User user, AlarmEvent alarmEvent) { log.debug("개발 환경이므로 메일을 보내지 않습니다."); return; } - // parseMessageToMailDto등과 같이 동사가 먼저오는 이름이어야 할 것 같습니다. - MailDto mailDto = messageParse(user.getName(), alarmEvent.getAlarm()); + MailDto mailDto = parseMessageToMailDto(user.getName(), alarmEvent.getAlarm()); try { sendMessage(user.getEmail(), mailDto); @@ -46,7 +45,7 @@ public void send(User user, AlarmEvent alarmEvent) { } } - private MailDto messageParse(String name, Alarm alarm) { + private MailDto parseMessageToMailDto(String name, Alarm alarm) { Context context = new Context(); context.setVariable("name", name); // private으로 각 알람별 메서드를 만들어서 호출하는 것이 좋을 것 같습니다. @@ -70,8 +69,8 @@ else if (alarm instanceof LentExpirationAlarm) { return new MailDto(alarmProperties.getOverdueSubject(), alarmProperties.getOverdueMailTemplateUrl(), context); } else if (alarm instanceof LentExpirationImminentAlarm) { - long overdueDays = ((LentExpirationImminentAlarm) alarm).getDaysAfterFromExpireDate(); - context.setVariable("overdueDays", overdueDays); + String expirationDate = ((LentExpirationImminentAlarm) alarm).getExpirationDate(); + context.setVariable("expireDate", expirationDate); return new MailDto(alarmProperties.getSoonOverdueSubject(), alarmProperties.getSoonOverdueMailTemplateUrl(), context); } else if (alarm instanceof ExtensionIssuanceAlarm) { From 4f1424c20149b96824db694b23f97e0d8a5447e2 Mon Sep 17 00:00:00 2001 From: space Date: Mon, 1 Jan 2024 16:04:25 +0900 Subject: [PATCH 0232/1029] =?UTF-8?q?[BE]=20TODO:=20REFACTOR=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EA=B0=9C=EC=84=A0=20=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/lent/service/LentServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java index 777918685..1e731518c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentServiceImpl.java @@ -262,6 +262,7 @@ public void handleLentFromRedisExpired(String cabinetIdString) { lentRedis.deleteCabinetIdInRedis(cabinetId.toString()); } + // TODO: 구조개선 필요 ActiveLentHistoryDto @Override public List getAllActiveLentHistories() { log.debug("Called getAllActiveLentHistories"); From 14335cd3866ab8cb5a54c5ea06c7ddad185302a8 Mon Sep 17 00:00:00 2001 From: space Date: Mon, 1 Jan 2024 16:13:13 +0900 Subject: [PATCH 0233/1029] =?UTF-8?q?[BE]=20TEST:=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=EB=AC=B8=20=EC=A3=BC=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/log4j2-local.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/resources/log4j2-local.xml b/backend/src/main/resources/log4j2-local.xml index e28b0701e..e1dd8a88d 100644 --- a/backend/src/main/resources/log4j2-local.xml +++ b/backend/src/main/resources/log4j2-local.xml @@ -20,6 +20,7 @@ + From 18254d4ee5f79d205dc8fc590abb98b12728022f Mon Sep 17 00:00:00 2001 From: space Date: Tue, 2 Jan 2024 01:48:19 +0900 Subject: [PATCH 0234/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EC=97=B0=EC=9E=A5?= =?UTF-8?q?=EA=B6=8C=20=EB=B0=9C=EA=B8=89=EB=A1=9C=EC=A7=81=20scheduler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/user/service/LentExtensionServiceImpl.java | 2 +- .../ftclub/cabinet/utils/scheduler/SystemScheduler.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java index 60df9114e..b83da556c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionServiceImpl.java @@ -45,8 +45,8 @@ public class LentExtensionServiceImpl implements LentExtensionService { private final CabinetOptionalFetcher cabinetOptionalFetcher; private final LentExtensionPolicy lentExtensionPolicy; +// @Scheduled(cron = "${cabinet.schedule.cron.extension-issue-time}") @Override - @Scheduled(cron = "${cabinet.schedule.cron.extension-issue-time}") public void issueLentExtension() { log.debug("Called issueLentExtension"); List userMonthDataDtos = occupiedTimeManager.filterToMetUserMonthlyTime( diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 5450037a0..3bd4ca949 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -8,6 +8,7 @@ import org.ftclub.cabinet.dto.UserBlackholeInfoDto; import org.ftclub.cabinet.lent.service.LentService; import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; +import org.ftclub.cabinet.user.service.LentExtensionService; import org.ftclub.cabinet.user.service.UserService; import org.ftclub.cabinet.utils.blackhole.manager.BlackholeManager; import org.ftclub.cabinet.utils.leave.absence.LeaveAbsenceManager; @@ -33,6 +34,7 @@ public class SystemScheduler { private final BlackholeManager blackholeManager; private final ReleaseManager releaseManager; private final OccupiedTimeManager occupiedTimeManager; + private final LentExtensionService lentExtensionService; private static final long DELAY_TIME = 2000; /** @@ -99,6 +101,12 @@ public void releasePendingCabinet() { releaseManager.releasingCabinets(); } + @Scheduled(cron = "${cabinet.schedule.cron.extension-issue-time}") + public void lentExtensionIssue(){ + log.info("called lentExtensionIssue"); + lentExtensionService.issueLentExtension(); + } + // @Scheduled(cron = "${cabinet.schedule.cron.extensible-user-check}") // public void checkUserQualifyForExtensible(){ // log.info("called checkUserQualifyForExtensible"); From c70f8207c63d32a5a561a5289bf764a034e65210 Mon Sep 17 00:00:00 2001 From: space Date: Tue, 2 Jan 2024 01:49:15 +0900 Subject: [PATCH 0235/1029] [BE] fix: config sync --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index cd35d5cd5..f9b5cfc4d 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit cd35d5cd56b674fbf2be59150eb6a6c5cff20af7 +Subproject commit f9b5cfc4d5f365d4f01ca39a4e4816a79d042e93 From 3bb643322525806bd87f365064ba2ade15499e37 Mon Sep 17 00:00:00 2001 From: space Date: Tue, 2 Jan 2024 02:25:32 +0900 Subject: [PATCH 0236/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EC=97=B0=EC=9E=A5?= =?UTF-8?q?=EA=B6=8C=20=EB=B0=9C=EA=B8=89=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/dto/UserMonthDataDto.java | 8 +++++++- .../java/org/ftclub/cabinet/ping/PingController.java | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/UserMonthDataDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/UserMonthDataDto.java index b8bc667b7..e88db28e7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/UserMonthDataDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/UserMonthDataDto.java @@ -2,15 +2,21 @@ import lombok.Getter; import lombok.ToString; +import lombok.extern.log4j.Log4j2; @Getter @ToString +@Log4j2 public class UserMonthDataDto { private int id; private String login; private int monthAccumationTime; - // Getters and setters + public int getMonthAccumationTime() { + log.info("called getMonthAccumationTime = {}", monthAccumationTime); + return monthAccumationTime; + } +// Getters and setters } diff --git a/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java b/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java index 020399df5..5e4a0f79c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java +++ b/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java @@ -1,14 +1,24 @@ package org.ftclub.cabinet.ping; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.user.service.LentExtensionService; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/ping") +@RequiredArgsConstructor public class PingController { + private final LentExtensionService lentExtensionService; @RequestMapping public String ping() { return "pong"; } + + @RequestMapping("/pong") + public String ok(){ + lentExtensionService.issueLentExtension(); + return "ok"; + } } From 3b26ae1bfc3dbf630b749253d626820c9f675d11 Mon Sep 17 00:00:00 2001 From: space Date: Tue, 2 Jan 2024 02:26:01 +0900 Subject: [PATCH 0237/1029] [BE] HOTFIX: config --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index f9b5cfc4d..1fcd2dabb 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit f9b5cfc4d5f365d4f01ca39a4e4816a79d042e93 +Subproject commit 1fcd2dabbdf095e8a3308a58341e969ea7dd3c64 From ab42326b118fc576d8dd7c60c7e142c99fa32f8d Mon Sep 17 00:00:00 2001 From: space Date: Tue, 2 Jan 2024 02:32:54 +0900 Subject: [PATCH 0238/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=EB=85=84=EB=8F=84=EA=B0=80=20=EC=A0=84=EB=85=84=EB=8F=84=2012?= =?UTF-8?q?=EC=9B=94=20=EC=9D=BC=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java index 1de0b973f..b88bd3360 100644 --- a/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/occupiedtime/OccupiedTimeManager.java @@ -61,8 +61,11 @@ public List filterToMetUserMonthlyTime( public UserMonthDataDto[] getUserLastMonthOccupiedTime() { LocalDateTime time = LocalDateTime.now(); - String currentYear = String.valueOf(time.getYear()); String currentMonth = String.valueOf(time.minusMonths(1).getMonthValue()); + String currentYear = String.valueOf(time.getYear()); + if (currentMonth.equals("12")) { + currentYear = String.valueOf(time.minusYears(1).getYear()); + } String apiUrl = haneProperties.getUrl() + "?year=" + currentYear + "&month=" + currentMonth; From 2941311d3cd3f9a63b1a14be442f5d4f131decb4 Mon Sep 17 00:00:00 2001 From: space Date: Tue, 2 Jan 2024 02:41:36 +0900 Subject: [PATCH 0239/1029] [BE] HOTFIX: Disable Pong & Time config change --- .../org/ftclub/cabinet/ping/PingController.java | 14 +++++++------- config | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java b/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java index 5e4a0f79c..2e26e9ca5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java +++ b/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java @@ -7,18 +7,18 @@ @RestController @RequestMapping("/ping") -@RequiredArgsConstructor +//@RequiredArgsConstructor public class PingController { - private final LentExtensionService lentExtensionService; +// private final LentExtensionService lentExtensionService; @RequestMapping public String ping() { return "pong"; } - @RequestMapping("/pong") - public String ok(){ - lentExtensionService.issueLentExtension(); - return "ok"; - } +// @RequestMapping("/pong") +// public String ok(){ +// lentExtensionService.issueLentExtension(); +// return "ok"; +// } } diff --git a/config b/config index 1fcd2dabb..cbb7ca34f 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 1fcd2dabbdf095e8a3308a58341e969ea7dd3c64 +Subproject commit cbb7ca34fd11a17480baec3e2768cd4d3956ee69 From de6b57708340b48997ee0885c308ffa2a3d00e36 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 2 Jan 2024 13:48:50 +0900 Subject: [PATCH 0240/1029] =?UTF-8?q?[COMMON]=20DOCS:=20readme=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EB=A9=A4=EB=B2=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#1502?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77138294e..0486cf4fc 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | |-----------------------------------------------|------------------------------------------------|---------------------------------------|----------------------------------------------|-----------------------------------------------|----------------------------------------| -| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | []() | []() | []() | +| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | [ 🛼 jeekim](https://github.com/jnkeniaem) | []() | []() | |-------------------------------------------|-------------------------------------------|--------------------------------------------|------|------|------| | | From 50063027fb90534618793e87789bd5a2f06328ee Mon Sep 17 00:00:00 2001 From: Minkyu01 Date: Tue, 2 Jan 2024 13:50:13 +0900 Subject: [PATCH 0241/1029] =?UTF-8?q?[COMMON]=20DOCS:=20onboarding=20saymy?= =?UTF-8?q?name=20=EC=88=98=ED=96=89=20#1504?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 +++++++++--------- config | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 77138294e..93cd5e862 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ - 클러스터 별 캐비닛 배치도를 바탕으로 실제 위치에 따른 캐비닛 대여 현황을 확인할 수 있습니다. - 클러스터에 방문할 필요 없이 간편하게 캐비닛 대여 및 반납이 가능합니다. - 캐비닛마다 `READ` / `UPDATE` 가능한 메모장을 제공합니다. - - 공유 캐비닛의 경우, 캐비닛 사용자들끼리만 공유 가능한 메모장을 제공합니다. + - 공유 캐비닛의 경우, 캐비닛 사용자들끼리만 공유 가능한 메모장을 제공합니다. ### 기술적 도전 @@ -156,25 +156,25 @@

| [🐇dongglee](https://github.com/leedonggyu1848) | [🍑 eunbikim](https://github.com/eunbi9n) | [🥔 gyuwlee](https://github.com/gyutato) | [🐬huchoi](https://github.com/hunjin-choi) | [👻 hybae](https://github.com/HyeonsikBae) | -|-------------------------------------------------|-------------------------------------------|------------------------------------------|--------------------------------------------|--------------------------------------------| +| ----------------------------------------------- | ----------------------------------------- | ---------------------------------------- | ------------------------------------------ | ------------------------------------------ | | [🍒 hyoon](https://github.com/kamg2218) | [🍏 hyospark](https://github.com/kyoshong) | [🙉 inshin](https://github.com/42inshin) | [🧑‍✈️ jaesjeon](https://github.com/Oris482) | [🐶 jiwchoi](https://github.com/jiwon-choi) | -|-----------------------------------------|--------------------------------------------|------------------------------------------|----------------------------------------------|---------------------------------------------| +| --------------------------------------- | ------------------------------------------ | ---------------------------------------- | ----------------------------------------- | ------------------------------------------- | | [🐯 joopark](https://github.com/joohongpark) | [🚀sanan](https://github.com/Ssuamje) | [🐻 seuan](https://github.com/aseungbo) | [🤑seycho](https://github.com/SeyoungCho) | [😺 sichoi](https://github.com/sichoi42) | -|----------------------------------------------|---------------------------------------|-----------------------------------------|-------------------------------------------|------------------------------------------| +| -------------------------------------------- | ------------------------------------- | --------------------------------------- | ----------------------------------------- | ---------------------------------------- | | [🍎 skim](https://github.com/subin195-09) | [🍪 spark](https://github.com/Hyunja27) | [✏️yooh](https://github.com/oyhoyhk) | [🪀 yoyoo](https://github.com/Yoowatney) | [🎒 yubchoi](https://github.com/yubinquitous) | -|-------------------------------------------|-----------------------------------------|--------------------------------------|------------------------------------------|-----------------------------------------------| +| ----------------------------------------- | --------------------------------------- | ------------------------------------ | ---------------------------------------- | --------------------------------------------- | | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | -|-----------------------------------------------|------------------------------------------------|---------------------------------------|----------------------------------------------|-----------------------------------------------|----------------------------------------| +| --------------------------------------------- | ---------------------------------------------- | -------------------------------------- | -------------------------------------------- | --------------------------------------------- | -------------------------------------- | -| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | []() | []() | []() | -|-------------------------------------------|-------------------------------------------|--------------------------------------------|------|------|------| +| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | [🍾 miyu](https://github.com/Minkyu01) | []() | []() | +| ----------------------------------------- | ----------------------------------------- | ------------------------------------------ | -------------------------------------- | ---- | ---- | | | -|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| --------------------------------------------------------------------------------------------------------------------------------------------------------- |

diff --git a/config b/config index cbb7ca34f..689668fd3 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit cbb7ca34fd11a17480baec3e2768cd4d3956ee69 +Subproject commit 689668fd3c05aa5d591c8711d6793d46617a6a0b From edfc7f08549061292f57a58a37b8b42037e4ee69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EB=AF=BC?= Date: Tue, 2 Jan 2024 13:56:38 +0900 Subject: [PATCH 0242/1029] [COMMON] KETWORD: DOCS #1503 --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 77138294e..7fd558e81 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ - 클러스터 별 캐비닛 배치도를 바탕으로 실제 위치에 따른 캐비닛 대여 현황을 확인할 수 있습니다. - 클러스터에 방문할 필요 없이 간편하게 캐비닛 대여 및 반납이 가능합니다. - 캐비닛마다 `READ` / `UPDATE` 가능한 메모장을 제공합니다. - - 공유 캐비닛의 경우, 캐비닛 사용자들끼리만 공유 가능한 메모장을 제공합니다. + - 공유 캐비닛의 경우, 캐비닛 사용자들끼리만 공유 가능한 메모장을 제공합니다. ### 기술적 도전 @@ -156,25 +156,25 @@
| [🐇dongglee](https://github.com/leedonggyu1848) | [🍑 eunbikim](https://github.com/eunbi9n) | [🥔 gyuwlee](https://github.com/gyutato) | [🐬huchoi](https://github.com/hunjin-choi) | [👻 hybae](https://github.com/HyeonsikBae) | -|-------------------------------------------------|-------------------------------------------|------------------------------------------|--------------------------------------------|--------------------------------------------| +| ----------------------------------------------- | ----------------------------------------- | ---------------------------------------- | ------------------------------------------ | ------------------------------------------ | | [🍒 hyoon](https://github.com/kamg2218) | [🍏 hyospark](https://github.com/kyoshong) | [🙉 inshin](https://github.com/42inshin) | [🧑‍✈️ jaesjeon](https://github.com/Oris482) | [🐶 jiwchoi](https://github.com/jiwon-choi) | -|-----------------------------------------|--------------------------------------------|------------------------------------------|----------------------------------------------|---------------------------------------------| +| --------------------------------------- | ------------------------------------------ | ---------------------------------------- | ----------------------------------------- | ------------------------------------------- | | [🐯 joopark](https://github.com/joohongpark) | [🚀sanan](https://github.com/Ssuamje) | [🐻 seuan](https://github.com/aseungbo) | [🤑seycho](https://github.com/SeyoungCho) | [😺 sichoi](https://github.com/sichoi42) | -|----------------------------------------------|---------------------------------------|-----------------------------------------|-------------------------------------------|------------------------------------------| +| -------------------------------------------- | ------------------------------------- | --------------------------------------- | ----------------------------------------- | ---------------------------------------- | | [🍎 skim](https://github.com/subin195-09) | [🍪 spark](https://github.com/Hyunja27) | [✏️yooh](https://github.com/oyhoyhk) | [🪀 yoyoo](https://github.com/Yoowatney) | [🎒 yubchoi](https://github.com/yubinquitous) | -|-------------------------------------------|-----------------------------------------|--------------------------------------|------------------------------------------|-----------------------------------------------| +| ----------------------------------------- | --------------------------------------- | ------------------------------------ | ---------------------------------------- | --------------------------------------------- | | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | -|-----------------------------------------------|------------------------------------------------|---------------------------------------|----------------------------------------------|-----------------------------------------------|----------------------------------------| +| --------------------------------------------- | ---------------------------------------------- | -------------------------------------- | -------------------------------------------- | --------------------------------------------- | -------------------------------------- | -| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | []() | []() | []() | -|-------------------------------------------|-------------------------------------------|--------------------------------------------|------|------|------| +| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | [🦝 jimchoi](https://github.com/jimchoi9) | []() | []() | +| ----------------------------------------- | ----------------------------------------- | ------------------------------------------ | ----------------------------------------- | ---- | ---- | | | -|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| --------------------------------------------------------------------------------------------------------------------------------------------------------- |

From efc255ba9e09ebb78d709f205d2cf62ebb5c6bc4 Mon Sep 17 00:00:00 2001 From: Gyeonga Koh Date: Tue, 2 Jan 2024 14:36:36 +0900 Subject: [PATCH 0243/1029] =?UTF-8?q?[COMMON]=20DOCS:=20README=EC=97=90=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a826e610c..906ee4a4a 100644 --- a/README.md +++ b/README.md @@ -170,8 +170,8 @@ | [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | | --------------------------------------------- | ---------------------------------------------- | -------------------------------------- | -------------------------------------------- | --------------------------------------------- | -------------------------------------- | -| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | [🦝 jimchoi](https://github.com/jimchoi9) | [ 🛼 jeekim](https://github.com/jnkeniaem) | [🍾 miyu](https://github.com/Minkyu01) | -| ----------------------------------------- | ----------------------------------------- | ------------------------------------------ | ----------------------------------------- | ------------------------------------------ | -------------------------------------- | +| [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | [🦝 jimchoi](https://github.com/jimchoi9) | [ 🛼 jeekim](https://github.com/jnkeniaem) | [🍾 miyu](https://github.com/Minkyu01) | [🧸 gykoh](https://github.com/gykoh42) | +| ----------------------------------------- | ----------------------------------------- | ------------------------------------------ | ----------------------------------------- | ------------------------------------------ | -------------------------------------- | -------------------------------------- | | | | --------------------------------------------------------------------------------------------------------------------------------------------------------- | From dce3bffa4b5644f387c6da93195b4a6aea32bf84 Mon Sep 17 00:00:00 2001 From: Minkyu01 Date: Tue, 2 Jan 2024 15:31:28 +0900 Subject: [PATCH 0244/1029] FIX : update config to dev --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 689668fd3..cbb7ca34f 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 689668fd3c05aa5d591c8711d6793d46617a6a0b +Subproject commit cbb7ca34fd11a17480baec3e2768cd4d3956ee69 From 292407c707038a9ee14438bfcca39c630d0263a7 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 2 Jan 2024 20:44:03 +0900 Subject: [PATCH 0245/1029] =?UTF-8?q?[BE]=20FEAT:=20redis=20backup=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/docker-compose.yaml | 6 ++- .../appendonlydir/appendonly.aof.1.base.rdb | Bin 0 -> 88 bytes .../appendonlydir/appendonly.aof.1.incr.aof | 45 ++++++++++++++++++ .../appendonlydir/appendonly.aof.manifest | 2 + redis/data/dump.rdb | Bin 0 -> 151 bytes redis/redis.conf | 31 ++++++++++++ 6 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 redis/data/appendonlydir/appendonly.aof.1.base.rdb create mode 100644 redis/data/appendonlydir/appendonly.aof.1.incr.aof create mode 100644 redis/data/appendonlydir/appendonly.aof.manifest create mode 100644 redis/data/dump.rdb create mode 100644 redis/redis.conf diff --git a/backend/docker-compose.yaml b/backend/docker-compose.yaml index a408a4844..8e4ec2c2a 100644 --- a/backend/docker-compose.yaml +++ b/backend/docker-compose.yaml @@ -32,7 +32,11 @@ services: restart: always ports: - "6379:6379" + volumes: + - ../redis/data/:/data/ + - ../redis/redis.conf:/user/local/etc/redis/redis.conf + command: redis-server /user/local/etc/redis/redis.conf healthcheck: test: [ "CMD", "redis-cli", "ping" ] timeout: 20s - retries: 10 \ No newline at end of file + retries: 10 diff --git a/redis/data/appendonlydir/appendonly.aof.1.base.rdb b/redis/data/appendonlydir/appendonly.aof.1.base.rdb new file mode 100644 index 0000000000000000000000000000000000000000..dff8b66b03a66de67ec4e5ca1839d0fbdc3382a2 GIT binary patch literal 88 zcmWG?b@2=~FfcUy#aWb^l3A=otNPkM`C`OZc<`#>H)_8Tt^s_lzW^hTqJL3>+q=hE_l|MF)Ze4g?7cGcYhP yF&sS1)SjAGk_t4HVmcK7 literal 0 HcmV?d00001 diff --git a/redis/redis.conf b/redis/redis.conf new file mode 100644 index 000000000..fefc7c114 --- /dev/null +++ b/redis/redis.conf @@ -0,0 +1,31 @@ +# 어떤 네트위크 인터페이스로부터 연결할 수 있도록 할 것인지 관리 +bind 0.0.0.0 + +# 사용 포트 관리 +port 6379 + +# Master 노드의 기본 사용자(default user)의 비밀번호 설정 +# requirepass RedisPassword + +# Redis 에서 사용할 수 있는 최대 메모리 용량. 지정하지 않으면 시스템 전체 용량 +maxmemory 1g + +# maxmemory 에 설정된 용량을 초과했을때 삭제할 데이터 선정 방식 +# - noeviction : 쓰기 동작에 대해 error 반환 (Default) +# - volatile-lru : expire 가 설정된 key 들중에서 LRU algorithm 에 의해서 선택된 key 제거 +# - allkeys-lru : 모든 key 들 중 LRU algorithm에 의해서 선택된 key 제거 +# - volatile-random : expire 가 설정된 key 들 중 임의의 key 제거 +# - allkeys-random : 모든 key 들 중 임의의 key 제거 +# - volatile-ttl : expire time(TTL)이 가장 적게 남은 key 제거 (minor TTL) +maxmemory-policy noeviction + +# DB 데이터를 주기적으로 파일로 백업하기 위한 설정 +# Redis 가 재시작되면 이 백업을 통해 DB 를 복구 +save 43200 10 +save 10800 1000 +save 3600 5000 +appendonly yes +dir /data + +# Expiration에 대한 Event 설정 +notify-keyspace-events Ex From 1e00076c2fb4b7abb92988283c7c8df28b5d8571 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 2 Jan 2024 20:46:12 +0900 Subject: [PATCH 0246/1029] =?UTF-8?q?[BE]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20=EB=B0=B1=EC=97=85=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appendonlydir/appendonly.aof.1.base.rdb | Bin 88 -> 0 bytes .../appendonlydir/appendonly.aof.1.incr.aof | 45 ------------------ .../appendonlydir/appendonly.aof.manifest | 2 - redis/data/dump.rdb | Bin 151 -> 0 bytes 4 files changed, 47 deletions(-) delete mode 100644 redis/data/appendonlydir/appendonly.aof.1.base.rdb delete mode 100644 redis/data/appendonlydir/appendonly.aof.1.incr.aof delete mode 100644 redis/data/appendonlydir/appendonly.aof.manifest delete mode 100644 redis/data/dump.rdb diff --git a/redis/data/appendonlydir/appendonly.aof.1.base.rdb b/redis/data/appendonlydir/appendonly.aof.1.base.rdb deleted file mode 100644 index dff8b66b03a66de67ec4e5ca1839d0fbdc3382a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88 zcmWG?b@2=~FfcUy#aWb^l3A=otNPkM`C`OZc<`#>H)_8Tt^s_lzW^hTqJL3>+q=hE_l|MF)Ze4g?7cGcYhP yF&sS1)SjAGk_t4HVmcK7 From dc0941481dc19b4cef55d810644e2b296998cd54 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 2 Jan 2024 22:32:50 +0900 Subject: [PATCH 0247/1029] =?UTF-8?q?[BE]=20redis=20=EB=B0=B1=EC=97=85?= =?UTF-8?q?=EC=9D=84=20=EC=A0=81=EC=9A=A9=ED=95=98=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20deploy-dev,=20deploy-main=20=EB=82=B4=EC=9D=98=20do?= =?UTF-8?q?cker-compose.yml=20=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy-dev/pinpoint-application/docker-compose.yml | 6 +++++- deploy-main/pinpoint-application/docker-compose.yml | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/deploy-dev/pinpoint-application/docker-compose.yml b/deploy-dev/pinpoint-application/docker-compose.yml index 69c047798..8460d61da 100644 --- a/deploy-dev/pinpoint-application/docker-compose.yml +++ b/deploy-dev/pinpoint-application/docker-compose.yml @@ -28,8 +28,12 @@ services: restart: always ports: - "6379:6379" + volumes: + - $HOME/redis/data/:/data/ + - $HOME/redis/redis.conf:/user/local/etc/redis/redis.conf + command: redis-server /user/local/etc/redis/redis.conf healthcheck: - test: ["CMD", "redis-cli", "ping"] + test: [ "CMD", "redis-cli", "ping" ] timeout: 20s retries: 10 networks: diff --git a/deploy-main/pinpoint-application/docker-compose.yml b/deploy-main/pinpoint-application/docker-compose.yml index 4ad6fbe2f..204899039 100644 --- a/deploy-main/pinpoint-application/docker-compose.yml +++ b/deploy-main/pinpoint-application/docker-compose.yml @@ -28,8 +28,12 @@ services: restart: always ports: - "6379:6379" + volumes: + - $HOME/redis/data/:/data/ + - $HOME/redis/redis.conf:/user/local/etc/redis/redis.conf + command: redis-server /user/local/etc/redis/redis.conf healthcheck: - test: ["CMD", "redis-cli", "ping"] + test: [ "CMD", "redis-cli", "ping" ] timeout: 20s retries: 10 networks: From 11473cf73f57c7747dccaa71dc1035948dfa67d5 Mon Sep 17 00:00:00 2001 From: space Date: Wed, 3 Jan 2024 22:55:31 +0900 Subject: [PATCH 0248/1029] =?UTF-8?q?[BE]=20HOTFIX:=20AlarmStatus=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=EC=97=90=20=EC=83=9D=EC=84=B1,=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=9B=84=20return=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/auth/service/AuthService.java | 1 + .../main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java | 4 ++++ .../src/main/java/org/ftclub/cabinet/user/domain/User.java | 1 + 3 files changed, 6 insertions(+) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthService.java index b057f713b..f77ce020c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthService.java @@ -55,6 +55,7 @@ public void addUserIfNotExistsByClaims(Map claims) { userService.createUser(name, email, DateUtil.stringToDate(blackHoledAtObject.toString()), USER); } + return; } throw new ServiceException(ExceptionStatus.INVALID_ARGUMENT); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java index 96cf7ada7..7bf1b884e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/AlarmStatus.java @@ -49,6 +49,10 @@ private AlarmStatus(User user, boolean slack, boolean email, boolean push) { this.push = push; } + public static AlarmStatus of(User user) { + return new AlarmStatus(user, true, true, true); + } + public static AlarmStatus of(User user, UpdateAlarmRequestDto dto) { return new AlarmStatus(user, dto.isSlack(), dto.isEmail(), dto.isPush()); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index a4993106b..e9d609ab3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -63,6 +63,7 @@ protected User(String name, String email, LocalDateTime blackholedAt, UserRole u this.email = email; this.blackholedAt = blackholedAt; this.role = userRole; + this.alarmStatus = AlarmStatus.of(this); } public static User of(String name, String email, LocalDateTime blackholedAt, From f5523441414e1810f21e5d0a2bfc5853c99a7fa4 Mon Sep 17 00:00:00 2001 From: space Date: Wed, 3 Jan 2024 22:57:54 +0900 Subject: [PATCH 0249/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EC=9D=B5=EB=AA=85?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java index ba4ece08e..41e9683f6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java @@ -75,20 +75,20 @@ public void logout(HttpServletResponse res) { authFacadeService.logout(res, ftApiProperties); } - @GetMapping("/challenge") - public void challengeLogin(HttpServletRequest req, HttpServletResponse res) throws IOException { - // 유저 랜덤생성 && 유저 역할은 일반 유저 - // 토큰 생성 및 response에 쿠키만들어서 심어주기 - authFacadeService.handleTestLogin(req, ftApiProperties, res, LocalDateTime.now()); - System.out.println("??????????????here??????????????????/"); - res.sendRedirect(DomainProperties.getFeHost() + "/home"); -// Map claims = tokenProvider -// String accessToken = tokenProvider.createTokenForTestUser(testUser, LocalDateTime.now()); -// Cookie cookie = cookieManager.cookieOf("access_token", -// accessToken); -// cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); -// System.out.println("?????????????????????? cookie domain = "); -// res.sendRedirect(DomainProperties.getLocal() + "/main"); +// @GetMapping("/challenge") +// public void challengeLogin(HttpServletRequest req, HttpServletResponse res) throws IOException { +// // 유저 랜덤생성 && 유저 역할은 일반 유저 +// // 토큰 생성 및 response에 쿠키만들어서 심어주기 +// authFacadeService.handleTestLogin(req, ftApiProperties, res, LocalDateTime.now()); +// System.out.println("??????????????here??????????????????/"); // res.sendRedirect(DomainProperties.getFeHost() + "/home"); - } +//// Map claims = tokenProvider +//// String accessToken = tokenProvider.createTokenForTestUser(testUser, LocalDateTime.now()); +//// Cookie cookie = cookieManager.cookieOf("access_token", +//// accessToken); +//// cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); +//// System.out.println("?????????????????????? cookie domain = "); +//// res.sendRedirect(DomainProperties.getLocal() + "/main"); +//// res.sendRedirect(DomainProperties.getFeHost() + "/home"); +// } } From 6022f82b77599b33e19e4dd88dd90451aca3f70c Mon Sep 17 00:00:00 2001 From: jusohn Date: Sun, 7 Jan 2024 01:19:06 +0900 Subject: [PATCH 0250/1029] =?UTF-8?q?[FE]=20FIX:=20MultiToggleSwitch=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=95=B4=EB=8B=B9=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=EB=A7=8C=20Transparent=20=ED=9A=A8=EA=B3=BC?= =?UTF-8?q?=EB=A5=BC=20=EC=A0=81=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#1512?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Common/MultiToggleSwitch.tsx | 20 ++++++++++--------- .../src/pages/PendingPage/PendingPage.tsx | 17 ++++++++++++++-- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx index 3f48ce838..14456fdae 100644 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/components/Common/MultiToggleSwitch.tsx @@ -1,7 +1,7 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useRef } from "react"; import styled from "styled-components"; -interface toggleItem { +export interface toggleItem { name: string; key: string; } @@ -17,10 +17,12 @@ const MultiToggleSwitch = ({ setState, toggleList, }: MultiToggleSwitchProps) => { + const wrapperRef = useRef(null); + useEffect(() => { - const buttons = document.querySelectorAll("button"); + const buttons = wrapperRef.current?.querySelectorAll("button"); - buttons.forEach((button) => { + buttons?.forEach((button) => { if (button.className === initialState) { button.style.color = "white"; button.style.backgroundColor = "var(--main-color)"; @@ -29,13 +31,13 @@ const MultiToggleSwitch = ({ }, []); function switchToggle(e: any) { - const target = e.target; + const target = e.target as HTMLButtonElement; if (target === e.currentTarget) return; - const buttons = document.querySelectorAll("button"); + const buttons = wrapperRef.current?.querySelectorAll("button"); - buttons.forEach((button) => { + buttons?.forEach((button) => { button.style.color = "black"; button.style.backgroundColor = "transparent"; }); @@ -43,11 +45,11 @@ const MultiToggleSwitch = ({ target.style.color = "white"; target.style.backgroundColor = "var(--main-color)"; - setState(target.className); + setState(target.className as React.SetStateAction); } return ( - + {toggleList.map((item) => ( + {pendingCabinetsList.length !== 0 ? ( @@ -49,7 +49,7 @@ const FloorContainer = ({ const FloorContainerStyled = styled.div` width: 70%; - margin-top: 50px; + margin-top: 30px; `; const FloorTitleStyled = styled.div<{ isToggled: boolean }>` From 31aab8d97f25db944e94bb56657e18015fd83b50 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 18 Jan 2024 13:55:04 +0900 Subject: [PATCH 0369/1029] =?UTF-8?q?[FE]=20FEAT:=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=EC=B9=A8=20=EB=B2=84=ED=8A=BC=EA=B3=BC=20=ED=83=80?= =?UTF-8?q?=EC=9D=B4=EB=A8=B8=20=ED=95=A9=EC=B9=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/images/rotateRight.svg | 5 ++++ .../src/pages/PendingPage/PendingPage.tsx | 25 +++++++++++++------ .../components/PendingCountdown.tsx | 17 +++++-------- 3 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 frontend/src/assets/images/rotateRight.svg diff --git a/frontend/src/assets/images/rotateRight.svg b/frontend/src/assets/images/rotateRight.svg new file mode 100644 index 000000000..ac1e99b24 --- /dev/null +++ b/frontend/src/assets/images/rotateRight.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 94394fc68..3fc37f4d5 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -148,11 +148,16 @@ const PendingPage = () => { {isRefreshing ? ( ) : ( - 새로고침 + <> + 새로고침 + setIsOpenTime(true)} /> + )} + {/* */} - setIsOpenTime(true)} /> + {/* setIsOpenTime(true)} /> */} + {/* */} {isLoaded && cabinets ? ( Object.entries(cabinets).map(([key, value]) => ( { if (serverTime.toLocaleTimeString() === Time.PENDING_OPEN) @@ -40,26 +43,18 @@ const PendingCountdown = ({ return ( <> - {remainingTime === 0 ? "OPEN" - : `${hours}시간 ${minutes}분 ${seconds}초`} + : `${twoDigitsHours}:${twoDigitsMinutes}:${twoDigitsSeconds} 남았습니다`} ); }; -const PendingCountdownIconStyled = styled.img` - height: 25px; - width: 25px; - margin-top: 50px; -`; - const PendingCountdownStyled = styled.div` - margin-top: 5px; - color: var(--main-color); - font-size: 1.8rem; + color: white; + font-size: 1.1rem; font-weight: 600; `; From 74cc6206416306c066e5e273a8dd3a39abf22317 Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 18 Jan 2024 14:21:06 +0900 Subject: [PATCH 0370/1029] =?UTF-8?q?[FE]=20FIX:=20PENDING=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=9D=98=20=EC=82=AC=EB=AC=BC=ED=95=A8=20border,=20sh?= =?UTF-8?q?adow=20=EC=88=98=EC=A0=95=20#1528?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CabinetInfoArea/AdminCabinetInfoArea.tsx | 7 ++++--- .../components/CabinetInfoArea/CabinetInfoArea.tsx | 4 ++-- .../CabinetListItem/AdminCabinetListItem.tsx | 3 ++- .../CabinetListItem/CabinetListItem.tsx | 3 ++- frontend/src/components/Home/ManualContentBox.tsx | 14 ++++++++++++-- .../CabinetColorTable/CabinetColorTable.tsx | 3 ++- .../components/Modals/ManualModal/ManualModal.tsx | 7 +++++-- 7 files changed, 29 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 8334f158b..120f66351 100644 --- a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -277,14 +277,15 @@ const CabinetRectangleStyled = styled.div<{ ? cabinetLabelColorMap["MINE"] : cabinetLabelColorMap[props.cabinetStatus]}; text-align: center; - ${({ cabinetStatus }) => css` border: ${cabinetStatus === "IN_SESSION" ? "2px solid var(--main-color)" : cabinetStatus === "PENDING" - ? "5px double var(--white)" + ? "2px double var(--main-color)" : "none"}; - ${cabinetStatus === "PENDING" && "line-height: 70px;"}; + `} + ${({ cabinetStatus }) => css` + box-shadow: ${cabinetStatus === "PENDING" && "inset 0px 0px 0px 2px white"}; `} `; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx index 2475f7560..ca3cde9ed 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -346,8 +346,8 @@ const CabinetRectangleStyled = styled.div<{ ${({ cabinetStatus }) => cabinetStatus === "PENDING" && css` - border: 5px double var(--white); - line-height: 70px; + border: 2px double var(--main-color); + box-shadow: inset 0px 0px 0px 2px var(--white); `} `; diff --git a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index 3978cc621..ddcb1f3b6 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -167,7 +167,8 @@ const CabinetListItemStyled = styled.div<{ ${({ status }) => status === "PENDING" && css` - border: 5px double var(--white); + border: 2px double var(--main-color); + box-shadow: inset 0px 0px 0px 2px var(--white); `} ${({ status }) => diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx index 8044f42c9..e1dd84de7 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -204,7 +204,8 @@ const CabinetListItemStyled = styled.div<{ ${({ status }) => status === "PENDING" && css` - border: 5px double var(--white); + border: 2px double var(--main-color); + box-shadow: inset 0px 0px 0px 2px var(--white); `} ${({ status }) => diff --git a/frontend/src/components/Home/ManualContentBox.tsx b/frontend/src/components/Home/ManualContentBox.tsx index 51d025b01..229c16f5a 100644 --- a/frontend/src/components/Home/ManualContentBox.tsx +++ b/frontend/src/components/Home/ManualContentBox.tsx @@ -85,7 +85,8 @@ const MaunalContentBoxStyled = styled.div<{ ${({ contentStatus }) => contentStatus === ContentStatus.PENDING && css` - border: 10px double var(--white); + border: 5px double var(--main-color); + box-shadow: inset 0px 0px 0px 5px var(--white); `} ${({ contentStatus }) => @@ -136,7 +137,16 @@ const MaunalContentBoxStyled = styled.div<{ :hover { transition: all 0.3s ease-in-out; - box-shadow: 10px 10px 25px 0 rgba(0, 0, 0, 0.2); + ${({ contentStatus }) => + contentStatus === ContentStatus.PENDING + ? css` + border: 5px double var(--main-color); + box-shadow: inset 0px 0px 0px 5px var(--white), + 10px 10px 25px 0 rgba(0, 0, 0, 0.2); + ` + : css` + box-shadow: 10px 10px 25px 0 rgba(0, 0, 0, 0.2); + `} p { transition: all 0.3s ease-in-out; transform: translateY(-5px); diff --git a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx index 1f8b91929..dcdf0d8f9 100644 --- a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx +++ b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx @@ -54,7 +54,8 @@ const ColorTableItemStyled = styled.div<{ color: string }>` ${({ color }) => color === "var(--pending)" && css` - border: 3px double var(--white); + border: 1px double var(--main-color); + box-shadow: inset 0px 0px 0px 1px var(--white); `} } `; diff --git a/frontend/src/components/Modals/ManualModal/ManualModal.tsx b/frontend/src/components/Modals/ManualModal/ManualModal.tsx index 522415bae..b3755a336 100644 --- a/frontend/src/components/Modals/ManualModal/ManualModal.tsx +++ b/frontend/src/components/Modals/ManualModal/ManualModal.tsx @@ -1,6 +1,6 @@ import React from "react"; import { useState } from "react"; -import styled, { keyframes } from "styled-components"; +import styled, { css, keyframes } from "styled-components"; import { manualContentData } from "@/assets/data/ManualContent"; import { ReactComponent as MoveBtnImg } from "@/assets/images/moveButton.svg"; import ContentStatus from "@/types/enum/content.status.enum"; @@ -141,10 +141,13 @@ const ModalWrapper = styled.div<{ border-radius: 40px 40px 0 0; border: ${(props) => props.contentStatus === ContentStatus.PENDING - ? "10px double var(--white)" + ? "5px double var(--main-color)" : props.contentStatus === ContentStatus.IN_SESSION ? "5px solid var(--main-color)" : "none"}; + box-shadow: ${(props) => + props.contentStatus === ContentStatus.PENDING && + "inset 0px 0px 0px 5px var(--white);"}; border-bottom: none; @media screen and (max-width: 700px) { width: 100%; From 82eb70e563f821d12f9cd37aff887c93f7f3d1fc Mon Sep 17 00:00:00 2001 From: Minkyu01 Date: Thu, 18 Jan 2024 14:27:06 +0900 Subject: [PATCH 0371/1029] =?UTF-8?q?[FE]=20FIX:=20=ED=86=A0=EA=B8=80=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EB=B2=94=EC=9C=84=20cursor=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PendingPage/components/FloorContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/PendingPage/components/FloorContainer.tsx b/frontend/src/pages/PendingPage/components/FloorContainer.tsx index 1c3359503..45d93a446 100644 --- a/frontend/src/pages/PendingPage/components/FloorContainer.tsx +++ b/frontend/src/pages/PendingPage/components/FloorContainer.tsx @@ -60,9 +60,9 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` padding-left: 5px; padding-right: 5px; border-bottom: 1.5px solid #d9d9d9; + cursor: pointer; button { all: initial; - cursor: pointer; z-index: 2; height: 30px; width: 20px; From c9e8b76db1f1a1416ea1c7dfa471dca92db462e6 Mon Sep 17 00:00:00 2001 From: Minkyu01 Date: Thu, 18 Jan 2024 14:38:16 +0900 Subject: [PATCH 0372/1029] =?UTF-8?q?[FE]=20FIX:=20=ED=86=A0=EA=B8=80=20?= =?UTF-8?q?=EC=BB=A4=EC=84=9C=20=EB=B3=80=ED=99=98=20=ED=99=95=EB=8C=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PendingPage/components/FloorContainer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/pages/PendingPage/components/FloorContainer.tsx b/frontend/src/pages/PendingPage/components/FloorContainer.tsx index 45d93a446..d3ce4e654 100644 --- a/frontend/src/pages/PendingPage/components/FloorContainer.tsx +++ b/frontend/src/pages/PendingPage/components/FloorContainer.tsx @@ -63,6 +63,7 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` cursor: pointer; button { all: initial; + cursor: inherit; z-index: 2; height: 30px; width: 20px; From c5d139ea9ba8ee3ea64583ffa3db64e2e3e69dd0 Mon Sep 17 00:00:00 2001 From: jiwon Date: Fri, 19 Jan 2024 15:44:27 +0900 Subject: [PATCH 0373/1029] =?UTF-8?q?[HOTFIX]=201=EC=8B=9C=20=EC=9D=B4?= =?UTF-8?q?=ED=9B=84=20=EB=B0=98=EB=82=A9=EB=90=9C=20=EC=82=AC=EB=AC=BC?= =?UTF-8?q?=ED=95=A8=EC=9D=B4=20pending=EC=97=90=EC=84=9C=20=EC=95=88?= =?UTF-8?q?=EB=B3=B4=EC=9D=B4=EB=8A=94=20=EA=B2=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/cabinet/service/CabinetFacadeService.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index 5527af72c..917203507 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -42,7 +42,6 @@ import org.ftclub.cabinet.mapper.LentMapper; import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.service.UserQueryService; -import org.ftclub.cabinet.utils.DateUtil; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -194,13 +193,11 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); } if (cabinet.isStatus(PENDING)) { - LocalDateTime latestEndedAt = lentHistoriesMap.get(cabinet.getId()).stream() + lentHistoriesMap.get(cabinet.getId()).stream() .map(LentHistory::getEndedAt) - .max(LocalDateTime::compareTo).orElse(null); - if (latestEndedAt != null && DateUtil.isSameDay(latestEndedAt, yesterday)) { - cabinetFloorMap.get(floor) - .add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); - } + .max(LocalDateTime::compareTo) + .ifPresent(latestEndedAt -> cabinetFloorMap.get(floor) + .add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null))); } }); return cabinetMapper.toCabinetPendingResponseDto(cabinetFloorMap); From b5d9383d226e0072e4e20775fbb03d592cd266e5 Mon Sep 17 00:00:00 2001 From: moonseonghui Date: Fri, 19 Jan 2024 16:02:39 +0900 Subject: [PATCH 0374/1029] =?UTF-8?q?[FE]=20HOTFIX=20:=20swap=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Modals/ResponseModal/ResponseModal.tsx | 2 ++ frontend/src/components/Modals/SwapModal/SwapModal.tsx | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Modals/ResponseModal/ResponseModal.tsx b/frontend/src/components/Modals/ResponseModal/ResponseModal.tsx index 23de0e6ab..a3eb2b329 100644 --- a/frontend/src/components/Modals/ResponseModal/ResponseModal.tsx +++ b/frontend/src/components/Modals/ResponseModal/ResponseModal.tsx @@ -20,11 +20,13 @@ export const SuccessResponseModal: React.FC<{ export const FailResponseModal: React.FC<{ modalTitle?: string; + modalContents?: string; closeModal: React.MouseEventHandler; }> = (props) => { const modalContents: IModalContents = { type: "noBtn", title: props.modalTitle ?? "실패했습니다", + detail: props.modalContents ?? "", closeModal: props.closeModal, iconType: IconType.ERRORICON, }; diff --git a/frontend/src/components/Modals/SwapModal/SwapModal.tsx b/frontend/src/components/Modals/SwapModal/SwapModal.tsx index 9fb479fc8..98aece572 100644 --- a/frontend/src/components/Modals/SwapModal/SwapModal.tsx +++ b/frontend/src/components/Modals/SwapModal/SwapModal.tsx @@ -29,6 +29,7 @@ const SwapModal: React.FC<{ const [showResponseModal, setShowResponseModal] = useState(false); const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false); const [modalTitle, setModalTitle] = useState(""); + const [modalContent, setModalContent] = useState(""); const [isLoading, setIsLoading] = useState(false); const currentCabinetId = useRecoilValue(currentCabinetIdState); const [myInfo, setMyInfo] = useRecoilState(userState); @@ -44,7 +45,8 @@ const SwapModal: React.FC<{ const swapDetail = `사물함 위치가 ${myLentInfo.floor}층 ${myLentInfo.section} ${myLentInfo.visibleNum}번에서 ${targetCabinetInfo.floor}층 ${targetCabinetInfo.section} ${targetCabinetInfo.visibleNum}번로 변경됩니다. 대여 기간은 그대로 유지되며, - 이사는 취소할 수 없습니다.`; + 이사는 취소할 수 없습니다. + 주 1회만 이사할 수 있습니다.`; const trySwapRequest = async () => { setIsLoading(true); @@ -71,7 +73,8 @@ const SwapModal: React.FC<{ throw error; } } catch (error: any) { - setModalTitle(error.response.data.message); + // setModalTitle(error.response.data.message); + setModalContent(error.response.data.message); setHasErrorOnResponse(true); } finally { setIsLoading(false); @@ -96,7 +99,8 @@ const SwapModal: React.FC<{ {showResponseModal && (hasErrorOnResponse ? ( ) : ( From 4f21d8dc578b6e47a9c2b58c9de7178f75f4aed7 Mon Sep 17 00:00:00 2001 From: jiwon Date: Fri, 19 Jan 2024 17:41:44 +0900 Subject: [PATCH 0375/1029] =?UTF-8?q?[HOTFIX]=20=EC=97=B0=EC=9E=A5?= =?UTF-8?q?=EA=B6=8C=20=EC=82=AC=EC=9A=A9=20=EC=8B=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/service/UserFacadeService.java | 24 +++++++++++-------- config | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 6c7fde454..76cce6540 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -7,6 +7,7 @@ import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.fcm.config.FirebaseConfig; +import org.ftclub.cabinet.alarm.fcm.service.FCMTokenRedisService; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.service.CabinetQueryService; import org.ftclub.cabinet.dto.LentExtensionPaginationDto; @@ -21,7 +22,6 @@ import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.alarm.fcm.service.FCMTokenRedisService; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.LentExtension; import org.ftclub.cabinet.user.domain.LentExtensionPolicy; @@ -58,13 +58,14 @@ public MyProfileResponseDto getProfile(UserSessionDto user) { LocalDateTime.now()).orElse(null); LentExtension lentExtension = lentExtensionQueryService.findActiveLentExtension( user.getUserId()); - LentExtensionResponseDto lentExtensionResponseDto = userMapper.toLentExtensionResponseDto(lentExtension); - User currentUser = userQueryService.getUser(user.getUserId()); + LentExtensionResponseDto lentExtensionResponseDto = userMapper.toLentExtensionResponseDto( + lentExtension); + User currentUser = userQueryService.getUser(user.getUserId()); boolean isDeviceTokenExpired = currentUser.getAlarmTypes().isPush() && fcmTokenRedisService.findByUserName(user.getName()).isEmpty(); - return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, - lentExtensionResponseDto, currentUser.getAlarmTypes(), isDeviceTokenExpired); - } + return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, + lentExtensionResponseDto, currentUser.getAlarmTypes(), isDeviceTokenExpired); + } /** * 유저의 모든 연장권 정보를 가져옵니다. @@ -106,7 +107,7 @@ public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto use * @param user 유저의 세션 정보 */ public void useLentExtension(UserSessionDto user) { - Cabinet cabinet = cabinetQueryService.getCabinet(user.getUserId()); + Cabinet cabinet = cabinetQueryService.getUserActiveCabinetForUpdate(user.getUserId()); List activeLentHistories = lentQueryService.findCabinetActiveLentHistories( cabinet.getId()); lentExtensionPolicy.verifyLentExtension(cabinet, activeLentHistories); @@ -126,18 +127,21 @@ public void useLentExtension(UserSessionDto user) { * @param updateAlarmRequestDto 변경할 알람 설정 정보 */ @Transactional - public void updateAlarmState(UserSessionDto userSessionDto, UpdateAlarmRequestDto updateAlarmRequestDto) { + public void updateAlarmState(UserSessionDto userSessionDto, + UpdateAlarmRequestDto updateAlarmRequestDto) { User user = userQueryService.getUser(userSessionDto.getUserId()); userCommandService.updateAlarmStatus(user, updateAlarmRequestDto); } /** * 유저의 디바이스 토큰 정보를 업데이트합니다. - * @param userSessionDto 유저의 세션 정보 + * + * @param userSessionDto 유저의 세션 정보 * @param updateDeviceTokenRequestDto 디바이스 토큰 정보 */ @Transactional - public void updateDeviceToken(UserSessionDto userSessionDto, UpdateDeviceTokenRequestDto updateDeviceTokenRequestDto) { + public void updateDeviceToken(UserSessionDto userSessionDto, + UpdateDeviceTokenRequestDto updateDeviceTokenRequestDto) { User user = userQueryService.getUser(userSessionDto.getUserId()); fcmTokenRedisService.saveToken( user.getName(), diff --git a/config b/config index 40d3b96ac..88b806001 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 40d3b96ac4bc9b20fd0d67ec04fa6e5d996ebb21 +Subproject commit 88b80600192796cd4a9c506b1f80aacf3ef4ae0a From f2eab5600873193b25b29b0429a9e887b37d54f0 Mon Sep 17 00:00:00 2001 From: space Date: Sat, 20 Jan 2024 00:14:00 +0900 Subject: [PATCH 0376/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EC=9D=B4=EC=A0=84?= =?UTF-8?q?=EC=97=90=20=EB=B8=94=EB=9E=99=ED=99=80=EC=9D=B4=EC=97=88?= =?UTF-8?q?=EB=8D=98=20=EC=9C=A0=EC=A0=80=EA=B0=80=20=EB=8C=80=EC=97=AC?= =?UTF-8?q?=EC=8B=9C=EB=8F=84=EC=8B=9C,=20=EC=83=88=EB=A1=9C=EA=B3=A0?= =?UTF-8?q?=EC=B9=A8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 3 ++ .../lent/service/LentFacadeService.java | 5 ++ .../org/ftclub/cabinet/user/domain/User.java | 26 +++++++---- .../blackhole/manager/BlackholeManager.java | 46 ++++++++++++++++--- 4 files changed, 66 insertions(+), 14 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 144e53513..9c38e0415 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,9 @@ + + + diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 5b4e80d25..e01748c03 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -18,6 +18,7 @@ import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.dto.MyCabinetResponseDto; +import org.ftclub.cabinet.dto.UserBlackHoleEvent; import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.dto.UserVerifyRequestDto; import org.ftclub.cabinet.exception.ExceptionStatus; @@ -156,6 +157,10 @@ public void startLentCabinet(Long userId, Long cabinetId) { List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); int userCount = lentQueryService.countCabinetUser(cabinetId); + if (user.isBlackholed()) { + eventPublisher.publishEvent(UserBlackHoleEvent.of(user)); + } + lentPolicyService.verifyCabinetLentCount( cabinet.getLentType(), cabinet.getMaxUser(), userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.PRIVATE); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index d82320ba2..0c81acd1f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -1,5 +1,18 @@ package org.ftclub.cabinet.user.domain; +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.regex.Pattern; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,13 +23,6 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.utils.ExceptionUtil; -import javax.persistence.*; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotNull; -import java.time.LocalDateTime; -import java.util.Objects; -import java.util.regex.Pattern; - @Entity @Table(name = "USER") @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -66,7 +72,7 @@ protected User(String name, String email, LocalDateTime blackholedAt, UserRole u } public static User of(String name, String email, LocalDateTime blackholedAt, - UserRole userRole) { + UserRole userRole) { User user = new User(name, email, blackholedAt, userRole); ExceptionUtil.throwIfFalse(user.isValid(), new DomainException(ExceptionStatus.INVALID_ARGUMENT)); @@ -134,4 +140,8 @@ public void changeAlarmStatus(UpdateAlarmRequestDto updateAlarmRequestDto) { this.emailAlarm = updateAlarmRequestDto.isEmail(); this.pushAlarm = updateAlarmRequestDto.isPush(); } + + public boolean isBlackholed() { + return blackholedAt != null && blackholedAt.isBefore(LocalDateTime.now()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java index 50bc53026..f8d38afc8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java @@ -1,5 +1,7 @@ package org.ftclub.cabinet.utils.blackhole.manager; +import com.fasterxml.jackson.core.JsonProcessingException; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.FtProfile; @@ -9,13 +11,12 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.service.UserCommandService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.client.HttpClientErrorException; -import java.time.LocalDateTime; - @Component @RequiredArgsConstructor @Log4j2 @@ -37,6 +38,23 @@ private void terminateInvalidUser(UserBlackHoleEvent userBlackHoleEvent, LocalDa userCommandService.deleteById(userBlackHoleEvent.getUserId(), now); } + /** + * 42 API를 통해 유저의 프로필을 가져온다. + * + * @param userName 42 intra name + * @return 유저 프로필 {@link FtProfile} + */ + + public FtProfile getUserRecentIntraProfile(String userName) { + try { + return userOauthService.getProfileByIntraName(tokenManager.getFtAccessToken(), + userName); + } catch (JsonProcessingException e) { + log.error("getUserRecentIntraProfile Exception: {}", userName, e); + throw ExceptionStatus.OAUTH_BAD_GATEWAY.asServiceException(); + } + } + /** * 스케쥴러가 샐행하는 블랙홀 처리 메서드 유저의 블랙홀 정보를 갱신하여 블랙홀에 빠졌는지 확인 후 처리한다. *

@@ -47,12 +65,12 @@ private void terminateInvalidUser(UserBlackHoleEvent userBlackHoleEvent, LocalDa public void handleBlackHole(UserBlackHoleEvent dto) { LocalDateTime now = LocalDateTime.now(); try { - FtProfile recentProfile = userOauthService.getProfileByIntraName(tokenManager.getFtAccessToken(), dto.getName()); - if (!recentProfile.getRole().isInCursus()) { + FtProfile userRecentIntraProfile = getUserRecentIntraProfile(dto.getName()); + if (!userRecentIntraProfile.getRole().isInCursus()) { terminateInvalidUser(dto, now); } - userCommandService.updateUserBlackholedAtById(dto.getUserId(), recentProfile.getBlackHoledAt()); - + userCommandService.updateUserBlackholedAtById(dto.getUserId(), + userRecentIntraProfile.getBlackHoledAt()); } catch (HttpClientErrorException e) { HttpStatus status = e.getStatusCode(); if (status.equals(HttpStatus.UNAUTHORIZED) || status.equals(HttpStatus.FORBIDDEN)) { @@ -74,4 +92,20 @@ public void handleBlackHole(UserBlackHoleEvent dto) { tokenManager.refreshFtAccessToken(); } } + + private boolean isBlackholed(LocalDateTime blackholedAt) { + if (blackholedAt == null) { + return false; + } + return blackholedAt.isBefore(LocalDateTime.now()); + } + + public void blackholeUpdate(User user) { + if (isBlackholed(user.getBlackholedAt())) { + return; + } + FtProfile userRecentIntraProfile = getUserRecentIntraProfile(user.getName()); + userCommandService.updateUserBlackholedAtById(user.getId(), + userRecentIntraProfile.getBlackHoledAt()); + } } From 37b297cecb69f03eb75528fa1bd0f553e226274d Mon Sep 17 00:00:00 2001 From: space Date: Sat, 20 Jan 2024 00:31:23 +0900 Subject: [PATCH 0377/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EC=97=B0=EC=9E=A5?= =?UTF-8?q?=EA=B6=8C=20=EC=95=88=EC=8D=A8=EC=A7=90=20=EC=9D=B4=EC=8A=88=20?= =?UTF-8?q?->=20@Transactional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/service/CabinetQueryService.java | 2 + .../lent/service/LentCommandService.java | 2 + .../service/BanHistoryCommandService.java | 2 + .../service/LentExtensionCommandService.java | 69 +++++++++---------- .../service/LentExtensionQueryService.java | 21 +++--- .../user/service/UserCommandService.java | 9 +-- .../user/service/UserFacadeService.java | 1 + .../user/service/UserQueryService.java | 2 + 8 files changed, 61 insertions(+), 47 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java index 04e9cea79..7be8e8df6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service; @Service +@Transactional @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) public class CabinetQueryService { diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java index 534bcd0d6..380feaf11 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentRepository; @@ -12,6 +13,7 @@ @Service @RequiredArgsConstructor +@Transactional @Logging(level = LogLevel.DEBUG) public class LentCommandService { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java index 6e7a688e8..802beac2c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; @@ -14,6 +15,7 @@ @Service @RequiredArgsConstructor +@Transactional @Logging(level = LogLevel.DEBUG) public class BanHistoryCommandService { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java index 5febf1b43..41ddff61f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java @@ -1,7 +1,9 @@ package org.ftclub.cabinet.user.service; +import java.time.LocalDateTime; +import java.util.List; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.config.CabinetProperties; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; @@ -11,45 +13,42 @@ import org.ftclub.cabinet.user.repository.LentExtensionRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; - @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) +@Transactional public class LentExtensionCommandService { - private final LentExtensionRepository lentExtensionRepository; - - private final LentExtensionPolicy policy; - private final CabinetProperties cabinetProperties; + private final LentExtensionRepository lentExtensionRepository; + private final LentExtensionPolicy policy; - /** - * 연장권을 생성합니다. - * - * @param userId 유저 ID - * @param type 연장권 타입 - * @param expiredAt 만료일 - * @return 생성된 연장권 - */ - public LentExtension createLentExtension(Long userId, LentExtensionType type, LocalDateTime expiredAt) { - LentExtension lentExtension = LentExtension.of(policy.getDefaultName(), - policy.getDefaultExtensionTerm(), - policy.getExpiry(expiredAt), - type, userId); - return lentExtensionRepository.save(lentExtension); - } + /** + * 연장권을 생성합니다. + * + * @param userId 유저 ID + * @param type 연장권 타입 + * @param expiredAt 만료일 + * @return 생성된 연장권 + */ + public LentExtension createLentExtension(Long userId, LentExtensionType type, + LocalDateTime expiredAt) { + LentExtension lentExtension = LentExtension.of(policy.getDefaultName(), + policy.getDefaultExtensionTerm(), + policy.getExpiry(expiredAt), + type, userId); + return lentExtensionRepository.save(lentExtension); + } - /** - * 연장권을 사용합니다. - * - * @param lentExtension 연장권 - * @param lentHistories 연장권을 사용할 사물함에 대한 대여 기록 - */ - public void useLentExtension(LentExtension lentExtension, List lentHistories) { - lentExtension.use(); - lentHistories.forEach(lentHistory -> - lentHistory.setExpiredAt( - lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); - } + /** + * 연장권을 사용합니다. + * + * @param lentExtension 연장권 + * @param lentHistories 연장권을 사용할 사물함에 대한 대여 기록 + */ + public void useLentExtension(LentExtension lentExtension, List lentHistories) { + lentExtension.use(); + lentHistories.forEach(lentHistory -> + lentHistory.setExpiredAt( + lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java index a765ac0ba..4f1ecf395 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java @@ -3,6 +3,7 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; @@ -12,28 +13,31 @@ import org.springframework.stereotype.Service; @Service +@Transactional @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) public class LentExtensionQueryService { - private final LentExtensionRepository lentExtensionRepository; + private final LentExtensionRepository lentExtensionRepository; /** * 유저의 사용 가능한 연장권 중 사용 기한이 가장 임박한 연장권을 가져옵니다. + * * @param userId * @return 사용 기한이 가장 임박한 연장권을 반환합니다. */ - public LentExtension findActiveLentExtension(Long userId) { + public LentExtension findActiveLentExtension(Long userId) { - return LentExtensions.builder() - .lentExtensions(lentExtensionRepository.findAll(userId)) - .build() - .findImminentActiveLentExtension() - .orElse(null); - } + return LentExtensions.builder() + .lentExtensions(lentExtensionRepository.findAll(userId)) + .build() + .findImminentActiveLentExtension() + .orElse(null); + } /** * 유저의 사용 가능한 연장권을 모두 가져옵니다. + * * @param userId * @return 사용 가능한 연장권을 모두 반환합니다. */ @@ -45,6 +49,7 @@ public LentExtensions findActiveLentExtensions(Long userId) { /** * 유저의 모든 연장권을 사용 기한 기준 최신 순서로 가져옵니다. + * * @param userId * @return 사용 기한을 기준으로 최신 순서로 정렬된 모든 연장권을 반환합니다. */ diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java index 23bfc95c9..de2aafe6e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java @@ -1,5 +1,7 @@ package org.ftclub.cabinet.user.service; +import java.time.LocalDateTime; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; @@ -11,11 +13,10 @@ import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; - @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) +@Transactional public class UserCommandService { private final UserRepository userRepository; @@ -66,7 +67,7 @@ public void updateClubName(User user, String clubName) { /** * 유저를 삭제합니다. * - * @param userId 삭제할 유저의 ID + * @param userId 삭제할 유저의 ID * @param deletedAt 삭제 시각 */ public void deleteById(Long userId, LocalDateTime deletedAt) { @@ -98,7 +99,7 @@ public void updateAlarmStatus(User user, UpdateAlarmRequestDto updateAlarmReques /** * 유저의 블랙홀을 변경합니다. * - * @param userId 유저의 ID + * @param userId 유저의 ID * @param blackholedAt 변경할 blackholedAt */ public void updateUserBlackholedAtById(Long userId, LocalDateTime blackholedAt) { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 76cce6540..9348d5ac0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -106,6 +106,7 @@ public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto use * * @param user 유저의 세션 정보 */ + @Transactional public void useLentExtension(UserSessionDto user) { Cabinet cabinet = cabinetQueryService.getUserActiveCabinetForUpdate(user.getUserId()); List activeLentHistories = lentQueryService.findCabinetActiveLentHistories( diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java index 8f78f8551..3fbe9eff8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.log.LogLevel; @@ -17,6 +18,7 @@ @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) +@Transactional public class UserQueryService { private final UserRepository userRepository; From 8d33bc1305dce302fb729bc3100cb77c4c1b96be Mon Sep 17 00:00:00 2001 From: SpaceChae <13278955+enaenen@users.noreply.github.com> Date: Sat, 20 Jan 2024 00:33:10 +0900 Subject: [PATCH 0378/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EC=9D=B4=EC=A0=84?= =?UTF-8?q?=EC=97=90=20=EB=B8=94=EB=9E=99=ED=99=80=EC=9D=B4=EC=97=88?= =?UTF-8?q?=EB=8D=98=20=EC=9C=A0=EC=A0=80=20=EB=8C=80=EC=97=AC=20=EC=8B=9C?= =?UTF-8?q?=EB=8F=84=EC=8B=9C,=20=EB=B8=94=EB=9E=99=ED=99=80=20=EC=83=88?= =?UTF-8?q?=EB=A1=9C=EA=B3=A0=EC=B9=A8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20+=20=EC=97=B0=EC=9E=A5=EA=B6=8C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95=20(#1537)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 3 + .../cabinet/service/CabinetQueryService.java | 2 + .../lent/service/LentCommandService.java | 2 + .../lent/service/LentFacadeService.java | 5 ++ .../org/ftclub/cabinet/user/domain/User.java | 26 ++++--- .../service/BanHistoryCommandService.java | 2 + .../service/LentExtensionCommandService.java | 69 +++++++++---------- .../service/LentExtensionQueryService.java | 21 +++--- .../user/service/UserCommandService.java | 9 +-- .../user/service/UserFacadeService.java | 1 + .../user/service/UserQueryService.java | 2 + .../blackhole/manager/BlackholeManager.java | 46 +++++++++++-- 12 files changed, 127 insertions(+), 61 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 144e53513..9c38e0415 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,9 @@ + + + diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java index 04e9cea79..7be8e8df6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service; @Service +@Transactional @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) public class CabinetQueryService { diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java index 534bcd0d6..380feaf11 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentCommandService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.repository.LentRepository; @@ -12,6 +13,7 @@ @Service @RequiredArgsConstructor +@Transactional @Logging(level = LogLevel.DEBUG) public class LentCommandService { diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 5b4e80d25..e01748c03 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -18,6 +18,7 @@ import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.dto.MyCabinetResponseDto; +import org.ftclub.cabinet.dto.UserBlackHoleEvent; import org.ftclub.cabinet.dto.UserSessionDto; import org.ftclub.cabinet.dto.UserVerifyRequestDto; import org.ftclub.cabinet.exception.ExceptionStatus; @@ -156,6 +157,10 @@ public void startLentCabinet(Long userId, Long cabinetId) { List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); int userCount = lentQueryService.countCabinetUser(cabinetId); + if (user.isBlackholed()) { + eventPublisher.publishEvent(UserBlackHoleEvent.of(user)); + } + lentPolicyService.verifyCabinetLentCount( cabinet.getLentType(), cabinet.getMaxUser(), userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.PRIVATE); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index d82320ba2..0c81acd1f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -1,5 +1,18 @@ package org.ftclub.cabinet.user.domain; +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.regex.Pattern; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,13 +23,6 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.utils.ExceptionUtil; -import javax.persistence.*; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotNull; -import java.time.LocalDateTime; -import java.util.Objects; -import java.util.regex.Pattern; - @Entity @Table(name = "USER") @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -66,7 +72,7 @@ protected User(String name, String email, LocalDateTime blackholedAt, UserRole u } public static User of(String name, String email, LocalDateTime blackholedAt, - UserRole userRole) { + UserRole userRole) { User user = new User(name, email, blackholedAt, userRole); ExceptionUtil.throwIfFalse(user.isValid(), new DomainException(ExceptionStatus.INVALID_ARGUMENT)); @@ -134,4 +140,8 @@ public void changeAlarmStatus(UpdateAlarmRequestDto updateAlarmRequestDto) { this.emailAlarm = updateAlarmRequestDto.isEmail(); this.pushAlarm = updateAlarmRequestDto.isPush(); } + + public boolean isBlackholed() { + return blackholedAt != null && blackholedAt.isBefore(LocalDateTime.now()); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java index 6e7a688e8..802beac2c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/BanHistoryCommandService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; @@ -14,6 +15,7 @@ @Service @RequiredArgsConstructor +@Transactional @Logging(level = LogLevel.DEBUG) public class BanHistoryCommandService { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java index 5febf1b43..41ddff61f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionCommandService.java @@ -1,7 +1,9 @@ package org.ftclub.cabinet.user.service; +import java.time.LocalDateTime; +import java.util.List; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.config.CabinetProperties; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; @@ -11,45 +13,42 @@ import org.ftclub.cabinet.user.repository.LentExtensionRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; - @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) +@Transactional public class LentExtensionCommandService { - private final LentExtensionRepository lentExtensionRepository; - - private final LentExtensionPolicy policy; - private final CabinetProperties cabinetProperties; + private final LentExtensionRepository lentExtensionRepository; + private final LentExtensionPolicy policy; - /** - * 연장권을 생성합니다. - * - * @param userId 유저 ID - * @param type 연장권 타입 - * @param expiredAt 만료일 - * @return 생성된 연장권 - */ - public LentExtension createLentExtension(Long userId, LentExtensionType type, LocalDateTime expiredAt) { - LentExtension lentExtension = LentExtension.of(policy.getDefaultName(), - policy.getDefaultExtensionTerm(), - policy.getExpiry(expiredAt), - type, userId); - return lentExtensionRepository.save(lentExtension); - } + /** + * 연장권을 생성합니다. + * + * @param userId 유저 ID + * @param type 연장권 타입 + * @param expiredAt 만료일 + * @return 생성된 연장권 + */ + public LentExtension createLentExtension(Long userId, LentExtensionType type, + LocalDateTime expiredAt) { + LentExtension lentExtension = LentExtension.of(policy.getDefaultName(), + policy.getDefaultExtensionTerm(), + policy.getExpiry(expiredAt), + type, userId); + return lentExtensionRepository.save(lentExtension); + } - /** - * 연장권을 사용합니다. - * - * @param lentExtension 연장권 - * @param lentHistories 연장권을 사용할 사물함에 대한 대여 기록 - */ - public void useLentExtension(LentExtension lentExtension, List lentHistories) { - lentExtension.use(); - lentHistories.forEach(lentHistory -> - lentHistory.setExpiredAt( - lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); - } + /** + * 연장권을 사용합니다. + * + * @param lentExtension 연장권 + * @param lentHistories 연장권을 사용할 사물함에 대한 대여 기록 + */ + public void useLentExtension(LentExtension lentExtension, List lentHistories) { + lentExtension.use(); + lentHistories.forEach(lentHistory -> + lentHistory.setExpiredAt( + lentHistory.getExpiredAt().plusDays(lentExtension.getExtensionPeriod()))); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java index a765ac0ba..4f1ecf395 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java @@ -3,6 +3,7 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; @@ -12,28 +13,31 @@ import org.springframework.stereotype.Service; @Service +@Transactional @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) public class LentExtensionQueryService { - private final LentExtensionRepository lentExtensionRepository; + private final LentExtensionRepository lentExtensionRepository; /** * 유저의 사용 가능한 연장권 중 사용 기한이 가장 임박한 연장권을 가져옵니다. + * * @param userId * @return 사용 기한이 가장 임박한 연장권을 반환합니다. */ - public LentExtension findActiveLentExtension(Long userId) { + public LentExtension findActiveLentExtension(Long userId) { - return LentExtensions.builder() - .lentExtensions(lentExtensionRepository.findAll(userId)) - .build() - .findImminentActiveLentExtension() - .orElse(null); - } + return LentExtensions.builder() + .lentExtensions(lentExtensionRepository.findAll(userId)) + .build() + .findImminentActiveLentExtension() + .orElse(null); + } /** * 유저의 사용 가능한 연장권을 모두 가져옵니다. + * * @param userId * @return 사용 가능한 연장권을 모두 반환합니다. */ @@ -45,6 +49,7 @@ public LentExtensions findActiveLentExtensions(Long userId) { /** * 유저의 모든 연장권을 사용 기한 기준 최신 순서로 가져옵니다. + * * @param userId * @return 사용 기한을 기준으로 최신 순서로 정렬된 모든 연장권을 반환합니다. */ diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java index 23bfc95c9..de2aafe6e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java @@ -1,5 +1,7 @@ package org.ftclub.cabinet.user.service; +import java.time.LocalDateTime; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.FtProfile; import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; @@ -11,11 +13,10 @@ import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; - @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) +@Transactional public class UserCommandService { private final UserRepository userRepository; @@ -66,7 +67,7 @@ public void updateClubName(User user, String clubName) { /** * 유저를 삭제합니다. * - * @param userId 삭제할 유저의 ID + * @param userId 삭제할 유저의 ID * @param deletedAt 삭제 시각 */ public void deleteById(Long userId, LocalDateTime deletedAt) { @@ -98,7 +99,7 @@ public void updateAlarmStatus(User user, UpdateAlarmRequestDto updateAlarmReques /** * 유저의 블랙홀을 변경합니다. * - * @param userId 유저의 ID + * @param userId 유저의 ID * @param blackholedAt 변경할 blackholedAt */ public void updateUserBlackholedAtById(Long userId, LocalDateTime blackholedAt) { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 76cce6540..9348d5ac0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -106,6 +106,7 @@ public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto use * * @param user 유저의 세션 정보 */ + @Transactional public void useLentExtension(UserSessionDto user) { Cabinet cabinet = cabinetQueryService.getUserActiveCabinetForUpdate(user.getUserId()); List activeLentHistories = lentQueryService.findCabinetActiveLentHistories( diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java index 8f78f8551..3fbe9eff8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.log.LogLevel; @@ -17,6 +18,7 @@ @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) +@Transactional public class UserQueryService { private final UserRepository userRepository; diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java index 50bc53026..f8d38afc8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java @@ -1,5 +1,7 @@ package org.ftclub.cabinet.utils.blackhole.manager; +import com.fasterxml.jackson.core.JsonProcessingException; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.FtProfile; @@ -9,13 +11,12 @@ import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.service.UserCommandService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.client.HttpClientErrorException; -import java.time.LocalDateTime; - @Component @RequiredArgsConstructor @Log4j2 @@ -37,6 +38,23 @@ private void terminateInvalidUser(UserBlackHoleEvent userBlackHoleEvent, LocalDa userCommandService.deleteById(userBlackHoleEvent.getUserId(), now); } + /** + * 42 API를 통해 유저의 프로필을 가져온다. + * + * @param userName 42 intra name + * @return 유저 프로필 {@link FtProfile} + */ + + public FtProfile getUserRecentIntraProfile(String userName) { + try { + return userOauthService.getProfileByIntraName(tokenManager.getFtAccessToken(), + userName); + } catch (JsonProcessingException e) { + log.error("getUserRecentIntraProfile Exception: {}", userName, e); + throw ExceptionStatus.OAUTH_BAD_GATEWAY.asServiceException(); + } + } + /** * 스케쥴러가 샐행하는 블랙홀 처리 메서드 유저의 블랙홀 정보를 갱신하여 블랙홀에 빠졌는지 확인 후 처리한다. *

@@ -47,12 +65,12 @@ private void terminateInvalidUser(UserBlackHoleEvent userBlackHoleEvent, LocalDa public void handleBlackHole(UserBlackHoleEvent dto) { LocalDateTime now = LocalDateTime.now(); try { - FtProfile recentProfile = userOauthService.getProfileByIntraName(tokenManager.getFtAccessToken(), dto.getName()); - if (!recentProfile.getRole().isInCursus()) { + FtProfile userRecentIntraProfile = getUserRecentIntraProfile(dto.getName()); + if (!userRecentIntraProfile.getRole().isInCursus()) { terminateInvalidUser(dto, now); } - userCommandService.updateUserBlackholedAtById(dto.getUserId(), recentProfile.getBlackHoledAt()); - + userCommandService.updateUserBlackholedAtById(dto.getUserId(), + userRecentIntraProfile.getBlackHoledAt()); } catch (HttpClientErrorException e) { HttpStatus status = e.getStatusCode(); if (status.equals(HttpStatus.UNAUTHORIZED) || status.equals(HttpStatus.FORBIDDEN)) { @@ -74,4 +92,20 @@ public void handleBlackHole(UserBlackHoleEvent dto) { tokenManager.refreshFtAccessToken(); } } + + private boolean isBlackholed(LocalDateTime blackholedAt) { + if (blackholedAt == null) { + return false; + } + return blackholedAt.isBefore(LocalDateTime.now()); + } + + public void blackholeUpdate(User user) { + if (isBlackholed(user.getBlackholedAt())) { + return; + } + FtProfile userRecentIntraProfile = getUserRecentIntraProfile(user.getName()); + userCommandService.updateUserBlackholedAtById(user.getId(), + userRecentIntraProfile.getBlackHoledAt()); + } } From 86ae296f0e429908f60024f458ce9d908064628f Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 20 Jan 2024 12:42:49 +0900 Subject: [PATCH 0379/1029] merge --- .../repository/LentExtensionRepository.java | 20 ++--------------- .../service/LentExtensionQueryService.java | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java index cfbd702f2..aee4045f3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java @@ -1,31 +1,15 @@ package org.ftclub.cabinet.user.repository; import java.util.List; - import org.ftclub.cabinet.user.domain.LentExtension; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface LentExtensionRepository extends JpaRepository { - @Query("SELECT le " + - "FROM LentExtension le") - Page findAll(Pageable pageable); - - @Query("SELECT le " + - "FROM LentExtension le " + - "WHERE le.expiredAt > CURRENT_TIMESTAMP") - Page findAllNotExpired(Pageable pageable); - - @Query("SELECT le " + - "FROM LentExtension le " + - "WHERE le.userId =:userId ") - List findAll(@Param("userId") Long userId); + List findAllByUserId(@Param("userId") Long userId); - List findAllByUserIdAndUsedAtIsNull(@Param("userId") Long userId); + List findAllByUserIdAndUsedAtIsNull(@Param("userId") Long userId); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java index a765ac0ba..f71d42198 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java @@ -16,24 +16,27 @@ @Logging(level = LogLevel.DEBUG) public class LentExtensionQueryService { - private final LentExtensionRepository lentExtensionRepository; + private final LentExtensionRepository lentExtensionRepository; /** * 유저의 사용 가능한 연장권 중 사용 기한이 가장 임박한 연장권을 가져옵니다. + * * @param userId * @return 사용 기한이 가장 임박한 연장권을 반환합니다. */ - public LentExtension findActiveLentExtension(Long userId) { + public LentExtension findActiveLentExtension(Long userId) { - return LentExtensions.builder() - .lentExtensions(lentExtensionRepository.findAll(userId)) - .build() - .findImminentActiveLentExtension() - .orElse(null); - } + List lentExtensions = lentExtensionRepository.findAllByUserId(userId); + return LentExtensions.builder() + .lentExtensions(lentExtensions) + .build() + .findImminentActiveLentExtension() + .orElse(null); + } /** * 유저의 사용 가능한 연장권을 모두 가져옵니다. + * * @param userId * @return 사용 가능한 연장권을 모두 반환합니다. */ @@ -45,11 +48,12 @@ public LentExtensions findActiveLentExtensions(Long userId) { /** * 유저의 모든 연장권을 사용 기한 기준 최신 순서로 가져옵니다. + * * @param userId * @return 사용 기한을 기준으로 최신 순서로 정렬된 모든 연장권을 반환합니다. */ public List findLentExtensionsInLatestOrder(Long userId) { - return lentExtensionRepository.findAll(userId) + return lentExtensionRepository.findAllByUserId(userId) .stream() .sorted(Comparator.comparing(LentExtension::getExpiredAt, Comparator.reverseOrder())) From 5d6af5f6f362d0d3ca9893cd360f8320862deb8e Mon Sep 17 00:00:00 2001 From: jiwon Date: Sat, 20 Jan 2024 13:09:25 +0900 Subject: [PATCH 0380/1029] =?UTF-8?q?[BE]=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=EC=9D=B4=ED=9B=84=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=A0=81=EC=9A=A9=20=EC=95=88=EB=90=9C=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/user/service/UserFacadeService.java | 5 ++++- .../org/ftclub/cabinet/user/service/UserQueryService.java | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 9348d5ac0..3dea61d63 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -4,7 +4,6 @@ import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; -import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.fcm.config.FirebaseConfig; import org.ftclub.cabinet.alarm.fcm.service.FCMTokenRedisService; @@ -28,6 +27,7 @@ import org.ftclub.cabinet.user.domain.LentExtensions; import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -52,6 +52,7 @@ public class UserFacadeService { * @param user 유저의 세션 정보 * @return 유저의 프로필 정보를 반환합니다. */ + @Transactional(readOnly = true) public MyProfileResponseDto getProfile(UserSessionDto user) { Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), @@ -73,6 +74,7 @@ public MyProfileResponseDto getProfile(UserSessionDto user) { * @param user 유저의 세션 정보 * @return 유저의 모든 연장권 정보를 반환합니다. */ + @Transactional(readOnly = true) public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { List lentExtensionResponseDtos = lentExtensionQueryService.findLentExtensionsInLatestOrder( user.getUserId()) @@ -89,6 +91,7 @@ public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { * @param user 유저의 세션 정보 * @return 유저의 사용 가능한 연장권 정보를 반환합니다. */ + @Transactional(readOnly = true) public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto user) { LentExtensions lentExtensions = lentExtensionQueryService.findActiveLentExtensions( user.getUserId()); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java index 3fbe9eff8..8f78f8551 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java @@ -3,7 +3,6 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; -import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.log.LogLevel; @@ -18,7 +17,6 @@ @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) -@Transactional public class UserQueryService { private final UserRepository userRepository; From a5945c6e4570bb3e6da33f6860f0bed1757f68e9 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Sun, 21 Jan 2024 17:36:21 +0900 Subject: [PATCH 0381/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EA=B3=B5=EC=9C=A0?= =?UTF-8?q?=EC=82=AC=EB=AC=BC=ED=95=A8=20=EB=B8=94=EB=9E=99=ED=99=80=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/lent/service/LentFacadeService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index e01748c03..00a614d7f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -199,6 +199,10 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + if (user.isBlackholed()) { + eventPublisher.publishEvent(UserBlackHoleEvent.of(user)); + } + Long attemptCount = lentRedisService.getAttemptCountOnShareCabinet(cabinetId, userId); lentPolicyService.verifyAttemptCountOnShareCabinet(attemptCount); From a912411a8215a355e12b5e6f1c080480dac91fb7 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Sun, 21 Jan 2024 17:39:03 +0900 Subject: [PATCH 0382/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EA=B3=B5=EC=9C=A0?= =?UTF-8?q?=EC=82=AC=EB=AC=BC=ED=95=A8=20=EB=B8=94=EB=9E=99=ED=99=80=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lent/service/LentFacadeService.java | 106 +++++++++--------- 1 file changed, 50 insertions(+), 56 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 00a614d7f..ba02a2e50 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -71,15 +71,12 @@ public class LentFacadeService { */ @Transactional(readOnly = true) public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, Pageable pageable) { - Page lentHistories = - lentQueryService.findUserLentHistories(user.getUserId(), pageable); + Page lentHistories = lentQueryService.findUserLentHistories(user.getUserId(), + pageable); List result = lentHistories.stream() .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) - .map(lentHistory -> lentMapper.toLentHistoryDto( - lentHistory, - lentHistory.getUser(), - lentHistory.getCabinet())) - .collect(Collectors.toList()); + .map(lentHistory -> lentMapper.toLentHistoryDto(lentHistory, lentHistory.getUser(), + lentHistory.getCabinet())).collect(Collectors.toList()); return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); } @@ -91,8 +88,8 @@ public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, Pageable pagea */ @Transactional(readOnly = true) public MyCabinetResponseDto getMyLentInfo(UserSessionDto user) { - LentHistory userLentHistory = - lentQueryService.findUserActiveLentHistoryWithCabinet(user.getUserId()); + LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithCabinet( + user.getUserId()); Long cabinetId; Cabinet userActiveCabinet; List lentDtoList; @@ -104,21 +101,21 @@ public MyCabinetResponseDto getMyLentInfo(UserSessionDto user) { List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); List userList = userQueryService.getUsers(usersInCabinet); userActiveCabinet = cabinetQueryService.getCabinet(cabinetId); - lentDtoList = userList.stream() - .map(u -> lentMapper.toLentDto(u, null)).collect(Collectors.toList()); + lentDtoList = userList.stream().map(u -> lentMapper.toLentDto(u, null)) + .collect(Collectors.toList()); } else { userActiveCabinet = userLentHistory.getCabinet(); cabinetId = userActiveCabinet.getId(); - List lentHistories = - lentQueryService.findCabinetActiveLentHistories(cabinetId); - lentDtoList = lentHistories.stream() - .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(Collectors.toList()); + List lentHistories = lentQueryService.findCabinetActiveLentHistories( + cabinetId); + lentDtoList = lentHistories.stream().map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) + .collect(Collectors.toList()); } String shareCode = lentRedisService.getShareCode(cabinetId); LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); String previousUserName = lentRedisService.getPreviousUserName(cabinetId); - return cabinetMapper.toMyCabinetResponseDto(userActiveCabinet, lentDtoList, - shareCode, sessionExpiredAt, previousUserName); + return cabinetMapper.toMyCabinetResponseDto(userActiveCabinet, lentDtoList, shareCode, + sessionExpiredAt, previousUserName); } /** @@ -129,14 +126,10 @@ public MyCabinetResponseDto getMyLentInfo(UserSessionDto user) { @Transactional(readOnly = true) public List getAllActiveLentHistories() { LocalDateTime now = LocalDateTime.now(); - List lentHistories = - lentQueryService.findAllActiveLentHistoriesWithCabinetAndUser(); + List lentHistories = lentQueryService.findAllActiveLentHistoriesWithCabinetAndUser(); return lentHistories.stream() - .map(lh -> lentMapper.toActiveLentHistoryDto(lh, - lh.getUser(), - lh.getCabinet(), - lh.isExpired(now), - lh.getDaysUntilExpiration(now))) + .map(lh -> lentMapper.toActiveLentHistoryDto(lh, lh.getUser(), lh.getCabinet(), + lh.isExpired(now), lh.getDaysUntilExpiration(now))) .collect(Collectors.toList()); } @@ -161,11 +154,12 @@ public void startLentCabinet(Long userId, Long cabinetId) { eventPublisher.publishEvent(UserBlackHoleEvent.of(user)); } - lentPolicyService.verifyCabinetLentCount( - cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetLentCount(cabinet.getLentType(), cabinet.getMaxUser(), + userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.PRIVATE); - lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), - user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + lentPolicyService.verifyUserForLent( + new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, + cabinetId, cabinet.getStatus(), banHistories)); lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, LentType.PRIVATE, @@ -173,8 +167,9 @@ public void startLentCabinet(Long userId, Long cabinetId) { lentCommandService.startLent(user.getId(), cabinet.getId(), now, expiredAt); cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); cabinetCommandService.changeUserCount(cabinet, lentCount + 1); - eventPublisher.publishEvent(AlarmEvent.of(userId, new LentSuccessAlarm( - cabinet.getCabinetPlace().getLocation(), cabinet.getVisibleNum(), expiredAt))); + eventPublisher.publishEvent(AlarmEvent.of(userId, + new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), + cabinet.getVisibleNum(), expiredAt))); } /** @@ -189,20 +184,21 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) LocalDateTime now = LocalDateTime.now(); Cabinet cabinet = cabinetQueryService.getCabinetForUpdate(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); - lentPolicyService.verifyCabinetLentCount( - cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetLentCount(cabinet.getLentType(), cabinet.getMaxUser(), + userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.SHARE); List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); int lentCount = lentQueryService.countUserActiveLent(userId); User user = userQueryService.getUser(userId); - lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), - user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); - if (user.isBlackholed()) { eventPublisher.publishEvent(UserBlackHoleEvent.of(user)); } + lentPolicyService.verifyUserForLent( + new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, + cabinetId, cabinet.getStatus(), banHistories)); + Long attemptCount = lentRedisService.getAttemptCountOnShareCabinet(cabinetId, userId); lentPolicyService.verifyAttemptCountOnShareCabinet(attemptCount); @@ -215,16 +211,15 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) lentRedisService.attemptJoinCabinet(cabinetId, userId, shareCode); if (lentRedisService.isCabinetSessionFull(cabinetId)) { List userIdsInCabinet = lentRedisService.getUsersInCabinet(cabinetId); - LocalDateTime expiredAt = - lentPolicyService.generateExpirationDate(now, LentType.SHARE, - userIdsInCabinet.size()); + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, LentType.SHARE, + userIdsInCabinet.size()); lentCommandService.startLent(userIdsInCabinet, cabinet.getId(), now, expiredAt); lentRedisService.clearCabinetSession(cabinetId); cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); cabinetCommandService.changeUserCount(cabinet, userIdsInCabinet.size()); - LentSuccessAlarm alarm = new LentSuccessAlarm( - cabinet.getCabinetPlace().getLocation(), cabinet.getVisibleNum(), expiredAt); + LentSuccessAlarm alarm = new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), + cabinet.getVisibleNum(), expiredAt); eventPublisher.publishEvent(AlarmEvent.of(userId, alarm)); } } @@ -242,12 +237,12 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { Cabinet cabinet = cabinetQueryService.getCabinet(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); - lentPolicyService.verifyCabinetLentCount( - cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetLentCount(cabinet.getLentType(), cabinet.getMaxUser(), + userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.CLUB); lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); - LocalDateTime expiredAt = - lentPolicyService.generateExpirationDate(now, cabinet.getLentType(), 1); + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, + cabinet.getLentType(), 1); lentCommandService.startLent(userId, cabinetId, now, expiredAt); cabinetCommandService.changeUserCount(cabinet, userCount + 1); } @@ -263,22 +258,21 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { @Transactional public void endUserLent(Long userId, String memo) { LocalDateTime now = LocalDateTime.now(); - List lentHistories = - lentQueryService.findUserActiveLentHistoriesInCabinetForUpdate(userId); + List lentHistories = lentQueryService.findUserActiveLentHistoriesInCabinetForUpdate( + userId); if (lentHistories.isEmpty()) { throw ExceptionStatus.NOT_FOUND_LENT_HISTORY.asServiceException(); } LentHistory userLentHistory = lentHistories.stream() .filter(lh -> lh.getUserId().equals(userId)).findFirst() .orElseThrow(ExceptionStatus.NOT_FOUND_LENT_HISTORY::asServiceException); - Cabinet cabinet = - cabinetQueryService.getCabinetForUpdate(lentHistories.get(0).getCabinetId()); + Cabinet cabinet = cabinetQueryService.getCabinetForUpdate( + lentHistories.get(0).getCabinetId()); int userRemainCount = lentHistories.size() - 1; cabinetCommandService.changeUserCount(cabinet, userRemainCount); lentCommandService.endLent(userLentHistory, now); - lentRedisService.setPreviousUserName( - cabinet.getId(), userLentHistory.getUser().getName()); + lentRedisService.setPreviousUserName(cabinet.getId(), userLentHistory.getUser().getName()); cabinetCommandService.updateTitle(cabinet, ""); cabinetCommandService.updateMemo(cabinet, (memo == null) ? "" : memo); @@ -292,8 +286,8 @@ public void endUserLent(Long userId, String memo) { LocalDateTime newExpiredAt = lentPolicyService.adjustShareCabinetExpirationDate( userRemainCount, now, expiredAt); List lentHistoryIds = lentHistories.stream() - .filter(lh -> !lh.equals(userLentHistory)) - .map(LentHistory::getId).collect(Collectors.toList()); + .filter(lh -> !lh.equals(userLentHistory)).map(LentHistory::getId) + .collect(Collectors.toList()); lentCommandService.setExpiredAt(lentHistoryIds, newExpiredAt); } } @@ -340,8 +334,8 @@ public void shareCabinetSessionExpired(Long cabinetId) { List usersInCabinetSession = lentRedisService.getUsersInCabinet(cabinetId); if (lentPolicyService.checkUserCountOnShareCabinet(usersInCabinetSession.size())) { LocalDateTime now = LocalDateTime.now(); - LocalDateTime expiredAt = lentPolicyService.generateExpirationDate( - now, LentType.SHARE, usersInCabinetSession.size()); + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, LentType.SHARE, + usersInCabinetSession.size()); cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); lentCommandService.startLent(usersInCabinetSession, cabinetId, now, expiredAt); } else { @@ -387,8 +381,8 @@ public void swapPrivateCabinet(Long userId, Long newCabinetId) { lentCommandService.endLent(oldLentHistory, now); cabinetCommandService.changeStatus(oldCabinet, CabinetStatus.PENDING); - lentRedisService.setPreviousUserName( - oldCabinet.getId(), oldLentHistory.getUser().getName()); + lentRedisService.setPreviousUserName(oldCabinet.getId(), + oldLentHistory.getUser().getName()); cabinetCommandService.changeUserCount(oldCabinet, 0); cabinetCommandService.updateTitle(oldCabinet, ""); cabinetCommandService.updateMemo(oldCabinet, ""); From 700e9a5c8c91d3e60235ae1e1c6a90817682fa75 Mon Sep 17 00:00:00 2001 From: SpaceChae <13278955+enaenen@users.noreply.github.com> Date: Sun, 21 Jan 2024 17:41:03 +0900 Subject: [PATCH 0383/1029] [BE] HOTFIX: DEV TO MAIN (#1538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BE] HOTFIX: 이전에 블랙홀이었던 유저가 대여시도시, 새로고침 로직 추가 * [BE] HOTFIX: 연장권 안써짐 이슈 -> @Transactional * merge * [BE] 리팩토링 이후 트랜잭션 적용 안된 부분 적용 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 --------- Co-authored-by: jiwon --- .../lent/service/LentFacadeService.java | 108 +++++++++--------- .../repository/LentExtensionRepository.java | 20 +--- .../service/LentExtensionQueryService.java | 5 +- .../user/service/UserFacadeService.java | 5 +- .../user/service/UserQueryService.java | 2 - 5 files changed, 62 insertions(+), 78 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index e01748c03..ba02a2e50 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -71,15 +71,12 @@ public class LentFacadeService { */ @Transactional(readOnly = true) public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, Pageable pageable) { - Page lentHistories = - lentQueryService.findUserLentHistories(user.getUserId(), pageable); + Page lentHistories = lentQueryService.findUserLentHistories(user.getUserId(), + pageable); List result = lentHistories.stream() .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) - .map(lentHistory -> lentMapper.toLentHistoryDto( - lentHistory, - lentHistory.getUser(), - lentHistory.getCabinet())) - .collect(Collectors.toList()); + .map(lentHistory -> lentMapper.toLentHistoryDto(lentHistory, lentHistory.getUser(), + lentHistory.getCabinet())).collect(Collectors.toList()); return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); } @@ -91,8 +88,8 @@ public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, Pageable pagea */ @Transactional(readOnly = true) public MyCabinetResponseDto getMyLentInfo(UserSessionDto user) { - LentHistory userLentHistory = - lentQueryService.findUserActiveLentHistoryWithCabinet(user.getUserId()); + LentHistory userLentHistory = lentQueryService.findUserActiveLentHistoryWithCabinet( + user.getUserId()); Long cabinetId; Cabinet userActiveCabinet; List lentDtoList; @@ -104,21 +101,21 @@ public MyCabinetResponseDto getMyLentInfo(UserSessionDto user) { List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); List userList = userQueryService.getUsers(usersInCabinet); userActiveCabinet = cabinetQueryService.getCabinet(cabinetId); - lentDtoList = userList.stream() - .map(u -> lentMapper.toLentDto(u, null)).collect(Collectors.toList()); + lentDtoList = userList.stream().map(u -> lentMapper.toLentDto(u, null)) + .collect(Collectors.toList()); } else { userActiveCabinet = userLentHistory.getCabinet(); cabinetId = userActiveCabinet.getId(); - List lentHistories = - lentQueryService.findCabinetActiveLentHistories(cabinetId); - lentDtoList = lentHistories.stream() - .map(lh -> lentMapper.toLentDto(lh.getUser(), lh)).collect(Collectors.toList()); + List lentHistories = lentQueryService.findCabinetActiveLentHistories( + cabinetId); + lentDtoList = lentHistories.stream().map(lh -> lentMapper.toLentDto(lh.getUser(), lh)) + .collect(Collectors.toList()); } String shareCode = lentRedisService.getShareCode(cabinetId); LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); String previousUserName = lentRedisService.getPreviousUserName(cabinetId); - return cabinetMapper.toMyCabinetResponseDto(userActiveCabinet, lentDtoList, - shareCode, sessionExpiredAt, previousUserName); + return cabinetMapper.toMyCabinetResponseDto(userActiveCabinet, lentDtoList, shareCode, + sessionExpiredAt, previousUserName); } /** @@ -129,14 +126,10 @@ public MyCabinetResponseDto getMyLentInfo(UserSessionDto user) { @Transactional(readOnly = true) public List getAllActiveLentHistories() { LocalDateTime now = LocalDateTime.now(); - List lentHistories = - lentQueryService.findAllActiveLentHistoriesWithCabinetAndUser(); + List lentHistories = lentQueryService.findAllActiveLentHistoriesWithCabinetAndUser(); return lentHistories.stream() - .map(lh -> lentMapper.toActiveLentHistoryDto(lh, - lh.getUser(), - lh.getCabinet(), - lh.isExpired(now), - lh.getDaysUntilExpiration(now))) + .map(lh -> lentMapper.toActiveLentHistoryDto(lh, lh.getUser(), lh.getCabinet(), + lh.isExpired(now), lh.getDaysUntilExpiration(now))) .collect(Collectors.toList()); } @@ -161,11 +154,12 @@ public void startLentCabinet(Long userId, Long cabinetId) { eventPublisher.publishEvent(UserBlackHoleEvent.of(user)); } - lentPolicyService.verifyCabinetLentCount( - cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetLentCount(cabinet.getLentType(), cabinet.getMaxUser(), + userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.PRIVATE); - lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), - user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + lentPolicyService.verifyUserForLent( + new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, + cabinetId, cabinet.getStatus(), banHistories)); lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, LentType.PRIVATE, @@ -173,8 +167,9 @@ public void startLentCabinet(Long userId, Long cabinetId) { lentCommandService.startLent(user.getId(), cabinet.getId(), now, expiredAt); cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); cabinetCommandService.changeUserCount(cabinet, lentCount + 1); - eventPublisher.publishEvent(AlarmEvent.of(userId, new LentSuccessAlarm( - cabinet.getCabinetPlace().getLocation(), cabinet.getVisibleNum(), expiredAt))); + eventPublisher.publishEvent(AlarmEvent.of(userId, + new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), + cabinet.getVisibleNum(), expiredAt))); } /** @@ -189,15 +184,20 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) LocalDateTime now = LocalDateTime.now(); Cabinet cabinet = cabinetQueryService.getCabinetForUpdate(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); - lentPolicyService.verifyCabinetLentCount( - cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetLentCount(cabinet.getLentType(), cabinet.getMaxUser(), + userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.SHARE); List banHistories = banHistoryQueryService.findActiveBanHistories(userId, now); int lentCount = lentQueryService.countUserActiveLent(userId); User user = userQueryService.getUser(userId); - lentPolicyService.verifyUserForLent(new UserVerifyRequestDto(user.getRole(), - user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); + if (user.isBlackholed()) { + eventPublisher.publishEvent(UserBlackHoleEvent.of(user)); + } + + lentPolicyService.verifyUserForLent( + new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, + cabinetId, cabinet.getStatus(), banHistories)); Long attemptCount = lentRedisService.getAttemptCountOnShareCabinet(cabinetId, userId); lentPolicyService.verifyAttemptCountOnShareCabinet(attemptCount); @@ -211,16 +211,15 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) lentRedisService.attemptJoinCabinet(cabinetId, userId, shareCode); if (lentRedisService.isCabinetSessionFull(cabinetId)) { List userIdsInCabinet = lentRedisService.getUsersInCabinet(cabinetId); - LocalDateTime expiredAt = - lentPolicyService.generateExpirationDate(now, LentType.SHARE, - userIdsInCabinet.size()); + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, LentType.SHARE, + userIdsInCabinet.size()); lentCommandService.startLent(userIdsInCabinet, cabinet.getId(), now, expiredAt); lentRedisService.clearCabinetSession(cabinetId); cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); cabinetCommandService.changeUserCount(cabinet, userIdsInCabinet.size()); - LentSuccessAlarm alarm = new LentSuccessAlarm( - cabinet.getCabinetPlace().getLocation(), cabinet.getVisibleNum(), expiredAt); + LentSuccessAlarm alarm = new LentSuccessAlarm(cabinet.getCabinetPlace().getLocation(), + cabinet.getVisibleNum(), expiredAt); eventPublisher.publishEvent(AlarmEvent.of(userId, alarm)); } } @@ -238,12 +237,12 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { Cabinet cabinet = cabinetQueryService.getCabinet(cabinetId); int userCount = lentQueryService.countCabinetUser(cabinetId); - lentPolicyService.verifyCabinetLentCount( - cabinet.getLentType(), cabinet.getMaxUser(), userCount); + lentPolicyService.verifyCabinetLentCount(cabinet.getLentType(), cabinet.getMaxUser(), + userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.CLUB); lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); - LocalDateTime expiredAt = - lentPolicyService.generateExpirationDate(now, cabinet.getLentType(), 1); + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, + cabinet.getLentType(), 1); lentCommandService.startLent(userId, cabinetId, now, expiredAt); cabinetCommandService.changeUserCount(cabinet, userCount + 1); } @@ -259,22 +258,21 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { @Transactional public void endUserLent(Long userId, String memo) { LocalDateTime now = LocalDateTime.now(); - List lentHistories = - lentQueryService.findUserActiveLentHistoriesInCabinetForUpdate(userId); + List lentHistories = lentQueryService.findUserActiveLentHistoriesInCabinetForUpdate( + userId); if (lentHistories.isEmpty()) { throw ExceptionStatus.NOT_FOUND_LENT_HISTORY.asServiceException(); } LentHistory userLentHistory = lentHistories.stream() .filter(lh -> lh.getUserId().equals(userId)).findFirst() .orElseThrow(ExceptionStatus.NOT_FOUND_LENT_HISTORY::asServiceException); - Cabinet cabinet = - cabinetQueryService.getCabinetForUpdate(lentHistories.get(0).getCabinetId()); + Cabinet cabinet = cabinetQueryService.getCabinetForUpdate( + lentHistories.get(0).getCabinetId()); int userRemainCount = lentHistories.size() - 1; cabinetCommandService.changeUserCount(cabinet, userRemainCount); lentCommandService.endLent(userLentHistory, now); - lentRedisService.setPreviousUserName( - cabinet.getId(), userLentHistory.getUser().getName()); + lentRedisService.setPreviousUserName(cabinet.getId(), userLentHistory.getUser().getName()); cabinetCommandService.updateTitle(cabinet, ""); cabinetCommandService.updateMemo(cabinet, (memo == null) ? "" : memo); @@ -288,8 +286,8 @@ public void endUserLent(Long userId, String memo) { LocalDateTime newExpiredAt = lentPolicyService.adjustShareCabinetExpirationDate( userRemainCount, now, expiredAt); List lentHistoryIds = lentHistories.stream() - .filter(lh -> !lh.equals(userLentHistory)) - .map(LentHistory::getId).collect(Collectors.toList()); + .filter(lh -> !lh.equals(userLentHistory)).map(LentHistory::getId) + .collect(Collectors.toList()); lentCommandService.setExpiredAt(lentHistoryIds, newExpiredAt); } } @@ -336,8 +334,8 @@ public void shareCabinetSessionExpired(Long cabinetId) { List usersInCabinetSession = lentRedisService.getUsersInCabinet(cabinetId); if (lentPolicyService.checkUserCountOnShareCabinet(usersInCabinetSession.size())) { LocalDateTime now = LocalDateTime.now(); - LocalDateTime expiredAt = lentPolicyService.generateExpirationDate( - now, LentType.SHARE, usersInCabinetSession.size()); + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, LentType.SHARE, + usersInCabinetSession.size()); cabinetCommandService.changeStatus(cabinet, CabinetStatus.FULL); lentCommandService.startLent(usersInCabinetSession, cabinetId, now, expiredAt); } else { @@ -383,8 +381,8 @@ public void swapPrivateCabinet(Long userId, Long newCabinetId) { lentCommandService.endLent(oldLentHistory, now); cabinetCommandService.changeStatus(oldCabinet, CabinetStatus.PENDING); - lentRedisService.setPreviousUserName( - oldCabinet.getId(), oldLentHistory.getUser().getName()); + lentRedisService.setPreviousUserName(oldCabinet.getId(), + oldLentHistory.getUser().getName()); cabinetCommandService.changeUserCount(oldCabinet, 0); cabinetCommandService.updateTitle(oldCabinet, ""); cabinetCommandService.updateMemo(oldCabinet, ""); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java index cfbd702f2..aee4045f3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java @@ -1,31 +1,15 @@ package org.ftclub.cabinet.user.repository; import java.util.List; - import org.ftclub.cabinet.user.domain.LentExtension; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface LentExtensionRepository extends JpaRepository { - @Query("SELECT le " + - "FROM LentExtension le") - Page findAll(Pageable pageable); - - @Query("SELECT le " + - "FROM LentExtension le " + - "WHERE le.expiredAt > CURRENT_TIMESTAMP") - Page findAllNotExpired(Pageable pageable); - - @Query("SELECT le " + - "FROM LentExtension le " + - "WHERE le.userId =:userId ") - List findAll(@Param("userId") Long userId); + List findAllByUserId(@Param("userId") Long userId); - List findAllByUserIdAndUsedAtIsNull(@Param("userId") Long userId); + List findAllByUserIdAndUsedAtIsNull(@Param("userId") Long userId); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java index 4f1ecf395..347236414 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java @@ -28,8 +28,9 @@ public class LentExtensionQueryService { */ public LentExtension findActiveLentExtension(Long userId) { + List lentExtensions = lentExtensionRepository.findAllByUserId(userId); return LentExtensions.builder() - .lentExtensions(lentExtensionRepository.findAll(userId)) + .lentExtensions(lentExtensions) .build() .findImminentActiveLentExtension() .orElse(null); @@ -54,7 +55,7 @@ public LentExtensions findActiveLentExtensions(Long userId) { * @return 사용 기한을 기준으로 최신 순서로 정렬된 모든 연장권을 반환합니다. */ public List findLentExtensionsInLatestOrder(Long userId) { - return lentExtensionRepository.findAll(userId) + return lentExtensionRepository.findAllByUserId(userId) .stream() .sorted(Comparator.comparing(LentExtension::getExpiredAt, Comparator.reverseOrder())) diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 9348d5ac0..3dea61d63 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -4,7 +4,6 @@ import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; -import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.alarm.fcm.config.FirebaseConfig; import org.ftclub.cabinet.alarm.fcm.service.FCMTokenRedisService; @@ -28,6 +27,7 @@ import org.ftclub.cabinet.user.domain.LentExtensions; import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -52,6 +52,7 @@ public class UserFacadeService { * @param user 유저의 세션 정보 * @return 유저의 프로필 정보를 반환합니다. */ + @Transactional(readOnly = true) public MyProfileResponseDto getProfile(UserSessionDto user) { Cabinet cabinet = cabinetQueryService.findUserActiveCabinet(user.getUserId()); BanHistory banHistory = banHistoryQueryService.findRecentActiveBanHistory(user.getUserId(), @@ -73,6 +74,7 @@ public MyProfileResponseDto getProfile(UserSessionDto user) { * @param user 유저의 세션 정보 * @return 유저의 모든 연장권 정보를 반환합니다. */ + @Transactional(readOnly = true) public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { List lentExtensionResponseDtos = lentExtensionQueryService.findLentExtensionsInLatestOrder( user.getUserId()) @@ -89,6 +91,7 @@ public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { * @param user 유저의 세션 정보 * @return 유저의 사용 가능한 연장권 정보를 반환합니다. */ + @Transactional(readOnly = true) public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto user) { LentExtensions lentExtensions = lentExtensionQueryService.findActiveLentExtensions( user.getUserId()); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java index 3fbe9eff8..8f78f8551 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java @@ -3,7 +3,6 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; -import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.log.LogLevel; @@ -18,7 +17,6 @@ @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) -@Transactional public class UserQueryService { private final UserRepository userRepository; From c2985a443255c32b3203b83b3ee378a52f5d7bde Mon Sep 17 00:00:00 2001 From: Siwon Choi <83565255+sichoi42@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:03:11 +0900 Subject: [PATCH 0384/1029] =?UTF-8?q?[COMMON]=20FEAT:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=9B=84=20=EC=B2=98=EB=A6=AC=20=EB=8F=99=EC=9E=91?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EA=B0=9C=EC=84=A0=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?(#1527)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FE] FEAT: PostLogin 페이지 추가 (임시) * [COMMON] FIX: 로그인 후 콜백 주소를 프론트에서 결정하도록 변경 * [FE] FEAT: login auth_page 디자인 변경 --------- Co-authored-by: moonseonghui --- .../auth/controller/AuthController.java | 4 +- .../auth/service/AuthFacadeService.java | 33 +++++++--- frontend/src/App.tsx | 2 + frontend/src/assets/images/sadCcabi.svg | 9 +++ .../AnnounceTemplate.tsx} | 40 ++++++++---- frontend/src/pages/LoginFailurePage.tsx | 5 +- frontend/src/pages/LoginPage.tsx | 3 +- frontend/src/pages/NotFoundPage.tsx | 5 +- frontend/src/pages/PostLogin.tsx | 63 +++++++++++++++++++ .../src/pages/admin/AdminLoginFailurePage.tsx | 5 +- frontend/src/types/enum/AnnounceType.enum.ts | 6 ++ frontend/stats.json | 8 +-- maintenance/index.html | 4 +- 13 files changed, 153 insertions(+), 34 deletions(-) create mode 100644 frontend/src/assets/images/sadCcabi.svg rename frontend/src/components/{Error/ErrorTemplate.tsx => Announce/AnnounceTemplate.tsx} (64%) create mode 100644 frontend/src/pages/PostLogin.tsx create mode 100644 frontend/src/types/enum/AnnounceType.enum.ts diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java index ce61210ab..5f3d28559 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java @@ -28,8 +28,8 @@ public class AuthController { * @throws IOException 입출력 예외 */ @GetMapping("/login") - public void login(HttpServletResponse response) throws IOException { - authFacadeService.requestUserLogin(response); + public void login(HttpServletRequest request, HttpServletResponse response) throws IOException { + authFacadeService.requestUserLogin(request, response); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index 13ff68277..f65483994 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -38,21 +38,30 @@ public class AuthFacadeService { private final TokenProvider tokenProvider; private final CookieManager cookieManager; + private static final String REDIRECT_COOKIE_NAME = "redirect"; /** * 유저 로그인 페이지로 리다이렉트합니다. * - * @param res 요청 시의 서블렛 {@link HttpServletResponse} + * @param req 요청 시의 서블렛 {@link HttpServletRequest} + * @param res 응답 시의 서블렛 {@link HttpServletResponse} * @throws IOException 입출력 예외 */ - public void requestUserLogin(HttpServletResponse res) throws IOException { + public void requestUserLogin(HttpServletRequest req, HttpServletResponse res) + throws IOException { + String redirect = req.getParameter(REDIRECT_COOKIE_NAME); + if (redirect != null) { + cookieManager.setCookieToClient( + res, cookieManager.cookieOf(REDIRECT_COOKIE_NAME, redirect), + "/", req.getServerName()); + } userOauthService.requestLogin(res); } /** * 관리자 로그인 페이지로 리다이렉트합니다. * - * @param res 요청 시의 서블렛 {@link HttpServletResponse} + * @param res 응답 시의 서블렛 {@link HttpServletResponse} * @throws IOException 입출력 예외 */ public void requestAdminLogin(HttpServletResponse res) throws IOException { @@ -69,13 +78,20 @@ public void requestAdminLogin(HttpServletResponse res) throws IOException { * @throws ExecutionException 비동기 처리시 스레드에서 발생한 오류 처리 예외 * @throws InterruptedException 비동기 처리시 스레드 종료를 위한 예외 */ - public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { + public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, String code) + throws IOException, ExecutionException, InterruptedException { FtProfile profile = userOauthService.getProfileByCode(code); User user = userQueryService.findUser(profile.getIntraName()) .orElseGet(() -> userCommandService.createUserByFtProfile(profile)); String token = tokenProvider.createUserToken(user, LocalDateTime.now()); Cookie cookie = cookieManager.cookieOf(TokenProvider.USER_TOKEN_NAME, token); cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + if (cookieManager.getCookieValue(req, REDIRECT_COOKIE_NAME) != null) { + String redirect = cookieManager.getCookieValue(req, REDIRECT_COOKIE_NAME); + cookieManager.deleteCookie(res, REDIRECT_COOKIE_NAME); + res.sendRedirect(redirect); + return; + } res.sendRedirect(authPolicyService.getMainHomeUrl()); } @@ -89,7 +105,8 @@ public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, Str * @throws ExecutionException 비동기 처리시 스레드에서 발생한 오류 처리 예외 * @throws InterruptedException 비동기 처리시 스레드 종료를 위한 예외 */ - public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { + public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, String code) + throws IOException, ExecutionException, InterruptedException { GoogleProfile profile = adminOauthService.getProfileByCode(code); Admin admin = adminQueryService.findByEmail(profile.getEmail()) .orElseGet(() -> adminCommandService.createAdminByEmail(profile.getEmail())); @@ -110,10 +127,12 @@ public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, St * @param now 현재 시각 */ public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now) { + HttpServletResponse res, LocalDateTime now) { // TODO : 서비스로 빼기 - if (!authPolicyService.isMatchWithMasterAuthInfo(masterLoginDto.getId(), masterLoginDto.getPassword())) + if (!authPolicyService.isMatchWithMasterAuthInfo(masterLoginDto.getId(), + masterLoginDto.getPassword())) { throw ExceptionStatus.UNAUTHORIZED_ADMIN.asServiceException(); + } Admin master = adminQueryService.findByEmail(authPolicyService.getMasterEmail()) .orElseThrow(ExceptionStatus.UNAUTHORIZED_ADMIN::asServiceException); String masterToken = tokenProvider.createAdminToken(master, now); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 42b3426ac..b812f2015 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,6 +8,7 @@ import MainPage from "@/pages/MainPage"; import PendingPage from "@/pages/PendingPage/PendingPage"; import AdminMainPage from "@/pages/admin/AdminMainPage"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; +import PostLogin from "./pages/PostLogin"; import ProfilePage from "./pages/ProfilePage"; const NotFoundPage = lazy(() => import("@/pages/NotFoundPage")); @@ -26,6 +27,7 @@ function App(): React.ReactElement { }> + } /> }> } /> } /> diff --git a/frontend/src/assets/images/sadCcabi.svg b/frontend/src/assets/images/sadCcabi.svg new file mode 100644 index 000000000..1dd12d5b3 --- /dev/null +++ b/frontend/src/assets/images/sadCcabi.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/components/Error/ErrorTemplate.tsx b/frontend/src/components/Announce/AnnounceTemplate.tsx similarity index 64% rename from frontend/src/components/Error/ErrorTemplate.tsx rename to frontend/src/components/Announce/AnnounceTemplate.tsx index 5d1777662..f4c831efc 100644 --- a/frontend/src/components/Error/ErrorTemplate.tsx +++ b/frontend/src/components/Announce/AnnounceTemplate.tsx @@ -1,50 +1,66 @@ +import { useEffect, useState } from "react"; import styled, { keyframes } from "styled-components"; interface Itext { - id?: string; title: string; subTitle: string; content: string; subContent?: string; - buttonText: string; - buttonHandler: React.MouseEventHandler; + buttonText?: string; + buttonHandler?: React.MouseEventHandler; + type: string; } -const ErrorTemplate = (props: Itext) => { +const AnnounceTemplate = (props: Itext) => { const { - id, title, subTitle, content, subContent, buttonText, buttonHandler, + type, } = props; + const [backgroundColor, setBackgroundColor] = useState(""); + + useEffect(() => { + if (type === "LOADING") { + setBackgroundColor("var(--sub-color)"); + } else if (type === "ERROR") { + setBackgroundColor("var(--main-color)"); + } + }, []); return ( - + {title} - sad_cabi + {type === "ERROR" ? ( + + ) : ( + + )} {subTitle} {content} {!!subContent && {subContent}} - {buttonText} - + {buttonHandler && ( + {buttonText} + )} + ); }; -const ErrorTemplateStyled = styled.div` +const AnnounceTemplateStyled = styled.div<{ backgroundColor: string }>` width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; flex-direction: column; - background-color: var(--main-color); + background-color: ${(props) => props.backgroundColor}; color: var(--white); `; @@ -109,4 +125,4 @@ const ButtonStyled = styled.button` box-shadow: 10px 10px 40px 0px rgba(0, 0, 0, 0.25); `; -export default ErrorTemplate; +export default AnnounceTemplate; diff --git a/frontend/src/pages/LoginFailurePage.tsx b/frontend/src/pages/LoginFailurePage.tsx index 84f4d5f1b..53872545e 100644 --- a/frontend/src/pages/LoginFailurePage.tsx +++ b/frontend/src/pages/LoginFailurePage.tsx @@ -1,17 +1,18 @@ import { useNavigate } from "react-router-dom"; -import ErrorTemplate from "@/components/Error/ErrorTemplate"; +import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); return ( - navigate("/login")} + type="ERORR" /> ); }; diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 6fb84e65c..38479f786 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -2,11 +2,12 @@ import LoginTemplate from "@/components/Login/LoginTemplate"; import "@/assets/css/loginPage.css"; const LoginPage = () => { + const ORIGIN_URL = window.location.origin; const url = `${import.meta.env.VITE_BE_HOST}/v4/auth/login`; return ( { const navigate = useNavigate(); return ( - navigate("/home")} + type="ERROR" /> ); }; diff --git a/frontend/src/pages/PostLogin.tsx b/frontend/src/pages/PostLogin.tsx new file mode 100644 index 000000000..d6e4f1bee --- /dev/null +++ b/frontend/src/pages/PostLogin.tsx @@ -0,0 +1,63 @@ +import { + deleteFcmToken, + requestFcmAndGetDeviceToken, +} from "@/firebase/firebase-messaging-sw"; +import { useEffect, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import { useRecoilState, useSetRecoilState } from "recoil"; +import { userState } from "@/recoil/atoms"; +import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; +import { UserDto } from "@/types/dto/user.dto"; +import { axiosMyInfo, axiosUpdateDeviceToken } from "@/api/axios/axios.custom"; +import { getCookie } from "@/api/react_cookie/cookies"; + +const PostLogin = (): JSX.Element => { + const [isLoading, setIsLoading] = useState(true); + const [isValidToken, setIsValidToken] = useState(false); + const [myInfo, setMyInfo] = useRecoilState(userState); + + const setUser = useSetRecoilState(userState); + const navigate = useNavigate(); + const token = getCookie("access_token"); + + const getMyInfo = async () => { + try { + const { data: myInfo } = await axiosMyInfo(); + setUser(myInfo); + setIsValidToken(true); + console.log(myInfo); + if (myInfo.alarmTypes?.push && myInfo.isDeviceTokenExpired) { + await deleteFcmToken(); + const deviceToken = await requestFcmAndGetDeviceToken(); + await axiosUpdateDeviceToken(deviceToken); + } + setMyInfo(myInfo); + } catch (error) { + navigate("/login"); + } + }; + + useEffect(() => { + if (!token) navigate("/login"); + else if (token) { + getMyInfo(); + let time = setTimeout(() => { + navigate("/home"); + }, 600); + return () => { + clearTimeout(time); + }; + } + }, []); + + return ( + + ); +}; + +export default PostLogin; diff --git a/frontend/src/pages/admin/AdminLoginFailurePage.tsx b/frontend/src/pages/admin/AdminLoginFailurePage.tsx index f6db5bc85..2e20c6cb8 100644 --- a/frontend/src/pages/admin/AdminLoginFailurePage.tsx +++ b/frontend/src/pages/admin/AdminLoginFailurePage.tsx @@ -1,17 +1,18 @@ import { useNavigate } from "react-router-dom"; -import ErrorTemplate from "@/components/Error/ErrorTemplate"; +import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); return ( - navigate("/admin/login")} + type="ERORR" /> ); }; diff --git a/frontend/src/types/enum/AnnounceType.enum.ts b/frontend/src/types/enum/AnnounceType.enum.ts new file mode 100644 index 000000000..4fca97199 --- /dev/null +++ b/frontend/src/types/enum/AnnounceType.enum.ts @@ -0,0 +1,6 @@ +enum AnnounceType { + ERROR = "ERROR", + LOADING = "LOADING", +} + +export default AnnounceType; diff --git a/frontend/stats.json b/frontend/stats.json index 7936b535d..57a04d367 100644 --- a/frontend/stats.json +++ b/frontend/stats.json @@ -22,10 +22,10 @@ ] }, { - "name": "assets/ErrorTemplate-d9af0d10.js", + "name": "assets/AnnounceTemplate-d9af0d10.js", "children": [ { - "name": "src/components/Error/ErrorTemplate.tsx", + "name": "src/components/Error/AnnounceTemplate.tsx", "uid": "a9f3-5" } ] @@ -6639,9 +6639,9 @@ ] }, "a9f3-4": { - "id": "/src/components/Error/ErrorTemplate.tsx", + "id": "/src/components/Error/AnnounceTemplate.tsx", "moduleParts": { - "assets/ErrorTemplate-d9af0d10.js": "a9f3-5" + "assets/AnnounceTemplate-d9af0d10.js": "a9f3-5" }, "imported": [ { diff --git a/maintenance/index.html b/maintenance/index.html index f46dfc2f7..e4ddf13d0 100644 --- a/maintenance/index.html +++ b/maintenance/index.html @@ -41,7 +41,7 @@ justify-content: center; align-items: center; } - .errorTemplate { + .AnnounceTemplate { width: 100%; height: 100%; display: flex; @@ -136,7 +136,7 @@

-
+
점검 중
From c9b4f9b06a883ef0f70fce2e138207bbd8f0595d Mon Sep 17 00:00:00 2001 From: Jiwon Park <82518170+Z1Park@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:05:30 +0900 Subject: [PATCH 0385/1029] =?UTF-8?q?[BE]=20FEAT:=20=EB=B0=98=EB=82=A9?= =?UTF-8?q?=EC=9D=BC=207,3,1=20=EC=9D=BC=EC=A0=84=20=EB=AF=B8=EB=A6=AC=20?= =?UTF-8?q?=EC=95=8C=EB=9E=8C=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80#1518=20(#1534)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BE] Redis 이벤트 관련 클래스 위치 이벤트 폴더로 이동 * [BE] 연체 전후 알림 3일, 7일차 추가 및 properties 정리 * [BE] 프로퍼티 이름 수정 * [BE] 연체 시 매일 알람 보내도록 수정 --------- Co-authored-by: jiwon --- .../cabinet/alarm/config/AlarmProperties.java | 16 +- .../RedisExpirationEventListener.java} | 6 +- .../utils/overdue/manager/OverdueManager.java | 91 ++++++----- .../utils/overdue/manager/OverdueType.java | 1 + .../utils/scheduler/SystemScheduler.java | 152 +++++++++--------- 5 files changed, 142 insertions(+), 124 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/{redis/ExpirationListener.java => event/RedisExpirationEventListener.java} (90%) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java index ede44b922..453380cd6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -38,9 +38,6 @@ public class AlarmProperties { private String overdueSlackTemplate; /*===================== soonOverdue =========================*/ - @Value("${cabinet.alarm.mail.soonOverdue.term}") - private Long soonOverdueTerm; - @Value("${cabinet.alarm.mail.soonOverdue.subject}") private String soonOverdueSubject; @@ -94,4 +91,17 @@ public class AlarmProperties { @Value("${cabinet.alarm.slack.announcement.template}") private String announcementSlackTemplate; + + /*======================== term =============================*/ + @Value("${cabinet.alarm.overdue-term.week-before}") + private Long overdueTermWeekBefore; + + @Value("${cabinet.alarm.overdue-term.three-days-before}") + private Long overdueTermThreeDaysBefore; + + @Value("${cabinet.alarm.overdue-term.soon-overdue}") + private Long overdueTermSoonOverdue; + + @Value("${cabinet.alarm.overdue-term.overdue}") + private Long overdueTermOverdue; } diff --git a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java b/backend/src/main/java/org/ftclub/cabinet/event/RedisExpirationEventListener.java similarity index 90% rename from backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java rename to backend/src/main/java/org/ftclub/cabinet/event/RedisExpirationEventListener.java index 9931c2988..6feaa58cd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java +++ b/backend/src/main/java/org/ftclub/cabinet/event/RedisExpirationEventListener.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.redis; +package org.ftclub.cabinet.event; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.lent.service.LentFacadeService; @@ -12,7 +12,7 @@ @Component @Log4j2 -public class ExpirationListener extends KeyExpirationEventMessageListener { +public class RedisExpirationEventListener extends KeyExpirationEventMessageListener { private final LentFacadeService lentFacadeService; @@ -22,7 +22,7 @@ public class ExpirationListener extends KeyExpirationEventMessageListener { * @param listenerContainer must not be {@literal null}. * @param lentFacadeService must not be {@literal null}. */ - public ExpirationListener( + public RedisExpirationEventListener( @Qualifier("redisMessageListenerContainer") RedisMessageListenerContainer listenerContainer, LentFacadeService lentFacadeService) { diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java index aa3737dd0..b314125a7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java @@ -23,48 +23,57 @@ */ public class OverdueManager { - private final CabinetFacadeService cabinetFacadeService; - private final AlarmProperties alarmProperties; - private final ApplicationEventPublisher eventPublisher; + private final CabinetFacadeService cabinetFacadeService; + private final AlarmProperties alarmProperties; + private final ApplicationEventPublisher eventPublisher; - /** - * 연체 타입을 반환하는 메소드 연체 예정인 경우, SOON_OVERDUE를 반환하고, 연체 기간이 지난 경우, OVERDUE를 반환한다. 그 외의 경우, NONE을 - * 반환한다. - * - * @param isExpired 연체 기간이 지났는지 여부 (true: 연체 기간이 지남, false: 연체 기간이 지나지 않음) - * @param daysLeftFromExpireDate 만료일까지 남은 일수 - * @return 연체 타입 - */ - public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate) { - log.info("called getOverdueType with {}, {}", isExpired, daysLeftFromExpireDate); - if (isExpired) { - return OverdueType.OVERDUE; - } - if (alarmProperties.getSoonOverdueTerm().equals(daysLeftFromExpireDate)) { - return OverdueType.SOON_OVERDUE; - } - return OverdueType.NONE; - } + /** + * 연체 타입을 반환하는 메소드 연체 예정인 경우, SOON_OVERDUE를 반환하고, 연체 기간이 지난 경우, OVERDUE를 반환한다. 그 외의 경우, NONE을 + * 반환한다. + * + * @param isExpired 연체 기간이 지났는지 여부 (true: 연체 기간이 지남, false: 연체 기간이 지나지 않음) + * @param daysLeftFromExpireDate 만료일까지 남은 일수 + * @return 연체 타입 + */ + public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate) { + log.info("called getOverdueType with {}, {}", isExpired, daysLeftFromExpireDate); + if (isExpired) { + return OverdueType.OVERDUE; + } - public void handleOverdue(ActiveLentHistoryDto activeLent) { - log.info("called handleOverdue with {}", activeLent); - OverdueType overdueType = getOverdueType(activeLent.getIsExpired(), - activeLent.getDaysLeftFromExpireDate()); - log.info("overdueType = {}", overdueType); - switch (overdueType) { - case NONE: - return; - case SOON_OVERDUE: - eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), - new LentExpirationImminentAlarm(activeLent.getDaysLeftFromExpireDate()))); - break; - case OVERDUE: - cabinetFacadeService.updateStatus(activeLent.getCabinetId(), - CabinetStatus.OVERDUE); - eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), - new LentExpirationAlarm(activeLent.getDaysLeftFromExpireDate()))); - break; - } - } + if (daysLeftFromExpireDate.equals(alarmProperties.getOverdueTermWeekBefore())) { + return OverdueType.SOON_OVERDUE; + } + if (daysLeftFromExpireDate.equals(alarmProperties.getOverdueTermThreeDaysBefore())) { + return OverdueType.SOON_OVERDUE; + } + if (daysLeftFromExpireDate.equals(alarmProperties.getOverdueTermSoonOverdue())) { + return OverdueType.SOON_OVERDUE; + } + if (daysLeftFromExpireDate >= alarmProperties.getOverdueTermOverdue()) { + return OverdueType.OVERDUE; + } + return OverdueType.NONE; + } + + public void handleOverdue(ActiveLentHistoryDto activeLent) { + OverdueType overdueType = getOverdueType(activeLent.getIsExpired(), + activeLent.getDaysLeftFromExpireDate()); + log.info("called handleOverdue: activeLent={}, overdueType={}", activeLent, overdueType); + switch (overdueType) { + case NONE: + return; + case SOON_OVERDUE: + eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), + new LentExpirationImminentAlarm(activeLent.getDaysLeftFromExpireDate()))); + break; + case OVERDUE: + cabinetFacadeService.updateStatus(activeLent.getCabinetId(), + CabinetStatus.OVERDUE); + eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), + new LentExpirationAlarm(activeLent.getDaysLeftFromExpireDate()))); + break; + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueType.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueType.java index 528820832..8f75750c7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueType.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueType.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.utils.overdue.manager; public enum OverdueType { + SOON_OVERDUE, OVERDUE, NONE diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 18dcd2473..c512f9335 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -1,25 +1,22 @@ package org.ftclub.cabinet.utils.scheduler; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.UserBlackHoleEvent; import org.ftclub.cabinet.lent.service.LentFacadeService; -import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; import org.ftclub.cabinet.user.service.LentExtensionManager; import org.ftclub.cabinet.user.service.UserQueryService; import org.ftclub.cabinet.utils.blackhole.manager.BlackholeManager; -import org.ftclub.cabinet.utils.leave.absence.LeaveAbsenceManager; import org.ftclub.cabinet.utils.overdue.manager.OverdueManager; import org.ftclub.cabinet.utils.release.ReleaseManager; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - /** * 시스템 스케줄러 */ @@ -29,25 +26,23 @@ @Log4j2 public class SystemScheduler { - private static final long DELAY_TIME = 2000; - private final UserQueryService userQueryService; - private final LeaveAbsenceManager leaveAbsenceManager; - private final LentExtensionManager lentExtensionManager; - private final OverdueManager overdueManager; - private final LentFacadeService lentFacadeService; - private final BlackholeManager blackholeManager; - private final ReleaseManager releaseManager; - private final OccupiedTimeManager occupiedTimeManager; + private static final long DELAY_TIME = 2000; + private final UserQueryService userQueryService; + private final LentExtensionManager lentExtensionManager; + private final OverdueManager overdueManager; + private final LentFacadeService lentFacadeService; + private final BlackholeManager blackholeManager; + private final ReleaseManager releaseManager; - /** - * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") - public void checkAllLents() { - log.info("called checkAllLents"); - List activeLents = lentFacadeService.getAllActiveLentHistories(); - for (ActiveLentHistoryDto activeLent : activeLents) { - overdueManager.handleOverdue(activeLent); + /** + * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거 + */ + @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") + public void checkAllLents() { + log.info("called checkAllLents"); + List activeLents = lentFacadeService.getAllActiveLentHistories(); + for (ActiveLentHistoryDto activeLent : activeLents) { + overdueManager.handleOverdue(activeLent); /* leaveAbsenceManager.handleLeaveAbsence(activeLent.getUserId(), activeLent.getName()); try { @@ -56,66 +51,69 @@ public void checkAllLents() { log.error(e.getMessage()); } */ - } - } + } + } - /** - * 매주 월요일 자정 42분에 블랙홀에 빠진 유저 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") - public void checkRiskOfBlackhole() { - log.info("called checkRiskOfBlackhole"); + /** + * 매주 월요일 자정 42분에 블랙홀에 빠진 유저 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") + public void checkRiskOfBlackhole() { + log.info("called checkRiskOfBlackhole"); - List closeWithBlackholeUsers = userQueryService.findUsersAtRiskOfBlackhole().stream() - .map(user -> UserBlackHoleEvent.of(user.getId(), user.getName(), user.getEmail(), user.getBlackholedAt())) - .collect(Collectors.toList()); - for (UserBlackHoleEvent blackholeInfo : closeWithBlackholeUsers) { - blackholeManager.handleBlackHole(blackholeInfo); - try { - Thread.sleep(DELAY_TIME); - } catch (InterruptedException e) { - log.error(e.getMessage()); - } - } - } + List closeWithBlackholeUsers = userQueryService.findUsersAtRiskOfBlackhole() + .stream() + .map(user -> UserBlackHoleEvent.of(user.getId(), user.getName(), user.getEmail(), + user.getBlackholedAt())) + .collect(Collectors.toList()); + for (UserBlackHoleEvent blackholeInfo : closeWithBlackholeUsers) { + blackholeManager.handleBlackHole(blackholeInfo); + try { + Thread.sleep(DELAY_TIME); + } catch (InterruptedException e) { + log.error(e.getMessage()); + } + } + } - /** - * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.no-risk-of-blackhole}") - public void checkNoRiskOfBlackhole() { - log.info("called checkNoRiskOfBlackhole"); + /** + * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + @Scheduled(cron = "${cabinet.schedule.cron.no-risk-of-blackhole}") + public void checkNoRiskOfBlackhole() { + log.info("called checkNoRiskOfBlackhole"); - List safeFromBlackholeUsers = userQueryService.findUsersAtNoRiskOfBlackhole() - .stream() - .map(user -> UserBlackHoleEvent.of(user.getId(), user.getName(), user.getEmail(), user.getBlackholedAt())) - .collect(Collectors.toList()); + List safeFromBlackholeUsers = userQueryService.findUsersAtNoRiskOfBlackhole() + .stream() + .map(user -> UserBlackHoleEvent.of(user.getId(), user.getName(), user.getEmail(), + user.getBlackholedAt())) + .collect(Collectors.toList()); - for (UserBlackHoleEvent blackholeUserInfo : safeFromBlackholeUsers) { - blackholeManager.handleBlackHole(blackholeUserInfo); - try { - Thread.sleep(DELAY_TIME); - } catch (InterruptedException e) { - log.error(e.getMessage()); - } - } - } + for (UserBlackHoleEvent blackholeUserInfo : safeFromBlackholeUsers) { + blackholeManager.handleBlackHole(blackholeUserInfo); + try { + Thread.sleep(DELAY_TIME); + } catch (InterruptedException e) { + log.error(e.getMessage()); + } + } + } - /** - * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - //현재 5분마다 도는 로직 0 */5 * * * * - @Scheduled(cron = "${cabinet.schedule.cron.cabinet-release-time}") - public void releasePendingCabinet() { - log.info("releasePendingCabinet {}", LocalDateTime.now()); - releaseManager.releasingCabinets(); - } + /** + * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + //현재 5분마다 도는 로직 0 */5 * * * * + @Scheduled(cron = "${cabinet.schedule.cron.cabinet-release-time}") + public void releasePendingCabinet() { + log.info("releasePendingCabinet {}", LocalDateTime.now()); + releaseManager.releasingCabinets(); + } - @Scheduled(cron = "${cabinet.schedule.cron.extension-issue-time}") - public void lentExtensionIssue() { - log.info("called lentExtensionIssue"); - lentExtensionManager.issueLentExtension(); - } + @Scheduled(cron = "${cabinet.schedule.cron.extension-issue-time}") + public void lentExtensionIssue() { + log.info("called lentExtensionIssue"); + lentExtensionManager.issueLentExtension(); + } // @Scheduled(cron = "${cabinet.schedule.cron.extensible-user-check}") // public void checkUserQualifyForExtensible(){ From 3c7d2294a5a187a8f46e67c1c3ffc0a9f2005727 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Wed, 31 Jan 2024 12:39:34 +0900 Subject: [PATCH 0386/1029] =?UTF-8?q?[FE]=20FEAT:=20=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=20=EC=8A=A4=ED=86=A0=EC=96=B4=20=EC=B6=9C=EC=8B=9C?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20assetlinks.json=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/public/.well-known/assetlinks.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 frontend/public/.well-known/assetlinks.json diff --git a/frontend/public/.well-known/assetlinks.json b/frontend/public/.well-known/assetlinks.json new file mode 100644 index 000000000..def9c05cc --- /dev/null +++ b/frontend/public/.well-known/assetlinks.json @@ -0,0 +1,8 @@ +[{ + "relation": ["delegate_permission/common.handle_all_urls"], + "target": { + "namespace": "android_app", + "package_name": "io.app_42seoul.cabi.dev.twa", + "sha256_cert_fingerprints": ["89:7E:47:82:AD:49:16:1E:BE:79:53:5D:D9:C4:D9:25:1C:E4:C5:7F:AC:46:95:B1:84:42:AD:56:D8:6A:E1:4E"] + } + }] From 886af58e1fd6ad415ee9e4c336643fd86adaf74e Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 1 Feb 2024 10:34:50 +0900 Subject: [PATCH 0387/1029] =?UTF-8?q?[HOTFIX]=20pending=20NPE=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/cabinet/service/CabinetFacadeService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index 917203507..a84ae7200 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -168,7 +168,6 @@ private String getCabinetTitle(Cabinet cabinet, List lentHistories) @Transactional public CabinetPendingResponseDto getPendingCabinets(String building) { final LocalDateTime now = LocalDateTime.now(); - final LocalDateTime yesterday = now.minusDays(1).withHour(13).withMinute(0).withSecond(0); List pendingCabinets = cabinetQueryService.findPendingCabinetsNotLentTypeAndStatus( building, LentType.CLUB, List.of(AVAILABLE, PENDING)); @@ -178,7 +177,7 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { Map> lentHistoriesMap; if (now.getHour() < 13) { lentHistoriesMap = lentQueryService.findPendingLentHistoriesOnDate( - yesterday.toLocalDate(), cabinetIds) + now.minusDays(1).toLocalDate(), cabinetIds) .stream().collect(groupingBy(LentHistory::getCabinetId)); } else { lentHistoriesMap = lentQueryService.findCabinetLentHistories(cabinetIds) @@ -193,7 +192,11 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); } if (cabinet.isStatus(PENDING)) { - lentHistoriesMap.get(cabinet.getId()).stream() + List lentHistories = lentHistoriesMap.get(cabinet.getId()); + if (lentHistories == null || lentHistories.isEmpty()) { + return; + } + lentHistories.stream() .map(LentHistory::getEndedAt) .max(LocalDateTime::compareTo) .ifPresent(latestEndedAt -> cabinetFloorMap.get(floor) From 4f8ec51deeed2aa21f7f60b8a8842ec3272008b4 Mon Sep 17 00:00:00 2001 From: Jiwon Park <82518170+Z1Park@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:37:32 +0900 Subject: [PATCH 0388/1029] =?UTF-8?q?[BE]=20HOTFIX:=20pending=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EC=8B=9C=20NPE=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#1545)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FE] FEAT: 사용가능페이지 핕터위치 변경 및 토글 선택범위 확대 * [FE] FEAT: 새로고침 버튼과 타이머 합침 * [FE] FIX: PENDING 상태의 사물함 border, shadow 수정 #1528 * [FE] FIX: 토글 선택범위 cursor 변경 * [FE] FIX: 토글 커서 변환 확대 * [BE] HOTFIX: 이전에 블랙홀이었던 유저가 대여시도시, 새로고침 로직 추가 * [BE] HOTFIX: 연장권 안써짐 이슈 -> @Transactional * merge * [BE] 리팩토링 이후 트랜잭션 적용 안된 부분 적용 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 * [COMMON] FEAT: 로그인 후 처리 동작 관련 개선사항 (#1527) * [FE] FEAT: PostLogin 페이지 추가 (임시) * [COMMON] FIX: 로그인 후 콜백 주소를 프론트에서 결정하도록 변경 * [FE] FEAT: login auth_page 디자인 변경 --------- Co-authored-by: moonseonghui * [BE] FEAT: 반납일 7,3,1 일전 미리 알람주도록 기능 추가#1518 (#1534) * [BE] Redis 이벤트 관련 클래스 위치 이벤트 폴더로 이동 * [BE] 연체 전후 알림 3일, 7일차 추가 및 properties 정리 * [BE] 프로퍼티 이름 수정 * [BE] 연체 시 매일 알람 보내도록 수정 --------- Co-authored-by: jiwon * [FE] FEAT: 플레이 스토어 출시를 위한 assetlinks.json 파일 추가 * [HOTFIX] pending NPE 수정 --------- Co-authored-by: Minkyu01 Co-authored-by: jnkeniaem Co-authored-by: jusohn Co-authored-by: space Co-authored-by: jiwon Co-authored-by: Gyeonga Koh <114395888+gykoh42@users.noreply.github.com> Co-authored-by: Siwon Choi <83565255+sichoi42@users.noreply.github.com> Co-authored-by: moonseonghui Co-authored-by: sichoi42 <42.4.sichoi@gmail.com> --- .../cabinet/alarm/config/AlarmProperties.java | 16 +- .../auth/controller/AuthController.java | 4 +- .../auth/service/AuthFacadeService.java | 33 +++- .../cabinet/service/CabinetFacadeService.java | 9 +- .../RedisExpirationEventListener.java} | 6 +- .../utils/overdue/manager/OverdueManager.java | 91 ++++++----- .../utils/overdue/manager/OverdueType.java | 1 + .../utils/scheduler/SystemScheduler.java | 152 +++++++++--------- frontend/public/.well-known/assetlinks.json | 8 + frontend/src/App.tsx | 2 + frontend/src/assets/images/rotateRight.svg | 5 + frontend/src/assets/images/sadCcabi.svg | 9 ++ .../AnnounceTemplate.tsx} | 40 +++-- .../CabinetInfoArea/AdminCabinetInfoArea.tsx | 7 +- .../CabinetInfoArea/CabinetInfoArea.tsx | 4 +- .../CabinetListItem/AdminCabinetListItem.tsx | 3 +- .../CabinetListItem/CabinetListItem.tsx | 3 +- .../src/components/Home/ManualContentBox.tsx | 14 +- .../CabinetColorTable/CabinetColorTable.tsx | 3 +- .../Modals/ManualModal/ManualModal.tsx | 7 +- frontend/src/pages/LoginFailurePage.tsx | 5 +- frontend/src/pages/LoginPage.tsx | 3 +- frontend/src/pages/NotFoundPage.tsx | 5 +- .../src/pages/PendingPage/PendingPage.tsx | 44 +++-- .../PendingPage/components/FloorContainer.tsx | 9 +- .../components/PendingCountdown.tsx | 17 +- frontend/src/pages/PostLogin.tsx | 63 ++++++++ .../src/pages/admin/AdminLoginFailurePage.tsx | 5 +- frontend/src/types/enum/AnnounceType.enum.ts | 6 + frontend/stats.json | 8 +- maintenance/index.html | 4 +- 31 files changed, 383 insertions(+), 203 deletions(-) rename backend/src/main/java/org/ftclub/cabinet/{redis/ExpirationListener.java => event/RedisExpirationEventListener.java} (90%) create mode 100644 frontend/public/.well-known/assetlinks.json create mode 100644 frontend/src/assets/images/rotateRight.svg create mode 100644 frontend/src/assets/images/sadCcabi.svg rename frontend/src/components/{Error/ErrorTemplate.tsx => Announce/AnnounceTemplate.tsx} (64%) create mode 100644 frontend/src/pages/PostLogin.tsx create mode 100644 frontend/src/types/enum/AnnounceType.enum.ts diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java index ede44b922..453380cd6 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -38,9 +38,6 @@ public class AlarmProperties { private String overdueSlackTemplate; /*===================== soonOverdue =========================*/ - @Value("${cabinet.alarm.mail.soonOverdue.term}") - private Long soonOverdueTerm; - @Value("${cabinet.alarm.mail.soonOverdue.subject}") private String soonOverdueSubject; @@ -94,4 +91,17 @@ public class AlarmProperties { @Value("${cabinet.alarm.slack.announcement.template}") private String announcementSlackTemplate; + + /*======================== term =============================*/ + @Value("${cabinet.alarm.overdue-term.week-before}") + private Long overdueTermWeekBefore; + + @Value("${cabinet.alarm.overdue-term.three-days-before}") + private Long overdueTermThreeDaysBefore; + + @Value("${cabinet.alarm.overdue-term.soon-overdue}") + private Long overdueTermSoonOverdue; + + @Value("${cabinet.alarm.overdue-term.overdue}") + private Long overdueTermOverdue; } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java index ce61210ab..5f3d28559 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/controller/AuthController.java @@ -28,8 +28,8 @@ public class AuthController { * @throws IOException 입출력 예외 */ @GetMapping("/login") - public void login(HttpServletResponse response) throws IOException { - authFacadeService.requestUserLogin(response); + public void login(HttpServletRequest request, HttpServletResponse response) throws IOException { + authFacadeService.requestUserLogin(request, response); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index 13ff68277..f65483994 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -38,21 +38,30 @@ public class AuthFacadeService { private final TokenProvider tokenProvider; private final CookieManager cookieManager; + private static final String REDIRECT_COOKIE_NAME = "redirect"; /** * 유저 로그인 페이지로 리다이렉트합니다. * - * @param res 요청 시의 서블렛 {@link HttpServletResponse} + * @param req 요청 시의 서블렛 {@link HttpServletRequest} + * @param res 응답 시의 서블렛 {@link HttpServletResponse} * @throws IOException 입출력 예외 */ - public void requestUserLogin(HttpServletResponse res) throws IOException { + public void requestUserLogin(HttpServletRequest req, HttpServletResponse res) + throws IOException { + String redirect = req.getParameter(REDIRECT_COOKIE_NAME); + if (redirect != null) { + cookieManager.setCookieToClient( + res, cookieManager.cookieOf(REDIRECT_COOKIE_NAME, redirect), + "/", req.getServerName()); + } userOauthService.requestLogin(res); } /** * 관리자 로그인 페이지로 리다이렉트합니다. * - * @param res 요청 시의 서블렛 {@link HttpServletResponse} + * @param res 응답 시의 서블렛 {@link HttpServletResponse} * @throws IOException 입출력 예외 */ public void requestAdminLogin(HttpServletResponse res) throws IOException { @@ -69,13 +78,20 @@ public void requestAdminLogin(HttpServletResponse res) throws IOException { * @throws ExecutionException 비동기 처리시 스레드에서 발생한 오류 처리 예외 * @throws InterruptedException 비동기 처리시 스레드 종료를 위한 예외 */ - public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { + public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, String code) + throws IOException, ExecutionException, InterruptedException { FtProfile profile = userOauthService.getProfileByCode(code); User user = userQueryService.findUser(profile.getIntraName()) .orElseGet(() -> userCommandService.createUserByFtProfile(profile)); String token = tokenProvider.createUserToken(user, LocalDateTime.now()); Cookie cookie = cookieManager.cookieOf(TokenProvider.USER_TOKEN_NAME, token); cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + if (cookieManager.getCookieValue(req, REDIRECT_COOKIE_NAME) != null) { + String redirect = cookieManager.getCookieValue(req, REDIRECT_COOKIE_NAME); + cookieManager.deleteCookie(res, REDIRECT_COOKIE_NAME); + res.sendRedirect(redirect); + return; + } res.sendRedirect(authPolicyService.getMainHomeUrl()); } @@ -89,7 +105,8 @@ public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, Str * @throws ExecutionException 비동기 처리시 스레드에서 발생한 오류 처리 예외 * @throws InterruptedException 비동기 처리시 스레드 종료를 위한 예외 */ - public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, String code) throws IOException, ExecutionException, InterruptedException { + public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, String code) + throws IOException, ExecutionException, InterruptedException { GoogleProfile profile = adminOauthService.getProfileByCode(code); Admin admin = adminQueryService.findByEmail(profile.getEmail()) .orElseGet(() -> adminCommandService.createAdminByEmail(profile.getEmail())); @@ -110,10 +127,12 @@ public void handleAdminLogin(HttpServletRequest req, HttpServletResponse res, St * @param now 현재 시각 */ public void masterLogin(MasterLoginDto masterLoginDto, HttpServletRequest req, - HttpServletResponse res, LocalDateTime now) { + HttpServletResponse res, LocalDateTime now) { // TODO : 서비스로 빼기 - if (!authPolicyService.isMatchWithMasterAuthInfo(masterLoginDto.getId(), masterLoginDto.getPassword())) + if (!authPolicyService.isMatchWithMasterAuthInfo(masterLoginDto.getId(), + masterLoginDto.getPassword())) { throw ExceptionStatus.UNAUTHORIZED_ADMIN.asServiceException(); + } Admin master = adminQueryService.findByEmail(authPolicyService.getMasterEmail()) .orElseThrow(ExceptionStatus.UNAUTHORIZED_ADMIN::asServiceException); String masterToken = tokenProvider.createAdminToken(master, now); diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index 917203507..a84ae7200 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -168,7 +168,6 @@ private String getCabinetTitle(Cabinet cabinet, List lentHistories) @Transactional public CabinetPendingResponseDto getPendingCabinets(String building) { final LocalDateTime now = LocalDateTime.now(); - final LocalDateTime yesterday = now.minusDays(1).withHour(13).withMinute(0).withSecond(0); List pendingCabinets = cabinetQueryService.findPendingCabinetsNotLentTypeAndStatus( building, LentType.CLUB, List.of(AVAILABLE, PENDING)); @@ -178,7 +177,7 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { Map> lentHistoriesMap; if (now.getHour() < 13) { lentHistoriesMap = lentQueryService.findPendingLentHistoriesOnDate( - yesterday.toLocalDate(), cabinetIds) + now.minusDays(1).toLocalDate(), cabinetIds) .stream().collect(groupingBy(LentHistory::getCabinetId)); } else { lentHistoriesMap = lentQueryService.findCabinetLentHistories(cabinetIds) @@ -193,7 +192,11 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); } if (cabinet.isStatus(PENDING)) { - lentHistoriesMap.get(cabinet.getId()).stream() + List lentHistories = lentHistoriesMap.get(cabinet.getId()); + if (lentHistories == null || lentHistories.isEmpty()) { + return; + } + lentHistories.stream() .map(LentHistory::getEndedAt) .max(LocalDateTime::compareTo) .ifPresent(latestEndedAt -> cabinetFloorMap.get(floor) diff --git a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java b/backend/src/main/java/org/ftclub/cabinet/event/RedisExpirationEventListener.java similarity index 90% rename from backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java rename to backend/src/main/java/org/ftclub/cabinet/event/RedisExpirationEventListener.java index 9931c2988..6feaa58cd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/redis/ExpirationListener.java +++ b/backend/src/main/java/org/ftclub/cabinet/event/RedisExpirationEventListener.java @@ -1,4 +1,4 @@ -package org.ftclub.cabinet.redis; +package org.ftclub.cabinet.event; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.lent.service.LentFacadeService; @@ -12,7 +12,7 @@ @Component @Log4j2 -public class ExpirationListener extends KeyExpirationEventMessageListener { +public class RedisExpirationEventListener extends KeyExpirationEventMessageListener { private final LentFacadeService lentFacadeService; @@ -22,7 +22,7 @@ public class ExpirationListener extends KeyExpirationEventMessageListener { * @param listenerContainer must not be {@literal null}. * @param lentFacadeService must not be {@literal null}. */ - public ExpirationListener( + public RedisExpirationEventListener( @Qualifier("redisMessageListenerContainer") RedisMessageListenerContainer listenerContainer, LentFacadeService lentFacadeService) { diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java index aa3737dd0..b314125a7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java @@ -23,48 +23,57 @@ */ public class OverdueManager { - private final CabinetFacadeService cabinetFacadeService; - private final AlarmProperties alarmProperties; - private final ApplicationEventPublisher eventPublisher; + private final CabinetFacadeService cabinetFacadeService; + private final AlarmProperties alarmProperties; + private final ApplicationEventPublisher eventPublisher; - /** - * 연체 타입을 반환하는 메소드 연체 예정인 경우, SOON_OVERDUE를 반환하고, 연체 기간이 지난 경우, OVERDUE를 반환한다. 그 외의 경우, NONE을 - * 반환한다. - * - * @param isExpired 연체 기간이 지났는지 여부 (true: 연체 기간이 지남, false: 연체 기간이 지나지 않음) - * @param daysLeftFromExpireDate 만료일까지 남은 일수 - * @return 연체 타입 - */ - public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate) { - log.info("called getOverdueType with {}, {}", isExpired, daysLeftFromExpireDate); - if (isExpired) { - return OverdueType.OVERDUE; - } - if (alarmProperties.getSoonOverdueTerm().equals(daysLeftFromExpireDate)) { - return OverdueType.SOON_OVERDUE; - } - return OverdueType.NONE; - } + /** + * 연체 타입을 반환하는 메소드 연체 예정인 경우, SOON_OVERDUE를 반환하고, 연체 기간이 지난 경우, OVERDUE를 반환한다. 그 외의 경우, NONE을 + * 반환한다. + * + * @param isExpired 연체 기간이 지났는지 여부 (true: 연체 기간이 지남, false: 연체 기간이 지나지 않음) + * @param daysLeftFromExpireDate 만료일까지 남은 일수 + * @return 연체 타입 + */ + public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate) { + log.info("called getOverdueType with {}, {}", isExpired, daysLeftFromExpireDate); + if (isExpired) { + return OverdueType.OVERDUE; + } - public void handleOverdue(ActiveLentHistoryDto activeLent) { - log.info("called handleOverdue with {}", activeLent); - OverdueType overdueType = getOverdueType(activeLent.getIsExpired(), - activeLent.getDaysLeftFromExpireDate()); - log.info("overdueType = {}", overdueType); - switch (overdueType) { - case NONE: - return; - case SOON_OVERDUE: - eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), - new LentExpirationImminentAlarm(activeLent.getDaysLeftFromExpireDate()))); - break; - case OVERDUE: - cabinetFacadeService.updateStatus(activeLent.getCabinetId(), - CabinetStatus.OVERDUE); - eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), - new LentExpirationAlarm(activeLent.getDaysLeftFromExpireDate()))); - break; - } - } + if (daysLeftFromExpireDate.equals(alarmProperties.getOverdueTermWeekBefore())) { + return OverdueType.SOON_OVERDUE; + } + if (daysLeftFromExpireDate.equals(alarmProperties.getOverdueTermThreeDaysBefore())) { + return OverdueType.SOON_OVERDUE; + } + if (daysLeftFromExpireDate.equals(alarmProperties.getOverdueTermSoonOverdue())) { + return OverdueType.SOON_OVERDUE; + } + if (daysLeftFromExpireDate >= alarmProperties.getOverdueTermOverdue()) { + return OverdueType.OVERDUE; + } + return OverdueType.NONE; + } + + public void handleOverdue(ActiveLentHistoryDto activeLent) { + OverdueType overdueType = getOverdueType(activeLent.getIsExpired(), + activeLent.getDaysLeftFromExpireDate()); + log.info("called handleOverdue: activeLent={}, overdueType={}", activeLent, overdueType); + switch (overdueType) { + case NONE: + return; + case SOON_OVERDUE: + eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), + new LentExpirationImminentAlarm(activeLent.getDaysLeftFromExpireDate()))); + break; + case OVERDUE: + cabinetFacadeService.updateStatus(activeLent.getCabinetId(), + CabinetStatus.OVERDUE); + eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), + new LentExpirationAlarm(activeLent.getDaysLeftFromExpireDate()))); + break; + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueType.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueType.java index 528820832..8f75750c7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueType.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueType.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.utils.overdue.manager; public enum OverdueType { + SOON_OVERDUE, OVERDUE, NONE diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 18dcd2473..c512f9335 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -1,25 +1,22 @@ package org.ftclub.cabinet.utils.scheduler; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.UserBlackHoleEvent; import org.ftclub.cabinet.lent.service.LentFacadeService; -import org.ftclub.cabinet.occupiedtime.OccupiedTimeManager; import org.ftclub.cabinet.user.service.LentExtensionManager; import org.ftclub.cabinet.user.service.UserQueryService; import org.ftclub.cabinet.utils.blackhole.manager.BlackholeManager; -import org.ftclub.cabinet.utils.leave.absence.LeaveAbsenceManager; import org.ftclub.cabinet.utils.overdue.manager.OverdueManager; import org.ftclub.cabinet.utils.release.ReleaseManager; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - /** * 시스템 스케줄러 */ @@ -29,25 +26,23 @@ @Log4j2 public class SystemScheduler { - private static final long DELAY_TIME = 2000; - private final UserQueryService userQueryService; - private final LeaveAbsenceManager leaveAbsenceManager; - private final LentExtensionManager lentExtensionManager; - private final OverdueManager overdueManager; - private final LentFacadeService lentFacadeService; - private final BlackholeManager blackholeManager; - private final ReleaseManager releaseManager; - private final OccupiedTimeManager occupiedTimeManager; + private static final long DELAY_TIME = 2000; + private final UserQueryService userQueryService; + private final LentExtensionManager lentExtensionManager; + private final OverdueManager overdueManager; + private final LentFacadeService lentFacadeService; + private final BlackholeManager blackholeManager; + private final ReleaseManager releaseManager; - /** - * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") - public void checkAllLents() { - log.info("called checkAllLents"); - List activeLents = lentFacadeService.getAllActiveLentHistories(); - for (ActiveLentHistoryDto activeLent : activeLents) { - overdueManager.handleOverdue(activeLent); + /** + * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거 + */ + @Scheduled(cron = "${cabinet.schedule.cron.leave-absence}") + public void checkAllLents() { + log.info("called checkAllLents"); + List activeLents = lentFacadeService.getAllActiveLentHistories(); + for (ActiveLentHistoryDto activeLent : activeLents) { + overdueManager.handleOverdue(activeLent); /* leaveAbsenceManager.handleLeaveAbsence(activeLent.getUserId(), activeLent.getName()); try { @@ -56,66 +51,69 @@ public void checkAllLents() { log.error(e.getMessage()); } */ - } - } + } + } - /** - * 매주 월요일 자정 42분에 블랙홀에 빠진 유저 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") - public void checkRiskOfBlackhole() { - log.info("called checkRiskOfBlackhole"); + /** + * 매주 월요일 자정 42분에 블랙홀에 빠진 유저 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") + public void checkRiskOfBlackhole() { + log.info("called checkRiskOfBlackhole"); - List closeWithBlackholeUsers = userQueryService.findUsersAtRiskOfBlackhole().stream() - .map(user -> UserBlackHoleEvent.of(user.getId(), user.getName(), user.getEmail(), user.getBlackholedAt())) - .collect(Collectors.toList()); - for (UserBlackHoleEvent blackholeInfo : closeWithBlackholeUsers) { - blackholeManager.handleBlackHole(blackholeInfo); - try { - Thread.sleep(DELAY_TIME); - } catch (InterruptedException e) { - log.error(e.getMessage()); - } - } - } + List closeWithBlackholeUsers = userQueryService.findUsersAtRiskOfBlackhole() + .stream() + .map(user -> UserBlackHoleEvent.of(user.getId(), user.getName(), user.getEmail(), + user.getBlackholedAt())) + .collect(Collectors.toList()); + for (UserBlackHoleEvent blackholeInfo : closeWithBlackholeUsers) { + blackholeManager.handleBlackHole(blackholeInfo); + try { + Thread.sleep(DELAY_TIME); + } catch (InterruptedException e) { + log.error(e.getMessage()); + } + } + } - /** - * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - @Scheduled(cron = "${cabinet.schedule.cron.no-risk-of-blackhole}") - public void checkNoRiskOfBlackhole() { - log.info("called checkNoRiskOfBlackhole"); + /** + * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + @Scheduled(cron = "${cabinet.schedule.cron.no-risk-of-blackhole}") + public void checkNoRiskOfBlackhole() { + log.info("called checkNoRiskOfBlackhole"); - List safeFromBlackholeUsers = userQueryService.findUsersAtNoRiskOfBlackhole() - .stream() - .map(user -> UserBlackHoleEvent.of(user.getId(), user.getName(), user.getEmail(), user.getBlackholedAt())) - .collect(Collectors.toList()); + List safeFromBlackholeUsers = userQueryService.findUsersAtNoRiskOfBlackhole() + .stream() + .map(user -> UserBlackHoleEvent.of(user.getId(), user.getName(), user.getEmail(), + user.getBlackholedAt())) + .collect(Collectors.toList()); - for (UserBlackHoleEvent blackholeUserInfo : safeFromBlackholeUsers) { - blackholeManager.handleBlackHole(blackholeUserInfo); - try { - Thread.sleep(DELAY_TIME); - } catch (InterruptedException e) { - log.error(e.getMessage()); - } - } - } + for (UserBlackHoleEvent blackholeUserInfo : safeFromBlackholeUsers) { + blackholeManager.handleBlackHole(blackholeUserInfo); + try { + Thread.sleep(DELAY_TIME); + } catch (InterruptedException e) { + log.error(e.getMessage()); + } + } + } - /** - * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 - */ - //현재 5분마다 도는 로직 0 */5 * * * * - @Scheduled(cron = "${cabinet.schedule.cron.cabinet-release-time}") - public void releasePendingCabinet() { - log.info("releasePendingCabinet {}", LocalDateTime.now()); - releaseManager.releasingCabinets(); - } + /** + * 매월 1일 01시 42분에 블랙홀에 빠질 위험이 없는 유저들의 블랙홀 처리를 트리거하는 메소드 2초 간격으로 블랙홀 검증 + */ + //현재 5분마다 도는 로직 0 */5 * * * * + @Scheduled(cron = "${cabinet.schedule.cron.cabinet-release-time}") + public void releasePendingCabinet() { + log.info("releasePendingCabinet {}", LocalDateTime.now()); + releaseManager.releasingCabinets(); + } - @Scheduled(cron = "${cabinet.schedule.cron.extension-issue-time}") - public void lentExtensionIssue() { - log.info("called lentExtensionIssue"); - lentExtensionManager.issueLentExtension(); - } + @Scheduled(cron = "${cabinet.schedule.cron.extension-issue-time}") + public void lentExtensionIssue() { + log.info("called lentExtensionIssue"); + lentExtensionManager.issueLentExtension(); + } // @Scheduled(cron = "${cabinet.schedule.cron.extensible-user-check}") // public void checkUserQualifyForExtensible(){ diff --git a/frontend/public/.well-known/assetlinks.json b/frontend/public/.well-known/assetlinks.json new file mode 100644 index 000000000..def9c05cc --- /dev/null +++ b/frontend/public/.well-known/assetlinks.json @@ -0,0 +1,8 @@ +[{ + "relation": ["delegate_permission/common.handle_all_urls"], + "target": { + "namespace": "android_app", + "package_name": "io.app_42seoul.cabi.dev.twa", + "sha256_cert_fingerprints": ["89:7E:47:82:AD:49:16:1E:BE:79:53:5D:D9:C4:D9:25:1C:E4:C5:7F:AC:46:95:B1:84:42:AD:56:D8:6A:E1:4E"] + } + }] diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 42b3426ac..b812f2015 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,6 +8,7 @@ import MainPage from "@/pages/MainPage"; import PendingPage from "@/pages/PendingPage/PendingPage"; import AdminMainPage from "@/pages/admin/AdminMainPage"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; +import PostLogin from "./pages/PostLogin"; import ProfilePage from "./pages/ProfilePage"; const NotFoundPage = lazy(() => import("@/pages/NotFoundPage")); @@ -26,6 +27,7 @@ function App(): React.ReactElement { }> + } /> }> } /> } /> diff --git a/frontend/src/assets/images/rotateRight.svg b/frontend/src/assets/images/rotateRight.svg new file mode 100644 index 000000000..ac1e99b24 --- /dev/null +++ b/frontend/src/assets/images/rotateRight.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/assets/images/sadCcabi.svg b/frontend/src/assets/images/sadCcabi.svg new file mode 100644 index 000000000..1dd12d5b3 --- /dev/null +++ b/frontend/src/assets/images/sadCcabi.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/components/Error/ErrorTemplate.tsx b/frontend/src/components/Announce/AnnounceTemplate.tsx similarity index 64% rename from frontend/src/components/Error/ErrorTemplate.tsx rename to frontend/src/components/Announce/AnnounceTemplate.tsx index 5d1777662..f4c831efc 100644 --- a/frontend/src/components/Error/ErrorTemplate.tsx +++ b/frontend/src/components/Announce/AnnounceTemplate.tsx @@ -1,50 +1,66 @@ +import { useEffect, useState } from "react"; import styled, { keyframes } from "styled-components"; interface Itext { - id?: string; title: string; subTitle: string; content: string; subContent?: string; - buttonText: string; - buttonHandler: React.MouseEventHandler; + buttonText?: string; + buttonHandler?: React.MouseEventHandler; + type: string; } -const ErrorTemplate = (props: Itext) => { +const AnnounceTemplate = (props: Itext) => { const { - id, title, subTitle, content, subContent, buttonText, buttonHandler, + type, } = props; + const [backgroundColor, setBackgroundColor] = useState(""); + + useEffect(() => { + if (type === "LOADING") { + setBackgroundColor("var(--sub-color)"); + } else if (type === "ERROR") { + setBackgroundColor("var(--main-color)"); + } + }, []); return ( - + {title} - sad_cabi + {type === "ERROR" ? ( + + ) : ( + + )} {subTitle} {content} {!!subContent && {subContent}} - {buttonText} - + {buttonHandler && ( + {buttonText} + )} + ); }; -const ErrorTemplateStyled = styled.div` +const AnnounceTemplateStyled = styled.div<{ backgroundColor: string }>` width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; flex-direction: column; - background-color: var(--main-color); + background-color: ${(props) => props.backgroundColor}; color: var(--white); `; @@ -109,4 +125,4 @@ const ButtonStyled = styled.button` box-shadow: 10px 10px 40px 0px rgba(0, 0, 0, 0.25); `; -export default ErrorTemplate; +export default AnnounceTemplate; diff --git a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 8334f158b..120f66351 100644 --- a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -277,14 +277,15 @@ const CabinetRectangleStyled = styled.div<{ ? cabinetLabelColorMap["MINE"] : cabinetLabelColorMap[props.cabinetStatus]}; text-align: center; - ${({ cabinetStatus }) => css` border: ${cabinetStatus === "IN_SESSION" ? "2px solid var(--main-color)" : cabinetStatus === "PENDING" - ? "5px double var(--white)" + ? "2px double var(--main-color)" : "none"}; - ${cabinetStatus === "PENDING" && "line-height: 70px;"}; + `} + ${({ cabinetStatus }) => css` + box-shadow: ${cabinetStatus === "PENDING" && "inset 0px 0px 0px 2px white"}; `} `; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx index 2475f7560..ca3cde9ed 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -346,8 +346,8 @@ const CabinetRectangleStyled = styled.div<{ ${({ cabinetStatus }) => cabinetStatus === "PENDING" && css` - border: 5px double var(--white); - line-height: 70px; + border: 2px double var(--main-color); + box-shadow: inset 0px 0px 0px 2px var(--white); `} `; diff --git a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index 3978cc621..ddcb1f3b6 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -167,7 +167,8 @@ const CabinetListItemStyled = styled.div<{ ${({ status }) => status === "PENDING" && css` - border: 5px double var(--white); + border: 2px double var(--main-color); + box-shadow: inset 0px 0px 0px 2px var(--white); `} ${({ status }) => diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx index 8044f42c9..e1dd84de7 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -204,7 +204,8 @@ const CabinetListItemStyled = styled.div<{ ${({ status }) => status === "PENDING" && css` - border: 5px double var(--white); + border: 2px double var(--main-color); + box-shadow: inset 0px 0px 0px 2px var(--white); `} ${({ status }) => diff --git a/frontend/src/components/Home/ManualContentBox.tsx b/frontend/src/components/Home/ManualContentBox.tsx index 51d025b01..229c16f5a 100644 --- a/frontend/src/components/Home/ManualContentBox.tsx +++ b/frontend/src/components/Home/ManualContentBox.tsx @@ -85,7 +85,8 @@ const MaunalContentBoxStyled = styled.div<{ ${({ contentStatus }) => contentStatus === ContentStatus.PENDING && css` - border: 10px double var(--white); + border: 5px double var(--main-color); + box-shadow: inset 0px 0px 0px 5px var(--white); `} ${({ contentStatus }) => @@ -136,7 +137,16 @@ const MaunalContentBoxStyled = styled.div<{ :hover { transition: all 0.3s ease-in-out; - box-shadow: 10px 10px 25px 0 rgba(0, 0, 0, 0.2); + ${({ contentStatus }) => + contentStatus === ContentStatus.PENDING + ? css` + border: 5px double var(--main-color); + box-shadow: inset 0px 0px 0px 5px var(--white), + 10px 10px 25px 0 rgba(0, 0, 0, 0.2); + ` + : css` + box-shadow: 10px 10px 25px 0 rgba(0, 0, 0, 0.2); + `} p { transition: all 0.3s ease-in-out; transform: translateY(-5px); diff --git a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx index 1f8b91929..dcdf0d8f9 100644 --- a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx +++ b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx @@ -54,7 +54,8 @@ const ColorTableItemStyled = styled.div<{ color: string }>` ${({ color }) => color === "var(--pending)" && css` - border: 3px double var(--white); + border: 1px double var(--main-color); + box-shadow: inset 0px 0px 0px 1px var(--white); `} } `; diff --git a/frontend/src/components/Modals/ManualModal/ManualModal.tsx b/frontend/src/components/Modals/ManualModal/ManualModal.tsx index 522415bae..b3755a336 100644 --- a/frontend/src/components/Modals/ManualModal/ManualModal.tsx +++ b/frontend/src/components/Modals/ManualModal/ManualModal.tsx @@ -1,6 +1,6 @@ import React from "react"; import { useState } from "react"; -import styled, { keyframes } from "styled-components"; +import styled, { css, keyframes } from "styled-components"; import { manualContentData } from "@/assets/data/ManualContent"; import { ReactComponent as MoveBtnImg } from "@/assets/images/moveButton.svg"; import ContentStatus from "@/types/enum/content.status.enum"; @@ -141,10 +141,13 @@ const ModalWrapper = styled.div<{ border-radius: 40px 40px 0 0; border: ${(props) => props.contentStatus === ContentStatus.PENDING - ? "10px double var(--white)" + ? "5px double var(--main-color)" : props.contentStatus === ContentStatus.IN_SESSION ? "5px solid var(--main-color)" : "none"}; + box-shadow: ${(props) => + props.contentStatus === ContentStatus.PENDING && + "inset 0px 0px 0px 5px var(--white);"}; border-bottom: none; @media screen and (max-width: 700px) { width: 100%; diff --git a/frontend/src/pages/LoginFailurePage.tsx b/frontend/src/pages/LoginFailurePage.tsx index 84f4d5f1b..53872545e 100644 --- a/frontend/src/pages/LoginFailurePage.tsx +++ b/frontend/src/pages/LoginFailurePage.tsx @@ -1,17 +1,18 @@ import { useNavigate } from "react-router-dom"; -import ErrorTemplate from "@/components/Error/ErrorTemplate"; +import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); return ( - navigate("/login")} + type="ERORR" /> ); }; diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 6fb84e65c..38479f786 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -2,11 +2,12 @@ import LoginTemplate from "@/components/Login/LoginTemplate"; import "@/assets/css/loginPage.css"; const LoginPage = () => { + const ORIGIN_URL = window.location.origin; const url = `${import.meta.env.VITE_BE_HOST}/v4/auth/login`; return ( { const navigate = useNavigate(); return ( - navigate("/home")} + type="ERROR" /> ); }; diff --git a/frontend/src/pages/PendingPage/PendingPage.tsx b/frontend/src/pages/PendingPage/PendingPage.tsx index 94394fc68..42309fba6 100644 --- a/frontend/src/pages/PendingPage/PendingPage.tsx +++ b/frontend/src/pages/PendingPage/PendingPage.tsx @@ -132,13 +132,7 @@ const PendingPage = () => { return ( - - - + 사용 가능 사물함

@@ -148,11 +142,22 @@ const PendingPage = () => { {isRefreshing ? ( ) : ( - 새로고침 + <> + 새로고침 + setIsOpenTime(true)} /> + )} + {/* */} - setIsOpenTime(true)} /> + + + + {isLoaded && cabinets ? ( Object.entries(cabinets).map(([key, value]) => ( - +

{floorNumber}층

- +
{pendingCabinetsList.length !== 0 ? ( @@ -49,7 +49,7 @@ const FloorContainer = ({ const FloorContainerStyled = styled.div` width: 70%; - margin-top: 50px; + margin-top: 30px; `; const FloorTitleStyled = styled.div<{ isToggled: boolean }>` @@ -60,9 +60,10 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` padding-left: 5px; padding-right: 5px; border-bottom: 1.5px solid #d9d9d9; + cursor: pointer; button { all: initial; - cursor: pointer; + cursor: inherit; z-index: 2; height: 30px; width: 20px; diff --git a/frontend/src/pages/PendingPage/components/PendingCountdown.tsx b/frontend/src/pages/PendingPage/components/PendingCountdown.tsx index 51c2ed711..317049b47 100644 --- a/frontend/src/pages/PendingPage/components/PendingCountdown.tsx +++ b/frontend/src/pages/PendingPage/components/PendingCountdown.tsx @@ -20,6 +20,9 @@ const PendingCountdown = ({ const hours = Math.floor(remainingTime / 3600000); const minutes = Math.floor((remainingTime % 3600000) / 60000); const seconds = Math.floor((remainingTime % 60000) / 1000); + const twoDigitsHours = String(hours).padStart(2, "0"); + const twoDigitsMinutes = String(minutes).padStart(2, "0"); + const twoDigitsSeconds = String(seconds).padStart(2, "0"); useEffect(() => { if (serverTime.toLocaleTimeString() === Time.PENDING_OPEN) @@ -40,26 +43,18 @@ const PendingCountdown = ({ return ( <> - {remainingTime === 0 ? "OPEN" - : `${hours}시간 ${minutes}분 ${seconds}초`} + : `${twoDigitsHours}:${twoDigitsMinutes}:${twoDigitsSeconds} 남았습니다`} ); }; -const PendingCountdownIconStyled = styled.img` - height: 25px; - width: 25px; - margin-top: 50px; -`; - const PendingCountdownStyled = styled.div` - margin-top: 5px; - color: var(--main-color); - font-size: 1.8rem; + color: white; + font-size: 1.1rem; font-weight: 600; `; diff --git a/frontend/src/pages/PostLogin.tsx b/frontend/src/pages/PostLogin.tsx new file mode 100644 index 000000000..d6e4f1bee --- /dev/null +++ b/frontend/src/pages/PostLogin.tsx @@ -0,0 +1,63 @@ +import { + deleteFcmToken, + requestFcmAndGetDeviceToken, +} from "@/firebase/firebase-messaging-sw"; +import { useEffect, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import { useRecoilState, useSetRecoilState } from "recoil"; +import { userState } from "@/recoil/atoms"; +import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; +import { UserDto } from "@/types/dto/user.dto"; +import { axiosMyInfo, axiosUpdateDeviceToken } from "@/api/axios/axios.custom"; +import { getCookie } from "@/api/react_cookie/cookies"; + +const PostLogin = (): JSX.Element => { + const [isLoading, setIsLoading] = useState(true); + const [isValidToken, setIsValidToken] = useState(false); + const [myInfo, setMyInfo] = useRecoilState(userState); + + const setUser = useSetRecoilState(userState); + const navigate = useNavigate(); + const token = getCookie("access_token"); + + const getMyInfo = async () => { + try { + const { data: myInfo } = await axiosMyInfo(); + setUser(myInfo); + setIsValidToken(true); + console.log(myInfo); + if (myInfo.alarmTypes?.push && myInfo.isDeviceTokenExpired) { + await deleteFcmToken(); + const deviceToken = await requestFcmAndGetDeviceToken(); + await axiosUpdateDeviceToken(deviceToken); + } + setMyInfo(myInfo); + } catch (error) { + navigate("/login"); + } + }; + + useEffect(() => { + if (!token) navigate("/login"); + else if (token) { + getMyInfo(); + let time = setTimeout(() => { + navigate("/home"); + }, 600); + return () => { + clearTimeout(time); + }; + } + }, []); + + return ( + + ); +}; + +export default PostLogin; diff --git a/frontend/src/pages/admin/AdminLoginFailurePage.tsx b/frontend/src/pages/admin/AdminLoginFailurePage.tsx index f6db5bc85..2e20c6cb8 100644 --- a/frontend/src/pages/admin/AdminLoginFailurePage.tsx +++ b/frontend/src/pages/admin/AdminLoginFailurePage.tsx @@ -1,17 +1,18 @@ import { useNavigate } from "react-router-dom"; -import ErrorTemplate from "@/components/Error/ErrorTemplate"; +import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); return ( - navigate("/admin/login")} + type="ERORR" /> ); }; diff --git a/frontend/src/types/enum/AnnounceType.enum.ts b/frontend/src/types/enum/AnnounceType.enum.ts new file mode 100644 index 000000000..4fca97199 --- /dev/null +++ b/frontend/src/types/enum/AnnounceType.enum.ts @@ -0,0 +1,6 @@ +enum AnnounceType { + ERROR = "ERROR", + LOADING = "LOADING", +} + +export default AnnounceType; diff --git a/frontend/stats.json b/frontend/stats.json index 7936b535d..57a04d367 100644 --- a/frontend/stats.json +++ b/frontend/stats.json @@ -22,10 +22,10 @@ ] }, { - "name": "assets/ErrorTemplate-d9af0d10.js", + "name": "assets/AnnounceTemplate-d9af0d10.js", "children": [ { - "name": "src/components/Error/ErrorTemplate.tsx", + "name": "src/components/Error/AnnounceTemplate.tsx", "uid": "a9f3-5" } ] @@ -6639,9 +6639,9 @@ ] }, "a9f3-4": { - "id": "/src/components/Error/ErrorTemplate.tsx", + "id": "/src/components/Error/AnnounceTemplate.tsx", "moduleParts": { - "assets/ErrorTemplate-d9af0d10.js": "a9f3-5" + "assets/AnnounceTemplate-d9af0d10.js": "a9f3-5" }, "imported": [ { diff --git a/maintenance/index.html b/maintenance/index.html index f46dfc2f7..e4ddf13d0 100644 --- a/maintenance/index.html +++ b/maintenance/index.html @@ -41,7 +41,7 @@ justify-content: center; align-items: center; } - .errorTemplate { + .AnnounceTemplate { width: 100%; height: 100%; display: flex; @@ -136,7 +136,7 @@
-
+
점검 중
From 63fff09ff6294d164b03188888c250bd63573b57 Mon Sep 17 00:00:00 2001 From: space Date: Thu, 8 Feb 2024 01:14:54 +0900 Subject: [PATCH 0389/1029] [BE]FIX: config before --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 88b806001..d3021ce2f 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 88b80600192796cd4a9c506b1f80aacf3ef4ae0a +Subproject commit d3021ce2fc0d7d3b55309df3e7957b309faf2cd4 From 8ed07a2f19d1b7911497c9e82ea283032801879f Mon Sep 17 00:00:00 2001 From: space Date: Thu, 8 Feb 2024 01:16:32 +0900 Subject: [PATCH 0390/1029] [BE] TEST: alarm test --- .../ftclub/cabinet/ping/PingController.java | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java b/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java index 887ed7482..9793f97f7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java +++ b/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.utils.overdue.manager.OverdueManager; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -10,21 +12,26 @@ @RequiredArgsConstructor @Log4j2 public class PingController { - // private final LentExtensionService lentExtensionService; -// private final OverdueManager overdueManager; - @RequestMapping - public String ping() { - return "pong"; - } + private final LentFacadeService lentFacadeService; + private final OverdueManager overdueManager; -// @RequestMapping("/pong") -// public String ok() { -// log.debug("called ok"); -// ActiveLentHistoryDto wchae = new ActiveLentHistoryDto(94L, "wchae", "wchae@student.42seoul.kr", 1L, false, -1L); -// overdueManager.handleOverdue(wchae); -// return "ok"; -// } + @RequestMapping + public String ping() { + return "pong"; + } + +// @RequestMapping("/pong") +// public String ok() { +// log.debug("called ok"); +// List activeLents = lentFacadeService.getAllActiveLentHistories(); +// //activeLents 에서 wchae를 찾아서 overdueManager.handleOverdue(wchae) 호출 +// ActiveLentHistoryDto wchae = activeLents.stream() +// .filter(lent -> lent.getName().equals("wchae")).findFirst().get(); +//// ActiveLentHistoryDto wchae = new ActiveLentHistoryDto(94L, "wchae", "wchae@student.42seoul.kr", 1L, false, -1L); +// overdueManager.handleOverdue(wchae); +// return "ok"; +// } // @RequestMapping("/pong") // public String ok(){ From 394837919625d5cae020fd5013b92cc1dbe1e614 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 8 Feb 2024 13:25:59 +0900 Subject: [PATCH 0391/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EB=8C=80=EC=97=AC?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EA=B3=BC?= =?UTF-8?q?=EA=B1=B0=EB=B6=80=ED=84=B0=2010=EA=B0=9C=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=B4=EC=98=A4=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/lent/repository/LentRepository.java | 3 ++- .../org/ftclub/cabinet/lent/service/LentFacadeService.java | 6 ++---- .../org/ftclub/cabinet/lent/service/LentQueryService.java | 2 +- .../ftclub/cabinet/lent/repository/LentRepositoryTest.java | 5 +++-- config | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index c0b957e7e..f28554b41 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -190,7 +190,8 @@ Page findPaginationByCabinetIdJoinCabinetAndUser( * @param pageable pagination 정보 * @return {@link LentHistory}의 {@link Page} */ - Page findPaginationByUserId(@Param("userId") Long userId, Pageable pageable); + Page findPaginationByUserIdOrderByStartedAtDesc( + @Param("userId") Long userId, Pageable pageable); /** * 특정 사물함의 아직 반납하지 않은 대여기록들를 모두 가져옵니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index ba02a2e50..31ccce58e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; -import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -71,10 +70,9 @@ public class LentFacadeService { */ @Transactional(readOnly = true) public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, Pageable pageable) { - Page lentHistories = lentQueryService.findUserLentHistories(user.getUserId(), - pageable); + Page lentHistories = + lentQueryService.findUserLentHistories(user.getUserId(), pageable); List result = lentHistories.stream() - .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) .map(lentHistory -> lentMapper.toLentHistoryDto(lentHistory, lentHistory.getUser(), lentHistory.getCabinet())).collect(Collectors.toList()); return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 3b9aab813..696a857c2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -33,7 +33,7 @@ public class LentQueryService { * @return 유저가 빌렸던 사물함의 대여 기록 {@link Page} */ public Page findUserLentHistories(Long userId, Pageable pageable) { - return lentRepository.findPaginationByUserId(userId, pageable); + return lentRepository.findPaginationByUserIdOrderByStartedAtDesc(userId, pageable); } /** diff --git a/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java index 78f839934..898a7e2a4 100644 --- a/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java @@ -62,13 +62,14 @@ void findFirstByUserIdAndEndedAtIsNull() { void findByUserId() { // 빌린 기록이 없는 user id long userId = 18L; - List lentHistories = lentRepository.findPaginationByUserId(userId, + List lentHistories = lentRepository.findPaginationByUserIdOrderByStartedAtDesc( + userId, PageRequest.of(0, 1)).toList(); assertTrue(lentHistories.isEmpty()); // 빌린 기록이 12개 있는 user id userId = 5L; - lentHistories = lentRepository.findPaginationByUserId(userId, + lentHistories = lentRepository.findPaginationByUserIdOrderByStartedAtDesc(userId, PageRequest.of(0, 20)).toList(); assertEquals(12, lentHistories.size()); } diff --git a/config b/config index d3021ce2f..ecf58be43 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit d3021ce2fc0d7d3b55309df3e7957b309faf2cd4 +Subproject commit ecf58be4311cdfb8eb59597bac7da6f94b37c29e From eb8d5236261005a51b301de1959c0bf8eb4185b4 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 8 Feb 2024 13:40:21 +0900 Subject: [PATCH 0392/1029] =?UTF-8?q?[BE]=20HOTFIX:=20Redis=20=EB=B0=B1?= =?UTF-8?q?=EC=97=85=20=EA=B8=B0=EB=8A=A5=20main,=20dev=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EB=8F=99=EC=9E=91=20=EC=95=88=EB=90=98=EB=8A=94=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/docker-compose.yaml | 8 +------- deploy-dev/pinpoint-application/docker-compose.yml | 2 +- deploy-main/pinpoint-application/docker-compose.yml | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/backend/docker-compose.yaml b/backend/docker-compose.yaml index 8e4ec2c2a..04f5229a1 100644 --- a/backend/docker-compose.yaml +++ b/backend/docker-compose.yaml @@ -14,12 +14,6 @@ services: ports: - "3307:3306" tty: true -# redis: -# container_name: redis -# image: redis:latest -# ports: -# - '6379:6379' -# tty: true gateway: container_name: nginx_gateway image: nginx:latest @@ -34,7 +28,7 @@ services: - "6379:6379" volumes: - ../redis/data/:/data/ - - ../redis/redis.conf:/user/local/etc/redis/redis.conf + - ../redis/config/:/user/local/etc/redis/ command: redis-server /user/local/etc/redis/redis.conf healthcheck: test: [ "CMD", "redis-cli", "ping" ] diff --git a/deploy-dev/pinpoint-application/docker-compose.yml b/deploy-dev/pinpoint-application/docker-compose.yml index 8460d61da..e5a0e1c6d 100644 --- a/deploy-dev/pinpoint-application/docker-compose.yml +++ b/deploy-dev/pinpoint-application/docker-compose.yml @@ -30,7 +30,7 @@ services: - "6379:6379" volumes: - $HOME/redis/data/:/data/ - - $HOME/redis/redis.conf:/user/local/etc/redis/redis.conf + - $HOME/redis/config:/user/local/etc/redis command: redis-server /user/local/etc/redis/redis.conf healthcheck: test: [ "CMD", "redis-cli", "ping" ] diff --git a/deploy-main/pinpoint-application/docker-compose.yml b/deploy-main/pinpoint-application/docker-compose.yml index 204899039..23df72128 100644 --- a/deploy-main/pinpoint-application/docker-compose.yml +++ b/deploy-main/pinpoint-application/docker-compose.yml @@ -30,7 +30,7 @@ services: - "6379:6379" volumes: - $HOME/redis/data/:/data/ - - $HOME/redis/redis.conf:/user/local/etc/redis/redis.conf + - $HOME/redis/config:/user/local/etc/redis command: redis-server /user/local/etc/redis/redis.conf healthcheck: test: [ "CMD", "redis-cli", "ping" ] From 8bf1df474dcb4d32b876db53e2eec4e837168c99 Mon Sep 17 00:00:00 2001 From: jiwon Date: Thu, 8 Feb 2024 13:41:12 +0900 Subject: [PATCH 0393/1029] =?UTF-8?q?[BE]=20HOTFIX:=20Redis=20=EB=B0=B1?= =?UTF-8?q?=EC=97=85=20=EA=B8=B0=EB=8A=A5=20=EC=84=A4=EC=A0=95=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20->=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy-dev/pinpoint-application/docker-compose.yml | 2 +- deploy-main/pinpoint-application/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy-dev/pinpoint-application/docker-compose.yml b/deploy-dev/pinpoint-application/docker-compose.yml index e5a0e1c6d..c95c98b90 100644 --- a/deploy-dev/pinpoint-application/docker-compose.yml +++ b/deploy-dev/pinpoint-application/docker-compose.yml @@ -30,7 +30,7 @@ services: - "6379:6379" volumes: - $HOME/redis/data/:/data/ - - $HOME/redis/config:/user/local/etc/redis + - $HOME/redis/config/:/user/local/etc/redis/ command: redis-server /user/local/etc/redis/redis.conf healthcheck: test: [ "CMD", "redis-cli", "ping" ] diff --git a/deploy-main/pinpoint-application/docker-compose.yml b/deploy-main/pinpoint-application/docker-compose.yml index 23df72128..95942d7fc 100644 --- a/deploy-main/pinpoint-application/docker-compose.yml +++ b/deploy-main/pinpoint-application/docker-compose.yml @@ -30,7 +30,7 @@ services: - "6379:6379" volumes: - $HOME/redis/data/:/data/ - - $HOME/redis/config:/user/local/etc/redis + - $HOME/redis/config/:/user/local/etc/redis/ command: redis-server /user/local/etc/redis/redis.conf healthcheck: test: [ "CMD", "redis-cli", "ping" ] From 45c6dcf7863d34437e91d8fcd09c157f2a40d281 Mon Sep 17 00:00:00 2001 From: SpaceChae <13278955+enaenen@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:10:10 +0900 Subject: [PATCH 0394/1029] =?UTF-8?q?[BE]=20FEAT:=20=EC=97=B0=EC=B2=B4=20?= =?UTF-8?q?=EB=8B=B9=EC=9D=BC=20=EC=95=8C=EB=9E=8C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#1547)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/alarm/config/AlarmProperties.java | 3 + .../domain/LentExpirationImminentAlarm.java | 21 +- .../alarm/handler/AlarmEventHandler.java | 71 +++---- .../alarm/handler/SlackAlarmSender.java | 182 +++++++++--------- config | 2 +- 5 files changed, 148 insertions(+), 131 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java index 453380cd6..4dd3ae0a1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -50,6 +50,9 @@ public class AlarmProperties { @Value("${cabinet.alarm.slack.soonOverdue.template}") private String soonOverdueSlackTemplate; + @Value("${cabinet.alarm.slack.soonOverdue.template-today}") + private String soonOverdueByTodayTemplate; + /*================== extensionIssuance ======================*/ @Value("${cabinet.alarm.mail.extensionIssuance.subject}") private String extensionIssuanceSubject; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index 2ebcbf333..f357b6e6e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -1,12 +1,11 @@ package org.ftclub.cabinet.alarm.domain; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - /** * 대여 만료 임박 알람 */ @@ -15,11 +14,15 @@ @AllArgsConstructor public class LentExpirationImminentAlarm implements Alarm { - private final Long daysAfterFromExpireDate; + private final Long daysAfterFromExpireDate; + + public String getExpirationDateAsString() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime expireDate = now.plusDays(daysAfterFromExpireDate * -1); + return expireDate.format(DateTimeFormatter.ofPattern("YYYY년 MM월 DD일")); + } - public String getExpirationDateAsString() { - LocalDateTime now = LocalDateTime.now(); - LocalDateTime expireDate = now.plusDays(daysAfterFromExpireDate * -1); - return expireDate.format(DateTimeFormatter.ofPattern("YYYY년 MM월 DD일")); - } + public boolean isExpirationToday() { + return daysAfterFromExpireDate == 0L; + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index d555f4fad..135d5c956 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -16,43 +16,44 @@ @Log4j2 public class AlarmEventHandler { - private final UserQueryService userQueryService; - private final SlackAlarmSender slackAlarmSender; - private final EmailAlarmSender emailAlarmSender; - private final PushAlarmSender pushAlarmSender; - private final AlarmProperties alarmProperties; + private final UserQueryService userQueryService; + private final SlackAlarmSender slackAlarmSender; + private final EmailAlarmSender emailAlarmSender; + private final PushAlarmSender pushAlarmSender; + private final AlarmProperties alarmProperties; - @TransactionalEventListener - public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactionalAlarmEvent) { - log.info("handleAlarmEventWithTransactional = {}", transactionalAlarmEvent); - if (!alarmProperties.getIsProduction()) { - log.info("handleAlarmEventWithTransactional is not production"); - return; - } - AlarmEvent alarmEvent = (AlarmEvent) transactionalAlarmEvent; - eventProceed(alarmEvent); - } + @TransactionalEventListener + public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactionalAlarmEvent) { + log.info("handleAlarmEventWithTransactional = {}", transactionalAlarmEvent); + if (!alarmProperties.getIsProduction()) { + log.info("handleAlarmEventWithTransactional is not production"); + return; + } + AlarmEvent alarmEvent = (AlarmEvent) transactionalAlarmEvent; + eventProceed(alarmEvent); + } - @EventListener - public void handleAlarmEvent(AlarmEvent alarmEvent) { - log.info("handleAlarmEvent = {}", alarmEvent); - if (!alarmProperties.getIsProduction()) { - return; - } - eventProceed(alarmEvent); - } + @EventListener + public void handleAlarmEvent(AlarmEvent alarmEvent) { + log.info("handleAlarmEvent = {}", alarmEvent); + if (!alarmProperties.getIsProduction()) { + log.info("handleAlarmEvent is not production"); + return; + } + eventProceed(alarmEvent); + } - private void eventProceed(AlarmEvent alarmEvent) { - User receiver = userQueryService.getUser(alarmEvent.getReceiverId()); + private void eventProceed(AlarmEvent alarmEvent) { + User receiver = userQueryService.getUser(alarmEvent.getReceiverId()); - if (receiver.isSlackAlarm()) { - slackAlarmSender.send(receiver, alarmEvent); - } - if (receiver.isEmailAlarm()) { - emailAlarmSender.send(receiver, alarmEvent); - } - if (receiver.isPushAlarm()) { - pushAlarmSender.send(receiver, alarmEvent); - } - } + if (receiver.isSlackAlarm()) { + slackAlarmSender.send(receiver, alarmEvent); + } + if (receiver.isEmailAlarm()) { + emailAlarmSender.send(receiver, alarmEvent); + } + if (receiver.isPushAlarm()) { + pushAlarmSender.send(receiver, alarmEvent); + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index 91edfd563..e81ff4f20 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -1,107 +1,117 @@ package org.ftclub.cabinet.alarm.handler; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.config.AlarmProperties; -import org.ftclub.cabinet.alarm.domain.*; +import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.alarm.dto.SlackDto; import org.ftclub.cabinet.alarm.slack.SlackApiManager; import org.ftclub.cabinet.alarm.slack.dto.SlackUserInfo; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; - @Log4j2 @Component @RequiredArgsConstructor public class SlackAlarmSender { - private final SlackApiManager slackApiManager; - private final AlarmProperties alarmProperties; - - @Async - public void send(User user, AlarmEvent alarmEvent) { - log.info("slack alarm Event : user = {}, alarmEvent = {}", user.getName(), alarmEvent); - - SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); - String id = slackUserInfo.getId(); - - if (id.isEmpty()) { - throw ExceptionStatus.SLACK_ID_NOT_FOUND.asServiceException(); - } - - SlackDto slackDto = parseMessage(alarmEvent.getAlarm()); - slackApiManager.sendMessage(id, slackDto.getContent()); - } - - - private SlackDto parseMessage(Alarm alarm) { - log.debug("alarm = {}", alarm); - if (alarm instanceof LentSuccessAlarm) { - return generateLentSuccessAlarm((LentSuccessAlarm) alarm); - } else if (alarm instanceof LentExpirationImminentAlarm) { - return generateLentExpirationImminentAlarm((LentExpirationImminentAlarm) alarm); - } else if (alarm instanceof LentExpirationAlarm) { - return generateLentExpirationAlarm((LentExpirationAlarm) alarm); - } else if (alarm instanceof ExtensionIssuanceAlarm) { - return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm); - } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - return generateExtensionExpirationImminent((ExtensionExpirationImminentAlarm) alarm); - } else if (alarm instanceof AnnouncementAlarm) { - return generateAnnouncementAlarm(); - } else { - throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); - } - } - - private SlackDto generateLentSuccessAlarm(LentSuccessAlarm alarm) { - String building = alarm.getLocation().getBuilding(); - Integer floor = alarm.getLocation().getFloor(); - Integer visibleNum = alarm.getVisibleNum(); - String body = String.format(alarmProperties.getLentSuccessSlackTemplate(), - building + " " + floor + "층 " + visibleNum + "번"); - return new SlackDto(body); - } - - private SlackDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm) { - Long daysAfterFromExpireDate = alarm.getDaysAfterFromExpireDate(); - String body = String.format(alarmProperties.getSoonOverdueSlackTemplate(), - Math.abs(daysAfterFromExpireDate)); - return new SlackDto(body); - } - - private SlackDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { - Long daysLeftFromExpireDate = alarm.getDaysLeftFromExpireDate(); - String body = String.format(alarmProperties.getOverdueSlackTemplate(), - Math.abs(daysLeftFromExpireDate)); - return new SlackDto(body); - } - - private SlackDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm) { - Integer daysToExtend = alarm.getDaysToExtend(); - String extensionName = alarm.getExtensionName(); - String body = String.format(alarmProperties.getExtensionIssuanceSlackTemplate(), - daysToExtend, extensionName); - return new SlackDto(body); - } - - private SlackDto generateExtensionExpirationImminent(ExtensionExpirationImminentAlarm alarm) { - String extensionName = alarm.getExtensionName(); - LocalDateTime extensionExpireDate = alarm.getExtensionExpirationDate(); - String body = String.format( - alarmProperties.getExtensionExpirationImminentSlackTemplate(), - extensionName, extensionExpireDate); - return new SlackDto(body); - } - - private SlackDto generateAnnouncementAlarm() { - String body = alarmProperties.getAnnouncementSlackTemplate(); - return new SlackDto(body); - } + private final SlackApiManager slackApiManager; + private final AlarmProperties alarmProperties; + + @Async + public void send(User user, AlarmEvent alarmEvent) { + log.info("slack alarm Event : user = {}, alarmEvent = {}", user.getName(), alarmEvent); + + SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); + String id = slackUserInfo.getId(); + + if (id.isEmpty()) { + throw ExceptionStatus.SLACK_ID_NOT_FOUND.asServiceException(); + } + + SlackDto slackDto = parseMessage(alarmEvent.getAlarm()); + slackApiManager.sendMessage(id, slackDto.getContent()); + } + + + private SlackDto parseMessage(Alarm alarm) { + log.debug("alarm = {}", alarm); + if (alarm instanceof LentSuccessAlarm) { + return generateLentSuccessAlarm((LentSuccessAlarm) alarm); + } else if (alarm instanceof LentExpirationImminentAlarm) { + return generateLentExpirationImminentAlarm((LentExpirationImminentAlarm) alarm); + } else if (alarm instanceof LentExpirationAlarm) { + return generateLentExpirationAlarm((LentExpirationAlarm) alarm); + } else if (alarm instanceof ExtensionIssuanceAlarm) { + return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm); + } else if (alarm instanceof ExtensionExpirationImminentAlarm) { + return generateExtensionExpirationImminent((ExtensionExpirationImminentAlarm) alarm); + } else if (alarm instanceof AnnouncementAlarm) { + return generateAnnouncementAlarm(); + } else { + throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); + } + } + + private SlackDto generateLentSuccessAlarm(LentSuccessAlarm alarm) { + String building = alarm.getLocation().getBuilding(); + Integer floor = alarm.getLocation().getFloor(); + Integer visibleNum = alarm.getVisibleNum(); + String body = String.format(alarmProperties.getLentSuccessSlackTemplate(), + building + " " + floor + "층 " + visibleNum + "번"); + return new SlackDto(body); + } + + private SlackDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm) { + Long daysAfterFromExpireDate = alarm.getDaysAfterFromExpireDate(); + String body; + if (alarm.isExpirationToday()) { + body = alarmProperties.getSoonOverdueByTodayTemplate(); + return new SlackDto(body); + } + body = String.format(alarmProperties.getSoonOverdueSlackTemplate(), + Math.abs(daysAfterFromExpireDate)); + return new SlackDto(body); + } + + private SlackDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { + Long daysLeftFromExpireDate = alarm.getDaysLeftFromExpireDate(); + String body = String.format(alarmProperties.getOverdueSlackTemplate(), + Math.abs(daysLeftFromExpireDate)); + return new SlackDto(body); + } + + private SlackDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm) { + Integer daysToExtend = alarm.getDaysToExtend(); + String extensionName = alarm.getExtensionName(); + String body = String.format(alarmProperties.getExtensionIssuanceSlackTemplate(), + daysToExtend, extensionName); + return new SlackDto(body); + } + + private SlackDto generateExtensionExpirationImminent(ExtensionExpirationImminentAlarm alarm) { + String extensionName = alarm.getExtensionName(); + LocalDateTime extensionExpireDate = alarm.getExtensionExpirationDate(); + String body = String.format( + alarmProperties.getExtensionExpirationImminentSlackTemplate(), + extensionName, extensionExpireDate); + return new SlackDto(body); + } + + private SlackDto generateAnnouncementAlarm() { + String body = alarmProperties.getAnnouncementSlackTemplate(); + return new SlackDto(body); + } } diff --git a/config b/config index ecf58be43..b4eae5f8d 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit ecf58be4311cdfb8eb59597bac7da6f94b37c29e +Subproject commit b4eae5f8d69845b3a5f726d985e5c9628a4128ea From aa3de57a5fbaa0a32c7576e2df09369b746740a0 Mon Sep 17 00:00:00 2001 From: Jiwon Park <82518170+Z1Park@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:11:49 +0900 Subject: [PATCH 0395/1029] DEV TO MAIN (#1548) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FE] FEAT: 사용가능페이지 핕터위치 변경 및 토글 선택범위 확대 * [FE] FEAT: 새로고침 버튼과 타이머 합침 * [FE] FIX: PENDING 상태의 사물함 border, shadow 수정 #1528 * [FE] FIX: 토글 선택범위 cursor 변경 * [FE] FIX: 토글 커서 변환 확대 * [BE] HOTFIX: 이전에 블랙홀이었던 유저가 대여시도시, 새로고침 로직 추가 * [BE] HOTFIX: 연장권 안써짐 이슈 -> @Transactional * merge * [BE] 리팩토링 이후 트랜잭션 적용 안된 부분 적용 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 * [COMMON] FEAT: 로그인 후 처리 동작 관련 개선사항 (#1527) * [FE] FEAT: PostLogin 페이지 추가 (임시) * [COMMON] FIX: 로그인 후 콜백 주소를 프론트에서 결정하도록 변경 * [FE] FEAT: login auth_page 디자인 변경 --------- Co-authored-by: moonseonghui * [BE] FEAT: 반납일 7,3,1 일전 미리 알람주도록 기능 추가#1518 (#1534) * [BE] Redis 이벤트 관련 클래스 위치 이벤트 폴더로 이동 * [BE] 연체 전후 알림 3일, 7일차 추가 및 properties 정리 * [BE] 프로퍼티 이름 수정 * [BE] 연체 시 매일 알람 보내도록 수정 --------- Co-authored-by: jiwon * [FE] FEAT: 플레이 스토어 출시를 위한 assetlinks.json 파일 추가 * [HOTFIX] pending NPE 수정 * [BE]FIX: config before * [BE] TEST: alarm test * [BE] HOTFIX: 대여기록 조회 시 과거부터 10개 조회해오는 오류 수정 * [BE] HOTFIX: Redis 백업 기능 main, dev에서 동작 안되는 오류 수정 * [BE] HOTFIX: Redis 백업 기능 설정 파일 -> 디렉토리 변경 * [BE] FEAT: 연체 당일 알람 추가 (#1547) --------- Co-authored-by: Minkyu01 Co-authored-by: jnkeniaem Co-authored-by: jusohn Co-authored-by: space Co-authored-by: jiwon Co-authored-by: Gyeonga Koh <114395888+gykoh42@users.noreply.github.com> Co-authored-by: Siwon Choi <83565255+sichoi42@users.noreply.github.com> Co-authored-by: moonseonghui Co-authored-by: sichoi42 <42.4.sichoi@gmail.com> Co-authored-by: SpaceChae <13278955+enaenen@users.noreply.github.com> --- backend/docker-compose.yaml | 8 +- .../cabinet/alarm/config/AlarmProperties.java | 3 + .../domain/LentExpirationImminentAlarm.java | 21 +- .../alarm/handler/AlarmEventHandler.java | 71 +++---- .../alarm/handler/SlackAlarmSender.java | 182 +++++++++--------- .../lent/repository/LentRepository.java | 3 +- .../lent/service/LentFacadeService.java | 6 +- .../lent/service/LentQueryService.java | 2 +- .../ftclub/cabinet/ping/PingController.java | 33 ++-- .../lent/repository/LentRepositoryTest.java | 5 +- config | 2 +- .../pinpoint-application/docker-compose.yml | 2 +- .../pinpoint-application/docker-compose.yml | 2 +- 13 files changed, 179 insertions(+), 161 deletions(-) diff --git a/backend/docker-compose.yaml b/backend/docker-compose.yaml index 8e4ec2c2a..04f5229a1 100644 --- a/backend/docker-compose.yaml +++ b/backend/docker-compose.yaml @@ -14,12 +14,6 @@ services: ports: - "3307:3306" tty: true -# redis: -# container_name: redis -# image: redis:latest -# ports: -# - '6379:6379' -# tty: true gateway: container_name: nginx_gateway image: nginx:latest @@ -34,7 +28,7 @@ services: - "6379:6379" volumes: - ../redis/data/:/data/ - - ../redis/redis.conf:/user/local/etc/redis/redis.conf + - ../redis/config/:/user/local/etc/redis/ command: redis-server /user/local/etc/redis/redis.conf healthcheck: test: [ "CMD", "redis-cli", "ping" ] diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java index 453380cd6..4dd3ae0a1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/config/AlarmProperties.java @@ -50,6 +50,9 @@ public class AlarmProperties { @Value("${cabinet.alarm.slack.soonOverdue.template}") private String soonOverdueSlackTemplate; + @Value("${cabinet.alarm.slack.soonOverdue.template-today}") + private String soonOverdueByTodayTemplate; + /*================== extensionIssuance ======================*/ @Value("${cabinet.alarm.mail.extensionIssuance.subject}") private String extensionIssuanceSubject; diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index 2ebcbf333..f357b6e6e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -1,12 +1,11 @@ package org.ftclub.cabinet.alarm.domain; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - /** * 대여 만료 임박 알람 */ @@ -15,11 +14,15 @@ @AllArgsConstructor public class LentExpirationImminentAlarm implements Alarm { - private final Long daysAfterFromExpireDate; + private final Long daysAfterFromExpireDate; + + public String getExpirationDateAsString() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime expireDate = now.plusDays(daysAfterFromExpireDate * -1); + return expireDate.format(DateTimeFormatter.ofPattern("YYYY년 MM월 DD일")); + } - public String getExpirationDateAsString() { - LocalDateTime now = LocalDateTime.now(); - LocalDateTime expireDate = now.plusDays(daysAfterFromExpireDate * -1); - return expireDate.format(DateTimeFormatter.ofPattern("YYYY년 MM월 DD일")); - } + public boolean isExpirationToday() { + return daysAfterFromExpireDate == 0L; + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java index d555f4fad..135d5c956 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/AlarmEventHandler.java @@ -16,43 +16,44 @@ @Log4j2 public class AlarmEventHandler { - private final UserQueryService userQueryService; - private final SlackAlarmSender slackAlarmSender; - private final EmailAlarmSender emailAlarmSender; - private final PushAlarmSender pushAlarmSender; - private final AlarmProperties alarmProperties; + private final UserQueryService userQueryService; + private final SlackAlarmSender slackAlarmSender; + private final EmailAlarmSender emailAlarmSender; + private final PushAlarmSender pushAlarmSender; + private final AlarmProperties alarmProperties; - @TransactionalEventListener - public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactionalAlarmEvent) { - log.info("handleAlarmEventWithTransactional = {}", transactionalAlarmEvent); - if (!alarmProperties.getIsProduction()) { - log.info("handleAlarmEventWithTransactional is not production"); - return; - } - AlarmEvent alarmEvent = (AlarmEvent) transactionalAlarmEvent; - eventProceed(alarmEvent); - } + @TransactionalEventListener + public void handleAlarmEventWithTransactional(TransactionalAlarmEvent transactionalAlarmEvent) { + log.info("handleAlarmEventWithTransactional = {}", transactionalAlarmEvent); + if (!alarmProperties.getIsProduction()) { + log.info("handleAlarmEventWithTransactional is not production"); + return; + } + AlarmEvent alarmEvent = (AlarmEvent) transactionalAlarmEvent; + eventProceed(alarmEvent); + } - @EventListener - public void handleAlarmEvent(AlarmEvent alarmEvent) { - log.info("handleAlarmEvent = {}", alarmEvent); - if (!alarmProperties.getIsProduction()) { - return; - } - eventProceed(alarmEvent); - } + @EventListener + public void handleAlarmEvent(AlarmEvent alarmEvent) { + log.info("handleAlarmEvent = {}", alarmEvent); + if (!alarmProperties.getIsProduction()) { + log.info("handleAlarmEvent is not production"); + return; + } + eventProceed(alarmEvent); + } - private void eventProceed(AlarmEvent alarmEvent) { - User receiver = userQueryService.getUser(alarmEvent.getReceiverId()); + private void eventProceed(AlarmEvent alarmEvent) { + User receiver = userQueryService.getUser(alarmEvent.getReceiverId()); - if (receiver.isSlackAlarm()) { - slackAlarmSender.send(receiver, alarmEvent); - } - if (receiver.isEmailAlarm()) { - emailAlarmSender.send(receiver, alarmEvent); - } - if (receiver.isPushAlarm()) { - pushAlarmSender.send(receiver, alarmEvent); - } - } + if (receiver.isSlackAlarm()) { + slackAlarmSender.send(receiver, alarmEvent); + } + if (receiver.isEmailAlarm()) { + emailAlarmSender.send(receiver, alarmEvent); + } + if (receiver.isPushAlarm()) { + pushAlarmSender.send(receiver, alarmEvent); + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index 91edfd563..e81ff4f20 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -1,107 +1,117 @@ package org.ftclub.cabinet.alarm.handler; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.config.AlarmProperties; -import org.ftclub.cabinet.alarm.domain.*; +import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.alarm.dto.SlackDto; import org.ftclub.cabinet.alarm.slack.SlackApiManager; import org.ftclub.cabinet.alarm.slack.dto.SlackUserInfo; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.exception.ServiceException; import org.ftclub.cabinet.user.domain.User; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; - @Log4j2 @Component @RequiredArgsConstructor public class SlackAlarmSender { - private final SlackApiManager slackApiManager; - private final AlarmProperties alarmProperties; - - @Async - public void send(User user, AlarmEvent alarmEvent) { - log.info("slack alarm Event : user = {}, alarmEvent = {}", user.getName(), alarmEvent); - - SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); - String id = slackUserInfo.getId(); - - if (id.isEmpty()) { - throw ExceptionStatus.SLACK_ID_NOT_FOUND.asServiceException(); - } - - SlackDto slackDto = parseMessage(alarmEvent.getAlarm()); - slackApiManager.sendMessage(id, slackDto.getContent()); - } - - - private SlackDto parseMessage(Alarm alarm) { - log.debug("alarm = {}", alarm); - if (alarm instanceof LentSuccessAlarm) { - return generateLentSuccessAlarm((LentSuccessAlarm) alarm); - } else if (alarm instanceof LentExpirationImminentAlarm) { - return generateLentExpirationImminentAlarm((LentExpirationImminentAlarm) alarm); - } else if (alarm instanceof LentExpirationAlarm) { - return generateLentExpirationAlarm((LentExpirationAlarm) alarm); - } else if (alarm instanceof ExtensionIssuanceAlarm) { - return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm); - } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - return generateExtensionExpirationImminent((ExtensionExpirationImminentAlarm) alarm); - } else if (alarm instanceof AnnouncementAlarm) { - return generateAnnouncementAlarm(); - } else { - throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); - } - } - - private SlackDto generateLentSuccessAlarm(LentSuccessAlarm alarm) { - String building = alarm.getLocation().getBuilding(); - Integer floor = alarm.getLocation().getFloor(); - Integer visibleNum = alarm.getVisibleNum(); - String body = String.format(alarmProperties.getLentSuccessSlackTemplate(), - building + " " + floor + "층 " + visibleNum + "번"); - return new SlackDto(body); - } - - private SlackDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm) { - Long daysAfterFromExpireDate = alarm.getDaysAfterFromExpireDate(); - String body = String.format(alarmProperties.getSoonOverdueSlackTemplate(), - Math.abs(daysAfterFromExpireDate)); - return new SlackDto(body); - } - - private SlackDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { - Long daysLeftFromExpireDate = alarm.getDaysLeftFromExpireDate(); - String body = String.format(alarmProperties.getOverdueSlackTemplate(), - Math.abs(daysLeftFromExpireDate)); - return new SlackDto(body); - } - - private SlackDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm) { - Integer daysToExtend = alarm.getDaysToExtend(); - String extensionName = alarm.getExtensionName(); - String body = String.format(alarmProperties.getExtensionIssuanceSlackTemplate(), - daysToExtend, extensionName); - return new SlackDto(body); - } - - private SlackDto generateExtensionExpirationImminent(ExtensionExpirationImminentAlarm alarm) { - String extensionName = alarm.getExtensionName(); - LocalDateTime extensionExpireDate = alarm.getExtensionExpirationDate(); - String body = String.format( - alarmProperties.getExtensionExpirationImminentSlackTemplate(), - extensionName, extensionExpireDate); - return new SlackDto(body); - } - - private SlackDto generateAnnouncementAlarm() { - String body = alarmProperties.getAnnouncementSlackTemplate(); - return new SlackDto(body); - } + private final SlackApiManager slackApiManager; + private final AlarmProperties alarmProperties; + + @Async + public void send(User user, AlarmEvent alarmEvent) { + log.info("slack alarm Event : user = {}, alarmEvent = {}", user.getName(), alarmEvent); + + SlackUserInfo slackUserInfo = slackApiManager.requestSlackUserInfo(user.getEmail()); + String id = slackUserInfo.getId(); + + if (id.isEmpty()) { + throw ExceptionStatus.SLACK_ID_NOT_FOUND.asServiceException(); + } + + SlackDto slackDto = parseMessage(alarmEvent.getAlarm()); + slackApiManager.sendMessage(id, slackDto.getContent()); + } + + + private SlackDto parseMessage(Alarm alarm) { + log.debug("alarm = {}", alarm); + if (alarm instanceof LentSuccessAlarm) { + return generateLentSuccessAlarm((LentSuccessAlarm) alarm); + } else if (alarm instanceof LentExpirationImminentAlarm) { + return generateLentExpirationImminentAlarm((LentExpirationImminentAlarm) alarm); + } else if (alarm instanceof LentExpirationAlarm) { + return generateLentExpirationAlarm((LentExpirationAlarm) alarm); + } else if (alarm instanceof ExtensionIssuanceAlarm) { + return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm); + } else if (alarm instanceof ExtensionExpirationImminentAlarm) { + return generateExtensionExpirationImminent((ExtensionExpirationImminentAlarm) alarm); + } else if (alarm instanceof AnnouncementAlarm) { + return generateAnnouncementAlarm(); + } else { + throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); + } + } + + private SlackDto generateLentSuccessAlarm(LentSuccessAlarm alarm) { + String building = alarm.getLocation().getBuilding(); + Integer floor = alarm.getLocation().getFloor(); + Integer visibleNum = alarm.getVisibleNum(); + String body = String.format(alarmProperties.getLentSuccessSlackTemplate(), + building + " " + floor + "층 " + visibleNum + "번"); + return new SlackDto(body); + } + + private SlackDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm) { + Long daysAfterFromExpireDate = alarm.getDaysAfterFromExpireDate(); + String body; + if (alarm.isExpirationToday()) { + body = alarmProperties.getSoonOverdueByTodayTemplate(); + return new SlackDto(body); + } + body = String.format(alarmProperties.getSoonOverdueSlackTemplate(), + Math.abs(daysAfterFromExpireDate)); + return new SlackDto(body); + } + + private SlackDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { + Long daysLeftFromExpireDate = alarm.getDaysLeftFromExpireDate(); + String body = String.format(alarmProperties.getOverdueSlackTemplate(), + Math.abs(daysLeftFromExpireDate)); + return new SlackDto(body); + } + + private SlackDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm) { + Integer daysToExtend = alarm.getDaysToExtend(); + String extensionName = alarm.getExtensionName(); + String body = String.format(alarmProperties.getExtensionIssuanceSlackTemplate(), + daysToExtend, extensionName); + return new SlackDto(body); + } + + private SlackDto generateExtensionExpirationImminent(ExtensionExpirationImminentAlarm alarm) { + String extensionName = alarm.getExtensionName(); + LocalDateTime extensionExpireDate = alarm.getExtensionExpirationDate(); + String body = String.format( + alarmProperties.getExtensionExpirationImminentSlackTemplate(), + extensionName, extensionExpireDate); + return new SlackDto(body); + } + + private SlackDto generateAnnouncementAlarm() { + String body = alarmProperties.getAnnouncementSlackTemplate(); + return new SlackDto(body); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index c0b957e7e..f28554b41 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -190,7 +190,8 @@ Page findPaginationByCabinetIdJoinCabinetAndUser( * @param pageable pagination 정보 * @return {@link LentHistory}의 {@link Page} */ - Page findPaginationByUserId(@Param("userId") Long userId, Pageable pageable); + Page findPaginationByUserIdOrderByStartedAtDesc( + @Param("userId") Long userId, Pageable pageable); /** * 특정 사물함의 아직 반납하지 않은 대여기록들를 모두 가져옵니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index ba02a2e50..31ccce58e 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; -import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -71,10 +70,9 @@ public class LentFacadeService { */ @Transactional(readOnly = true) public LentHistoryPaginationDto getMyLentLog(UserSessionDto user, Pageable pageable) { - Page lentHistories = lentQueryService.findUserLentHistories(user.getUserId(), - pageable); + Page lentHistories = + lentQueryService.findUserLentHistories(user.getUserId(), pageable); List result = lentHistories.stream() - .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) .map(lentHistory -> lentMapper.toLentHistoryDto(lentHistory, lentHistory.getUser(), lentHistory.getCabinet())).collect(Collectors.toList()); return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 3b9aab813..696a857c2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -33,7 +33,7 @@ public class LentQueryService { * @return 유저가 빌렸던 사물함의 대여 기록 {@link Page} */ public Page findUserLentHistories(Long userId, Pageable pageable) { - return lentRepository.findPaginationByUserId(userId, pageable); + return lentRepository.findPaginationByUserIdOrderByStartedAtDesc(userId, pageable); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java b/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java index 887ed7482..9793f97f7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java +++ b/backend/src/main/java/org/ftclub/cabinet/ping/PingController.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.utils.overdue.manager.OverdueManager; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -10,21 +12,26 @@ @RequiredArgsConstructor @Log4j2 public class PingController { - // private final LentExtensionService lentExtensionService; -// private final OverdueManager overdueManager; - @RequestMapping - public String ping() { - return "pong"; - } + private final LentFacadeService lentFacadeService; + private final OverdueManager overdueManager; -// @RequestMapping("/pong") -// public String ok() { -// log.debug("called ok"); -// ActiveLentHistoryDto wchae = new ActiveLentHistoryDto(94L, "wchae", "wchae@student.42seoul.kr", 1L, false, -1L); -// overdueManager.handleOverdue(wchae); -// return "ok"; -// } + @RequestMapping + public String ping() { + return "pong"; + } + +// @RequestMapping("/pong") +// public String ok() { +// log.debug("called ok"); +// List activeLents = lentFacadeService.getAllActiveLentHistories(); +// //activeLents 에서 wchae를 찾아서 overdueManager.handleOverdue(wchae) 호출 +// ActiveLentHistoryDto wchae = activeLents.stream() +// .filter(lent -> lent.getName().equals("wchae")).findFirst().get(); +//// ActiveLentHistoryDto wchae = new ActiveLentHistoryDto(94L, "wchae", "wchae@student.42seoul.kr", 1L, false, -1L); +// overdueManager.handleOverdue(wchae); +// return "ok"; +// } // @RequestMapping("/pong") // public String ok(){ diff --git a/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java b/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java index 78f839934..898a7e2a4 100644 --- a/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/lent/repository/LentRepositoryTest.java @@ -62,13 +62,14 @@ void findFirstByUserIdAndEndedAtIsNull() { void findByUserId() { // 빌린 기록이 없는 user id long userId = 18L; - List lentHistories = lentRepository.findPaginationByUserId(userId, + List lentHistories = lentRepository.findPaginationByUserIdOrderByStartedAtDesc( + userId, PageRequest.of(0, 1)).toList(); assertTrue(lentHistories.isEmpty()); // 빌린 기록이 12개 있는 user id userId = 5L; - lentHistories = lentRepository.findPaginationByUserId(userId, + lentHistories = lentRepository.findPaginationByUserIdOrderByStartedAtDesc(userId, PageRequest.of(0, 20)).toList(); assertEquals(12, lentHistories.size()); } diff --git a/config b/config index 88b806001..b4eae5f8d 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 88b80600192796cd4a9c506b1f80aacf3ef4ae0a +Subproject commit b4eae5f8d69845b3a5f726d985e5c9628a4128ea diff --git a/deploy-dev/pinpoint-application/docker-compose.yml b/deploy-dev/pinpoint-application/docker-compose.yml index 8460d61da..c95c98b90 100644 --- a/deploy-dev/pinpoint-application/docker-compose.yml +++ b/deploy-dev/pinpoint-application/docker-compose.yml @@ -30,7 +30,7 @@ services: - "6379:6379" volumes: - $HOME/redis/data/:/data/ - - $HOME/redis/redis.conf:/user/local/etc/redis/redis.conf + - $HOME/redis/config/:/user/local/etc/redis/ command: redis-server /user/local/etc/redis/redis.conf healthcheck: test: [ "CMD", "redis-cli", "ping" ] diff --git a/deploy-main/pinpoint-application/docker-compose.yml b/deploy-main/pinpoint-application/docker-compose.yml index 204899039..95942d7fc 100644 --- a/deploy-main/pinpoint-application/docker-compose.yml +++ b/deploy-main/pinpoint-application/docker-compose.yml @@ -30,7 +30,7 @@ services: - "6379:6379" volumes: - $HOME/redis/data/:/data/ - - $HOME/redis/redis.conf:/user/local/etc/redis/redis.conf + - $HOME/redis/config/:/user/local/etc/redis/ command: redis-server /user/local/etc/redis/redis.conf healthcheck: test: [ "CMD", "redis-cli", "ping" ] From 65ff87e9a9c41febaeee8333728fc9637c58e22a Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Thu, 8 Feb 2024 19:43:56 +0900 Subject: [PATCH 0396/1029] [BE] redis config added --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index ecf58be43..e49af2983 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit ecf58be4311cdfb8eb59597bac7da6f94b37c29e +Subproject commit e49af298315cc91c429d79e147970f160b420b14 From 829b2060579cff4ac586d8e0354a9bc054c65fc9 Mon Sep 17 00:00:00 2001 From: jusohn Date: Fri, 9 Feb 2024 15:34:19 +0900 Subject: [PATCH 0397/1029] =?UTF-8?q?[FE]=20FIX:=20/lent/me=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=EC=8B=9C=20myCabinetInfoState=20=EA=B0=80=20undefi?= =?UTF-8?q?ned=20=EC=9D=BC=EB=95=8C=EB=A5=BC=20=EA=B3=A0=EB=A0=A4=ED=95=B4?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4=EB=AC=B8=20=EC=88=98=EC=A0=95=20#1549?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CabinetInfoArea/CabinetInfoArea.container.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx index 258678b7f..b3c494081 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx @@ -233,7 +233,7 @@ const CabinetInfoAreaContainer = (): JSX.Element => { : null; const openModal = (modalName: TModalState) => { - if (modalName === "lentModal" && myCabinetInfo.cabinetId) { + if (modalName === "lentModal" && myCabinetInfo?.cabinetId) { modalName = "unavailableModal"; } else if ( modalName === "returnModal" && @@ -330,17 +330,17 @@ const CabinetInfoAreaContainer = (): JSX.Element => { isAvailable={ (cabinetViewData?.status === "AVAILABLE" || cabinetViewData?.status === "IN_SESSION") && - !myCabinetInfo.cabinetId + !myCabinetInfo?.cabinetId } isExtensible={!!myInfo.lentExtensionResponseDto && !myInfo.unbannedAt} userModal={userModal} openModal={openModal} closeModal={closeModal} isSwappable={ - myCabinetInfo.lentType === CabinetType.PRIVATE && - !!myCabinetInfo.cabinetId && + myCabinetInfo?.lentType === CabinetType.PRIVATE && + !!myCabinetInfo?.cabinetId && cabinetViewData?.lentType === CabinetType.PRIVATE && - cabinetViewData?.cabinetId !== myCabinetInfo.cabinetId && + cabinetViewData?.cabinetId !== myCabinetInfo?.cabinetId && cabinetViewData?.status === CabinetStatus.AVAILABLE } /> From 62ce996579f738d05074bc1546d1617aff6a4e64 Mon Sep 17 00:00:00 2001 From: space Date: Fri, 9 Feb 2024 19:52:11 +0900 Subject: [PATCH 0398/1029] [BE] config fixed --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index e49af2983..5b0b7baa2 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit e49af298315cc91c429d79e147970f160b420b14 +Subproject commit 5b0b7baa2ce65b39bc4a49075ad00b76a7d495cc From 32d2311ab2dbb600cc621a11b940d4faa0a54020 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sun, 11 Feb 2024 18:25:20 +0900 Subject: [PATCH 0399/1029] =?UTF-8?q?[BE]=20HOTFIX:=20Admin=20=EC=82=AC?= =?UTF-8?q?=EB=AC=BC=ED=95=A8=20=EB=8C=80=EC=97=AC=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EA=B3=BC=EA=B1=B0=EB=B6=80?= =?UTF-8?q?=ED=84=B0=20=EB=B3=B4=EC=9D=B4=EB=8A=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/cabinet/service/CabinetFacadeService.java | 4 +++- .../org/ftclub/cabinet/lent/repository/LentRepository.java | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index a84ae7200..68aa48a2b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.cabinet.service; +<<<<<<< Updated upstream import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toMap; @@ -11,6 +12,8 @@ import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; +======= +>>>>>>> Stashed changes import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -264,7 +267,6 @@ public LentHistoryPaginationDto getLentHistoryPagination(Long cabinetId, Pageabl Page lentHistories = lentQueryService.findCabinetLentHistoriesWithUserAndCabinet( cabinetId, pageable); List result = lentHistories.stream() - .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) .collect(Collectors.toList()); return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index f28554b41..20bcda0a2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -173,7 +173,13 @@ int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDat + "FROM LentHistory lh " + "LEFT JOIN FETCH lh.user u " + "LEFT JOIN FETCH lh.cabinet c " +<<<<<<< Updated upstream + "WHERE lh.cabinetId = :cabinetId ", +======= + + "LEFT JOIN FETCH c.cabinetPlace cp " + + "WHERE lh.cabinetId = :cabinetId " + + "ORDER BY lh.startedAt DESC", +>>>>>>> Stashed changes countQuery = "SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.cabinetId = :cabinetId ") From 5019ee0f7970f3e9348b10a9e7236b60209a3713 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sun, 11 Feb 2024 18:30:10 +0900 Subject: [PATCH 0400/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftclub/cabinet/cabinet/service/CabinetFacadeService.java | 5 ++--- .../org/ftclub/cabinet/lent/repository/LentRepository.java | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index a84ae7200..b59159168 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -261,10 +261,9 @@ public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, */ @Transactional(readOnly = true) public LentHistoryPaginationDto getLentHistoryPagination(Long cabinetId, Pageable pageable) { - Page lentHistories = lentQueryService.findCabinetLentHistoriesWithUserAndCabinet( - cabinetId, pageable); + Page lentHistories = + lentQueryService.findCabinetLentHistoriesWithUserAndCabinet(cabinetId, pageable); List result = lentHistories.stream() - .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) .collect(Collectors.toList()); return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index f28554b41..21105e2f4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -173,7 +173,8 @@ int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDat + "FROM LentHistory lh " + "LEFT JOIN FETCH lh.user u " + "LEFT JOIN FETCH lh.cabinet c " - + "WHERE lh.cabinetId = :cabinetId ", + + "WHERE lh.cabinetId = :cabinetId " + + "ORDER BY lh.startedAt DESC", countQuery = "SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.cabinetId = :cabinetId ") From 6f2ceb99a60e104ae7c261308e8c480eddb7bb54 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sun, 11 Feb 2024 18:32:35 +0900 Subject: [PATCH 0401/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95(git=20=EC=9D=B4=EC=83=81=ED=95=98?= =?UTF-8?q?=EB=84=A4=20=EC=99=9C=20=EC=9E=90=EA=BE=B8=20=EC=9D=B4=20?= =?UTF-8?q?=EB=AA=A8=EC=96=91=EC=9C=BC=EB=A1=9C=20merge=ED=95=98=EC=A7=80.?= =?UTF-8?q?..)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftclub/cabinet/cabinet/service/CabinetFacadeService.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index 692731b04..b59159168 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -1,6 +1,5 @@ package org.ftclub.cabinet.cabinet.service; -<<<<<<< Updated upstream import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toMap; @@ -12,8 +11,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; -======= ->>>>>>> Stashed changes import java.util.List; import java.util.Map; import java.util.stream.Collectors; From 59956df8d7588787fe39a6e1219c3e9a93b99eac Mon Sep 17 00:00:00 2001 From: jiwon Date: Mon, 12 Feb 2024 17:07:22 +0900 Subject: [PATCH 0402/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EA=B3=B5=EC=9C=A0?= =?UTF-8?q?=20=EC=82=AC=EB=AC=BC=ED=95=A8=20=EB=8C=80=EC=97=AC=20=ED=9B=84?= =?UTF-8?q?=20=EB=A7=88=EC=A7=80=EB=A7=89=20=EC=82=AC=EB=9E=8C=EC=9D=B4=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EC=8B=9C=20IN=5FSESSION=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=9C=A0=EC=A7=80=EB=90=98=EB=8A=94=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ftclub/cabinet/lent/service/LentFacadeService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 31ccce58e..b9f4549f4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -315,7 +315,7 @@ public void updateLentCabinetInfo(Long userId, String title, String memo) { @Transactional public void cancelShareCabinetLent(Long userId, Long cabinetId) { lentRedisService.deleteUserInCabinet(cabinetId, userId); - if (lentRedisService.isInCabinetSession(cabinetId)) { + if (!lentRedisService.isInCabinetSession(cabinetId)) { Cabinet cabinet = cabinetQueryService.getCabinetForUpdate(cabinetId); cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); } From 4f10bdc895c89be73d2a741978d89a58d0c6607d Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 13 Feb 2024 15:39:37 +0900 Subject: [PATCH 0403/1029] =?UTF-8?q?[FE]:=20=EC=8B=9C=EC=8A=A4=ED=85=9C?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=EC=9D=B4=EB=82=98=20=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=20=EB=88=84=EB=A5=B4=EB=A9=B4=20=EB=B0=B0=EA=B2=BD=EC=83=89=20?= =?UTF-8?q?=EB=B0=94=EB=80=9C#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SectionPagination/SectionPagination.tsx | 2 +- .../components/TopNav/DarkMode/DarkMode.tsx | 21 ++++++ frontend/src/components/TopNav/TopNav.tsx | 5 +- frontend/src/index.css | 5 ++ frontend/src/pages/Layout.tsx | 74 +++++++++++-------- frontend/src/recoil/atoms.ts | 5 ++ 6 files changed, 79 insertions(+), 33 deletions(-) create mode 100644 frontend/src/components/TopNav/DarkMode/DarkMode.tsx diff --git a/frontend/src/components/SectionPagination/SectionPagination.tsx b/frontend/src/components/SectionPagination/SectionPagination.tsx index 2e42cedd5..a5dc2b578 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.tsx +++ b/frontend/src/components/SectionPagination/SectionPagination.tsx @@ -59,7 +59,7 @@ const SectionPaginationStyled = styled.div` padding: 10px 0; position: sticky; top: 0; - background: rgba(255, 255, 255, 0.95); + /* background: rgba(255, 255, 255, 0.95); */ z-index: 1; `; diff --git a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx new file mode 100644 index 000000000..501074b05 --- /dev/null +++ b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx @@ -0,0 +1,21 @@ +import { useRecoilState } from "recoil"; +import { darkModeState } from "@/recoil/atoms"; + +const DarkMode: React.FC<{}> = () => { + const [test, setTest] = useRecoilState(darkModeState); + console.log(test); + + return ( + <> + + + ); +}; + +export default DarkMode; diff --git a/frontend/src/components/TopNav/TopNav.tsx b/frontend/src/components/TopNav/TopNav.tsx index b8c130dc3..4e427c00f 100644 --- a/frontend/src/components/TopNav/TopNav.tsx +++ b/frontend/src/components/TopNav/TopNav.tsx @@ -1,6 +1,7 @@ import React from "react"; import { SetterOrUpdater } from "recoil"; import styled from "styled-components"; +import DarkMode from "@/components/TopNav/DarkMode/DarkMode"; import SearchBar from "@/components/TopNav/SearchBar/SearchBar"; import TopNavButtonGroup from "@/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; @@ -79,6 +80,8 @@ const TopNav: React.FC<{ + + {/* TODO : 임시 위치 */} {isAdmin && } @@ -92,7 +95,7 @@ const TopNavContainerStyled = styled.nav` display: flex; justify-content: space-between; align-items: center; - background-color: white; + /* background-color: white; */ border-bottom: 1px solid #bcbcbc; padding: 0 28px; color: var(--gray-color); diff --git a/frontend/src/index.css b/frontend/src/index.css index 97573b293..a8ca6b616 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -69,6 +69,11 @@ body { display: flex; flex-direction: column; overflow: hidden; + background-color: var(--white); + + @media (prefers-color-scheme: dark) { + /* background-color: var(--black); */ + } } h1 { diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index c2b5dd6e3..3ffe2c11d 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -1,10 +1,9 @@ import React, { useEffect, useState } from "react"; -import { set } from "react-ga"; import { Outlet } from "react-router"; import { useLocation, useNavigate } from "react-router-dom"; -import { useSetRecoilState } from "recoil"; +import { useRecoilValue, useSetRecoilState } from "recoil"; import styled, { css } from "styled-components"; -import { serverTimeState, userState } from "@/recoil/atoms"; +import { darkModeState, serverTimeState, userState } from "@/recoil/atoms"; import CabinetInfoAreaContainer from "@/components/CabinetInfoArea/CabinetInfoArea.container"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; import LeftNav from "@/components/LeftNav/LeftNav"; @@ -13,7 +12,6 @@ import OverduePenaltyModal from "@/components/Modals/OverduePenaltyModal/Overdue import TopNav from "@/components/TopNav/TopNav.container"; import { additionalModalType } from "@/assets/data/maps"; import { UserDto, UserInfo } from "@/types/dto/user.dto"; -import ColorType from "@/types/enum/color.type.enum"; import { axiosMyInfo } from "@/api/axios/axios.custom"; import { getCookie } from "@/api/react_cookie/cookies"; import useMenu from "@/hooks/useMenu"; @@ -25,6 +23,7 @@ const Layout = (): JSX.Element => { const [myInfoData, setMyInfoData] = useState(null); const setServerTime = useSetRecoilState(serverTimeState); const setUser = useSetRecoilState(userState); + const darkMode = useRecoilValue(darkModeState); const navigate = useNavigate(); const location = useLocation(); const token = getCookie("access_token"); @@ -98,38 +97,51 @@ const Layout = (): JSX.Element => { ) : ( - {isValidToken && } - {isLoading ? ( - - ) : ( - - - - - - - - - - - {isModalOpen && myInfoData && myInfoData.unbannedAt !== undefined && ( - - )} - - )} + + {isValidToken && } + {isLoading ? ( + + ) : ( + + + + + + + + + + + {isModalOpen && + myInfoData && + myInfoData.unbannedAt !== undefined && ( + + )} + + )} + ); }; export default Layout; +const ContainerStyled = styled.div<{ darkMode: string }>` + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; + background-color: ${(props) => props.darkMode}; +`; + const WrapperStyled = styled.div` width: 100%; height: 100%; @@ -149,7 +161,7 @@ const DetailInfoContainerStyled = styled.div<{ isHomePage: boolean }>` padding: 45px 40px 20px; position: relative; border-left: 1px solid var(--line-color); - background-color: var(--white); + /* background-color: var(--white); */ overflow-y: auto; ${(props) => props.isHomePage && diff --git a/frontend/src/recoil/atoms.ts b/frontend/src/recoil/atoms.ts index acf45855a..2a617f1b9 100644 --- a/frontend/src/recoil/atoms.ts +++ b/frontend/src/recoil/atoms.ts @@ -169,3 +169,8 @@ export const serverTimeState = atom({ key: "serverTime", default: new Date(), }); + +export const darkModeState = atom({ + key: "darkMode", + default: "black", +}); From c86be8d6b1d7774bfdb3fdda30fdabfada561dca Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 13 Feb 2024 16:00:47 +0900 Subject: [PATCH 0404/1029] =?UTF-8?q?[FE]:=20white,=20gray,=20black=20?= =?UTF-8?q?=EC=83=89=20=EB=B3=80=EC=88=98=EB=93=A4=20=EC=A0=95=EB=A6=AC#15?= =?UTF-8?q?51?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/TopNav/DarkMode/DarkMode.tsx | 2 +- frontend/src/index.css | 18 ++++-- frontend/src/pages/Layout.tsx | 58 +++++++++---------- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx index 501074b05..864bc1fd0 100644 --- a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx +++ b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx @@ -3,7 +3,7 @@ import { darkModeState } from "@/recoil/atoms"; const DarkMode: React.FC<{}> = () => { const [test, setTest] = useRecoilState(darkModeState); - console.log(test); + // console.log(test); return ( <> diff --git a/frontend/src/index.css b/frontend/src/index.css index a8ca6b616..1742decee 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -2,13 +2,9 @@ :root { /* color variable */ - --black: #181818; - --white: #ffffff; --main-color: #9747ff; --sub-color: #b18cff; --line-color: #bcbcbc; - --gray-color: #7b7b7b; - --lightgray-color: #f5f5f5; --bg-shadow: rgba(0, 0, 0, 0.4); /* cabinet color variable */ @@ -25,6 +21,18 @@ --default-sub-color: #b18cff; --default-mine-color: #47ffa7; + /* white, gray, black color variable */ + --white: #ffffff; + /* 1 */ + --lightgray-color: #f5f5f5; + /* 4 */ + --gray-color: #7b7b7b; + /* 7 */ + --darkgray-color: #434343; + /* 9 */ + --black: #181818; + /* 11 */ + /* font variable */ --main-font: "Noto Sans KR", sans-serif; --building-font: "Do Hyeon", sans-serif; @@ -36,7 +44,7 @@ font-weight: 400; color: var(--black); - background-color: #ffffff; + /* background-color: var(--white); */ } a { diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index 3ffe2c11d..9b065122c 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -97,36 +97,34 @@ const Layout = (): JSX.Element => { ) : ( - - {isValidToken && } - {isLoading ? ( - - ) : ( - - - - - - - - - - - {isModalOpen && - myInfoData && - myInfoData.unbannedAt !== undefined && ( - - )} - - )} - + {/* */} + {isValidToken && } + {isLoading ? ( + + ) : ( + + + + + + + + + + + {isModalOpen && myInfoData && myInfoData.unbannedAt !== undefined && ( + + )} + + )} + {/* */} ); }; From 2db99098ede2d52ed1600ecd018ec334a4e2c839 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 13 Feb 2024 19:46:57 +0900 Subject: [PATCH 0405/1029] =?UTF-8?q?[BE]=20HOTFIX:=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EB=A9=94=EC=9D=BC=20=EC=8B=9C=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=EC=9D=B4=EC=83=81=ED=95=98=EA=B2=8C=20=EC=B0=8D=ED=9E=88?= =?UTF-8?q?=EB=8D=98=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/domain/LentExpirationAlarm.java | 2 +- .../domain/LentExpirationImminentAlarm.java | 6 +- .../alarm/handler/EmailAlarmSender.java | 230 +++++++++--------- .../alarm/handler/PushAlarmSender.java | 26 +- .../alarm/handler/SlackAlarmSender.java | 4 +- .../cabinet/dto/ActiveLentHistoryDto.java | 2 +- .../utils/overdue/manager/OverdueManager.java | 8 +- .../ftclub/cabinet/mapper/LentMapperTest.java | 3 +- config | 2 +- 9 files changed, 150 insertions(+), 133 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java index b4a9050a5..aaa8ad61f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java @@ -12,5 +12,5 @@ @AllArgsConstructor public class LentExpirationAlarm implements Alarm { - private final Long daysLeftFromExpireDate; + private final Long daysFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index f357b6e6e..f2659cb0c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -14,15 +14,15 @@ @AllArgsConstructor public class LentExpirationImminentAlarm implements Alarm { - private final Long daysAfterFromExpireDate; + private final Long daysFromExpireDate; public String getExpirationDateAsString() { LocalDateTime now = LocalDateTime.now(); - LocalDateTime expireDate = now.plusDays(daysAfterFromExpireDate * -1); + LocalDateTime expireDate = now.minusDays(daysFromExpireDate); return expireDate.format(DateTimeFormatter.ofPattern("YYYY년 MM월 DD일")); } public boolean isExpirationToday() { - return daysAfterFromExpireDate == 0L; + return daysFromExpireDate == 0L; } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java index 71b829acf..c7a2cb890 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java @@ -1,10 +1,19 @@ package org.ftclub.cabinet.alarm.handler; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.alarm.config.GmailProperties; -import org.ftclub.cabinet.alarm.domain.*; +import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.alarm.dto.MailDto; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.user.domain.User; @@ -16,118 +25,119 @@ import org.thymeleaf.ITemplateEngine; import org.thymeleaf.context.Context; -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; - @Slf4j @Component @RequiredArgsConstructor public class EmailAlarmSender { - private final JavaMailSender javaMailSender; - private final ITemplateEngine templateEngine; - private final GmailProperties gmailProperties; - private final AlarmProperties alarmProperties; - - @Async - public void send(User user, AlarmEvent alarmEvent) { - log.info("Email Alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); - if (!gmailProperties.getIsProduction()) { - log.debug("개발 환경이므로 메일을 보내지 않습니다."); - return; - } - MailDto mailDto = parseMessageToMailDto(user.getName(), alarmEvent.getAlarm()); - - try { - sendMessage(user.getEmail(), mailDto); - } catch (MessagingException e) { - throw ExceptionStatus.MAIL_BAD_GATEWAY.asServiceException(); - } - } - - private MailDto parseMessageToMailDto(String name, Alarm alarm) { - Context context = new Context(); - context.setVariable("name", name); - if (alarm instanceof LentSuccessAlarm) { - return generateLentSuccessAlarm((LentSuccessAlarm) alarm, context); - } else if (alarm instanceof LentExpirationAlarm) { - return generateLentExpirationAlarm((LentExpirationAlarm) alarm, context); - } else if (alarm instanceof LentExpirationImminentAlarm) { - return generateLentExpirationImminentAlarm((LentExpirationImminentAlarm) alarm, context); - } else if (alarm instanceof ExtensionIssuanceAlarm) { - return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm, context); - } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - return generateExtensionExpirationImminentAlarm((ExtensionExpirationImminentAlarm) alarm, context); - } else if (alarm instanceof AnnouncementAlarm) { - return generateAnnouncementAlarm((AnnouncementAlarm) alarm, context); - } else { - throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); - } - } - - @NotNull - private MailDto generateAnnouncementAlarm(AnnouncementAlarm alarm, Context context) { - context.setVariable("announcementContent", alarm.getAnnouncementContent()); - return new MailDto(alarmProperties.getAnnouncementSubject(), - alarmProperties.getAnnouncementMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateExtensionExpirationImminentAlarm(ExtensionExpirationImminentAlarm alarm, Context context) { - context.setVariable("extensionName", alarm.getExtensionName()); - context.setVariable("expireDate", alarm.getExtensionExpirationDate()); - return new MailDto(alarmProperties.getExtensionExpirationImminentSubject(), - alarmProperties.getExtensionExpirationImminentMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm, Context context) { - context.setVariable("extensionName", alarm.getExtensionName()); - context.setVariable("expireDate", alarm.getExtensionExpirationDate()); - context.setVariable("daysToExtend", alarm.getDaysToExtend()); - return new MailDto(alarmProperties.getExtensionIssuanceSubject(), - alarmProperties.getExtensionIssuanceMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm, Context context) { - String expirationDate = alarm.getExpirationDateAsString(); - context.setVariable("expireDate", expirationDate); - return new MailDto(alarmProperties.getSoonOverdueSubject(), - alarmProperties.getSoonOverdueMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateLentExpirationAlarm(LentExpirationAlarm alarm, Context context) { - context.setVariable("expireDate", - alarm.getDaysLeftFromExpireDate()); - return new MailDto(alarmProperties.getOverdueSubject(), - alarmProperties.getOverdueMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateLentSuccessAlarm(LentSuccessAlarm alarm, Context context) { - String building = alarm.getLocation().getBuilding(); - Integer floor = alarm.getLocation().getFloor(); - Integer visibleNum = alarm.getVisibleNum(); - context.setVariable("location", building + " " + floor + "층 " + visibleNum + "번"); - context.setVariable("expireDate", alarm.getLentExpirationDate()); - return new MailDto(alarmProperties.getLentSuccessSubject(), - alarmProperties.getLentSuccessMailTemplateUrl(), context); - } - - private void sendMessage(String email, MailDto mailDto) throws MessagingException { - log.info("send Message : email = {}, mailDto = {}", email, mailDto); - MimeMessage message = javaMailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); - - helper.setFrom(gmailProperties.getDisplaySenderName() + " <" + gmailProperties.getUsername() + ">"); - helper.setTo(email); - helper.setSubject(mailDto.getSubject()); - - String htmlContent = templateEngine.process(mailDto.getTemplate(), mailDto.getContext()); - helper.setText(htmlContent, true); - - javaMailSender.send(message); - } + private final JavaMailSender javaMailSender; + private final ITemplateEngine templateEngine; + private final GmailProperties gmailProperties; + private final AlarmProperties alarmProperties; + + @Async + public void send(User user, AlarmEvent alarmEvent) { + log.info("Email Alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); + if (!gmailProperties.getIsProduction()) { + log.debug("개발 환경이므로 메일을 보내지 않습니다."); + return; + } + MailDto mailDto = parseMessageToMailDto(user.getName(), alarmEvent.getAlarm()); + + try { + sendMessage(user.getEmail(), mailDto); + } catch (MessagingException e) { + throw ExceptionStatus.MAIL_BAD_GATEWAY.asServiceException(); + } + } + + private MailDto parseMessageToMailDto(String name, Alarm alarm) { + Context context = new Context(); + context.setVariable("name", name); + if (alarm instanceof LentSuccessAlarm) { + return generateLentSuccessAlarm((LentSuccessAlarm) alarm, context); + } else if (alarm instanceof LentExpirationAlarm) { + return generateLentExpirationAlarm((LentExpirationAlarm) alarm, context); + } else if (alarm instanceof LentExpirationImminentAlarm) { + return generateLentExpirationImminentAlarm((LentExpirationImminentAlarm) alarm, + context); + } else if (alarm instanceof ExtensionIssuanceAlarm) { + return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm, context); + } else if (alarm instanceof ExtensionExpirationImminentAlarm) { + return generateExtensionExpirationImminentAlarm( + (ExtensionExpirationImminentAlarm) alarm, context); + } else if (alarm instanceof AnnouncementAlarm) { + return generateAnnouncementAlarm((AnnouncementAlarm) alarm, context); + } else { + throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); + } + } + + @NotNull + private MailDto generateAnnouncementAlarm(AnnouncementAlarm alarm, Context context) { + context.setVariable("announcementContent", alarm.getAnnouncementContent()); + return new MailDto(alarmProperties.getAnnouncementSubject(), + alarmProperties.getAnnouncementMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateExtensionExpirationImminentAlarm(ExtensionExpirationImminentAlarm alarm, + Context context) { + context.setVariable("extensionName", alarm.getExtensionName()); + context.setVariable("expireDate", alarm.getExtensionExpirationDate()); + return new MailDto(alarmProperties.getExtensionExpirationImminentSubject(), + alarmProperties.getExtensionExpirationImminentMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm, Context context) { + context.setVariable("extensionName", alarm.getExtensionName()); + context.setVariable("expireDate", alarm.getExtensionExpirationDate()); + context.setVariable("daysToExtend", alarm.getDaysToExtend()); + return new MailDto(alarmProperties.getExtensionIssuanceSubject(), + alarmProperties.getExtensionIssuanceMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm, + Context context) { + String expirationDate = alarm.getExpirationDateAsString(); + context.setVariable("expireDate", expirationDate); + return new MailDto(alarmProperties.getSoonOverdueSubject(), + alarmProperties.getSoonOverdueMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateLentExpirationAlarm(LentExpirationAlarm alarm, Context context) { + context.setVariable("expireDate", alarm.getDaysFromExpireDate()); + return new MailDto(alarmProperties.getOverdueSubject(), + alarmProperties.getOverdueMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateLentSuccessAlarm(LentSuccessAlarm alarm, Context context) { + String building = alarm.getLocation().getBuilding(); + Integer floor = alarm.getLocation().getFloor(); + Integer visibleNum = alarm.getVisibleNum(); + context.setVariable("location", building + " " + floor + "층 " + visibleNum + "번"); + context.setVariable("expireDate", alarm.getLentExpirationDate()); + return new MailDto(alarmProperties.getLentSuccessSubject(), + alarmProperties.getLentSuccessMailTemplateUrl(), context); + } + + private void sendMessage(String email, MailDto mailDto) throws MessagingException { + log.info("send Message : email = {}, mailDto = {}", email, mailDto); + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + + helper.setFrom(gmailProperties.getDisplaySenderName() + + " <" + gmailProperties.getUsername() + ">"); + helper.setTo(email); + helper.setSubject(mailDto.getSubject()); + + String htmlContent = templateEngine.process(mailDto.getTemplate(), mailDto.getContext()); + helper.setText(htmlContent, true); + + javaMailSender.send(message); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java index 2020646cf..de642539f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java @@ -2,21 +2,27 @@ import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.Message; +import java.time.LocalDateTime; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.config.AlarmProperties; -import org.ftclub.cabinet.alarm.domain.*; +import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.alarm.dto.FCMDto; +import org.ftclub.cabinet.alarm.fcm.service.FCMTokenRedisService; import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.alarm.fcm.service.FCMTokenRedisService; import org.ftclub.cabinet.user.domain.User; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; -import java.util.Optional; - @Log4j2 @Component @RequiredArgsConstructor @@ -50,7 +56,8 @@ private FCMDto parseMessage(Alarm alarm) { } else if (alarm instanceof ExtensionIssuanceAlarm) { return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm); } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - return generateExtensionExpirationImminentAlarm((ExtensionExpirationImminentAlarm) alarm); + return generateExtensionExpirationImminentAlarm( + (ExtensionExpirationImminentAlarm) alarm); } else if (alarm instanceof AnnouncementAlarm) { return generateAnnouncementAlarm(); } else { @@ -66,7 +73,8 @@ private FCMDto generateAnnouncementAlarm() { } @NotNull - private FCMDto generateExtensionExpirationImminentAlarm(ExtensionExpirationImminentAlarm alarm) { + private FCMDto generateExtensionExpirationImminentAlarm( + ExtensionExpirationImminentAlarm alarm) { String extensionName = alarm.getExtensionName(); LocalDateTime extensionExpireDate = alarm.getExtensionExpirationDate(); String title = alarmProperties.getExtensionExpirationImminentSubject(); @@ -87,7 +95,7 @@ private FCMDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm) { @NotNull private FCMDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { - Long daysLeftFromExpireDate = alarm.getDaysLeftFromExpireDate(); + Long daysLeftFromExpireDate = alarm.getDaysFromExpireDate(); String title = alarmProperties.getOverdueSubject(); String body = String.format(alarmProperties.getOverdueFcmTemplate(), Math.abs(daysLeftFromExpireDate)); @@ -96,7 +104,7 @@ private FCMDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { @NotNull private FCMDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm) { - Long daysAfterFromExpireDate = alarm.getDaysAfterFromExpireDate(); + Long daysAfterFromExpireDate = alarm.getDaysFromExpireDate(); String title = alarmProperties.getSoonOverdueSubject(); String body = String.format(alarmProperties.getSoonOverdueFcmTemplate(), Math.abs(daysAfterFromExpireDate)); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index e81ff4f20..3d6a4177f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -73,7 +73,7 @@ private SlackDto generateLentSuccessAlarm(LentSuccessAlarm alarm) { } private SlackDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm) { - Long daysAfterFromExpireDate = alarm.getDaysAfterFromExpireDate(); + Long daysAfterFromExpireDate = alarm.getDaysFromExpireDate(); String body; if (alarm.isExpirationToday()) { body = alarmProperties.getSoonOverdueByTodayTemplate(); @@ -85,7 +85,7 @@ private SlackDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm } private SlackDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { - Long daysLeftFromExpireDate = alarm.getDaysLeftFromExpireDate(); + Long daysLeftFromExpireDate = alarm.getDaysFromExpireDate(); String body = String.format(alarmProperties.getOverdueSlackTemplate(), Math.abs(daysLeftFromExpireDate)); return new SlackDto(body); diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java index dd7086a5e..dcabcec1b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java @@ -14,5 +14,5 @@ public class ActiveLentHistoryDto { private final String email; private final Long cabinetId; private final Boolean isExpired; - private final Long daysLeftFromExpireDate; + private final Long daysFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java index b314125a7..a8bd7237a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java @@ -58,21 +58,21 @@ public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate } public void handleOverdue(ActiveLentHistoryDto activeLent) { - OverdueType overdueType = getOverdueType(activeLent.getIsExpired(), - activeLent.getDaysLeftFromExpireDate()); + OverdueType overdueType = + getOverdueType(activeLent.getIsExpired(), activeLent.getDaysFromExpireDate()); log.info("called handleOverdue: activeLent={}, overdueType={}", activeLent, overdueType); switch (overdueType) { case NONE: return; case SOON_OVERDUE: eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), - new LentExpirationImminentAlarm(activeLent.getDaysLeftFromExpireDate()))); + new LentExpirationImminentAlarm(activeLent.getDaysFromExpireDate()))); break; case OVERDUE: cabinetFacadeService.updateStatus(activeLent.getCabinetId(), CabinetStatus.OVERDUE); eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), - new LentExpirationAlarm(activeLent.getDaysLeftFromExpireDate()))); + new LentExpirationAlarm(activeLent.getDaysFromExpireDate()))); break; } } diff --git a/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java b/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java index 390fb00c7..386f2dd03 100644 --- a/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java @@ -5,7 +5,6 @@ import static org.mockito.Mockito.when; import java.time.LocalDateTime; - import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetPlace; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; @@ -99,7 +98,7 @@ void toActiveLentHistoryDto() { assertEquals(activeLentHistoryDto.getEmail(), user.getEmail()); assertEquals(activeLentHistoryDto.getCabinetId(), cabinet.getId()); assertEquals(activeLentHistoryDto.getIsExpired(), lentHistory.isExpired(now)); - assertEquals(activeLentHistoryDto.getDaysLeftFromExpireDate(), + assertEquals(activeLentHistoryDto.getDaysFromExpireDate(), lentHistory.getDaysUntilExpiration(now)); } } \ No newline at end of file diff --git a/config b/config index 5b0b7baa2..c31aa887c 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 5b0b7baa2ce65b39bc4a49075ad00b76a7d495cc +Subproject commit c31aa887c99ee2cb15a4f730c08ec1d60e6c9f79 From e0db6007a4b6ec15facb016c4cb30ebf1ee14166 Mon Sep 17 00:00:00 2001 From: Jiwon Park <82518170+Z1Park@users.noreply.github.com> Date: Tue, 13 Feb 2024 20:07:03 +0900 Subject: [PATCH 0406/1029] DEV TO MAIN (#1553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FE] FEAT: 사용가능페이지 핕터위치 변경 및 토글 선택범위 확대 * [FE] FEAT: 새로고침 버튼과 타이머 합침 * [FE] FIX: PENDING 상태의 사물함 border, shadow 수정 #1528 * [FE] FIX: 토글 선택범위 cursor 변경 * [FE] FIX: 토글 커서 변환 확대 * [BE] HOTFIX: 이전에 블랙홀이었던 유저가 대여시도시, 새로고침 로직 추가 * [BE] HOTFIX: 연장권 안써짐 이슈 -> @Transactional * merge * [BE] 리팩토링 이후 트랜잭션 적용 안된 부분 적용 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 * [COMMON] FEAT: 로그인 후 처리 동작 관련 개선사항 (#1527) * [FE] FEAT: PostLogin 페이지 추가 (임시) * [COMMON] FIX: 로그인 후 콜백 주소를 프론트에서 결정하도록 변경 * [FE] FEAT: login auth_page 디자인 변경 --------- Co-authored-by: moonseonghui * [BE] FEAT: 반납일 7,3,1 일전 미리 알람주도록 기능 추가#1518 (#1534) * [BE] Redis 이벤트 관련 클래스 위치 이벤트 폴더로 이동 * [BE] 연체 전후 알림 3일, 7일차 추가 및 properties 정리 * [BE] 프로퍼티 이름 수정 * [BE] 연체 시 매일 알람 보내도록 수정 --------- Co-authored-by: jiwon * [FE] FEAT: 플레이 스토어 출시를 위한 assetlinks.json 파일 추가 * [HOTFIX] pending NPE 수정 * [BE]FIX: config before * [BE] TEST: alarm test * [BE] HOTFIX: 대여기록 조회 시 과거부터 10개 조회해오는 오류 수정 * [BE] HOTFIX: Redis 백업 기능 main, dev에서 동작 안되는 오류 수정 * [BE] HOTFIX: Redis 백업 기능 설정 파일 -> 디렉토리 변경 * [BE] FEAT: 연체 당일 알람 추가 (#1547) * [BE] redis config added * [FE] FIX: /lent/me 실패 시 myCabinetInfoState 가 undefined 일때를 고려해 조건문 수정 #1549 * [BE] config fixed * [BE] HOTFIX: Admin 사물함 대여기록 조회 시 과거부터 보이는 오류 수정 * [BE] HOTFIX: 오류 수정 * [BE] HOTFIX: 오류 수정(git 이상하네 왜 자꾸 이 모양으로 merge하지...) * [BE] HOTFIX: 공유 사물함 대여 후 마지막 사람이 취소 시 IN_SESSION 상태 유지되는 버그 수정 * [BE] HOTFIX: 알림 메일 시 날짜 이상하게 찍히던 오류 수정 --------- Co-authored-by: Minkyu01 Co-authored-by: jnkeniaem Co-authored-by: jusohn Co-authored-by: space Co-authored-by: jiwon Co-authored-by: Gyeonga Koh <114395888+gykoh42@users.noreply.github.com> Co-authored-by: Siwon Choi <83565255+sichoi42@users.noreply.github.com> Co-authored-by: moonseonghui Co-authored-by: sichoi42 <42.4.sichoi@gmail.com> Co-authored-by: SpaceChae <13278955+enaenen@users.noreply.github.com> --- .../alarm/domain/LentExpirationAlarm.java | 2 +- .../domain/LentExpirationImminentAlarm.java | 6 +- .../alarm/handler/EmailAlarmSender.java | 230 +++++++++--------- .../alarm/handler/PushAlarmSender.java | 26 +- .../alarm/handler/SlackAlarmSender.java | 4 +- .../cabinet/service/CabinetFacadeService.java | 5 +- .../cabinet/dto/ActiveLentHistoryDto.java | 2 +- .../lent/repository/LentRepository.java | 3 +- .../lent/service/LentFacadeService.java | 2 +- .../utils/overdue/manager/OverdueManager.java | 8 +- .../ftclub/cabinet/mapper/LentMapperTest.java | 3 +- config | 2 +- .../CabinetInfoArea.container.tsx | 10 +- 13 files changed, 160 insertions(+), 143 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java index b4a9050a5..aaa8ad61f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationAlarm.java @@ -12,5 +12,5 @@ @AllArgsConstructor public class LentExpirationAlarm implements Alarm { - private final Long daysLeftFromExpireDate; + private final Long daysFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java index f357b6e6e..f2659cb0c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/domain/LentExpirationImminentAlarm.java @@ -14,15 +14,15 @@ @AllArgsConstructor public class LentExpirationImminentAlarm implements Alarm { - private final Long daysAfterFromExpireDate; + private final Long daysFromExpireDate; public String getExpirationDateAsString() { LocalDateTime now = LocalDateTime.now(); - LocalDateTime expireDate = now.plusDays(daysAfterFromExpireDate * -1); + LocalDateTime expireDate = now.minusDays(daysFromExpireDate); return expireDate.format(DateTimeFormatter.ofPattern("YYYY년 MM월 DD일")); } public boolean isExpirationToday() { - return daysAfterFromExpireDate == 0L; + return daysFromExpireDate == 0L; } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java index 71b829acf..c7a2cb890 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/EmailAlarmSender.java @@ -1,10 +1,19 @@ package org.ftclub.cabinet.alarm.handler; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.alarm.config.GmailProperties; -import org.ftclub.cabinet.alarm.domain.*; +import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.alarm.dto.MailDto; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.user.domain.User; @@ -16,118 +25,119 @@ import org.thymeleaf.ITemplateEngine; import org.thymeleaf.context.Context; -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; - @Slf4j @Component @RequiredArgsConstructor public class EmailAlarmSender { - private final JavaMailSender javaMailSender; - private final ITemplateEngine templateEngine; - private final GmailProperties gmailProperties; - private final AlarmProperties alarmProperties; - - @Async - public void send(User user, AlarmEvent alarmEvent) { - log.info("Email Alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); - if (!gmailProperties.getIsProduction()) { - log.debug("개발 환경이므로 메일을 보내지 않습니다."); - return; - } - MailDto mailDto = parseMessageToMailDto(user.getName(), alarmEvent.getAlarm()); - - try { - sendMessage(user.getEmail(), mailDto); - } catch (MessagingException e) { - throw ExceptionStatus.MAIL_BAD_GATEWAY.asServiceException(); - } - } - - private MailDto parseMessageToMailDto(String name, Alarm alarm) { - Context context = new Context(); - context.setVariable("name", name); - if (alarm instanceof LentSuccessAlarm) { - return generateLentSuccessAlarm((LentSuccessAlarm) alarm, context); - } else if (alarm instanceof LentExpirationAlarm) { - return generateLentExpirationAlarm((LentExpirationAlarm) alarm, context); - } else if (alarm instanceof LentExpirationImminentAlarm) { - return generateLentExpirationImminentAlarm((LentExpirationImminentAlarm) alarm, context); - } else if (alarm instanceof ExtensionIssuanceAlarm) { - return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm, context); - } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - return generateExtensionExpirationImminentAlarm((ExtensionExpirationImminentAlarm) alarm, context); - } else if (alarm instanceof AnnouncementAlarm) { - return generateAnnouncementAlarm((AnnouncementAlarm) alarm, context); - } else { - throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); - } - } - - @NotNull - private MailDto generateAnnouncementAlarm(AnnouncementAlarm alarm, Context context) { - context.setVariable("announcementContent", alarm.getAnnouncementContent()); - return new MailDto(alarmProperties.getAnnouncementSubject(), - alarmProperties.getAnnouncementMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateExtensionExpirationImminentAlarm(ExtensionExpirationImminentAlarm alarm, Context context) { - context.setVariable("extensionName", alarm.getExtensionName()); - context.setVariable("expireDate", alarm.getExtensionExpirationDate()); - return new MailDto(alarmProperties.getExtensionExpirationImminentSubject(), - alarmProperties.getExtensionExpirationImminentMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm, Context context) { - context.setVariable("extensionName", alarm.getExtensionName()); - context.setVariable("expireDate", alarm.getExtensionExpirationDate()); - context.setVariable("daysToExtend", alarm.getDaysToExtend()); - return new MailDto(alarmProperties.getExtensionIssuanceSubject(), - alarmProperties.getExtensionIssuanceMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm, Context context) { - String expirationDate = alarm.getExpirationDateAsString(); - context.setVariable("expireDate", expirationDate); - return new MailDto(alarmProperties.getSoonOverdueSubject(), - alarmProperties.getSoonOverdueMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateLentExpirationAlarm(LentExpirationAlarm alarm, Context context) { - context.setVariable("expireDate", - alarm.getDaysLeftFromExpireDate()); - return new MailDto(alarmProperties.getOverdueSubject(), - alarmProperties.getOverdueMailTemplateUrl(), context); - } - - @NotNull - private MailDto generateLentSuccessAlarm(LentSuccessAlarm alarm, Context context) { - String building = alarm.getLocation().getBuilding(); - Integer floor = alarm.getLocation().getFloor(); - Integer visibleNum = alarm.getVisibleNum(); - context.setVariable("location", building + " " + floor + "층 " + visibleNum + "번"); - context.setVariable("expireDate", alarm.getLentExpirationDate()); - return new MailDto(alarmProperties.getLentSuccessSubject(), - alarmProperties.getLentSuccessMailTemplateUrl(), context); - } - - private void sendMessage(String email, MailDto mailDto) throws MessagingException { - log.info("send Message : email = {}, mailDto = {}", email, mailDto); - MimeMessage message = javaMailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); - - helper.setFrom(gmailProperties.getDisplaySenderName() + " <" + gmailProperties.getUsername() + ">"); - helper.setTo(email); - helper.setSubject(mailDto.getSubject()); - - String htmlContent = templateEngine.process(mailDto.getTemplate(), mailDto.getContext()); - helper.setText(htmlContent, true); - - javaMailSender.send(message); - } + private final JavaMailSender javaMailSender; + private final ITemplateEngine templateEngine; + private final GmailProperties gmailProperties; + private final AlarmProperties alarmProperties; + + @Async + public void send(User user, AlarmEvent alarmEvent) { + log.info("Email Alarm Event : user = {}, alarmEvent = {}", user, alarmEvent); + if (!gmailProperties.getIsProduction()) { + log.debug("개발 환경이므로 메일을 보내지 않습니다."); + return; + } + MailDto mailDto = parseMessageToMailDto(user.getName(), alarmEvent.getAlarm()); + + try { + sendMessage(user.getEmail(), mailDto); + } catch (MessagingException e) { + throw ExceptionStatus.MAIL_BAD_GATEWAY.asServiceException(); + } + } + + private MailDto parseMessageToMailDto(String name, Alarm alarm) { + Context context = new Context(); + context.setVariable("name", name); + if (alarm instanceof LentSuccessAlarm) { + return generateLentSuccessAlarm((LentSuccessAlarm) alarm, context); + } else if (alarm instanceof LentExpirationAlarm) { + return generateLentExpirationAlarm((LentExpirationAlarm) alarm, context); + } else if (alarm instanceof LentExpirationImminentAlarm) { + return generateLentExpirationImminentAlarm((LentExpirationImminentAlarm) alarm, + context); + } else if (alarm instanceof ExtensionIssuanceAlarm) { + return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm, context); + } else if (alarm instanceof ExtensionExpirationImminentAlarm) { + return generateExtensionExpirationImminentAlarm( + (ExtensionExpirationImminentAlarm) alarm, context); + } else if (alarm instanceof AnnouncementAlarm) { + return generateAnnouncementAlarm((AnnouncementAlarm) alarm, context); + } else { + throw ExceptionStatus.NOT_FOUND_ALARM.asServiceException(); + } + } + + @NotNull + private MailDto generateAnnouncementAlarm(AnnouncementAlarm alarm, Context context) { + context.setVariable("announcementContent", alarm.getAnnouncementContent()); + return new MailDto(alarmProperties.getAnnouncementSubject(), + alarmProperties.getAnnouncementMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateExtensionExpirationImminentAlarm(ExtensionExpirationImminentAlarm alarm, + Context context) { + context.setVariable("extensionName", alarm.getExtensionName()); + context.setVariable("expireDate", alarm.getExtensionExpirationDate()); + return new MailDto(alarmProperties.getExtensionExpirationImminentSubject(), + alarmProperties.getExtensionExpirationImminentMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm, Context context) { + context.setVariable("extensionName", alarm.getExtensionName()); + context.setVariable("expireDate", alarm.getExtensionExpirationDate()); + context.setVariable("daysToExtend", alarm.getDaysToExtend()); + return new MailDto(alarmProperties.getExtensionIssuanceSubject(), + alarmProperties.getExtensionIssuanceMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm, + Context context) { + String expirationDate = alarm.getExpirationDateAsString(); + context.setVariable("expireDate", expirationDate); + return new MailDto(alarmProperties.getSoonOverdueSubject(), + alarmProperties.getSoonOverdueMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateLentExpirationAlarm(LentExpirationAlarm alarm, Context context) { + context.setVariable("expireDate", alarm.getDaysFromExpireDate()); + return new MailDto(alarmProperties.getOverdueSubject(), + alarmProperties.getOverdueMailTemplateUrl(), context); + } + + @NotNull + private MailDto generateLentSuccessAlarm(LentSuccessAlarm alarm, Context context) { + String building = alarm.getLocation().getBuilding(); + Integer floor = alarm.getLocation().getFloor(); + Integer visibleNum = alarm.getVisibleNum(); + context.setVariable("location", building + " " + floor + "층 " + visibleNum + "번"); + context.setVariable("expireDate", alarm.getLentExpirationDate()); + return new MailDto(alarmProperties.getLentSuccessSubject(), + alarmProperties.getLentSuccessMailTemplateUrl(), context); + } + + private void sendMessage(String email, MailDto mailDto) throws MessagingException { + log.info("send Message : email = {}, mailDto = {}", email, mailDto); + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + + helper.setFrom(gmailProperties.getDisplaySenderName() + + " <" + gmailProperties.getUsername() + ">"); + helper.setTo(email); + helper.setSubject(mailDto.getSubject()); + + String htmlContent = templateEngine.process(mailDto.getTemplate(), mailDto.getContext()); + helper.setText(htmlContent, true); + + javaMailSender.send(message); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java index 2020646cf..de642539f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/PushAlarmSender.java @@ -2,21 +2,27 @@ import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.Message; +import java.time.LocalDateTime; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.config.AlarmProperties; -import org.ftclub.cabinet.alarm.domain.*; +import org.ftclub.cabinet.alarm.domain.Alarm; +import org.ftclub.cabinet.alarm.domain.AlarmEvent; +import org.ftclub.cabinet.alarm.domain.AnnouncementAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.ExtensionIssuanceAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; +import org.ftclub.cabinet.alarm.domain.LentExpirationImminentAlarm; +import org.ftclub.cabinet.alarm.domain.LentSuccessAlarm; import org.ftclub.cabinet.alarm.dto.FCMDto; +import org.ftclub.cabinet.alarm.fcm.service.FCMTokenRedisService; import org.ftclub.cabinet.config.DomainProperties; import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.alarm.fcm.service.FCMTokenRedisService; import org.ftclub.cabinet.user.domain.User; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; -import java.util.Optional; - @Log4j2 @Component @RequiredArgsConstructor @@ -50,7 +56,8 @@ private FCMDto parseMessage(Alarm alarm) { } else if (alarm instanceof ExtensionIssuanceAlarm) { return generateExtensionIssuanceAlarm((ExtensionIssuanceAlarm) alarm); } else if (alarm instanceof ExtensionExpirationImminentAlarm) { - return generateExtensionExpirationImminentAlarm((ExtensionExpirationImminentAlarm) alarm); + return generateExtensionExpirationImminentAlarm( + (ExtensionExpirationImminentAlarm) alarm); } else if (alarm instanceof AnnouncementAlarm) { return generateAnnouncementAlarm(); } else { @@ -66,7 +73,8 @@ private FCMDto generateAnnouncementAlarm() { } @NotNull - private FCMDto generateExtensionExpirationImminentAlarm(ExtensionExpirationImminentAlarm alarm) { + private FCMDto generateExtensionExpirationImminentAlarm( + ExtensionExpirationImminentAlarm alarm) { String extensionName = alarm.getExtensionName(); LocalDateTime extensionExpireDate = alarm.getExtensionExpirationDate(); String title = alarmProperties.getExtensionExpirationImminentSubject(); @@ -87,7 +95,7 @@ private FCMDto generateExtensionIssuanceAlarm(ExtensionIssuanceAlarm alarm) { @NotNull private FCMDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { - Long daysLeftFromExpireDate = alarm.getDaysLeftFromExpireDate(); + Long daysLeftFromExpireDate = alarm.getDaysFromExpireDate(); String title = alarmProperties.getOverdueSubject(); String body = String.format(alarmProperties.getOverdueFcmTemplate(), Math.abs(daysLeftFromExpireDate)); @@ -96,7 +104,7 @@ private FCMDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { @NotNull private FCMDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm) { - Long daysAfterFromExpireDate = alarm.getDaysAfterFromExpireDate(); + Long daysAfterFromExpireDate = alarm.getDaysFromExpireDate(); String title = alarmProperties.getSoonOverdueSubject(); String body = String.format(alarmProperties.getSoonOverdueFcmTemplate(), Math.abs(daysAfterFromExpireDate)); diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java index e81ff4f20..3d6a4177f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/handler/SlackAlarmSender.java @@ -73,7 +73,7 @@ private SlackDto generateLentSuccessAlarm(LentSuccessAlarm alarm) { } private SlackDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm alarm) { - Long daysAfterFromExpireDate = alarm.getDaysAfterFromExpireDate(); + Long daysAfterFromExpireDate = alarm.getDaysFromExpireDate(); String body; if (alarm.isExpirationToday()) { body = alarmProperties.getSoonOverdueByTodayTemplate(); @@ -85,7 +85,7 @@ private SlackDto generateLentExpirationImminentAlarm(LentExpirationImminentAlarm } private SlackDto generateLentExpirationAlarm(LentExpirationAlarm alarm) { - Long daysLeftFromExpireDate = alarm.getDaysLeftFromExpireDate(); + Long daysLeftFromExpireDate = alarm.getDaysFromExpireDate(); String body = String.format(alarmProperties.getOverdueSlackTemplate(), Math.abs(daysLeftFromExpireDate)); return new SlackDto(body); diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index a84ae7200..b59159168 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -261,10 +261,9 @@ public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, */ @Transactional(readOnly = true) public LentHistoryPaginationDto getLentHistoryPagination(Long cabinetId, Pageable pageable) { - Page lentHistories = lentQueryService.findCabinetLentHistoriesWithUserAndCabinet( - cabinetId, pageable); + Page lentHistories = + lentQueryService.findCabinetLentHistoriesWithUserAndCabinet(cabinetId, pageable); List result = lentHistories.stream() - .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) .collect(Collectors.toList()); return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java index dd7086a5e..dcabcec1b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java @@ -14,5 +14,5 @@ public class ActiveLentHistoryDto { private final String email; private final Long cabinetId; private final Boolean isExpired; - private final Long daysLeftFromExpireDate; + private final Long daysFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index f28554b41..21105e2f4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -173,7 +173,8 @@ int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDat + "FROM LentHistory lh " + "LEFT JOIN FETCH lh.user u " + "LEFT JOIN FETCH lh.cabinet c " - + "WHERE lh.cabinetId = :cabinetId ", + + "WHERE lh.cabinetId = :cabinetId " + + "ORDER BY lh.startedAt DESC", countQuery = "SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.cabinetId = :cabinetId ") diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 31ccce58e..b9f4549f4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -315,7 +315,7 @@ public void updateLentCabinetInfo(Long userId, String title, String memo) { @Transactional public void cancelShareCabinetLent(Long userId, Long cabinetId) { lentRedisService.deleteUserInCabinet(cabinetId, userId); - if (lentRedisService.isInCabinetSession(cabinetId)) { + if (!lentRedisService.isInCabinetSession(cabinetId)) { Cabinet cabinet = cabinetQueryService.getCabinetForUpdate(cabinetId); cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java index b314125a7..a8bd7237a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java @@ -58,21 +58,21 @@ public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate } public void handleOverdue(ActiveLentHistoryDto activeLent) { - OverdueType overdueType = getOverdueType(activeLent.getIsExpired(), - activeLent.getDaysLeftFromExpireDate()); + OverdueType overdueType = + getOverdueType(activeLent.getIsExpired(), activeLent.getDaysFromExpireDate()); log.info("called handleOverdue: activeLent={}, overdueType={}", activeLent, overdueType); switch (overdueType) { case NONE: return; case SOON_OVERDUE: eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), - new LentExpirationImminentAlarm(activeLent.getDaysLeftFromExpireDate()))); + new LentExpirationImminentAlarm(activeLent.getDaysFromExpireDate()))); break; case OVERDUE: cabinetFacadeService.updateStatus(activeLent.getCabinetId(), CabinetStatus.OVERDUE); eventPublisher.publishEvent(AlarmEvent.of(activeLent.getUserId(), - new LentExpirationAlarm(activeLent.getDaysLeftFromExpireDate()))); + new LentExpirationAlarm(activeLent.getDaysFromExpireDate()))); break; } } diff --git a/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java b/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java index 390fb00c7..386f2dd03 100644 --- a/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java @@ -5,7 +5,6 @@ import static org.mockito.Mockito.when; import java.time.LocalDateTime; - import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetPlace; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; @@ -99,7 +98,7 @@ void toActiveLentHistoryDto() { assertEquals(activeLentHistoryDto.getEmail(), user.getEmail()); assertEquals(activeLentHistoryDto.getCabinetId(), cabinet.getId()); assertEquals(activeLentHistoryDto.getIsExpired(), lentHistory.isExpired(now)); - assertEquals(activeLentHistoryDto.getDaysLeftFromExpireDate(), + assertEquals(activeLentHistoryDto.getDaysFromExpireDate(), lentHistory.getDaysUntilExpiration(now)); } } \ No newline at end of file diff --git a/config b/config index b4eae5f8d..c31aa887c 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit b4eae5f8d69845b3a5f726d985e5c9628a4128ea +Subproject commit c31aa887c99ee2cb15a4f730c08ec1d60e6c9f79 diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx index 258678b7f..b3c494081 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx @@ -233,7 +233,7 @@ const CabinetInfoAreaContainer = (): JSX.Element => { : null; const openModal = (modalName: TModalState) => { - if (modalName === "lentModal" && myCabinetInfo.cabinetId) { + if (modalName === "lentModal" && myCabinetInfo?.cabinetId) { modalName = "unavailableModal"; } else if ( modalName === "returnModal" && @@ -330,17 +330,17 @@ const CabinetInfoAreaContainer = (): JSX.Element => { isAvailable={ (cabinetViewData?.status === "AVAILABLE" || cabinetViewData?.status === "IN_SESSION") && - !myCabinetInfo.cabinetId + !myCabinetInfo?.cabinetId } isExtensible={!!myInfo.lentExtensionResponseDto && !myInfo.unbannedAt} userModal={userModal} openModal={openModal} closeModal={closeModal} isSwappable={ - myCabinetInfo.lentType === CabinetType.PRIVATE && - !!myCabinetInfo.cabinetId && + myCabinetInfo?.lentType === CabinetType.PRIVATE && + !!myCabinetInfo?.cabinetId && cabinetViewData?.lentType === CabinetType.PRIVATE && - cabinetViewData?.cabinetId !== myCabinetInfo.cabinetId && + cabinetViewData?.cabinetId !== myCabinetInfo?.cabinetId && cabinetViewData?.status === CabinetStatus.AVAILABLE } /> From bcb97977389b8fe6588cf3c97f9d95fc2e0d3d21 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 13 Feb 2024 20:09:28 +0900 Subject: [PATCH 0407/1029] =?UTF-8?q?[BE]=20HOTFIX:=20LentMapper=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EA=B8=B4=EA=B8=89=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ftclub/cabinet/mapper/LentMapper.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java index 1a22f47eb..cb06315ec 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java @@ -1,5 +1,8 @@ package org.ftclub.cabinet.mapper; +import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; + +import java.util.List; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.LentDto; @@ -11,10 +14,6 @@ import org.mapstruct.Mapping; import org.springframework.stereotype.Component; -import java.util.List; - -import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; - //@NullableMapper @Mapper(componentModel = "spring", nullValueMappingStrategy = RETURN_DEFAULT, @@ -42,14 +41,13 @@ public interface LentMapper { @Mapping(target = "totalLength", source = "totalLength") LentHistoryPaginationDto toLentHistoryPaginationDto(List result, - Long totalLength); + Long totalLength); @Mapping(target = "userId", source = "lentHistory.userId") @Mapping(target = "cabinetId", source = "cabinet.id") ActiveLentHistoryDto toActiveLentHistoryDto(LentHistory lentHistory, - User user, - Cabinet cabinet, - Boolean isExpired, - Long daysLeftFromExpireDate - ); + User user, + Cabinet cabinet, + Boolean isExpired, + Long daysFromExpireDate); } From 27a9509ddf6cbea65b351923252ed2d91784b08e Mon Sep 17 00:00:00 2001 From: Jiwon Park <82518170+Z1Park@users.noreply.github.com> Date: Tue, 13 Feb 2024 20:11:49 +0900 Subject: [PATCH 0408/1029] DEV TO MAIN (#1554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FE] FEAT: 사용가능페이지 핕터위치 변경 및 토글 선택범위 확대 * [FE] FEAT: 새로고침 버튼과 타이머 합침 * [FE] FIX: PENDING 상태의 사물함 border, shadow 수정 #1528 * [FE] FIX: 토글 선택범위 cursor 변경 * [FE] FIX: 토글 커서 변환 확대 * [BE] HOTFIX: 이전에 블랙홀이었던 유저가 대여시도시, 새로고침 로직 추가 * [BE] HOTFIX: 연장권 안써짐 이슈 -> @Transactional * merge * [BE] 리팩토링 이후 트랜잭션 적용 안된 부분 적용 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 * [BE] HOTFIX: 공유사물함 블랙홀 에러 수정 * [COMMON] FEAT: 로그인 후 처리 동작 관련 개선사항 (#1527) * [FE] FEAT: PostLogin 페이지 추가 (임시) * [COMMON] FIX: 로그인 후 콜백 주소를 프론트에서 결정하도록 변경 * [FE] FEAT: login auth_page 디자인 변경 --------- Co-authored-by: moonseonghui * [BE] FEAT: 반납일 7,3,1 일전 미리 알람주도록 기능 추가#1518 (#1534) * [BE] Redis 이벤트 관련 클래스 위치 이벤트 폴더로 이동 * [BE] 연체 전후 알림 3일, 7일차 추가 및 properties 정리 * [BE] 프로퍼티 이름 수정 * [BE] 연체 시 매일 알람 보내도록 수정 --------- Co-authored-by: jiwon * [FE] FEAT: 플레이 스토어 출시를 위한 assetlinks.json 파일 추가 * [HOTFIX] pending NPE 수정 * [BE]FIX: config before * [BE] TEST: alarm test * [BE] HOTFIX: 대여기록 조회 시 과거부터 10개 조회해오는 오류 수정 * [BE] HOTFIX: Redis 백업 기능 main, dev에서 동작 안되는 오류 수정 * [BE] HOTFIX: Redis 백업 기능 설정 파일 -> 디렉토리 변경 * [BE] FEAT: 연체 당일 알람 추가 (#1547) * [BE] redis config added * [FE] FIX: /lent/me 실패 시 myCabinetInfoState 가 undefined 일때를 고려해 조건문 수정 #1549 * [BE] config fixed * [BE] HOTFIX: Admin 사물함 대여기록 조회 시 과거부터 보이는 오류 수정 * [BE] HOTFIX: 오류 수정 * [BE] HOTFIX: 오류 수정(git 이상하네 왜 자꾸 이 모양으로 merge하지...) * [BE] HOTFIX: 공유 사물함 대여 후 마지막 사람이 취소 시 IN_SESSION 상태 유지되는 버그 수정 * [BE] HOTFIX: 알림 메일 시 날짜 이상하게 찍히던 오류 수정 * [BE] HOTFIX: LentMapper 오류 긴급 수정 --------- Co-authored-by: Minkyu01 Co-authored-by: jnkeniaem Co-authored-by: jusohn Co-authored-by: space Co-authored-by: jiwon Co-authored-by: Gyeonga Koh <114395888+gykoh42@users.noreply.github.com> Co-authored-by: Siwon Choi <83565255+sichoi42@users.noreply.github.com> Co-authored-by: moonseonghui Co-authored-by: sichoi42 <42.4.sichoi@gmail.com> Co-authored-by: SpaceChae <13278955+enaenen@users.noreply.github.com> --- .../org/ftclub/cabinet/mapper/LentMapper.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java index 1a22f47eb..cb06315ec 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java @@ -1,5 +1,8 @@ package org.ftclub.cabinet.mapper; +import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; + +import java.util.List; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.LentDto; @@ -11,10 +14,6 @@ import org.mapstruct.Mapping; import org.springframework.stereotype.Component; -import java.util.List; - -import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; - //@NullableMapper @Mapper(componentModel = "spring", nullValueMappingStrategy = RETURN_DEFAULT, @@ -42,14 +41,13 @@ public interface LentMapper { @Mapping(target = "totalLength", source = "totalLength") LentHistoryPaginationDto toLentHistoryPaginationDto(List result, - Long totalLength); + Long totalLength); @Mapping(target = "userId", source = "lentHistory.userId") @Mapping(target = "cabinetId", source = "cabinet.id") ActiveLentHistoryDto toActiveLentHistoryDto(LentHistory lentHistory, - User user, - Cabinet cabinet, - Boolean isExpired, - Long daysLeftFromExpireDate - ); + User user, + Cabinet cabinet, + Boolean isExpired, + Long daysFromExpireDate); } From c944be926e139909c3ca603d5a5143025e90332e Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 13 Feb 2024 21:13:22 +0900 Subject: [PATCH 0409/1029] =?UTF-8?q?[FE]=20CHORE:=20lightgray,=20darkgray?= =?UTF-8?q?=20=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Card/Card.tsx | 2 +- .../NotificationCard.container.tsx | 4 +- frontend/src/components/Common/Button.tsx | 2 +- .../components/Common/MultiToggleSwitch.tsx | 2 +- .../src/components/Common/ToggleSwitch.tsx | 2 +- .../components/Modals/MemoModal/MemoModal.tsx | 2 +- .../Modals/StatusModal/StatusModal.tsx | 2 +- .../components/Search/SearchItemByIntraId.tsx | 2 +- .../src/components/Search/SearchItemByNum.tsx | 2 +- frontend/src/index.css | 24 +-- frontend/src/pages/admin/AdminClubPage.tsx | 4 +- frontend/src/test.css | 177 ++++++++++++++++++ 12 files changed, 199 insertions(+), 26 deletions(-) create mode 100644 frontend/src/test.css diff --git a/frontend/src/components/Card/Card.tsx b/frontend/src/components/Card/Card.tsx index fbfcc5954..592f06bd3 100644 --- a/frontend/src/components/Card/Card.tsx +++ b/frontend/src/components/Card/Card.tsx @@ -58,7 +58,7 @@ export const CardStyled = styled.div<{ }>` width: ${(props) => props.width}; border-radius: 10px; - background-color: var(--lightgray-color); + background-color: var(--light-gray-color); display: flex; flex-direction: column; align-items: center; diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx index d10d3bb62..ec4900eec 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx @@ -99,8 +99,8 @@ const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { { label: "-", isClickable: false, - color: "var(--lightgray-color)", - backgroundColor: "var(--lightgray-color)", + color: "var(--light-gray-color)", + backgroundColor: "var(--light-gray-color)", }, ] } diff --git a/frontend/src/components/Common/Button.tsx b/frontend/src/components/Common/Button.tsx index c5818725f..08dce0473 100644 --- a/frontend/src/components/Common/Button.tsx +++ b/frontend/src/components/Common/Button.tsx @@ -53,7 +53,7 @@ const ButtonContainerStyled = styled.button` border: 1px solid var(--main-color); `} ${(props) => - props.theme === "lightGrayLine" && + props.theme === "light-grayLine" && css` background: var(--white); color: var(--line-color); diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx index 14456fdae..398f5de68 100644 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/components/Common/MultiToggleSwitch.tsx @@ -63,7 +63,7 @@ const WrapperStyled = styled.div` width: fit-content; display: flex; align-items: center; - background-color: var(--lightgray-color); + background-color: var(--light-gray-color); border-radius: 10px; button { diff --git a/frontend/src/components/Common/ToggleSwitch.tsx b/frontend/src/components/Common/ToggleSwitch.tsx index 88dab14be..e91a558fb 100644 --- a/frontend/src/components/Common/ToggleSwitch.tsx +++ b/frontend/src/components/Common/ToggleSwitch.tsx @@ -57,7 +57,7 @@ const ToggleSwitchStyled = styled.label<{ display: inline-block; position: relative; background: ${(props) => - props.checked ? "var(--main-color)" : "var(--lightgray-color)"}; + props.checked ? "var(--main-color)" : "var(--light-gray-color)"}; width: 56px; height: 28px; border-radius: 50px; diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/components/Modals/MemoModal/MemoModal.tsx index 4221996ee..53bbbc245 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/components/Modals/MemoModal/MemoModal.tsx @@ -105,7 +105,7 @@ const MemoModal = ({ } } text={mode === "read" ? "닫기" : "취소"} - theme={mode === "read" ? "lightGrayLine" : "line"} + theme={mode === "read" ? "light-grayLine" : "line"} /> diff --git a/frontend/src/components/Modals/StatusModal/StatusModal.tsx b/frontend/src/components/Modals/StatusModal/StatusModal.tsx index 8c31e0b45..61a54ad82 100644 --- a/frontend/src/components/Modals/StatusModal/StatusModal.tsx +++ b/frontend/src/components/Modals/StatusModal/StatusModal.tsx @@ -153,7 +153,7 @@ const StatusModal = ({ } } text={mode === "read" ? "닫기" : "취소"} - theme={mode === "read" ? "lightGrayLine" : "line"} + theme={mode === "read" ? "light-grayLine" : "line"} /> diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/components/Search/SearchItemByIntraId.tsx index c09a3c399..1b0e05edb 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/components/Search/SearchItemByIntraId.tsx @@ -132,7 +132,7 @@ const WrapperStyled = styled.div<{ isSelected: boolean }>` height: 110px; border-radius: 10px; padding: 25px; - background-color: var(--lightgray-color); + background-color: var(--light-gray-color); display: flex; align-items: center; transition: transform 0.2s, opacity 0.2s; diff --git a/frontend/src/components/Search/SearchItemByNum.tsx b/frontend/src/components/Search/SearchItemByNum.tsx index 7ce23e71b..199adaaa1 100644 --- a/frontend/src/components/Search/SearchItemByNum.tsx +++ b/frontend/src/components/Search/SearchItemByNum.tsx @@ -81,7 +81,7 @@ const WrapperStyled = styled.div<{ isSelected: boolean }>` height: 110px; border-radius: 10px; padding: 25px; - background-color: var(--lightgray-color); + background-color: var(--light-gray-color); display: flex; align-items: center; transition: transform 0.2s, opacity 0.2s; diff --git a/frontend/src/index.css b/frontend/src/index.css index 1742decee..fda825cbd 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -7,32 +7,28 @@ --line-color: #bcbcbc; --bg-shadow: rgba(0, 0, 0, 0.4); + /* white, gray, black color variable */ + --white: #ffffff; + --soft-white-color: #eeeeee; + --light-gray-color: #f5f5f5; + --gray-color: #7b7b7b; + --dark-gray-color: #434343; + --black: #181818; + /* cabinet color variable */ --mine: #47ffa7; --expired: #ff4e4e; --available: var(--main-color); - --full: #eeeeee; + --full: var(--soft-white-color); --broken: #3c3c3c; --banned: #3c3c3c; - --session: #eeeeee; + --session: var(--soft-white-color); --pending: var(--main-color); --default-main-color: #9747ff; --default-sub-color: #b18cff; --default-mine-color: #47ffa7; - /* white, gray, black color variable */ - --white: #ffffff; - /* 1 */ - --lightgray-color: #f5f5f5; - /* 4 */ - --gray-color: #7b7b7b; - /* 7 */ - --darkgray-color: #434343; - /* 9 */ - --black: #181818; - /* 11 */ - /* font variable */ --main-font: "Noto Sans KR", sans-serif; --building-font: "Do Hyeon", sans-serif; diff --git a/frontend/src/pages/admin/AdminClubPage.tsx b/frontend/src/pages/admin/AdminClubPage.tsx index 7b679101f..f602f8511 100644 --- a/frontend/src/pages/admin/AdminClubPage.tsx +++ b/frontend/src/pages/admin/AdminClubPage.tsx @@ -47,13 +47,13 @@ const AdminClubPage = () => {
`, - pointColor: "white", + pointColor: "var(--white)", }, [ContentStatus.SHARE]: { contentTitle: "공유 사물함", @@ -59,7 +59,7 @@ export const manualContentData: Record = { 연체 페널티는 누적됩니다.
`, - pointColor: "#white", + pointColor: "var(--white)", }, [ContentStatus.CLUB]: { contentTitle: "동아리 사물함", @@ -86,7 +86,7 @@ export const manualContentData: Record = { 상세 페이지가 제공되지 않습니다.
비밀번호는 동아리 내에서 공유하여 이용하세요.
`, - pointColor: "white", + pointColor: "var(--white)", }, [ContentStatus.PENDING]: { contentTitle: "오픈예정", diff --git a/frontend/src/components/AdminInfo/Chart/BarChart.tsx b/frontend/src/components/AdminInfo/Chart/BarChart.tsx index 29f2977cb..0ccabdc8a 100644 --- a/frontend/src/components/AdminInfo/Chart/BarChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/BarChart.tsx @@ -54,7 +54,7 @@ const BarChart = ({ data }: { data: IRentInfo[] }) => ( padding={0.3} valueScale={{ type: "linear" }} indexScale={{ type: "band", round: true }} - colors={["#595959", "#ff4e4e", "#e2e4e3", "var(--main-color)"]} + colors={["#595959", "var(--expired)", "#e2e4e3", "var(--main-color)"]} borderColor={{ from: "color", modifiers: [["darker", 1.6]], diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/components/AdminInfo/Chart/PieChart.tsx index 8dbdcff6b..c85bddff0 100644 --- a/frontend/src/components/AdminInfo/Chart/PieChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/PieChart.tsx @@ -61,7 +61,12 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { labels: { text: { fontSize: "15px" } }, }} margin={{ top: 40, right: 80, bottom: 80, left: 80 }} - colors={["#d9d9d9", "#ff4e4e", "var(--main-color)", "#3c3c3c "]} + colors={[ + "#d9d9d9", + "var(--expired)", + "var(--main-color)", + "var(--dark-gray-color)", + ]} innerRadius={0.5} padAngle={0.7} cornerRadius={3} @@ -99,7 +104,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { { on: "hover", style: { - itemTextColor: "#000", + itemTextColor: "var(--black)", }, }, ], diff --git a/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx b/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx index 67d12969b..c7327dc0f 100644 --- a/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx +++ b/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx @@ -71,7 +71,7 @@ const TooltipCard = styled.div<{ hasEnoughWidth: boolean }>` justify-content: center; & ${ToolTipIcon}:hover + ${TooltipBox} { visibility: visible; - color: #fff; + color: var(--white); background-color: rgba(0, 0, 0, 0.8); &:before { border-color: transparent transparent rgba(0, 0, 0, 0.8) diff --git a/frontend/src/components/Common/WarningNotification.tsx b/frontend/src/components/Common/WarningNotification.tsx index 213da972e..c63605163 100644 --- a/frontend/src/components/Common/WarningNotification.tsx +++ b/frontend/src/components/Common/WarningNotification.tsx @@ -58,7 +58,7 @@ const WarningWrapper = styled.div<{ isVisible: boolean }>` justify-content: center; & ${WarningIcon}:hover + ${WarningBox} { visibility: visible; - color: #fff; + color: var(--white); background-color: rgba(0, 0, 0, 0.8); &:before { border-color: transparent transparent rgba(0, 0, 0, 0.8) diff --git a/frontend/src/components/Modals/Modal.tsx b/frontend/src/components/Modals/Modal.tsx index 4b0e16a3e..89c9bed79 100644 --- a/frontend/src/components/Modals/Modal.tsx +++ b/frontend/src/components/Modals/Modal.tsx @@ -184,7 +184,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: rgb(0, 0, 0); + background: var(--black); opacity: 0.4; animation: fadeInBg 0.5s; @keyframes fadeInBg { diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx b/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx index e79f9d06e..2aecc7485 100644 --- a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx +++ b/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx @@ -152,7 +152,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: rgb(0, 0, 0); + background: var(--black); opacity: 0.4; animation: fadeInBg 0.5s; @keyframes fadeInBg { diff --git a/frontend/src/components/TopNav/SearchBar/SearchBar.tsx b/frontend/src/components/TopNav/SearchBar/SearchBar.tsx index 3c0f046b9..cfcbe34b3 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchBar.tsx +++ b/frontend/src/components/TopNav/SearchBar/SearchBar.tsx @@ -206,14 +206,14 @@ const SearchBarStyled = styled.div` const SearchBarInputStyled = styled.input` width: 300px; height: 40px; - border: 1px solid #7b7b7b; + border: 1px solid var(--gray-color); border-radius: 10px; text-align: left; padding: 0 20px; - color: #7b7b7b; + color: var(--gray-color); background-color: rgba(255, 255, 255, 0.2); &::placeholder { - color: #7b7b7b; + color: var(--gray-color); } `; diff --git a/frontend/src/components/TopNav/TopNav.tsx b/frontend/src/components/TopNav/TopNav.tsx index 4e427c00f..6500a0d54 100644 --- a/frontend/src/components/TopNav/TopNav.tsx +++ b/frontend/src/components/TopNav/TopNav.tsx @@ -96,7 +96,7 @@ const TopNavContainerStyled = styled.nav` justify-content: space-between; align-items: center; /* background-color: white; */ - border-bottom: 1px solid #bcbcbc; + border-bottom: 1px solid var(--line-color); padding: 0 28px; color: var(--gray-color); z-index: 10; diff --git a/frontend/src/index.css b/frontend/src/index.css index fda825cbd..37b26ec38 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -12,7 +12,7 @@ --soft-white-color: #eeeeee; --light-gray-color: #f5f5f5; --gray-color: #7b7b7b; - --dark-gray-color: #434343; + --dark-gray-color: #3c3c3c; --black: #181818; /* cabinet color variable */ @@ -20,8 +20,8 @@ --expired: #ff4e4e; --available: var(--main-color); --full: var(--soft-white-color); - --broken: #3c3c3c; - --banned: #3c3c3c; + --broken: var(--dark-gray-color); + --banned: var(--dark-gray-color); --session: var(--soft-white-color); --pending: var(--main-color); From 03e738af2f6ee784fd9670a6c44a3d19d017dc11 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 17:47:06 +0900 Subject: [PATCH 0412/1029] =?UTF-8?q?[FE]=20FIX:=20black=20=EB=8B=A4=20var?= =?UTF-8?q?(--black)=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/ManualContent.ts | 3 ++- .../src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx | 6 ++++-- frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx | 3 ++- .../CabinetList/CabinetListItem/AdminCabinetListItem.tsx | 3 ++- .../CabinetList/CabinetListItem/CabinetListItem.tsx | 3 ++- frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx | 3 ++- frontend/src/components/Common/ClubListDropdown.tsx | 3 ++- frontend/src/components/Common/Dropdown.tsx | 3 ++- frontend/src/components/Common/MultiToggleSwitch.tsx | 6 ++++-- frontend/src/components/Home/ManualContentBox.tsx | 6 ++++-- frontend/src/components/Home/ServiceManual.tsx | 6 ++++-- .../MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx | 3 ++- frontend/src/components/MapInfo/MapInfo.tsx | 3 ++- frontend/src/components/Modals/ClubModal/ClubModal.tsx | 3 ++- frontend/src/components/Modals/ManualModal/ManualModal.tsx | 6 ++++-- frontend/src/components/Modals/MemoModal/MemoModal.tsx | 3 ++- frontend/src/components/UserInfoArea/UserInfoArea.tsx | 3 ++- frontend/src/recoil/atoms.ts | 3 ++- 18 files changed, 46 insertions(+), 23 deletions(-) diff --git a/frontend/src/assets/data/ManualContent.ts b/frontend/src/assets/data/ManualContent.ts index ec9b1f43e..cd8c59281 100644 --- a/frontend/src/assets/data/ManualContent.ts +++ b/frontend/src/assets/data/ManualContent.ts @@ -143,6 +143,7 @@ export const manualContentData: Record = { 연장권은 해당 월의 마지막 날까지 사용 가능합니다.
공유 사물함에 한 명만 남거나 연체된 상태라면 연장권 사용이 불가합니다.
`, - pointColor: "black", + pointColor: "var(--black)", + // pointColor: "black", }, }; diff --git a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 120f66351..9b4a8fe3c 100644 --- a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -142,7 +142,8 @@ const AdminCabinetInfoArea: React.FC<{ title={selectedCabinetInfo!.lentType} cabinetType={selectedCabinetInfo!.lentType} /> - + {/* */} + {selectedCabinetInfo!.userNameList} @@ -323,7 +324,8 @@ const MultiCabinetIconStyled = styled.div<{ status: CabinetStatus }>` align-items: center; background-color: ${({ status }) => cabinetStatusColorMap[status]}; border-radius: 5px; - color: ${({ status }) => (status === CabinetStatus.FULL ? "black" : "white")}; + /* color: ${({ status }) => (status === CabinetStatus.FULL ? "black" : "white")}; */ + color: ${({ status }) => (status === CabinetStatus.FULL ? "var(--black)" : "white")}; `; export default AdminCabinetInfoArea; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx index ca3cde9ed..f95c6ae99 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -90,7 +90,8 @@ const CabinetInfoArea: React.FC<{ title={selectedCabinetInfo!.lentType} cabinetType={selectedCabinetInfo!.lentType} /> - + {/* */} + {selectedCabinetInfo!.userNameList} diff --git a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index ddcb1f3b6..970683fda 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -232,7 +232,8 @@ const CabinetNumberStyled = styled.p<{ ${({ status }) => status === "IN_SESSION" && css` - color: black; + /* color: black; */ + color: var(--black); `} `; diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx index e1dd84de7..96e8a1727 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -282,7 +282,8 @@ const CabinetNumberStyled = styled.p<{ ${({ status }) => status === "IN_SESSION" && css` - color: black; + /* color: black; */ + color: var(--black); `} `; diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx index 4fffdbf07..c164273b1 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx @@ -68,7 +68,8 @@ const LentInfoCard = ({ /> {cabinetInfo.userNameList} diff --git a/frontend/src/components/Common/ClubListDropdown.tsx b/frontend/src/components/Common/ClubListDropdown.tsx index 65569c513..6b0bc4559 100644 --- a/frontend/src/components/Common/ClubListDropdown.tsx +++ b/frontend/src/components/Common/ClubListDropdown.tsx @@ -120,7 +120,8 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` text-align: start; padding-left: 20px; font-size: 1.125rem; - color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "black")}; + /* color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "black")}; */ + color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "var(--black)")}; cursor: pointer; &:first-child { border-radius: 10px 10px 0px 0px; diff --git a/frontend/src/components/Common/Dropdown.tsx b/frontend/src/components/Common/Dropdown.tsx index b1733efef..d87b64868 100644 --- a/frontend/src/components/Common/Dropdown.tsx +++ b/frontend/src/components/Common/Dropdown.tsx @@ -116,7 +116,8 @@ const DropdownItemStyled = styled.div<{ isSelected: boolean }>` text-align: start; padding-left: 20px; font-size: 1.125rem; - color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "black")}; + /* color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "black")}; */ + color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "var(--black)")}; cursor: pointer; &:first-child { border-radius: 10px 10px 0px 0px; diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx index 398f5de68..168d6268e 100644 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/components/Common/MultiToggleSwitch.tsx @@ -38,7 +38,8 @@ const MultiToggleSwitch = ({ const buttons = wrapperRef.current?.querySelectorAll("button"); buttons?.forEach((button) => { - button.style.color = "black"; + // button.style.color = "black"; + button.style.color = "var(--black)"; button.style.backgroundColor = "transparent"; }); @@ -77,7 +78,8 @@ const WrapperStyled = styled.div` height: 30px; font-weight: 500; background-color: transparent; - color: black; + /* color: black; */ + color: var(--black); } `; diff --git a/frontend/src/components/Home/ManualContentBox.tsx b/frontend/src/components/Home/ManualContentBox.tsx index 229c16f5a..1d83a4204 100644 --- a/frontend/src/components/Home/ManualContentBox.tsx +++ b/frontend/src/components/Home/ManualContentBox.tsx @@ -100,7 +100,8 @@ const MaunalContentBoxStyled = styled.div<{ contentStatus === ContentStatus.EXTENSION && css` width: 900px; - color: black; + /* color: black; */ + color: var(--black); @media screen and (max-width: 1000px) { width: 280px; .peopleImg { @@ -130,7 +131,8 @@ const MaunalContentBoxStyled = styled.div<{ props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" : props.contentStatus === ContentStatus.EXTENSION - ? "black" + // ? "black" + ? "var(--black)" : "white"}; cursor: pointer; } diff --git a/frontend/src/components/Home/ServiceManual.tsx b/frontend/src/components/Home/ServiceManual.tsx index 4ff428e69..ef5323627 100644 --- a/frontend/src/components/Home/ServiceManual.tsx +++ b/frontend/src/components/Home/ServiceManual.tsx @@ -136,7 +136,8 @@ const TitleContainerStyled = styled.div` margin-bottom: 20px; } .title > span { - color: black; + /* color: black; */ + color: var(--black); } `; @@ -149,7 +150,8 @@ const NotionBtn = styled.button` background: white; border: 1px solid #d9d9d9; :hover { - color: black; + /* color: black; */ + color: var(--black); font-weight: 400; } `; diff --git a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx b/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx index ee1bad412..92cd0d035 100644 --- a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx +++ b/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx @@ -42,7 +42,8 @@ const OptionStyled = styled.div` justify-content: center; align-items: center; background: var(--white); - color: black; + /* color: black; */ + color: var(--black); cursor: pointer; `; diff --git a/frontend/src/components/MapInfo/MapInfo.tsx b/frontend/src/components/MapInfo/MapInfo.tsx index 6d7e138f7..d870d5c2f 100644 --- a/frontend/src/components/MapInfo/MapInfo.tsx +++ b/frontend/src/components/MapInfo/MapInfo.tsx @@ -48,7 +48,8 @@ const HeaderStyled = styled.div` justify-content: space-between; width: 100%; align-items: center; - color: black; + /* color: black; */ + color: var(--black); font-weight: bold; `; diff --git a/frontend/src/components/Modals/ClubModal/ClubModal.tsx b/frontend/src/components/Modals/ClubModal/ClubModal.tsx index f21f03723..31eb0dbd1 100644 --- a/frontend/src/components/Modals/ClubModal/ClubModal.tsx +++ b/frontend/src/components/Modals/ClubModal/ClubModal.tsx @@ -232,7 +232,8 @@ const ContentItemInputStyled = styled.input` text-indent: 20px; font-size: 1.125rem; /* cursor: input; */ - color: black; + /* color: black; */ + color: var(--black); &::placeholder { color: var(--line-color); diff --git a/frontend/src/components/Modals/ManualModal/ManualModal.tsx b/frontend/src/components/Modals/ManualModal/ManualModal.tsx index b3755a336..eeddae3b7 100644 --- a/frontend/src/components/Modals/ManualModal/ManualModal.tsx +++ b/frontend/src/components/Modals/ManualModal/ManualModal.tsx @@ -166,7 +166,8 @@ const ModalContent = styled.div<{ props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" : props.contentStatus === ContentStatus.EXTENSION - ? "black" + // ? "black" + ? "var(--black)" : "white"}; font-size: 2.5rem; font-weight: bold; @@ -216,7 +217,8 @@ const CloseButton = styled.div<{ props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" : props.contentStatus === ContentStatus.EXTENSION - ? "black" + // ? "black" + ? "var(--black)" : "white"}; } :hover { diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/components/Modals/MemoModal/MemoModal.tsx index 53bbbc245..57fbcfcb8 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/components/Modals/MemoModal/MemoModal.tsx @@ -179,7 +179,8 @@ const ContentItemInputStyled = styled.input<{ text-indent: 20px; font-size: 1.125rem; cursor: ${({ mode }) => (mode === "read" ? "default" : "input")}; - color: ${({ mode }) => (mode === "read" ? "var(--main-color)" : "black")}; + /* color: ${({ mode }) => (mode === "read" ? "var(--main-color)" : "black")}; */ + color: ${({ mode }) => (mode === "read" ? "var(--main-color)" : "var(--black)")}; &::placeholder { color: ${({ mode }) => mode === "read" ? "var(--main-color)" : "var(--line-color)"}; diff --git a/frontend/src/components/UserInfoArea/UserInfoArea.tsx b/frontend/src/components/UserInfoArea/UserInfoArea.tsx index 2ef8ef0aa..a778b6199 100644 --- a/frontend/src/components/UserInfoArea/UserInfoArea.tsx +++ b/frontend/src/components/UserInfoArea/UserInfoArea.tsx @@ -60,7 +60,8 @@ const UserInfoArea: React.FC<{ {selectedUserInfo.isBanned ? "!" : "-"} - + {/* */} + {selectedUserInfo.name} diff --git a/frontend/src/recoil/atoms.ts b/frontend/src/recoil/atoms.ts index 2a617f1b9..0740b0d9f 100644 --- a/frontend/src/recoil/atoms.ts +++ b/frontend/src/recoil/atoms.ts @@ -172,5 +172,6 @@ export const serverTimeState = atom({ export const darkModeState = atom({ key: "darkMode", - default: "black", + // default: "black", + default: "var(--black)", }); From 952e006081fc0b40f8ffb1de616afd58252743d1 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 19:18:43 +0900 Subject: [PATCH 0413/1029] =?UTF-8?q?[FE]=20FIX:red=20=EA=B0=99=EC=9D=B4?= =?UTF-8?q?=20primitive=EB=A1=9C=20=EC=A0=81=ED=98=80=EC=9E=88=EB=8A=94?= =?UTF-8?q?=EA=B2=83=20hex=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EB=B0=94=EA=BF=88?= =?UTF-8?q?#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/LentLog/LogTable/LogTable.tsx | 2 +- frontend/src/components/Modals/Modal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/LentLog/LogTable/LogTable.tsx b/frontend/src/components/LentLog/LogTable/LogTable.tsx index 94e162a7f..f93c1908b 100644 --- a/frontend/src/components/LentLog/LogTable/LogTable.tsx +++ b/frontend/src/components/LentLog/LogTable/LogTable.tsx @@ -97,7 +97,7 @@ const TbodyStyled = styled.tbody` width: 33.3%; } & > tr:nth-child(2n) { - background-color: hsl(228, 100%, 98%); + background-color: #f5f7ff; } & > tr > td:first-child { padding-left: 20px; diff --git a/frontend/src/components/Modals/Modal.tsx b/frontend/src/components/Modals/Modal.tsx index 89c9bed79..cad839b2f 100644 --- a/frontend/src/components/Modals/Modal.tsx +++ b/frontend/src/components/Modals/Modal.tsx @@ -213,7 +213,7 @@ const DropdownStyled = styled.select` `; const Option = styled.option` - background-color: red; + background-color: var(--expired); `; export default Modal; From 3ed8ef636a050b56ffccd16ac55bd3a81687e5ad Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 20:28:58 +0900 Subject: [PATCH 0414/1029] =?UTF-8?q?[FE]=20FIX:pale=20main=20color=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=ED=95=B4=EC=84=9C=20=EC=A0=81=EC=9A=A9#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/AdminInfo/Table/AdminTable.tsx | 4 ++-- frontend/src/components/Club/ClubLogTable.tsx | 2 +- .../src/components/Common/ClubListDropdown.tsx | 6 ++++-- frontend/src/components/Common/Dropdown.tsx | 8 +++++--- .../LentLog/LogTable/AdminCabinetLogTable.tsx | 14 +++++++++----- frontend/src/index.css | 11 +++++++++++ 6 files changed, 32 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/AdminInfo/Table/AdminTable.tsx b/frontend/src/components/AdminInfo/Table/AdminTable.tsx index 27def6ccb..0c6e2438a 100644 --- a/frontend/src/components/AdminInfo/Table/AdminTable.tsx +++ b/frontend/src/components/AdminInfo/Table/AdminTable.tsx @@ -1,5 +1,5 @@ -import { ITableData } from "src/types/dto/admin.dto"; import { useState } from "react"; +import { ITableData } from "src/types/dto/admin.dto"; import styled from "styled-components"; import Pagination from "./Pagination"; @@ -117,7 +117,7 @@ const TbodyStyled = styled.tbody` line-height: 45px; } & > tr:nth-child(2n) { - background: #f9f6ff; + background: var(--pale-main-color); } cursor: pointer; diff --git a/frontend/src/components/Club/ClubLogTable.tsx b/frontend/src/components/Club/ClubLogTable.tsx index 673f1a7a6..371a9a562 100644 --- a/frontend/src/components/Club/ClubLogTable.tsx +++ b/frontend/src/components/Club/ClubLogTable.tsx @@ -85,7 +85,7 @@ const TbodyStyled = styled.tbody` width: 33.3%; } & > tr:nth-child(2n) { - background: #f9f6ff; + background: var(--pale-main-color); } & > tr.selected { background-color: var(--sub-color); diff --git a/frontend/src/components/Common/ClubListDropdown.tsx b/frontend/src/components/Common/ClubListDropdown.tsx index 6b0bc4559..f28755e7f 100644 --- a/frontend/src/components/Common/ClubListDropdown.tsx +++ b/frontend/src/components/Common/ClubListDropdown.tsx @@ -120,8 +120,10 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` text-align: start; padding-left: 20px; font-size: 1.125rem; - /* color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "black")}; */ - color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "var(--black)")}; + /* color: ${({ isSelected }) => + isSelected ? "var(--main-color)" : "black"}; */ + color: ${({ isSelected }) => + isSelected ? "var(--main-color)" : "var(--black)"}; cursor: pointer; &:first-child { border-radius: 10px 10px 0px 0px; diff --git a/frontend/src/components/Common/Dropdown.tsx b/frontend/src/components/Common/Dropdown.tsx index d87b64868..775c83029 100644 --- a/frontend/src/components/Common/Dropdown.tsx +++ b/frontend/src/components/Common/Dropdown.tsx @@ -108,7 +108,7 @@ const DropdownItemStyled = styled.div<{ isSelected: boolean }>` position: relative; display: flex; align-items: center; - background-color: ${({ isSelected }) => (isSelected ? "#f1f1f1" : "white")}; + background-color: ${({ isSelected }) => (isSelected ? "#eeeeee" : "white")}; border: 1px solid var(--line-color); border-width: 0px 1px 1px 1px; width: 100%; @@ -116,8 +116,10 @@ const DropdownItemStyled = styled.div<{ isSelected: boolean }>` text-align: start; padding-left: 20px; font-size: 1.125rem; - /* color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "black")}; */ - color: ${({ isSelected }) => (isSelected ? "var(--main-color)" : "var(--black)")}; + /* color: ${({ isSelected }) => + isSelected ? "var(--main-color)" : "black"}; */ + color: ${({ isSelected }) => + isSelected ? "var(--main-color)" : "var(--black)"}; cursor: pointer; &:first-child { border-radius: 10px 10px 0px 0px; diff --git a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx b/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx index 8296cb404..938a44bc4 100644 --- a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx +++ b/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx @@ -35,10 +35,14 @@ const AdminCabinetLogTable = ({

- ) @@ -94,7 +98,7 @@ const TbodyStyled = styled.tbody` width: 33.3%; } & > tr:nth-child(2n) { - background: #f9f6ff; + background: var(--pale-main-color); } & > tr > td:first-child { padding-left: 20px; diff --git a/frontend/src/index.css b/frontend/src/index.css index 37b26ec38..d41daee3e 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,9 +1,20 @@ @import url("https://fonts.googleapis.com/css2?family=Do+Hyeon&family=Noto+Sans+KR:wght@300;400;700&display=swap"); +/* +#ffffff +#f5f5f5 +#eeeeee +#d9d9d9 +#7b7b7b +#3c3c3c +#181818 +*/ + :root { /* color variable */ --main-color: #9747ff; --sub-color: #b18cff; + --pale-main-color: #f9f6ff; --line-color: #bcbcbc; --bg-shadow: rgba(0, 0, 0, 0.4); From c3bcbce76a5635f55a131310810bcb028aa553f9 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 20:42:01 +0900 Subject: [PATCH 0415/1029] =?UTF-8?q?[FE]=20FIX:=ED=91=B8=EB=A5=B8=20?= =?UTF-8?q?=ED=9D=B0=EC=83=89=20pale=20main=20color=EB=A1=9C=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/LentLog/LogTable/LogTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/LentLog/LogTable/LogTable.tsx b/frontend/src/components/LentLog/LogTable/LogTable.tsx index f93c1908b..58b2a0a97 100644 --- a/frontend/src/components/LentLog/LogTable/LogTable.tsx +++ b/frontend/src/components/LentLog/LogTable/LogTable.tsx @@ -97,7 +97,7 @@ const TbodyStyled = styled.tbody` width: 33.3%; } & > tr:nth-child(2n) { - background-color: #f5f7ff; + background-color: var(--pale-main-color); } & > tr > td:first-child { padding-left: 20px; From 4baa92595f50be29b2290e8c2dfe2917723f31e9 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 20:42:37 +0900 Subject: [PATCH 0416/1029] =?UTF-8?q?[FE]=20FEAT:=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EC=9A=B4=20=EC=83=89=EC=83=81=20=EC=B6=94=EA=B0=80=20&=20light?= =?UTF-8?q?=20gray=20color=EB=A5=BC=20gray=20100=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=94=EA=BE=B8=EA=B3=A0=20=EC=A0=81=20#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Card/Card.tsx | 2 +- .../Card/NotificationCard/NotificationCard.container.tsx | 4 ++-- frontend/src/components/Common/MultiToggleSwitch.tsx | 2 +- frontend/src/components/Common/ToggleSwitch.tsx | 2 +- frontend/src/components/Search/SearchItemByIntraId.tsx | 2 +- frontend/src/components/Search/SearchItemByNum.tsx | 2 +- frontend/src/index.css | 5 +++-- frontend/src/test.css | 2 +- 8 files changed, 11 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/Card/Card.tsx b/frontend/src/components/Card/Card.tsx index 592f06bd3..2e0a8a604 100644 --- a/frontend/src/components/Card/Card.tsx +++ b/frontend/src/components/Card/Card.tsx @@ -58,7 +58,7 @@ export const CardStyled = styled.div<{ }>` width: ${(props) => props.width}; border-radius: 10px; - background-color: var(--light-gray-color); + background-color: var(--gray-100); display: flex; flex-direction: column; align-items: center; diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx index ec4900eec..9f0aa0aa9 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx @@ -99,8 +99,8 @@ const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { { label: "-", isClickable: false, - color: "var(--light-gray-color)", - backgroundColor: "var(--light-gray-color)", + color: "var(--gray-100)", + backgroundColor: "var(--gray-100)", }, ] } diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx index 168d6268e..62226a232 100644 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/components/Common/MultiToggleSwitch.tsx @@ -64,7 +64,7 @@ const WrapperStyled = styled.div` width: fit-content; display: flex; align-items: center; - background-color: var(--light-gray-color); + background-color: var(--gray-100); border-radius: 10px; button { diff --git a/frontend/src/components/Common/ToggleSwitch.tsx b/frontend/src/components/Common/ToggleSwitch.tsx index e91a558fb..27ebaac53 100644 --- a/frontend/src/components/Common/ToggleSwitch.tsx +++ b/frontend/src/components/Common/ToggleSwitch.tsx @@ -57,7 +57,7 @@ const ToggleSwitchStyled = styled.label<{ display: inline-block; position: relative; background: ${(props) => - props.checked ? "var(--main-color)" : "var(--light-gray-color)"}; + props.checked ? "var(--main-color)" : "var(--gray-100)"}; width: 56px; height: 28px; border-radius: 50px; diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/components/Search/SearchItemByIntraId.tsx index 1b0e05edb..a32ae4176 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/components/Search/SearchItemByIntraId.tsx @@ -132,7 +132,7 @@ const WrapperStyled = styled.div<{ isSelected: boolean }>` height: 110px; border-radius: 10px; padding: 25px; - background-color: var(--light-gray-color); + background-color: var(--gray-100); display: flex; align-items: center; transition: transform 0.2s, opacity 0.2s; diff --git a/frontend/src/components/Search/SearchItemByNum.tsx b/frontend/src/components/Search/SearchItemByNum.tsx index 199adaaa1..0d89aeec0 100644 --- a/frontend/src/components/Search/SearchItemByNum.tsx +++ b/frontend/src/components/Search/SearchItemByNum.tsx @@ -81,7 +81,7 @@ const WrapperStyled = styled.div<{ isSelected: boolean }>` height: 110px; border-radius: 10px; padding: 25px; - background-color: var(--light-gray-color); + background-color: var(--gray-100); display: flex; align-items: center; transition: transform 0.2s, opacity 0.2s; diff --git a/frontend/src/index.css b/frontend/src/index.css index d41daee3e..3b646bef8 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -15,13 +15,14 @@ --main-color: #9747ff; --sub-color: #b18cff; --pale-main-color: #f9f6ff; - --line-color: #bcbcbc; --bg-shadow: rgba(0, 0, 0, 0.4); /* white, gray, black color variable */ --white: #ffffff; + --gray-100: #f5f5f5; --soft-white-color: #eeeeee; - --light-gray-color: #f5f5f5; + --gray-200: #d9d9d9; + --line-color: #bcbcbc; --gray-color: #7b7b7b; --dark-gray-color: #3c3c3c; --black: #181818; diff --git a/frontend/src/test.css b/frontend/src/test.css index 1a88498c9..26d9e7676 100644 --- a/frontend/src/test.css +++ b/frontend/src/test.css @@ -11,7 +11,7 @@ /* white, gray, black color variable */ --white: #ffffff; --soft-white-color: #eeeeee; - --light-gray-color: #f5f5f5; + --gray-100: #f5f5f5; --gray-color: #7b7b7b; --dark-gray-color: #3c3c3c; --black: #181818; From 0d66263faf717559826adb16cbf4ef4dd96a96b4 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 20:49:49 +0900 Subject: [PATCH 0417/1029] =?UTF-8?q?[FE]=20FEAT:=20line-color=EB=A5=BC=20?= =?UTF-8?q?gray=20400=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AdminInfo/Table/Pagination.tsx | 2 +- frontend/src/components/Common/Button.tsx | 4 +- .../components/Common/ClubListDropdown.tsx | 4 +- frontend/src/components/Common/Dropdown.tsx | 4 +- .../Common/MultiSelectFilterButton.tsx | 2 +- frontend/src/components/Common/PillButton.tsx | 2 +- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 4 +- .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 4 +- .../components/Login/AdminLoginTemplate.tsx | 2 +- .../components/Modals/ClubModal/ClubModal.tsx | 4 +- .../components/Modals/MemoModal/MemoModal.tsx | 4 +- .../Modals/StatusModal/StatusModal.tsx | 2 +- frontend/src/components/TopNav/TopNav.tsx | 2 +- frontend/src/index.css | 11 +- frontend/src/pages/Layout.tsx | 2 +- frontend/src/pages/admin/AdminLayout.tsx | 2 +- frontend/src/test.css | 177 ------------------ 17 files changed, 28 insertions(+), 204 deletions(-) delete mode 100644 frontend/src/test.css diff --git a/frontend/src/components/AdminInfo/Table/Pagination.tsx b/frontend/src/components/AdminInfo/Table/Pagination.tsx index a9fd9f2ac..ac20a972c 100644 --- a/frontend/src/components/AdminInfo/Table/Pagination.tsx +++ b/frontend/src/components/AdminInfo/Table/Pagination.tsx @@ -33,7 +33,7 @@ const Pagination = ({ const PageButtonStyled = styled.div` width: 10px; height: 10px; - border: 2px solid var(--line-color); + border: 2px solid var(--gray-400); border-radius: 100%; margin: 0 5px; cursor: pointer; diff --git a/frontend/src/components/Common/Button.tsx b/frontend/src/components/Common/Button.tsx index 08dce0473..d9791fdf5 100644 --- a/frontend/src/components/Common/Button.tsx +++ b/frontend/src/components/Common/Button.tsx @@ -56,8 +56,8 @@ const ButtonContainerStyled = styled.button` props.theme === "light-grayLine" && css` background: var(--white); - color: var(--line-color); - border: 1px solid var(--line-color); + color: var(--gray-400); + border: 1px solid var(--gray-400); `} ${(props) => props.theme === "grayLine" && diff --git a/frontend/src/components/Common/ClubListDropdown.tsx b/frontend/src/components/Common/ClubListDropdown.tsx index f28755e7f..be510c2cd 100644 --- a/frontend/src/components/Common/ClubListDropdown.tsx +++ b/frontend/src/components/Common/ClubListDropdown.tsx @@ -71,7 +71,7 @@ const ClubListDropdSelectionBoxStyled = styled.div<{ isOpen: boolean }>` position: relative; display: flex; align-items: center; - border: 1px solid var(--line-color); + border: 1px solid var(--gray-400); width: 100%; height: 60px; border-radius: 10px; @@ -113,7 +113,7 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` display: flex; align-items: center; background-color: ${({ isSelected }) => (isSelected ? "#f1f1f1" : "white")}; - border: 1px solid var(--line-color); + border: 1px solid var(--gray-400); border-width: 0px 1px 1px 1px; width: 100%; height: 60px; diff --git a/frontend/src/components/Common/Dropdown.tsx b/frontend/src/components/Common/Dropdown.tsx index 775c83029..8b55017ec 100644 --- a/frontend/src/components/Common/Dropdown.tsx +++ b/frontend/src/components/Common/Dropdown.tsx @@ -67,7 +67,7 @@ const DropdownSelectionBoxStyled = styled.div<{ isOpen: boolean }>` position: relative; display: flex; align-items: center; - border: 1px solid var(--line-color); + border: 1px solid var(--gray-400); width: 100%; height: 60px; border-radius: 10px; @@ -109,7 +109,7 @@ const DropdownItemStyled = styled.div<{ isSelected: boolean }>` display: flex; align-items: center; background-color: ${({ isSelected }) => (isSelected ? "#eeeeee" : "white")}; - border: 1px solid var(--line-color); + border: 1px solid var(--gray-400); border-width: 0px 1px 1px 1px; width: 100%; height: 60px; diff --git a/frontend/src/components/Common/MultiSelectFilterButton.tsx b/frontend/src/components/Common/MultiSelectFilterButton.tsx index 223af324b..2fcb34ba3 100644 --- a/frontend/src/components/Common/MultiSelectFilterButton.tsx +++ b/frontend/src/components/Common/MultiSelectFilterButton.tsx @@ -50,7 +50,7 @@ const FilterTextWrapperStyled = styled.div<{ isClicked: boolean }>` justify-content: center; align-items: center; color: ${({ isClicked }) => - isClicked ? "var(--main-color)" : "var(--line-color)"}; + isClicked ? "var(--main-color)" : "var(--gray-400)"}; font-size: 1rem; `; diff --git a/frontend/src/components/Common/PillButton.tsx b/frontend/src/components/Common/PillButton.tsx index 1e516d1af..94cf359a3 100644 --- a/frontend/src/components/Common/PillButton.tsx +++ b/frontend/src/components/Common/PillButton.tsx @@ -31,7 +31,7 @@ const PillButtonContainerStyled = styled.button<{ background-color: ${({ isSelected }) => isSelected ? "var(--main-color)" : "transparent"}; border: ${({ isSelected }) => - isSelected ? "1px solid var(--main-color)" : "1px solid var(--line-color)"}; + isSelected ? "1px solid var(--main-color)" : "1px solid var(--gray-400)"}; color: ${({ isSelected }) => (isSelected ? "var(--white)" : "var(--black)")}; padding: 2px 16px 4px 16px; text-align: center; diff --git a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx index 018b7ae3d..718c8a262 100644 --- a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx +++ b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -143,7 +143,7 @@ const LeftNavStyled = styled.nav` min-width: 90px; position: relative; height: 100%; - border-right: 1px solid var(--line-color); + border-right: 1px solid var(--gray-400); position: relative; display: flex; flex-direction: column; @@ -193,7 +193,7 @@ const BottomSectionStyled = styled.section` margin: 0 auto; width: 56px; height: 1px; - background-color: var(--line-color); + background-color: var(--gray-400); } `; const BottomBtnsStyled = styled.ul` diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index c1f4b532d..7cf3cd566 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -94,7 +94,7 @@ const LeftNavOptionStyled = styled.div<{ min-width: 240px; height: 100%; padding: 32px 10px; - border-right: 1px solid var(--line-color); + border-right: 1px solid var(--gray-400); font-weight: 300; position: relative; `; @@ -106,7 +106,7 @@ const ProfileLeftNavOptionStyled = styled.div<{ min-width: 240px; height: 100%; padding: 32px 10px; - border-right: 1px solid var(--line-color); + border-right: 1px solid var(--gray-400); font-weight: 300; position: relative; & hr { diff --git a/frontend/src/components/Login/AdminLoginTemplate.tsx b/frontend/src/components/Login/AdminLoginTemplate.tsx index 6d6cab21a..ec8e91480 100644 --- a/frontend/src/components/Login/AdminLoginTemplate.tsx +++ b/frontend/src/components/Login/AdminLoginTemplate.tsx @@ -258,7 +258,7 @@ const CardInputStyled = styled.input<{ isFocus: boolean }>` border: ${(props) => props.isFocus ? "1px solid var(--main-color)" - : "1px solid var(--line-color)"}; + : "1px solid var(--gray-400)"}; `; const CardGoogleOauthStyled = styled.button` diff --git a/frontend/src/components/Modals/ClubModal/ClubModal.tsx b/frontend/src/components/Modals/ClubModal/ClubModal.tsx index 31eb0dbd1..8deae1a7e 100644 --- a/frontend/src/components/Modals/ClubModal/ClubModal.tsx +++ b/frontend/src/components/Modals/ClubModal/ClubModal.tsx @@ -224,7 +224,7 @@ const ContentItemTitleStyled = styled.h3` `; const ContentItemInputStyled = styled.input` - border: 1px solid var(--line-color); + border: 1px solid var(--gray-400); width: 100%; height: 60px; border-radius: 10px; @@ -236,7 +236,7 @@ const ContentItemInputStyled = styled.input` color: var(--black); &::placeholder { - color: var(--line-color); + color: var(--gray-400); } `; diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/components/Modals/MemoModal/MemoModal.tsx index 57fbcfcb8..9538ff597 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/components/Modals/MemoModal/MemoModal.tsx @@ -171,7 +171,7 @@ const ContentItemTitleStyled = styled.h3` const ContentItemInputStyled = styled.input<{ mode: string; }>` - border: 1px solid var(--line-color); + border: 1px solid var(--gray-400); width: 100%; height: 60px; border-radius: 10px; @@ -183,7 +183,7 @@ const ContentItemInputStyled = styled.input<{ color: ${({ mode }) => (mode === "read" ? "var(--main-color)" : "var(--black)")}; &::placeholder { color: ${({ mode }) => - mode === "read" ? "var(--main-color)" : "var(--line-color)"}; + mode === "read" ? "var(--main-color)" : "var(--gray-400)"}; } `; diff --git a/frontend/src/components/Modals/StatusModal/StatusModal.tsx b/frontend/src/components/Modals/StatusModal/StatusModal.tsx index 61a54ad82..aafc9c3c5 100644 --- a/frontend/src/components/Modals/StatusModal/StatusModal.tsx +++ b/frontend/src/components/Modals/StatusModal/StatusModal.tsx @@ -222,7 +222,7 @@ const ContentItemContainerStyled = styled.div<{ mode: string }>` position: relative; display: flex; align-items: center; - border: 1px solid var(--line-color); + border: 1px solid var(--gray-400); width: 100%; height: 60px; border-radius: 10px; diff --git a/frontend/src/components/TopNav/TopNav.tsx b/frontend/src/components/TopNav/TopNav.tsx index 6500a0d54..424d483ef 100644 --- a/frontend/src/components/TopNav/TopNav.tsx +++ b/frontend/src/components/TopNav/TopNav.tsx @@ -96,7 +96,7 @@ const TopNavContainerStyled = styled.nav` justify-content: space-between; align-items: center; /* background-color: white; */ - border-bottom: 1px solid var(--line-color); + border-bottom: 1px solid var(--gray-400); padding: 0 28px; color: var(--gray-color); z-index: 10; diff --git a/frontend/src/index.css b/frontend/src/index.css index 3b646bef8..afd593b3b 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -20,9 +20,10 @@ /* white, gray, black color variable */ --white: #ffffff; --gray-100: #f5f5f5; - --soft-white-color: #eeeeee; - --gray-200: #d9d9d9; - --line-color: #bcbcbc; + --gray-200: #eeeeee; + --gray-300: #d9d9d9; + --gray-400: #bcbcbc; + /* TODO : bcbcbc는 line-color*/ --gray-color: #7b7b7b; --dark-gray-color: #3c3c3c; --black: #181818; @@ -31,10 +32,10 @@ --mine: #47ffa7; --expired: #ff4e4e; --available: var(--main-color); - --full: var(--soft-white-color); + --full: var(--gray-200); --broken: var(--dark-gray-color); --banned: var(--dark-gray-color); - --session: var(--soft-white-color); + --session: var(--gray-200); --pending: var(--main-color); --default-main-color: #9747ff; diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index 9b065122c..d555cb5cb 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -158,7 +158,7 @@ const DetailInfoContainerStyled = styled.div<{ isHomePage: boolean }>` min-width: 330px; padding: 45px 40px 20px; position: relative; - border-left: 1px solid var(--line-color); + border-left: 1px solid var(--gray-400); /* background-color: var(--white); */ overflow-y: auto; ${(props) => diff --git a/frontend/src/pages/admin/AdminLayout.tsx b/frontend/src/pages/admin/AdminLayout.tsx index e0ec0e3b2..ee671c0a8 100644 --- a/frontend/src/pages/admin/AdminLayout.tsx +++ b/frontend/src/pages/admin/AdminLayout.tsx @@ -107,7 +107,7 @@ const DetailInfoContainerStyled = styled.div<{ isFloat: boolean }>` min-width: 330px; padding: 45px 40px 20px; position: relative; - border-left: 1px solid var(--line-color); + border-left: 1px solid var(--gray-400); background-color: var(--white); overflow-x: hidden; height: 100%; diff --git a/frontend/src/test.css b/frontend/src/test.css deleted file mode 100644 index 26d9e7676..000000000 --- a/frontend/src/test.css +++ /dev/null @@ -1,177 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Do+Hyeon&family=Noto+Sans+KR:wght@300;400;700&display=swap"); - -:root { - /* color variable */ - --main-color: #9747ff; - --sub-color: #b18cff; - --line-color: #bcbcbc; - --bg-shadow: rgba(0, 0, 0, 0.4); - --red-color: #ff4e4e; - - /* white, gray, black color variable */ - --white: #ffffff; - --soft-white-color: #eeeeee; - --gray-100: #f5f5f5; - --gray-color: #7b7b7b; - --dark-gray-color: #3c3c3c; - --black: #181818; - - /* cabinet color variable */ - --mine: #47ffa7; - --expired: var(--red-color); - --available: var(--main-color); - --full: var(--soft-white-color); - --broken: var(--dark-gray-color); - --banned: var(--dark-gray-color); - --session: var(--soft-white-color); - --pending: var(--main-color); - - --default-main-color: var(--main-color); - --default-sub-color: var(--sub-color); - --default-mine: var(--mine); - - /* font variable */ - --main-font: "Noto Sans KR", sans-serif; - --building-font: "Do Hyeon", sans-serif; - - /* default setting */ - font-family: "Noto Sans KR", Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; - - color: var(--black); - /* background-color: var(--white); */ -} - -a { - color: var(--main-color); - -webkit-tap-highlight-color: transparent; -} - -@media (hover: hover) and (pointer: fine) { - a:hover { - opacity: 0.9; - } -} - -html, -body { - width: 100%; - height: 100%; - user-select: none; - overflow: hidden; -} - -/* iOS Pinch Zoom disabled */ -body { - touch-action: none; -} - -#root { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - overflow: hidden; - background-color: var(--white); - - @media (prefers-color-scheme: dark) { - /* background-color: var(--black); */ - } -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - cursor: pointer; - border: none; - background-color: var(--main-color); - color: var(--white); - width: 200px; - height: 60px; - font-size: 1.125rem; - text-align: center; - font-family: var(--main-font); - border-radius: 6px; - font-weight: 300; - -webkit-tap-highlight-color: transparent; -} - -@media (hover: hover) and (pointer: fine) { - button:hover { - opacity: 0.9; - } -} - -button:disabled { - cursor: wait; - opacity: 0.9; -} - -img { - -webkit-user-drag: none; - max-width: 100%; - width: 100%; - height: 100%; - vertical-align: top; - object-fit: cover; -} - -input { - color: var(--black); - text-align: center; - border: none; - font-size: 1rem; - outline: none; - background: none; - box-sizing: border-box; -} - -.modal { - border-radius: 10px; - box-shadow: 10px 10px 40px 0px rgba(0, 0, 0, 0.25); -} - -.textNowrap { - text-overflow: ellipsis; - overflow: hidden; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; -} - -.noScrollbar { - -ms-overflow-style: none; - scrollbar-width: none; -} -.noScrollbar::-webkit-scrollbar { - display: none; -} - -.blind { - position: absolute; - width: 1px; - height: 1px; - margin: -1px; - clip: rect(0 0 0 0); - overflow: hidden; -} - -.clear::after { - display: block; - clear: both; - content: ""; -} - -.leftNavButtonActive { - background: var(--main-color); - color: var(--white) !important; -} - -.cabiButton { - -webkit-tap-highlight-color: transparent; -} From d34e6ef3a730373f25155dde8b4b34921876504d Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 20:57:35 +0900 Subject: [PATCH 0418/1029] =?UTF-8?q?[FE]=20FIX:=20gray=20color=EB=A5=BC?= =?UTF-8?q?=20gray=20500=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AdminInfo/Table/Pagination.tsx | 4 ++-- .../CabinetInfoArea/AdminCabinetInfoArea.tsx | 12 +++++++----- .../CabinetInfoArea/CabinetInfoArea.tsx | 4 ++-- frontend/src/components/Card/Card.tsx | 2 +- .../Card/LentInfoCard/LentInfoCard.tsx | 2 +- .../components/Card/ProfileCard/ProfileCard.tsx | 2 +- frontend/src/components/Common/Button.tsx | 8 ++++---- .../CabinetColorTable/CabinetColorTable.tsx | 2 +- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 16 ++++++++-------- .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 4 ++-- frontend/src/components/Search/NoSearch.tsx | 2 +- frontend/src/components/Search/SearchDefault.tsx | 2 +- .../components/Search/SearchItemByIntraId.tsx | 2 +- .../src/components/Search/SearchItemByNum.tsx | 2 +- .../SectionPagination/SectionPagination.tsx | 2 +- .../components/TopNav/SearchBar/SearchBar.tsx | 6 +++--- .../SearchBar/SearchBarList/SearchBarList.tsx | 2 +- frontend/src/components/TopNav/TopNav.tsx | 2 +- .../src/components/UserInfoArea/UserInfoArea.tsx | 4 ++-- frontend/src/index.css | 2 +- .../PendingPage/components/FloorContainer.tsx | 2 +- frontend/src/pages/admin/AdminPagination.tsx | 6 +++--- 22 files changed, 46 insertions(+), 44 deletions(-) diff --git a/frontend/src/components/AdminInfo/Table/Pagination.tsx b/frontend/src/components/AdminInfo/Table/Pagination.tsx index ac20a972c..93f32e692 100644 --- a/frontend/src/components/AdminInfo/Table/Pagination.tsx +++ b/frontend/src/components/AdminInfo/Table/Pagination.tsx @@ -40,8 +40,8 @@ const PageButtonStyled = styled.div` `; const ActivaPageButtonStyled = styled(PageButtonStyled)` - background: var(--gray-color); - border: 2px solid var(--gray-color); + background: var(--gray-500); + border: 2px solid var(--gray-500); `; const ButtonContainerStyled = styled.div` diff --git a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 9b4a8fe3c..20cfcc349 100644 --- a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -67,7 +67,7 @@ const AdminCabinetInfoArea: React.FC<{ - + 사물함/유저를
선택해주세요
@@ -77,7 +77,7 @@ const AdminCabinetInfoArea: React.FC<{ if (multiSelectTargetInfo) { return ( - + {currentFloor + "F - " + currentSection} @@ -129,7 +129,7 @@ const AdminCabinetInfoArea: React.FC<{ return ( 대여기록 - + {selectedCabinetInfo!.floor + "F - " + selectedCabinetInfo!.section} ` align-items: center; background-color: ${({ status }) => cabinetStatusColorMap[status]}; border-radius: 5px; - /* color: ${({ status }) => (status === CabinetStatus.FULL ? "black" : "white")}; */ - color: ${({ status }) => (status === CabinetStatus.FULL ? "var(--black)" : "white")}; + /* color: ${({ status }) => + status === CabinetStatus.FULL ? "black" : "white"}; */ + color: ${({ status }) => + status === CabinetStatus.FULL ? "var(--black)" : "white"}; `; export default AdminCabinetInfoArea; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx index f95c6ae99..1b5cf3560 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -66,14 +66,14 @@ const CabinetInfoArea: React.FC<{ - + 사물함을
선택해주세요
) : ( - + {selectedCabinetInfo!.floor !== 0 ? selectedCabinetInfo!.floor + "F - " + selectedCabinetInfo!.section : "-"} diff --git a/frontend/src/components/Card/Card.tsx b/frontend/src/components/Card/Card.tsx index 2e0a8a604..e855123b3 100644 --- a/frontend/src/components/Card/Card.tsx +++ b/frontend/src/components/Card/Card.tsx @@ -97,7 +97,7 @@ export const CardButtonStyled = styled.div<{ ? props.color : props.isExtensible ? "var(--main-color)" - : "var(--gray-color)"}; + : "var(--gray-500)"}; padding: 5px 15px; border: none; border-radius: 5px; diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx index c164273b1..a804408ff 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx @@ -54,7 +54,7 @@ const LentInfoCard = ({ {cabinetInfo.floor !== 0 ? cabinetInfo.floor + "층 - " + cabinetInfo.section diff --git a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx b/frontend/src/components/Card/ProfileCard/ProfileCard.tsx index 92efc4b8b..07e16ccc4 100644 --- a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx +++ b/frontend/src/components/Card/ProfileCard/ProfileCard.tsx @@ -75,7 +75,7 @@ const ProfileDetail = styled.div` const EmailDetail = styled(ProfileDetail)` font-size: 0.9rem; - color: var(--gray-color); + color: var(--gray-500); `; export default ProfileCard; diff --git a/frontend/src/components/Common/Button.tsx b/frontend/src/components/Common/Button.tsx index d9791fdf5..b5fb3540f 100644 --- a/frontend/src/components/Common/Button.tsx +++ b/frontend/src/components/Common/Button.tsx @@ -63,8 +63,8 @@ const ButtonContainerStyled = styled.button` props.theme === "grayLine" && css` background: var(--white); - color: var(--gray-color); - border: 1px solid var(--gray-color); + color: var(--gray-500); + border: 1px solid var(--gray-500); `} ${(props) => props.theme === "smallGrayLine" && @@ -72,9 +72,9 @@ const ButtonContainerStyled = styled.button` max-width: 200px; height: 40px; background: var(--white); - color: var(--gray-color); + color: var(--gray-500); font-size: 0.875rem; - border: 1px solid var(--gray-color); + border: 1px solid var(--gray-500); `} @media (max-height: 745px) { diff --git a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx index dcdf0d8f9..dfb5d34e8 100644 --- a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx +++ b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx @@ -36,7 +36,7 @@ const ColorTableStyled = styled.div` left: 30px; width: auto; overflow: hidden; - color: var(--gray-color); + color: var(--gray-500); `; const ColorTableItemStyled = styled.div<{ color: string }>` diff --git a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx index 718c8a262..340e800b2 100644 --- a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx +++ b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -86,7 +86,7 @@ const LeftMainNav = ({ } onClick={onClickSearchButton} > - + Search @@ -95,7 +95,7 @@ const LeftMainNav = ({ target="_blank" title="슬랙 캐비닛 채널 새창으로 열기" > - + Contact @@ -107,14 +107,14 @@ const LeftMainNav = ({ } onClick={onClickClubButton} > - + Club - + Logout @@ -128,7 +128,7 @@ const LeftMainNav = ({ } onClick={onClickProfileButton} > - + Profile )} @@ -167,7 +167,7 @@ const TopBtnStyled = styled.li` font-weight: 300; margin-bottom: 2.5vh; border-radius: 10px; - color: var(--gray-color); + color: var(--gray-500); cursor: pointer; &:last-child { margin-bottom: 0; @@ -208,7 +208,7 @@ const BottomBtnStyled = styled.li` font-weight: 300; margin-top: 2.5vh; border-radius: 10px; - color: var(--gray-color); + color: var(--gray-500); cursor: pointer; display: flex; flex-direction: column; @@ -217,7 +217,7 @@ const BottomBtnStyled = styled.li` margin-top: 0; } & a { - color: var(--gray-color); + color: var(--gray-500); } & div { width: 24px; diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index 7cf3cd566..328fb79cf 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -125,7 +125,7 @@ const FloorSectionStyled = styled.div` line-height: 40px; border-radius: 10px; text-indent: 20px; - color: var(--gray-color); + color: var(--gray-500); margin: 2px 0; cursor: pointer; @media (hover: hover) and (pointer: fine) { @@ -146,7 +146,7 @@ const SectionLinkStyled = styled.div` cursor: pointer; display: flex; align-items: center; - color: var(--gray-color); + color: var(--gray-500); & img { width: 15px; height: 15px; diff --git a/frontend/src/components/Search/NoSearch.tsx b/frontend/src/components/Search/NoSearch.tsx index 8b7f36302..6a7fd4ae6 100644 --- a/frontend/src/components/Search/NoSearch.tsx +++ b/frontend/src/components/Search/NoSearch.tsx @@ -28,7 +28,7 @@ const NoSearchStyled = styled.div` & > p { margin-top: 10px; font-size: 1.125rem; - color: var(--gray-color); + color: var(--gray-500); } `; diff --git a/frontend/src/components/Search/SearchDefault.tsx b/frontend/src/components/Search/SearchDefault.tsx index b0fd9fd59..a94058bb6 100644 --- a/frontend/src/components/Search/SearchDefault.tsx +++ b/frontend/src/components/Search/SearchDefault.tsx @@ -29,7 +29,7 @@ const SearchDefaultStyled = styled.div` & > p { margin-top: 10px; font-size: 1.125rem; - color: var(--gray-color); + color: var(--gray-500); text-align: center; line-height: 1.75rem; } diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/components/Search/SearchItemByIntraId.tsx index a32ae4176..16a2b4cf7 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/components/Search/SearchItemByIntraId.tsx @@ -187,7 +187,7 @@ const TextWrapper = styled.div` const LocationStyled = styled.p` font-size: 0.875rem; line-height: 28px; - color: var(--gray-color); + color: var(--gray-500); `; const NameWrapperStyled = styled.div` diff --git a/frontend/src/components/Search/SearchItemByNum.tsx b/frontend/src/components/Search/SearchItemByNum.tsx index 0d89aeec0..ae6145f34 100644 --- a/frontend/src/components/Search/SearchItemByNum.tsx +++ b/frontend/src/components/Search/SearchItemByNum.tsx @@ -124,7 +124,7 @@ const TextWrapper = styled.div` const LocationStyled = styled.p` font-size: 0.875rem; line-height: 28px; - color: var(--gray-color); + color: var(--gray-500); `; const NameWrapperStyled = styled.div` diff --git a/frontend/src/components/SectionPagination/SectionPagination.tsx b/frontend/src/components/SectionPagination/SectionPagination.tsx index a5dc2b578..bcf192640 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.tsx +++ b/frontend/src/components/SectionPagination/SectionPagination.tsx @@ -92,7 +92,7 @@ const SectionNameTextStyled = styled.div` min-width: 220px; font-size: 1rem; text-align: center; - color: var(--gray-color); + color: var(--gray-500); `; const SectionIndexStyled = styled.div` diff --git a/frontend/src/components/TopNav/SearchBar/SearchBar.tsx b/frontend/src/components/TopNav/SearchBar/SearchBar.tsx index cfcbe34b3..24a7066e4 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchBar.tsx +++ b/frontend/src/components/TopNav/SearchBar/SearchBar.tsx @@ -206,14 +206,14 @@ const SearchBarStyled = styled.div` const SearchBarInputStyled = styled.input` width: 300px; height: 40px; - border: 1px solid var(--gray-color); + border: 1px solid var(--gray-500); border-radius: 10px; text-align: left; padding: 0 20px; - color: var(--gray-color); + color: var(--gray-500); background-color: rgba(255, 255, 255, 0.2); &::placeholder { - color: var(--gray-color); + color: var(--gray-500); } `; diff --git a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx b/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx index cc56979f0..7de075045 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx +++ b/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx @@ -74,7 +74,7 @@ const UlStyled = styled.ul` const TotalStyled = styled.li` font-size: 0.875rem; - color: var(--gray-color); + color: var(--gray-500); text-align: right; padding: 10px; `; diff --git a/frontend/src/components/TopNav/TopNav.tsx b/frontend/src/components/TopNav/TopNav.tsx index 424d483ef..57d34b19c 100644 --- a/frontend/src/components/TopNav/TopNav.tsx +++ b/frontend/src/components/TopNav/TopNav.tsx @@ -98,7 +98,7 @@ const TopNavContainerStyled = styled.nav` /* background-color: white; */ border-bottom: 1px solid var(--gray-400); padding: 0 28px; - color: var(--gray-color); + color: var(--gray-500); z-index: 10; `; diff --git a/frontend/src/components/UserInfoArea/UserInfoArea.tsx b/frontend/src/components/UserInfoArea/UserInfoArea.tsx index a778b6199..148aec51b 100644 --- a/frontend/src/components/UserInfoArea/UserInfoArea.tsx +++ b/frontend/src/components/UserInfoArea/UserInfoArea.tsx @@ -39,7 +39,7 @@ const UserInfoArea: React.FC<{ return ( - + 사물함/유저를
선택해주세요
@@ -49,7 +49,7 @@ const UserInfoArea: React.FC<{ return ( 대여기록 - + 대여 중이 아닌 사용자 ` margin-top: 20px; margin-left: 5px; p { - color: var(--gray-color); + color: var(--gray-500); line-height: 1.5; word-break: keep-all; } diff --git a/frontend/src/pages/admin/AdminPagination.tsx b/frontend/src/pages/admin/AdminPagination.tsx index 5963b56fd..1e51f40c5 100644 --- a/frontend/src/pages/admin/AdminPagination.tsx +++ b/frontend/src/pages/admin/AdminPagination.tsx @@ -26,13 +26,13 @@ const PointStyled = styled.div<{ active: boolean }>` width: 23px; height: 23px; border-radius: 100%; - border: 3px solid var(--gray-color); + border: 3px solid var(--gray-500); cursor: pointer; transition: 0.5s; &:hover { - background: var(--gray-color); + background: var(--gray-500); } - background: ${(props) => (props.active ? "var(--gray-color)" : "none")}; + background: ${(props) => (props.active ? "var(--gray-500)" : "none")}; `; const AdminPaginationStyled = styled.div<{ page: number }>` From c0f6dffe3ff39db7e5976c9425b9d01edb093a4a Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 20:58:29 +0900 Subject: [PATCH 0419/1029] =?UTF-8?q?[FE]=20FIX:=20dark=20gray=20color?= =?UTF-8?q?=EB=A5=BC=20gray=20600=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD#1?= =?UTF-8?q?551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/AdminInfo/Chart/PieChart.tsx | 2 +- frontend/src/index.css | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/components/AdminInfo/Chart/PieChart.tsx index c85bddff0..33702516c 100644 --- a/frontend/src/components/AdminInfo/Chart/PieChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/PieChart.tsx @@ -65,7 +65,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { "#d9d9d9", "var(--expired)", "var(--main-color)", - "var(--dark-gray-color)", + "var(--gray-600)", ]} innerRadius={0.5} padAngle={0.7} diff --git a/frontend/src/index.css b/frontend/src/index.css index 0f72d010d..0ab866aae 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -25,7 +25,7 @@ --gray-400: #bcbcbc; /* TODO : bcbcbc는 line-color*/ --gray-500: #7b7b7b; - --dark-gray-color: #3c3c3c; + --gray-600: #3c3c3c; --black: #181818; /* cabinet color variable */ @@ -33,8 +33,8 @@ --expired: #ff4e4e; --available: var(--main-color); --full: var(--gray-200); - --broken: var(--dark-gray-color); - --banned: var(--dark-gray-color); + --broken: var(--gray-600); + --banned: var(--gray-600); --session: var(--gray-200); --pending: var(--main-color); From 768da4435f0963c90fc33e5332cdc787e929fa5c Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 21:06:28 +0900 Subject: [PATCH 0420/1029] =?UTF-8?q?[FE]=20FIX:=20gray=20300=EA=B9=8C?= =?UTF-8?q?=EC=A7=80=20=EB=B3=80=EC=88=98=20=EC=A0=81=EC=9A=A9#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/ManualContent.ts | 4 ++-- frontend/src/components/AdminInfo/Chart/BarChart.tsx | 2 +- frontend/src/components/AdminInfo/Chart/PieChart.tsx | 2 +- frontend/src/components/Club/AdminClubLog.tsx | 2 +- frontend/src/components/Common/ClubListDropdown.tsx | 5 +++-- frontend/src/components/Common/Dropdown.tsx | 2 +- frontend/src/components/Home/ServiceManual.tsx | 4 ++-- .../src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx | 2 +- .../MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx | 2 +- frontend/src/components/MapInfo/MapGrid/MapGrid.tsx | 2 +- .../src/components/SectionPagination/SectionPagination.tsx | 2 +- frontend/src/pages/PendingPage/components/FloorContainer.tsx | 2 +- 12 files changed, 16 insertions(+), 15 deletions(-) diff --git a/frontend/src/assets/data/ManualContent.ts b/frontend/src/assets/data/ManualContent.ts index cd8c59281..b7346c18f 100644 --- a/frontend/src/assets/data/ManualContent.ts +++ b/frontend/src/assets/data/ManualContent.ts @@ -107,7 +107,7 @@ export const manualContentData: Record = { [ContentStatus.IN_SESSION]: { contentTitle: "대기중", imagePath: "/src/assets/images/clock.svg", - background: "#F5F5F7", + background: "var(--gray-100)", contentText: `◦ 상세 내용
공유 사물함 대여 시 10분간의 대기 시간이 발생합니다.
대여 과정에서 생성된 초대 코드를 통해 공유 사물함에 입장할 수 있습니다.
@@ -127,7 +127,7 @@ export const manualContentData: Record = { [ContentStatus.EXTENSION]: { contentTitle: "연장권 이용방법 안내서", imagePath: "/src/assets/images/extension.svg", - background: "#F5F5F7", + background: "var(--gray-100)", contentText: `◦ 연장권 취득 조건
월 출석 시간이 기준 시간 이상일 시 연장권이 부여됩니다.
diff --git a/frontend/src/components/AdminInfo/Chart/BarChart.tsx b/frontend/src/components/AdminInfo/Chart/BarChart.tsx index 0ccabdc8a..ccb0e8842 100644 --- a/frontend/src/components/AdminInfo/Chart/BarChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/BarChart.tsx @@ -54,7 +54,7 @@ const BarChart = ({ data }: { data: IRentInfo[] }) => ( padding={0.3} valueScale={{ type: "linear" }} indexScale={{ type: "band", round: true }} - colors={["#595959", "var(--expired)", "#e2e4e3", "var(--main-color)"]} + colors={["#595959", "var(--expired)", "var(--gray-300)", "var(--main-color)"]} borderColor={{ from: "color", modifiers: [["darker", 1.6]], diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/components/AdminInfo/Chart/PieChart.tsx index 33702516c..104923d02 100644 --- a/frontend/src/components/AdminInfo/Chart/PieChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/PieChart.tsx @@ -62,7 +62,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { }} margin={{ top: 40, right: 80, bottom: 80, left: 80 }} colors={[ - "#d9d9d9", + "var(--gray-300)", "var(--expired)", "var(--main-color)", "var(--gray-600)", diff --git a/frontend/src/components/Club/AdminClubLog.tsx b/frontend/src/components/Club/AdminClubLog.tsx index d0dd18094..6fed110cc 100644 --- a/frontend/src/components/Club/AdminClubLog.tsx +++ b/frontend/src/components/Club/AdminClubLog.tsx @@ -20,7 +20,7 @@ const AdminClubLog = ({ indexButtons.push( changePageOnClickIndexButton(i)} className="cabiButton" /> diff --git a/frontend/src/components/Common/ClubListDropdown.tsx b/frontend/src/components/Common/ClubListDropdown.tsx index be510c2cd..58d2dc3e5 100644 --- a/frontend/src/components/Common/ClubListDropdown.tsx +++ b/frontend/src/components/Common/ClubListDropdown.tsx @@ -112,7 +112,8 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` position: relative; display: flex; align-items: center; - background-color: ${({ isSelected }) => (isSelected ? "#f1f1f1" : "white")}; + background-color: ${({ isSelected }) => + isSelected ? "var(--gray-200)" : "white"}; border: 1px solid var(--gray-400); border-width: 0px 1px 1px 1px; width: 100%; @@ -133,7 +134,7 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` border-radius: 0px 0px 10px 10px; } &:hover { - background-color: #f1f1f1; + background-color: var(--gray-200); } `; diff --git a/frontend/src/components/Common/Dropdown.tsx b/frontend/src/components/Common/Dropdown.tsx index 8b55017ec..3e401eaed 100644 --- a/frontend/src/components/Common/Dropdown.tsx +++ b/frontend/src/components/Common/Dropdown.tsx @@ -129,7 +129,7 @@ const DropdownItemStyled = styled.div<{ isSelected: boolean }>` border-radius: 0px 0px 10px 10px; } &:hover { - background-color: #f1f1f1; + background-color: var(--gray-200); } `; diff --git a/frontend/src/components/Home/ServiceManual.tsx b/frontend/src/components/Home/ServiceManual.tsx index ef5323627..230c86371 100644 --- a/frontend/src/components/Home/ServiceManual.tsx +++ b/frontend/src/components/Home/ServiceManual.tsx @@ -121,7 +121,7 @@ const TitleContainerStyled = styled.div` display: flex; justify-content: space-between; align-items: center; - border-bottom: 2px solid #d9d9d9; + border-bottom: 2px solid var(--gray-300); margin-bottom: 70px; color: var(--main-color); font-weight: 700; @@ -148,7 +148,7 @@ const NotionBtn = styled.button` font-size: 0.875rem; color: #333333; background: white; - border: 1px solid #d9d9d9; + border: 1px solid var(--gray-300); :hover { /* color: black; */ color: var(--black); diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index 328fb79cf..83e2f0993 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -112,7 +112,7 @@ const ProfileLeftNavOptionStyled = styled.div<{ & hr { width: 80%; height: 1px; - background-color: #d9d9d9; + background-color: var(--gray-300); border: 0; margin-top: 20px; margin-bottom: 20px; diff --git a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx b/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx index 92cd0d035..8be6195be 100644 --- a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx +++ b/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx @@ -37,7 +37,7 @@ const OptionWrapperStyled = styled.div` const OptionStyled = styled.div` width: 65px; height: 40px; - border-bottom: 1px solid #e6e6e6; + border-bottom: 1px solid var(--gray-200); display: flex; justify-content: center; align-items: center; diff --git a/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx b/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx index 5ec8c0e7e..10872131e 100644 --- a/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx +++ b/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx @@ -36,7 +36,7 @@ const MapGridStyled = styled.div` text-align: center; max-height: 580px; height: 100%; - background: #e7e7e7; + background: var(--gray-200); display: grid; grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(8, 1fr); diff --git a/frontend/src/components/SectionPagination/SectionPagination.tsx b/frontend/src/components/SectionPagination/SectionPagination.tsx index bcf192640..fc523da13 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.tsx +++ b/frontend/src/components/SectionPagination/SectionPagination.tsx @@ -22,7 +22,7 @@ const SectionPagination: React.FC<{ changeSectionOnClickIndexButton(index)} className="cabiButton" diff --git a/frontend/src/pages/PendingPage/components/FloorContainer.tsx b/frontend/src/pages/PendingPage/components/FloorContainer.tsx index 78de2b3f9..52b5aeb3f 100644 --- a/frontend/src/pages/PendingPage/components/FloorContainer.tsx +++ b/frontend/src/pages/PendingPage/components/FloorContainer.tsx @@ -59,7 +59,7 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` color: var(--black); padding-left: 5px; padding-right: 5px; - border-bottom: 1.5px solid #d9d9d9; + border-bottom: 1.5px solid var(--gray-300); cursor: pointer; button { all: initial; From 1533b8987d70c55b545a8a877902ad5cb1fbdca8 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 21:17:02 +0900 Subject: [PATCH 0421/1029] =?UTF-8?q?[FE]=20FIX:=20gray=20600=EA=B9=8C?= =?UTF-8?q?=EC=A7=80=20=EB=B3=80=EC=88=98=EB=A1=9C=20=EA=B5=90=EC=B2=B4#15?= =?UTF-8?q?51?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/AdminInfo/Chart/BarChart.tsx | 2 +- frontend/src/components/AdminInfo/Chart/PieChart.tsx | 4 ++-- frontend/src/components/Common/Dropdown.tsx | 2 +- frontend/src/components/Home/ServiceManual.tsx | 2 +- frontend/src/components/MapInfo/MapItem/MapItem.tsx | 5 +++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/AdminInfo/Chart/BarChart.tsx b/frontend/src/components/AdminInfo/Chart/BarChart.tsx index ccb0e8842..74824662e 100644 --- a/frontend/src/components/AdminInfo/Chart/BarChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/BarChart.tsx @@ -54,7 +54,7 @@ const BarChart = ({ data }: { data: IRentInfo[] }) => ( padding={0.3} valueScale={{ type: "linear" }} indexScale={{ type: "band", round: true }} - colors={["#595959", "var(--expired)", "var(--gray-300)", "var(--main-color)"]} + colors={["var(--gray-600)", "var(--expired)", "var(--gray-300)", "var(--main-color)"]} borderColor={{ from: "color", modifiers: [["darker", 1.6]], diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/components/AdminInfo/Chart/PieChart.tsx index 104923d02..0c556184d 100644 --- a/frontend/src/components/AdminInfo/Chart/PieChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/PieChart.tsx @@ -77,7 +77,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { modifiers: [["darker", 0.2]], }} arcLinkLabelsSkipAngle={10} - arcLinkLabelsTextColor="#333333" + arcLinkLabelsTextColor="var(--gray-600)" arcLinkLabelsThickness={2} arcLinkLabelsColor={{ from: "color" }} arcLabelsSkipAngle={10} @@ -95,7 +95,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { itemsSpacing: 5, itemWidth: 70, itemHeight: 18, - itemTextColor: "#999", + itemTextColor: "var(--gray-500)", itemDirection: "top-to-bottom", itemOpacity: 1, symbolSize: 12, diff --git a/frontend/src/components/Common/Dropdown.tsx b/frontend/src/components/Common/Dropdown.tsx index 3e401eaed..92ac5d840 100644 --- a/frontend/src/components/Common/Dropdown.tsx +++ b/frontend/src/components/Common/Dropdown.tsx @@ -108,7 +108,7 @@ const DropdownItemStyled = styled.div<{ isSelected: boolean }>` position: relative; display: flex; align-items: center; - background-color: ${({ isSelected }) => (isSelected ? "#eeeeee" : "white")}; + background-color: ${({ isSelected }) => (isSelected ? "var(--gray-200)" : "white")}; border: 1px solid var(--gray-400); border-width: 0px 1px 1px 1px; width: 100%; diff --git a/frontend/src/components/Home/ServiceManual.tsx b/frontend/src/components/Home/ServiceManual.tsx index 230c86371..0f87fa700 100644 --- a/frontend/src/components/Home/ServiceManual.tsx +++ b/frontend/src/components/Home/ServiceManual.tsx @@ -146,7 +146,7 @@ const NotionBtn = styled.button` height: 40px; border-radius: 8px; font-size: 0.875rem; - color: #333333; + color: var(--gray-600); background: white; border: 1px solid var(--gray-300); :hover { diff --git a/frontend/src/components/MapInfo/MapItem/MapItem.tsx b/frontend/src/components/MapInfo/MapItem/MapItem.tsx index de08f87c4..e06cb9ed0 100644 --- a/frontend/src/components/MapInfo/MapItem/MapItem.tsx +++ b/frontend/src/components/MapInfo/MapItem/MapItem.tsx @@ -42,7 +42,8 @@ const ItemStyled = styled.div<{ padding: 3px; font-size: ${({ info }) => (info.type === "floorInfo" ? "1.8rem" : "0.8rem")}; cursor: ${({ info }) => (info.type === "floorInfo" ? "default" : "pointer")}; - color: ${({ info }) => (info.type === "floorInfo" ? "#bcb9b9" : "white")}; + color: ${({ info }) => + info.type === "floorInfo" ? "var(--gray-400)" : "white"}; display: flex; justify-content: center; align-items: center; @@ -57,7 +58,7 @@ const ItemStyled = styled.div<{ ? "var(--main-color)" : info.type === "floorInfo" ? "transparent" - : "#bcb9b9"}; + : "var(--gray-400)"}; &:hover { opacity: ${({ info }) => (info.type === "cabinet" ? 0.9 : 1)}; } From 101dcb96fbb8b4a7e0f4a56629602e8c8ece4e30 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 21:34:08 +0900 Subject: [PATCH 0422/1029] =?UTF-8?q?[FE]=20FEAT:=20main=20color=20variabl?= =?UTF-8?q?es=20=EC=B6=94=EA=B0=80=ED=95=98=EA=B3=A0=20pale=20main=20color?= =?UTF-8?q?=20>=20main=20100=EC=9C=BC=EB=A1=9C=20=EB=B0=94=EA=BF=88#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AdminInfo/Table/AdminTable.tsx | 2 +- frontend/src/components/Club/ClubLogTable.tsx | 2 +- .../LentLog/LogTable/AdminCabinetLogTable.tsx | 2 +- .../components/LentLog/LogTable/LogTable.tsx | 2 +- frontend/src/index.css | 22 ++++++++----------- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/AdminInfo/Table/AdminTable.tsx b/frontend/src/components/AdminInfo/Table/AdminTable.tsx index 0c6e2438a..135acb1aa 100644 --- a/frontend/src/components/AdminInfo/Table/AdminTable.tsx +++ b/frontend/src/components/AdminInfo/Table/AdminTable.tsx @@ -117,7 +117,7 @@ const TbodyStyled = styled.tbody` line-height: 45px; } & > tr:nth-child(2n) { - background: var(--pale-main-color); + background: var(--main-100); } cursor: pointer; diff --git a/frontend/src/components/Club/ClubLogTable.tsx b/frontend/src/components/Club/ClubLogTable.tsx index 371a9a562..71c3d69e7 100644 --- a/frontend/src/components/Club/ClubLogTable.tsx +++ b/frontend/src/components/Club/ClubLogTable.tsx @@ -85,7 +85,7 @@ const TbodyStyled = styled.tbody` width: 33.3%; } & > tr:nth-child(2n) { - background: var(--pale-main-color); + background: var(--main-100); } & > tr.selected { background-color: var(--sub-color); diff --git a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx b/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx index 938a44bc4..f1e1f5488 100644 --- a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx +++ b/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx @@ -98,7 +98,7 @@ const TbodyStyled = styled.tbody` width: 33.3%; } & > tr:nth-child(2n) { - background: var(--pale-main-color); + background: var(--main-100); } & > tr > td:first-child { padding-left: 20px; diff --git a/frontend/src/components/LentLog/LogTable/LogTable.tsx b/frontend/src/components/LentLog/LogTable/LogTable.tsx index 58b2a0a97..06eeb9aa4 100644 --- a/frontend/src/components/LentLog/LogTable/LogTable.tsx +++ b/frontend/src/components/LentLog/LogTable/LogTable.tsx @@ -97,7 +97,7 @@ const TbodyStyled = styled.tbody` width: 33.3%; } & > tr:nth-child(2n) { - background-color: var(--pale-main-color); + background-color: var(--main-100); } & > tr > td:first-child { padding-left: 20px; diff --git a/frontend/src/index.css b/frontend/src/index.css index 0ab866aae..9f86a3441 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,22 +1,18 @@ @import url("https://fonts.googleapis.com/css2?family=Do+Hyeon&family=Noto+Sans+KR:wght@300;400;700&display=swap"); -/* -#ffffff -#f5f5f5 -#eeeeee -#d9d9d9 -#7b7b7b -#3c3c3c -#181818 -*/ - :root { /* color variable */ - --main-color: #9747ff; - --sub-color: #b18cff; - --pale-main-color: #f9f6ff; --bg-shadow: rgba(0, 0, 0, 0.4); + /* main color variable */ + --main-100: #f9f6ff; + --main-200: #dfd0fe; + --sub-color: #b18cff; + --main-400: #a17bf3; + --main-color: #9747ff; + --main-600: #8337e5; + --main-custom: #a29bfe; + /* white, gray, black color variable */ --white: #ffffff; --gray-100: #f5f5f5; From 8404fece1d94158a1a5d1438fe238c41aad40976 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 14 Feb 2024 21:39:28 +0900 Subject: [PATCH 0423/1029] =?UTF-8?q?[FE]=20FIX:=20main=20color=20variable?= =?UTF-8?q?s=20=EC=A0=81=EC=9A=A9#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/ManualContent.ts | 2 +- frontend/src/components/Card/ThemeColorCard/colorInfo.ts | 4 ++-- .../Modals/PasswordCheckModal/PasswordCheckModal.tsx | 2 +- frontend/src/index.css | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/assets/data/ManualContent.ts b/frontend/src/assets/data/ManualContent.ts index b7346c18f..0778b616d 100644 --- a/frontend/src/assets/data/ManualContent.ts +++ b/frontend/src/assets/data/ManualContent.ts @@ -14,7 +14,7 @@ export const manualContentData: Record = { [ContentStatus.PRIVATE]: { contentTitle: "개인 사물함", imagePath: "/src/assets/images/privateIcon.svg", - background: "linear-gradient(to bottom, #A17BF3, #8337E5)", + background: "linear-gradient(to bottom, var(--main-400), #8337E5)", rentalPeriod: `${import.meta.env.VITE_PRIVATE_LENT_PERIOD}일`, capacity: "1인", contentText: `◦ 이용 방법
diff --git a/frontend/src/components/Card/ThemeColorCard/colorInfo.ts b/frontend/src/components/Card/ThemeColorCard/colorInfo.ts index 66b284607..ce5ac47c3 100644 --- a/frontend/src/components/Card/ThemeColorCard/colorInfo.ts +++ b/frontend/src/components/Card/ThemeColorCard/colorInfo.ts @@ -37,6 +37,6 @@ export const customColors = [ "#74b9ff", "#0984e3", "#0D4C92", - "#a29bfe", - "#9747FF", + "var(--main-custom)", + "var(--main-color)", ]; diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx b/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx index 2aecc7485..99b8bd35c 100644 --- a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx +++ b/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx @@ -71,7 +71,7 @@ const Input = styled.input<{ isEmpty: number | null }>` border-radius: 10px; outline: none; border: ${({ isEmpty }) => - isEmpty ? "1px solid var(--main-color)" : "1px solid #dfd0fe"}; + isEmpty ? "1px solid var(--main-color)" : "1px solid var(--main-200)"}; `; const PasswordContainer = styled.div` diff --git a/frontend/src/index.css b/frontend/src/index.css index 9f86a3441..02a3672d1 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -3,7 +3,8 @@ :root { /* color variable */ --bg-shadow: rgba(0, 0, 0, 0.4); - + /* subcolor variable */ + /* main color variable */ --main-100: #f9f6ff; --main-200: #dfd0fe; From 03489e9e7c3c34107c3853f690a16094c791ea64 Mon Sep 17 00:00:00 2001 From: SpaceChae <13278955+enaenen@users.noreply.github.com> Date: Wed, 14 Feb 2024 21:43:13 +0900 Subject: [PATCH 0424/1029] =?UTF-8?q?[COMMON]=20FEAT:=20=EB=8F=99=EC=95=84?= =?UTF-8?q?=EB=A6=AC=20=ED=95=A0=EB=8B=B9=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#1539)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BE] Club 패키지 주석 추가 및 불필요 Join 연산 삭제 [BE] ClubCommandService 주석 추가 [BE] ClubController, ClubFacadeService 주석 추가 [BE] ClubPolicyService 주석 추가 [BE] ClubQueryService 주석 추가 [BE] ClubRegistrationCommandService 주석 추가 [BE] ClubRegistrationQueryService 주석 추가 [BE] 불필요한 Join 연산 삭제 [BE] Club 관련 Repository 주석 추가 * [BE] Admin Club 패키지 관련 주석 추가 및 일부 로직 수정 [BE] Admin User 관련 미사용 로직 삭제 및 주석 일부 수정 [BE] AdminClubContorller 주석 추가 및 일부 로직 수정 [BE] AdminClubFacadeService 주석 추가 * [BE] CabinetFacadeService 줄맞춤 * [BE] long -> Long * [FE] FEAT: 동아리 페이지 생성 및 Route 적용 * [BE] 생성자 반복 호출 개선 * [BE] 연장권 사용 get -> post 요청으로 수정 및 api 변경 * [FE] FEAT: 동아리 조회를 위한 api 추가 #1519 * [FE] FEAT: 동아리 페이지 페이지네이션 및 데이터 조회 prototype 작성 #1519 * [FE] FIX: Mock 데이터 삽입 * [BE] 안쓰는 api 및 로직 삭제, LentExtensions 일급 컬렉션 로직 수정 * [BE] FIX: 트랜잭션 추가 * [BE] FIX: 안쓰는 API 삭제 * [BE] FIX: repository에 비즈니스 관련 메서드 명 수정 * [FE] ETC: 멤버 배열 정렬 완료 * [BE] FIX: 피드백 반영 및 사용하지 않는 api 관련 로직 삭제 * [FE] ETC: 멤버 리스트 정보 다 가져와서 배치 * [BE] FIX: 미사용 API 삭제 * [BE] FIX: 빌드 오류 수정 * [FE] ETC: title bar 완료 * [BE] FIX: redis 관련 id 값 그대로 key로 사용하는 일부 로직 수정 * [BE] FIX: 명명 수정 * [FE] WIP: Set 고치는 중 * [BE] cabinet 상태 변경 로직 일부 수정 * [BE] Club cabinet 상태 변경 로직 일부 수정 * [FE] FEAT: my club info axios로 정보 받아옴 * [BE] 로그인 오류 수정 * [FE] ETC: 동아리 멤버 title bar 쪽 아이콘이랑 멤버 총합 css * [BE] 동아리를 동아리 사물함에 할당하는 기능과 동아리 대여를 반납 시키는 기능 구현 * [BE] api 명세 안맞는 부분 수정 * [FE] ETC: 동아리 멤버 배열 정상화 * [FE] ETC: 파일 알맞은 위치로 옮김 * [BE] 안쓰는 api 및 로직 삭제 * [BE] 안쓰는 api 및 로직 삭제 * [BE] 안쓰는 api 및 로직 삭제 * [FE] FEAT: 동아리 멤버 추가 모달 전반적으로 잘 돌아감 * [FE] FEAT: 동아리 멤버 추가 axios로 데이터 요청 * [FE] ETC: 동아리 페이지 토글 스위치 적용 중 * [FE] FEAT: 동아리 페이지에 기존 multi toggle switch 적용 * [FE] FEAT: 동아리 페이지에 multi toggle switch2 적용 * [FE] FEAT: 동아리 페이지 pagenation? 적용 * [FE] FEAT: 동아리 페이지 leftNav 띄움 * [FE] FEAT: 동아리 페이지 레이아웃 설정 * [FE] FEAT: 동아리 프로필, 동아리 메모모달 추가 * [FE] FIX: API 명세에 맞춰 동아리 생성 및 관련 Dto 변경 #1519 * [FE] ETC: 멤버 카드에 닫기 버튼 추가 #1519 * [FE] FIX: API 명세에 맞춰 동아리수정 변경 #1519 * [FE] ETC: 멤버 카드에 닫기 버튼 추가 안돼서 다시 #1519 * [FE] ETC: 동아리 프로필, 메모모달 수정 #1519 * [FE] FIX: axiosAddClubMember 를 명세에 맞게 수정 #1519 * [FE] FEAT: 동아리원 삭제 API 추가 #1519 * [BE] 어드민 동아리 목록에서도 동아리 마스터 보이도록 로직 수정, 기존 DTO와 중복되어 하나 삭제 후 통일 * [BE] 동아리 사물함에 동아리가 대여 시도 시 teapot 에러 발생하는 현상 수정 * [FE] 동아리 프로필 수정 #1519 * [FE] 동아리 프로필 비밀번호 호버기능 추가 #1519 * [FE] FIX: 동아리 API 를 사용해 데이터를 불러오도록 수정 #1519 * [FE] MERGE: merge함 #1519 * [FE] ETC: 불필요한 파일 삭제 * [FE] TEST: git pull 받고 이어서 작업 * [FE] FEAT: 위임 모달 연결 및 모달연결구조 변경 * [FE] FEAT: 동아리사물함 멤버 삭제,위임 모달 추가 #1519 * [FE] FIX: 오타 수정 #1519 * [FE] FEAT: 동아리멤버 추가 api 정상 작동#1519 * [FE] FIX: 동아리 사물함 멤버 제거 axios연동 * [FE] FIX: 동아리 선택 시 해당 동아리 정보를 불러오도록 수정 #1519 * [FE] FEAT: 동아리 사물함 권한 위임 axios연동 #1519 * [FE] FIX: 동아리 정보 수정, 동아리메모, 동아리 비밀번호 수정 #1519 * [FE] FIX: recoil-Persist 에서 currentFloor 과 currentSection 을 삭제하는 함수를 분리 후 AdminClubPage 에 적용 #1519 * [FE] FIX: 명세에 맞게 ClubUserDto 및 사용되는 변수 변경, 이에 맞춰 Admin ClubModal 변경, 레이아웃 정리 #1519 * [FE] FIX: ClubPage 의 컴포넌트를 Card 컴포넌트로 분리하기 전 Card 컴포넌트 정리 * [FE] FEAT: 더보기 버튼 추가 #1519 * [FE] FIX: 소속된 동아리가 없을 시, 동아리가 사물함이 없을 시 관리를 위해 Dto 수정 및 추가 #1519 * [FE] ETC: 동아리 페이지 레이아웃 수정 및 메모 버그 수정 #1519 * [FE] ETC: 동아리 메모 버그 수정 #1519 * [FE] FIX: 동아리 페이지 레이아웃 수정 및 메모 버그 수정 #1519 * [FE] FIX: 동아리 권한 위임 수정 #1519 * [FE] FIX: 동아리 할당 리펙토링 첫번쨰 #1519 * [FE] FIX: 동아리 할당기능 리펙토링 오타 수정 #1519 * [BE] 동아리원 추가 및 삭제 시 에러 수정 * [FE] FIX: 동아리 멤버 추가 삭제 위임시 리렌더 바로 되게 함 #1519 * [BE] 동아리원 페이지네이션 오류 수정 * [FE] ETC: 더보기 버튼 ing#1519 * [BE] 동아리 공지 기능 추가 * [BE] 메서드 명 변경 및 User toString 추가 * [BE] 메서드 명 정리 및 API 변경(pending -> available) * [FE] FIX: 동아리 멤버 추가 삭제 위임시 리렌더2 #1519 * [BE] DDL 동기화, Club 생성 및 조회 시 notice도 같이 기능하도록 수정 * [FE] CHORE: 동아리 멤버수 띄우는 데이터 변경 #1519 * [BE] 불필요한 countQuery 삭제 * [FE] FEAT: 멤버 수만큼 멤버 카드들이 보여지면 더보기 버튼 안보이게 #1519 * [FE] FEAT: 다른 동아리 버튼 눌렀을때 page 0으로 설정#1519 * [FE] FEAT: ClubMembers container 만들어서 나눔#1519 * [FE] CHORE: AddClubMemModal.tsx엔 jsx만 남겨둠, x 버튼 수정#1519 * [FE] FIX: delete모달 함수명 및 오타 수정 * [FE] FEAT: 비번 hover시 실제 값으로 대체#1519 * [BE] 동아리 유저 soft delete로 변경 * [FE] FIX: 클럽 멤버를 더보기로 출력할 수 있도록 수정 #1519 * [BE] deletedAt nullable로 변경 * [BE] club 관련 검색 시 deletedAt이 null인 값만 조회하도록 수정 * [FE] FIX: 동아리 맴버 추가, 등록 시 페이지 다시 불러오기 추가 #1519 * [FE] FIX: 리펙토링 undo #1519 * [FE] FIX: ClubCabinetInfo에 있는 카드들을 ClubDetail과 ClubMemo로 나눔#1519 * [FE] FIX: 메모모달과 비번 모달을 각각에 해당하는 카드 컴포넌트에 붙임#1519 * [FE] FIX 동아리 멤버 디자인 변경 * [FE] FIX: Styled component 오타 수정 * [FE] ETC: 비번 api 적용 준비시킴#1519 * [FE] FIX: 동아리 멤버 색상 수정 * [FE] FIX: ClubPage 내에서 데이터가 중복되지 않게 구조 변경 #1519 * [FE] FEAT: 메모 모달 api 적용#1519 * [FE] ETC: 비번모달 버그 픽스 중 * [FE] FIX: 동아리 멤버 svg크기 조정 * [FE] FIX: merge conflict 해결 #1519 * [FE] FIX: 더보기 버튼 클릭 시 추가로 Member 요청하도록 수정 #1519 * [FE] FIX : 메모 모달 관련 컴포넌트들 이름 변경#1519 * [FE] FIX : ClubMemo.tsx까지 정리#1519 * [FE] FIX: 비번 모달 값 바꿔도 초기값 뜨는거 해결 #1519 * [FE] FIX: 오타 수정 * [FE] FIX: 동아리 정보 Card 컴포넌트로 분리, Card 에 이미지 버튼 추가 #1519 * [FE] FIX: Figma 에 맞춰 동아리 페이지 레이아웃 조정 #1519 * [FE] FIX: 가입한 동아리가 없을 시, 동아리 사물함이 없을 시 문구가 제대로 보이도록 수정 #1519 * [FE] FIX: 동아리 멤버 헤더 width 수정 #1519 * [BE] 반복되는 조회-수정 쿼리 수정 및 cqrs 코드 삭제 * [FE] FIX: pending 사물함 조회 로직 api 변경 : pending -> available * ClubLentHistory expiredAt Not Null 조건 해제 * [FE] 클럽 유저 정보 표시를 위한 clubMemberInfoArea 추가 #1519 * [FE] feat: clubMemberInfoArea 를 위한 recoil state 추가 #1519 * [FE] feat: clubMemberInfoArea 추가, 모달 위치 변경 및 props 간소화#1519 * [FE] fix: clubMemberInfoArea 의 동아리 이름 강조, 유저별 아이콘 수정 #1519 * [FE] fix: ClubPageModals 와 ClubMembersContainer 통합 #1519 * [FE] fix: 동아리 Master 판별 조건 수정 #1519 * [FE] FIX: Club 폴더에 ClubMemberInfoArea 통합, 혼동되는 ClubModalState 명 변경 #1519 * [BE] 동아리 정보 보낼 때, 동아리장 이름만 보내던 것에서 ClubUserResponseDto로 보내도록 변경 * [FE] FIX: DTO 변경에 따른 ClubMaster 변경, 동아리원 선택 시 효과 추가 #1519 * [FE] FIX: 자기 자신에게 동아리장 위임 / 내보내기를 할 수 없도록 수정 #1519 * [FE] FIX: 복수형 컴포넌트명 단수형으로 변경, DTO 변경에 따른 추가 변경 #1519 * [FE] FIX: getCabinetUserList 주석 추가 * [BE] 프론트 피드백 반영 및 오류 수정, 사물함 비밀번호 기능 구현 * [BE] 트랜잭션 빠진 부분 ㅊ천 * [BE] 트랜잭션 빠진 부분 수정 * [BE] 트랜잭션 애노테이션 일원화 및 값 저장 변경 감지 방식으로 수정 * [FE] FIX: 필요없는 파일 제거 #1519 * [FE] FEAT: 동아리 사물함 비밀번호 업데이트 API 추가 #1519 * [FE] FIX: 메모 모달과 위임 모달 문구 수정#1519 * [FE] FIX: club memo modal container과 club mem modal 코드 분리#1519 * [FE] FIX: club memo modal container,club mem modal 리팩토링#1519 * [FE] FIX: club pw modal container,club pw modal 리팩토링#1519 * [FE] FIX: clubpage 메모 모달 비번 모달에 맞춰 리팩토링#1519 * [FE] FIX: Admin 페이지에서 동아리 삭제가 안되는 문제 수정 #1519 * [FE] FIX: ClubMemberInfoArea 에서 ClubMaster / ClubMember icon 이 잘못 나타나는 문제 수정 #1519 * [FE] FIX: 모바일 환경에서 ClubMemberInfoArea 의 아이콘이 제대로 보이지 않는 문제 수정 #1519 * [FE] FIX: 더보기 버튼 클릭 후 다른 동아리 선택 시 API 요청을 추가로 보내는 문제 수정 #1519 * [FE] FIX: 클럽 정보 변경 시 새로 랜더링 되지 않던 문제 수정 #1519 * [FE] FIX: Card 컴포넌트 icon 버튼 NOTE 추가 #1519 * [FE] FIX: 동아리장이 항상 ClubMember 목록 중 앞에 오도록 수정 #1519 * [FE] FIX: 더보기 버튼이 미리 랜더링된 동아리장을 카운트하지 않는 문제 수정 #1519 * [FE] FIX: 내가 동아리장일 때 더보기 버튼이 나타나지 않는 오류 수정 #1519 * [FE] FIX: 동아리 변경 시 로딩 애니메이면 복구 #1519 * [BE] FIX: CabinetLentHistory - startedAt JPA 자체 갱신으로 수정 * [BE] FIX: ADMIN - delete club시, registration 에도 deletedAt 업데이트 * [FE] FIX: 동아리 사물함 비밀번호 업데이트 시 비밀번호가 업데이트 되지 않는 문제 수정 #1519 * [FE] FIX: ClubMemberInfoArea 에서 사물함 정보 삭제 #1519 * [BE] FIX: ADMIN - 동아리에 사물함 할당 * [FE] FIX: 더보기 버튼이 내가 동아리장일 시 제대로 나타나지 않던 문제 수정, 동아리장 색깔을 sub-color 에서 main-color 로 변경 #1519 * [FE] FIX: 동아리 메모 개행 반영#1519 * [FE] FIX: 동아리 맴버 위임 / 내보내기 이후 ClubMemberInfoArea 닫도록 수정 #1519 * [FE] FIX: 자식에서 불러올 수 있는 정보 props 에서 제거 #1519 * [FE] FIX: 동아리 페이지의 메모 줄 간격 넓히기#1519 * [FE] FIX: 비밀번호 변경 이후 로딩 추가 #1519 * [FE] FIX: 동아리 멤버 hover 시 transform 과 cursur 적용 #1519 * [FE] FIX: 동아리 멤버 추가 시 ResponseModal 띄우기 #1519 * [FE] FIX: 내가 동아리장이 아닐 시 더보기 버튼이 잘못 렌더링되는 문제 수정 #1519 * [FE] FIX: 메모 모달 입력창 크기 고정 #1519 * [FE] FIX: 파일명 및 컴포넌트명 수정#1519 * [FE] FEAT: 동아리 장이 아니면 비번 설정&메모 설정 아이콘 안보이게함#1519 * [FE] ETC: console 지움#1519 * [BE] HOTFIX: Redis 백업 기능 설정 파일 -> 디렉토리 변경 * [FE] FIX: Admin 반환결과에서 lents 가 null 일 시의 예외 처리 * [FE] FIX: ClubInfo 컴포넌트에서 사용되는 Card 들의 레이아웃 일관성을 위한 버튼 추가 #1519 * [FE] FIX: 2월 7일 Figma 변경에 따라 Club 페이지 디자인 및 구성 변경 #1519 * [FE] FIX: 필요없는 import 및 코드 제거 * [FE] FIX: import 수정 * [FE] FIX: 가입한 동아리가 없을 시 문구를 Figma 에 맞춰 수정 #1519 * [FE] FIX: 가입한 동아리가 없을 시, 동아리 사물함이 없을 시 문구 및 로고 수정 #1519 * [FE] FIX: 동아리 페이지 LeftNav 에 제목 추가 #1519 * [FE] FIX: 프로필 페이지 링크 아이콘 보이지 않던 문제 수정 * [FE] FIX: 필요없는 css 제거 #1519 * [FE] FIX: ClubPasswordModal 폴더별로 정리, 이전 ClubPasswordModal 삭제 #1519 * [FE] FIX: pending 페이지를 available 로 변경 * [FE] FIX: 동아리 페이지에서 새로고침 시 Floor & cabinets 정보를 요청하지 않도록 recoil-persist 삭제 #1519 * [FE] FEAT: 동아리 유저 더보기 클릭 시 로딩 에니메이션 추가 #1519 * [FE] FEAT: 비번 설정시 숫자 4개 입력해야 설정버튼 활성화#1519 * [FE] FIX: 메모 한글로 작성시 100자쯤의 글자수 에러 해결#1519 * [FE] FIX: 메모 수정시 response modal 띄움#1519 * [FE] FIX: 동아리멤버 추가,삭제,위임 모달 메시지 통일#1519 * [FE] CHORE: 필요없는 코드 지움#1519 * [BE] FIX: Admin에서 전체 동아리 목록 조회 시 Pagination의 전체 개수 잘못 전달하는 오류 수정 * [FE] FIX : adminpage 동아리 목록 디자인 변경#1519 * [FE] BUG : MandateClubMemberModal의 정의되지 않은 인자 clubName 지움#1519 * [FE] FIX: 클럽 컴포넌트 책임 분리 #1519 * [FE] FIX: props 간소화, Club 컴포넌트 내 Styled 및 랜더링 간소화 #1519 * [FE] FIX: 내가 동아리장일때 비번설정아이콘, 메모설정아이콘 보이게함#1519 * [FE] FIX: 동아리 사물함이 할당되지 않았을 시 문구 출력 대신 앱이 렌더링 되지 않는 문제 수정 #1519 * [BE] Club name unique 제약 조건 추가 * [FE] FIX: 내가 동아리장이 아닐 시 더보기 버튼이 잘못 렌더링되는 문제 수정 + state 간소화 #1519 * [FE] FIX: 비번 카드에 호버시 마우스 포인터 바뀌게 함#1519 * [FE] FIX: 동아리 변경 시 열린 ClubUserInfoArea 가 닫히도록 수정 #1519 * [FE] FIX: 동아리 메모 오류 수정 #1519 * [FE] FIX: 동아리 메모 오류 재수정 #1519 * [FE] REMOVE: setting.svg 삭제 #1519 * [FE] FEAT: lock.svg 추가 #1519 * [FE] FEAT: 프로필 사물함 정보 card 우측 아이콘 톱니 모양에서 자물쇠 모양으로 변경 #1519 * [FE] FIX: 더보기 버튼 색깔이 main-color 을 따라가지 않는 문제 수정 #1519 * [FE] FIX: import 수정 #1519 * [FE] FIX: import 수정 #1519 * [BE] FEAT: 동아리 반납 로직 추가 * [FE] FIX: import 수정 #1519 * [BE] Club 대여 시 이름 안보이는 현상 수정 [BE] redis 에러 수정 --------- Co-authored-by: jiwon Co-authored-by: jusohn Co-authored-by: jnkeniaem Co-authored-by: 최지민 Co-authored-by: gykoh42 Co-authored-by: Minkyu01 Co-authored-by: chyo1 Co-authored-by: moonseonghui --- .gitignore | 3 + backend/database/cabi_local.sql | 1082 +++++++++-------- .../ftclub/cabinet/CabinetApplication.java | 2 + .../admin/controller/AdminController.java | 35 - .../admin/service/AdminCommandService.java | 10 - .../admin/service/AdminFacadeService.java | 32 - .../controller/AdminCabinetController.java | 81 +- .../club/controller/AdminClubController.java | 112 ++ .../club/service/AdminClubFacadeService.java | 121 ++ .../admin/dto/AdminClubUserRequestDto.java | 5 +- .../lent/service/AdminLentFacadeService.java | 32 +- .../service/AdminSearchFacadeService.java | 37 +- .../user/controller/AdminUserController.java | 70 +- .../AdminLentExtensionFacadeService.java | 15 - .../user/service/AdminUserFacadeService.java | 67 +- .../cabinet/auth/service/TokenProvider.java | 7 +- .../cabinet/controller/CabinetController.java | 102 +- .../cabinet/repository/CabinetRepository.java | 19 - .../service/CabinetCommandService.java | 38 +- .../cabinet/service/CabinetFacadeService.java | 144 +-- .../cabinet/service/CabinetQueryService.java | 28 +- .../club/controller/ClubController.java | 122 ++ .../org/ftclub/cabinet/club/domain/Club.java | 93 ++ .../cabinet/club/domain/ClubLentHistory.java | 98 ++ .../cabinet/club/domain/ClubRegistration.java | 94 ++ .../repository/ClubRegistrationRepoitory.java | 81 ++ .../club/repository/ClubRepository.java | 39 + .../club/service/ClubCommandService.java | 53 + .../club/service/ClubFacadeService.java | 214 ++++ .../club/service/ClubPolicyService.java | 55 + .../club/service/ClubQueryService.java | 55 + .../ClubRegistrationCommandService.java | 48 + .../service/ClubRegistrationQueryService.java | 96 ++ .../cabinet/dto/AddClubUserRequestDto.java | 17 + .../org/ftclub/cabinet/dto/ClubCreateDto.java | 21 + ...atusRequestDto.java => ClubDeleteDto.java} | 14 +- .../org/ftclub/cabinet/dto/ClubInfoDto.java | 14 + .../cabinet/dto/ClubInfoPaginationDto.java | 13 + .../cabinet/dto/ClubInfoResponseDto.java | 24 + .../ftclub/cabinet/dto/ClubMemoUpdateDto.java | 15 + .../cabinet/dto/ClubNoticeUpdateDto.java | 15 + .../cabinet/dto/ClubUpdateRequestDto.java | 21 + .../cabinet/dto/ClubUserResponseDto.java | 14 + .../cabinet/dto/LentExtensionResponseDto.java | 4 +- .../dto/MandateClubMasterRequestDto.java | 17 + .../cabinet/dto/UserVerifyRequestDto.java | 3 +- .../cabinet/exception/ExceptionStatus.java | 9 +- .../cabinet/lent/domain/LentPolicyStatus.java | 2 +- .../lent/repository/ClubLentRepository.java | 42 + .../cabinet/lent/repository/LentRedis.java | 28 +- .../lent/repository/LentRepository.java | 20 +- .../lent/service/ClubLentCommandService.java | 26 + .../lent/service/ClubLentQueryService.java | 42 + .../lent/service/LentFacadeService.java | 70 +- .../lent/service/LentPolicyService.java | 58 +- .../lent/service/LentQueryService.java | 4 + .../org/ftclub/cabinet/mapper/ClubMapper.java | 35 + .../org/ftclub/cabinet/mapper/LentMapper.java | 9 +- .../user/controller/UserController.java | 24 +- .../cabinet/user/domain/LentExtensions.java | 51 +- .../org/ftclub/cabinet/user/domain/User.java | 45 +- .../ftclub/cabinet/user/domain/UserRole.java | 9 +- .../repository/LentExtensionRepository.java | 8 +- .../user/repository/UserRepository.java | 19 +- .../service/LentExtensionQueryService.java | 40 +- .../user/service/UserCommandService.java | 21 +- .../user/service/UserFacadeService.java | 36 +- .../user/service/UserQueryService.java | 34 +- .../utils/cqrs/DatabaseSynchronizer.java | 21 + .../cabinet/utils/release/ReleaseManager.java | 40 +- .../cabinet/mapper/CabinetMapperTest2.java | 49 +- .../ftclub/cabinet/mapper/LentMapperTest.java | 102 +- .../cabinet/mapper/UserSessionMapperTest.java | 46 +- .../ftclub/cabinet/user/domain/UserTest.java | 203 ++-- .../utils/mail/EmailServiceUnitTest.java | 35 +- .../java/org/ftclub/testutils/TestUtils.java | 28 +- config | 2 +- frontend/src/App.tsx | 14 +- frontend/src/api/axios/axios.custom.ts | 189 ++- frontend/src/assets/css/media.css | 15 + frontend/src/assets/data/maps.ts | 25 + frontend/src/assets/images/close-circle.svg | 4 + frontend/src/assets/images/crown.svg | 3 + frontend/src/assets/images/edit.svg | 5 + frontend/src/assets/images/leader.svg | 3 + frontend/src/assets/images/lock.svg | 5 + frontend/src/assets/images/maru.svg | 9 + frontend/src/assets/images/more.svg | 5 + .../src/assets/images/selectMaincolor.svg | 3 + .../Available/AvailableCountdown.tsx} | 27 +- .../Available}/FloorContainer.tsx | 8 +- .../CabinetInfoArea.container.tsx | 7 +- .../CabinetInfoArea/CabinetInfoArea.tsx | 8 +- .../CabinetList/CabinetList.container.tsx | 6 +- frontend/src/components/Card/Card.tsx | 92 +- frontend/src/components/Card/CardStyles.ts | 2 +- .../ClubCabinetInfoCard.tsx | 188 +++ .../Card/ClubNoticeCard/ClubNoticeCard.tsx | 92 ++ .../Card/ExtensionCard/ExtensionCard.tsx | 10 +- .../LentInfoCard/LentInfoCard.container.tsx | 1 + .../Card/LentInfoCard/LentInfoCard.tsx | 18 +- frontend/src/components/Club/AdminClubLog.tsx | 1 - frontend/src/components/Club/ClubInfo.tsx | 117 ++ frontend/src/components/Club/ClubList.tsx | 56 + frontend/src/components/Club/ClubLogTable.tsx | 82 +- .../ClubMemberInfoArea.container.tsx | 60 + .../ClubMemberInfoArea/ClubMemberInfoArea.tsx | 222 ++++ .../ClubMemberList.container.tsx | 107 ++ .../Club/ClubMemberList/ClubMemberList.tsx | 222 ++++ .../ClubMemberListItem/ClubMemberListItem.tsx | 92 ++ .../components/Common/MultiToggleSwitch.tsx | 3 +- .../Common/MultiToggleSwitchSeparated.tsx | 86 ++ .../src/components/Home/ManualContentBox.tsx | 6 +- .../LeftMainNav/LeftMainNav.container.tsx | 16 +- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 51 +- .../LeftSectionNav.container.tsx | 2 + .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 14 +- .../LeftSectionNav/LeftSectionNavClubs.tsx | 68 ++ .../AddClubMemberModal.container.tsx | 77 ++ .../Modals/ClubModal/AddClubMemberModal.tsx | 140 +++ .../ClubModal/ClubMemoModal.container.tsx | 133 ++ .../Modals/ClubModal/ClubMemoModal.tsx | 199 +++ .../components/Modals/ClubModal/ClubModal.tsx | 119 +- .../ClubModal/ClubPasswordModal.container.tsx | 163 +++ .../Modals/ClubModal/ClubPasswordModal.tsx | 190 +++ .../ClubModal/DeleteClubMemberModal.tsx | 82 ++ .../ClubModal/MandateClubMemberModal.tsx | 82 ++ .../Modals/LentModal/ClubLentModal.tsx | 8 +- .../PasswordCheckModal/PasswordCheckModal.tsx | 2 +- .../src/components/Search/SearchItemByNum.tsx | 4 +- frontend/src/hooks/useClubInfo.ts | 87 ++ frontend/src/hooks/useMenu.ts | 56 +- .../PendingPage.tsx => AvailablePage.tsx} | 120 +- frontend/src/pages/ClubPage.tsx | 69 ++ frontend/src/pages/Layout.tsx | 34 +- frontend/src/pages/MainPage.tsx | 6 +- frontend/src/pages/ProfilePage.tsx | 13 +- frontend/src/pages/admin/AdminClubPage.tsx | 7 +- frontend/src/pages/admin/AdminMainPage.tsx | 6 +- frontend/src/pages/admin/SearchPage.tsx | 1 - frontend/src/recoil/atoms.ts | 37 + frontend/src/types/dto/cabinet.dto.ts | 2 +- frontend/src/types/dto/club.dto.ts | 43 + frontend/src/types/dto/lent.dto.ts | 5 +- frontend/src/utils/recoilPersistUtils.ts | 11 + package-lock.json | 2 +- redis/{ => config}/redis.conf | 0 147 files changed, 6251 insertions(+), 1831 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/club/controller/AdminClubController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/club/service/AdminClubFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/controller/ClubController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/domain/Club.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/domain/ClubLentHistory.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/domain/ClubRegistration.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRegistrationRepoitory.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRepository.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/service/ClubCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/service/ClubPolicyService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/service/ClubQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/AddClubUserRequestDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ClubCreateDto.java rename backend/src/main/java/org/ftclub/cabinet/dto/{CabinetClubStatusRequestDto.java => ClubDeleteDto.java} (58%) create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoPaginationDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ClubMemoUpdateDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ClubNoticeUpdateDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ClubUpdateRequestDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/ClubUserResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/MandateClubMasterRequestDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/repository/ClubLentRepository.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentCommandService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/mapper/ClubMapper.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/utils/cqrs/DatabaseSynchronizer.java create mode 100644 frontend/src/assets/images/close-circle.svg create mode 100644 frontend/src/assets/images/crown.svg create mode 100644 frontend/src/assets/images/edit.svg create mode 100644 frontend/src/assets/images/leader.svg create mode 100644 frontend/src/assets/images/lock.svg create mode 100644 frontend/src/assets/images/maru.svg create mode 100644 frontend/src/assets/images/more.svg create mode 100644 frontend/src/assets/images/selectMaincolor.svg rename frontend/src/{pages/PendingPage/components/PendingCountdown.tsx => components/Available/AvailableCountdown.tsx} (76%) rename frontend/src/{pages/PendingPage/components => components/Available}/FloorContainer.tsx (91%) create mode 100644 frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx create mode 100644 frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx create mode 100644 frontend/src/components/Club/ClubInfo.tsx create mode 100644 frontend/src/components/Club/ClubList.tsx create mode 100644 frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx create mode 100644 frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx create mode 100644 frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx create mode 100644 frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx create mode 100644 frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx create mode 100644 frontend/src/components/Common/MultiToggleSwitchSeparated.tsx create mode 100644 frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx create mode 100644 frontend/src/components/Modals/ClubModal/AddClubMemberModal.container.tsx create mode 100644 frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx create mode 100644 frontend/src/components/Modals/ClubModal/ClubMemoModal.container.tsx create mode 100644 frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx create mode 100644 frontend/src/components/Modals/ClubModal/ClubPasswordModal.container.tsx create mode 100644 frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx create mode 100644 frontend/src/components/Modals/ClubModal/DeleteClubMemberModal.tsx create mode 100644 frontend/src/components/Modals/ClubModal/MandateClubMemberModal.tsx create mode 100644 frontend/src/hooks/useClubInfo.ts rename frontend/src/pages/{PendingPage/PendingPage.tsx => AvailablePage.tsx} (62%) create mode 100644 frontend/src/pages/ClubPage.tsx create mode 100644 frontend/src/types/dto/club.dto.ts create mode 100644 frontend/src/utils/recoilPersistUtils.ts rename redis/{ => config}/redis.conf (100%) diff --git a/.gitignore b/.gitignore index 22a1f7b83..130950640 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,9 @@ ibdata* ibtmp* *nohup.out /backend/src/main/resources/test/resources/ +.idea/modules.xml +.idea/workspace.xml redis/data/* /idea workspace.xml misc.xml +/d382cf00936882c2/ diff --git a/backend/database/cabi_local.sql b/backend/database/cabi_local.sql index 50f90038b..2268f4a07 100644 --- a/backend/database/cabi_local.sql +++ b/backend/database/cabi_local.sql @@ -16,48 +16,61 @@ /*!40111 SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0 */; -- --- Table structure for table `admin_user` +-- Table structure for table `admin` -- DROP TABLE IF EXISTS `admin`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; -create table admin +CREATE TABLE `admin` ( - id bigint auto_increment - primary key, - email varchar(128) not null, - role varchar(16) not null, - constraint UK_6etwowal6qxvr7xuvqcqmnnk7 - unique (email) -)ENGINE = InnoDB + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `email` varchar(128) NOT NULL, + `role` varchar(16) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `admin_email_unique_key` (`email`) +) ENGINE = InnoDB AUTO_INCREMENT = 6 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `admin_user` +-- LOCK TABLES `admin` WRITE; +/*!40000 ALTER TABLE `admin` + DISABLE KEYS */; INSERT INTO `admin` VALUES (1, 'admin0@gmail.com', 'NONE'), (2, 'admin1@gmail.com', 'ADMIN'), (3, 'admin2@gmail.com', 'MASTER'), (4, 'innoaca@cabi.42seoul.io', 'MASTER'); +/*!40000 ALTER TABLE `admin` + ENABLE KEYS */; UNLOCK TABLES; -DROP TABLE IF EXISTS `ban_history`; +-- +-- Table structure for table `ban_history` +-- -create table ban_history +DROP TABLE IF EXISTS `ban_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `ban_history` ( - id bigint auto_increment - primary key, - ban_type varchar(32) not null, - banned_at datetime(6) not null, - unbanned_at datetime(6) null, - user_id bigint not null, - constraint FK6hig35mfe4ski2gmrsesnicu2 - foreign key (user_id) references user (id) + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `ban_type` varchar(32) NOT NULL, + `banned_at` datetime(6) NOT NULL, + `unbanned_at` datetime(6) DEFAULT NULL, + `user_id` bigint(20) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `ban_history_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `ban_history` @@ -77,428 +90,437 @@ UNLOCK TABLES; DROP TABLE IF EXISTS `cabinet`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; -create table cabinet +CREATE TABLE `cabinet` ( - id bigint auto_increment - primary key, - col int null, - row int null, - lent_type varchar(16) not null, - max_user int not null, - status varchar(32) not null, - status_note varchar(64) null, - visible_num int null, - cabinet_place_id bigint null, - title varchar(64) null, - memo varchar(64) null, - constraint FKip05uw7xaywjxlu4ljswturit - foreign key (cabinet_place_id) references cabinet_place (id) + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `col` int(11) DEFAULT NULL, + `row` int(11) DEFAULT NULL, + `lent_type` varchar(16) NOT NULL, + `max_user` int(11) NOT NULL, + `status` varchar(32) NOT NULL, + `status_note` varchar(64) DEFAULT NULL, + `visible_num` int(11) DEFAULT NULL, + `cabinet_place_id` bigint(20) DEFAULT NULL, + `title` varchar(64) DEFAULT NULL, + `memo` varchar(64) DEFAULT NULL, + `version` bigint(20) DEFAULT 1, + PRIMARY KEY (`id`), + CONSTRAINT `cabinet_place_cabinet_id` + FOREIGN KEY (`cabinet_place_id`) REFERENCES `cabinet_place` (`id`) ) ENGINE = InnoDB AUTO_INCREMENT = 399 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; -LOCK TABLES `cabinet` WRITE; +-- +-- Dumping data for table `cabinet` +-- +LOCK TABLES `cabinet` WRITE; +/*!40000 ALTER TABLE `cabinet` + DISABLE KEYS */; INSERT INTO `cabinet` -VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', ''), - (2, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 90, 5, '', ''), - (3, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 91, 5, '', ''), - (4, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 92, 5, '', ''), - (5, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 93, 5, '', ''), - (6, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 94, 5, '', ''), - (7, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 95, 5, '', ''), - (8, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 96, 5, '', ''), - (9, 8, 0, 'SHARE', 3, 'AVAILABLE', '', 97, 5, '', ''), - (10, 9, 0, 'SHARE', 3, 'AVAILABLE', '', 98, 5, '', ''), - (11, 10, 0, 'SHARE', 3, 'AVAILABLE', '', 99, 5, '', ''), - (12, 11, 0, 'SHARE', 3, 'AVAILABLE', '', 100, 5, '', ''), - (13, 12, 0, 'SHARE', 3, 'AVAILABLE', '', 101, 5, '', ''), - (14, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 102, 5, '', ''), - (15, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 103, 5, '', ''), - (16, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 104, 5, '', ''), - (17, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 105, 5, '', ''), - (18, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 106, 5, '', ''), - (19, 5, 1, 'PRIVATE', 1, 'AVAILABLE', '', 107, 5, '', ''), - (20, 6, 1, 'PRIVATE', 1, 'AVAILABLE', '', 108, 5, '', ''), - (21, 7, 1, 'PRIVATE', 1, 'AVAILABLE', '', 109, 5, '', ''), - (22, 8, 1, 'PRIVATE', 1, 'AVAILABLE', '', 110, 5, '', ''), - (23, 9, 1, 'PRIVATE', 1, 'AVAILABLE', '', 111, 5, '', ''), - (24, 10, 1, 'PRIVATE', 1, 'AVAILABLE', '', 112, 5, '', ''), - (25, 11, 1, 'PRIVATE', 1, 'AVAILABLE', '', 113, 5, '', ''), - (26, 12, 1, 'PRIVATE', 1, 'AVAILABLE', '', 114, 5, '', ''), - (27, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 115, 5, '', ''), - (28, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 116, 5, '', ''), - (29, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 117, 5, '', ''), - (30, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 118, 5, '', ''), - (31, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 119, 5, '', ''), - (32, 5, 2, 'PRIVATE', 1, 'AVAILABLE', '', 120, 5, '', ''), - (33, 6, 2, 'PRIVATE', 1, 'AVAILABLE', '', 121, 5, '', ''), - (34, 7, 2, 'PRIVATE', 1, 'AVAILABLE', '', 122, 5, '', ''), - (35, 8, 2, 'PRIVATE', 1, 'AVAILABLE', '', 123, 5, '', ''), - (36, 9, 2, 'PRIVATE', 1, 'AVAILABLE', '', 124, 5, '', ''), - (37, 10, 2, 'PRIVATE', 1, 'AVAILABLE', '', 125, 5, '', ''), - (38, 11, 2, 'PRIVATE', 1, 'AVAILABLE', '', 126, 5, '', ''), - (39, 12, 2, 'PRIVATE', 1, 'AVAILABLE', '', 127, 5, '', ''), - (40, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 128, 5, '', ''), - (41, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 129, 5, '', ''), - (42, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 130, 5, '', ''), - (43, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 131, 5, '', ''), - (44, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 132, 5, '', ''), - (45, 5, 3, 'PRIVATE', 1, 'AVAILABLE', '', 133, 5, '', ''), - (46, 6, 3, 'PRIVATE', 1, 'AVAILABLE', '', 134, 5, '', ''), - (47, 7, 3, 'PRIVATE', 1, 'AVAILABLE', '', 135, 5, '', ''), - (48, 8, 3, 'PRIVATE', 1, 'AVAILABLE', '', 136, 5, '', ''), - (49, 9, 3, 'PRIVATE', 1, 'AVAILABLE', '', 137, 5, '', ''), - (50, 10, 3, 'PRIVATE', 1, 'AVAILABLE', '', 138, 5, '', ''), - (51, 11, 3, 'PRIVATE', 1, 'AVAILABLE', '', 139, 5, '', ''), - (52, 12, 3, 'PRIVATE', 1, 'AVAILABLE', '', 140, 5, '', ''), - (53, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 141, 6, '', ''), - (54, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 142, 6, '', ''), - (55, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 143, 6, '', ''), - (56, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 144, 6, '', ''), - (57, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 145, 6, '', ''), - (58, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 146, 6, '', ''), - (59, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 147, 6, '', ''), - (60, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 148, 6, '', ''), - (61, 0, 0, 'PRIVATE', 1, 'AVAILABLE', '', 17, 2, '', ''), - (62, 1, 0, 'PRIVATE', 1, 'AVAILABLE', '', 18, 2, '', ''), - (63, 2, 0, 'PRIVATE', 1, 'AVAILABLE', '', 19, 2, '', ''), - (64, 3, 0, 'PRIVATE', 1, 'AVAILABLE', '', 20, 2, '', ''), - (65, 4, 0, 'PRIVATE', 1, 'AVAILABLE', '', 21, 2, '', ''), - (66, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 22, 2, '', ''), - (67, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 23, 2, '', ''), - (68, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 24, 2, '', ''), - (69, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 25, 2, '', ''), - (70, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 26, 2, '', ''), - (71, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 27, 2, '', ''), - (72, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 28, 2, '', ''), - (73, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 29, 2, '', ''), - (74, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 30, 2, '', ''), - (75, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 31, 2, '', ''), - (76, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 32, 2, '', ''), - (77, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 33, 2, '', ''), - (78, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 34, 2, '', ''), - (79, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 35, 2, '', ''), - (80, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 36, 2, '', ''), - (81, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 1, 1, '', ''), - (82, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 2, 1, '', ''), - (83, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 3, 1, '', ''), - (84, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 4, 1, '', ''), - (85, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 5, 1, '', ''), - (86, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 6, 1, '', ''), - (87, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 7, 1, '', ''), - (88, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 8, 1, '', ''), - (89, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 9, 1, '', ''), - (90, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 10, 1, '', ''), - (91, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 11, 1, '', ''), - (92, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 12, 1, '', ''), - (93, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 13, 1, '', ''), - (94, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 14, 1, '', ''), - (95, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 15, 1, '', ''), - (96, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 16, 1, '', ''), - (97, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 37, 3, '', ''), - (98, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 38, 3, '', ''), - (99, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 39, 3, '', ''), - (100, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 40, 3, '', ''), - (101, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 3, '', ''), - (102, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 3, '', ''), - (103, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 3, '', ''), - (104, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 3, '', ''), - (105, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 45, 3, '', ''), - (106, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 46, 3, '', ''), - (107, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 47, 3, '', ''), - (108, 3, 1, 'SHARE', 3, 'AVAILABLE', '', 48, 3, '', ''), - (109, 4, 1, 'SHARE', 3, 'AVAILABLE', '', 49, 3, '', ''), - (110, 5, 1, 'SHARE', 3, 'AVAILABLE', '', 50, 3, '', ''), - (111, 6, 1, 'SHARE', 3, 'AVAILABLE', '', 51, 3, '', ''), - (112, 7, 1, 'SHARE', 3, 'AVAILABLE', '', 52, 3, '', ''), - (113, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 53, 3, '', ''), - (114, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 54, 3, '', ''), - (115, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 55, 3, '', ''), - (116, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 56, 3, '', ''), - (117, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 57, 3, '', ''), - (118, 5, 2, 'PRIVATE', 1, 'AVAILABLE', '', 58, 3, '', ''), - (119, 6, 2, 'PRIVATE', 1, 'AVAILABLE', '', 59, 3, '', ''), - (120, 7, 2, 'PRIVATE', 1, 'AVAILABLE', '', 60, 3, '', ''), - (121, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 61, 3, '', ''), - (122, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 62, 3, '', ''), - (123, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 63, 3, '', ''), - (124, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 64, 3, '', ''), - (125, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 65, 3, '', ''), - (126, 5, 3, 'PRIVATE', 1, 'AVAILABLE', '', 66, 3, '', ''), - (127, 6, 3, 'PRIVATE', 1, 'AVAILABLE', '', 67, 3, '', ''), - (128, 7, 3, 'PRIVATE', 1, 'AVAILABLE', '', 68, 3, '', ''), - (129, 0, 0, 'PRIVATE', 1, 'AVAILABLE', '', 69, 4, '', ''), - (130, 1, 0, 'PRIVATE', 1, 'AVAILABLE', '', 70, 4, '', ''), - (131, 2, 0, 'PRIVATE', 1, 'AVAILABLE', '', 71, 4, '', ''), - (132, 3, 0, 'PRIVATE', 1, 'AVAILABLE', '', 72, 4, '', ''), - (133, 4, 0, 'PRIVATE', 1, 'AVAILABLE', '', 73, 4, '', ''), - (134, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 74, 4, '', ''), - (135, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 75, 4, '', ''), - (136, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 76, 4, '', ''), - (137, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 77, 4, '', ''), - (138, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 78, 4, '', ''), - (139, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 79, 4, '', ''), - (140, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 80, 4, '', ''), - (141, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 81, 4, '', ''), - (142, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 82, 4, '', ''), - (143, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 83, 4, '', ''), - (144, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 84, 4, '', ''), - (145, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 85, 4, '', ''), - (146, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 86, 4, '', ''), - (147, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 87, 4, '', ''), - (148, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 88, 4, '', ''), - (149, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 37, 16, '', ''), - (150, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 38, 16, '', ''), - (151, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 39, 16, '', ''), - (152, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 40, 16, '', ''), - (153, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 16, '', ''), - (154, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 16, '', ''), - (155, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 16, '', ''), - (156, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 16, '', ''), - (157, 8, 0, 'SHARE', 3, 'AVAILABLE', '', 45, 16, '', ''), - (158, 9, 0, 'SHARE', 3, 'AVAILABLE', '', 46, 16, '', ''), - (159, 10, 0, 'SHARE', 3, 'AVAILABLE', '', 47, 16, '', ''), - (160, 11, 0, 'SHARE', 3, 'AVAILABLE', '', 48, 16, '', ''), - (161, 12, 0, 'SHARE', 3, 'AVAILABLE', '', 49, 16, '', ''), - (162, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 50, 16, '', ''), - (163, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 51, 16, '', ''), - (164, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 52, 16, '', ''), - (165, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 53, 16, '', ''), - (166, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 54, 16, '', ''), - (167, 5, 1, 'PRIVATE', 1, 'AVAILABLE', '', 55, 16, '', ''), - (168, 6, 1, 'PRIVATE', 1, 'AVAILABLE', '', 56, 16, '', ''), - (169, 7, 1, 'PRIVATE', 1, 'AVAILABLE', '', 57, 16, '', ''), - (170, 8, 1, 'PRIVATE', 1, 'AVAILABLE', '', 58, 16, '', ''), - (171, 9, 1, 'PRIVATE', 1, 'AVAILABLE', '', 59, 16, '', ''), - (172, 10, 1, 'PRIVATE', 1, 'AVAILABLE', '', 60, 16, '', ''), - (173, 11, 1, 'PRIVATE', 1, 'AVAILABLE', '', 61, 16, '', ''), - (174, 12, 1, 'PRIVATE', 1, 'AVAILABLE', '', 62, 16, '', ''), - (175, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 63, 16, '', ''), - (176, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 64, 16, '', ''), - (177, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 65, 16, '', ''), - (178, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 66, 16, '', ''), - (179, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 67, 16, '', ''), - (180, 5, 2, 'PRIVATE', 1, 'AVAILABLE', '', 68, 16, '', ''), - (181, 6, 2, 'PRIVATE', 1, 'AVAILABLE', '', 69, 16, '', ''), - (182, 7, 2, 'PRIVATE', 1, 'AVAILABLE', '', 70, 16, '', ''), - (183, 8, 2, 'PRIVATE', 1, 'AVAILABLE', '', 71, 16, '', ''), - (184, 9, 2, 'PRIVATE', 1, 'AVAILABLE', '', 72, 16, '', ''), - (185, 10, 2, 'PRIVATE', 1, 'AVAILABLE', '', 73, 16, '', ''), - (186, 11, 2, 'PRIVATE', 1, 'AVAILABLE', '', 74, 16, '', ''), - (187, 12, 2, 'PRIVATE', 1, 'AVAILABLE', '', 75, 16, '', ''), - (188, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 76, 16, '', ''), - (189, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 77, 16, '', ''), - (190, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 78, 16, '', ''), - (191, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 79, 16, '', ''), - (192, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 80, 16, '', ''), - (193, 5, 3, 'PRIVATE', 1, 'AVAILABLE', '', 81, 16, '', ''), - (194, 6, 3, 'PRIVATE', 1, 'AVAILABLE', '', 82, 16, '', ''), - (195, 7, 3, 'PRIVATE', 1, 'AVAILABLE', '', 83, 16, '', ''), - (196, 8, 3, 'PRIVATE', 1, 'AVAILABLE', '', 84, 16, '', ''), - (197, 9, 3, 'PRIVATE', 1, 'AVAILABLE', '', 85, 16, '', ''), - (198, 10, 3, 'PRIVATE', 1, 'AVAILABLE', '', 86, 16, '', ''), - (199, 11, 3, 'PRIVATE', 1, 'AVAILABLE', '', 87, 16, '', ''), - (200, 12, 3, 'PRIVATE', 1, 'AVAILABLE', '', 88, 16, '', ''), - (201, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 17, '', ''), - (202, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 90, 17, '', ''), - (203, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 91, 17, '', ''), - (204, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 92, 17, '', ''), - (205, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 93, 17, '', ''), - (206, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 94, 17, '', ''), - (207, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 95, 17, '', ''), - (208, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 96, 17, '', ''), - (209, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 97, 17, '', ''), - (210, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 98, 17, '', ''), - (211, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 99, 17, '', ''), - (212, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 100, 17, '', ''), - (213, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 17, 15, '', ''), - (214, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 18, 15, '', ''), - (215, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 19, 15, '', ''), - (216, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 20, 15, '', ''), - (217, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 21, 15, '', ''), - (218, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 22, 15, '', ''), - (219, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 23, 15, '', ''), - (220, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 24, 15, '', ''), - (221, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 25, 15, '', ''), - (222, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 26, 15, '', ''), - (223, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 27, 15, '', ''), - (224, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 28, 15, '', ''), - (225, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 29, 15, '', ''), - (226, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 30, 15, '', ''), - (227, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 31, 15, '', ''), - (228, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 32, 15, '', ''), - (229, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 33, 15, '', ''), - (230, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 34, 15, '', ''), - (231, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 35, 15, '', ''), - (232, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 36, 15, '', ''), - (233, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 1, 14, '', ''), - (234, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 2, 14, '', ''), - (235, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 3, 14, '', ''), - (236, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 4, 14, '', ''), - (237, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 5, 14, '', ''), - (238, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 6, 14, '', ''), - (239, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 7, 14, '', ''), - (240, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 8, 14, '', ''), - (241, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 9, 14, '', ''), - (242, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 10, 14, '', ''), - (243, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 11, 14, '', ''), - (244, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 12, 14, '', ''), - (245, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 13, 14, '', ''), - (246, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 14, 14, '', ''), - (247, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 15, 14, '', ''), - (248, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 16, 14, '', ''), - (249, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 37, 20, '', ''), - (250, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 38, 20, '', ''), - (251, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 39, 20, '', ''), - (252, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 40, 20, '', ''), - (253, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 20, '', ''), - (254, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 20, '', ''), - (255, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 20, '', ''), - (256, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 20, '', ''), - (257, 8, 0, 'SHARE', 3, 'AVAILABLE', '', 45, 20, '', ''), - (258, 9, 0, 'SHARE', 3, 'AVAILABLE', '', 46, 20, '', ''), - (259, 10, 0, 'SHARE', 3, 'AVAILABLE', '', 47, 20, '', ''), - (260, 11, 0, 'SHARE', 3, 'AVAILABLE', '', 48, 20, '', ''), - (261, 12, 0, 'SHARE', 3, 'AVAILABLE', '', 49, 20, '', ''), - (262, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 50, 20, '', ''), - (263, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 51, 20, '', ''), - (264, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 52, 20, '', ''), - (265, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 53, 20, '', ''), - (266, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 54, 20, '', ''), - (267, 5, 1, 'PRIVATE', 1, 'AVAILABLE', '', 55, 20, '', ''), - (268, 6, 1, 'PRIVATE', 1, 'AVAILABLE', '', 56, 20, '', ''), - (269, 7, 1, 'PRIVATE', 1, 'AVAILABLE', '', 57, 20, '', ''), - (270, 8, 1, 'PRIVATE', 1, 'AVAILABLE', '', 58, 20, '', ''), - (271, 9, 1, 'PRIVATE', 1, 'AVAILABLE', '', 59, 20, '', ''), - (272, 10, 1, 'PRIVATE', 1, 'AVAILABLE', '', 60, 20, '', ''), - (273, 11, 1, 'PRIVATE', 1, 'AVAILABLE', '', 61, 20, '', ''), - (274, 12, 1, 'PRIVATE', 1, 'AVAILABLE', '', 62, 20, '', ''), - (275, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 63, 20, '', ''), - (276, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 64, 20, '', ''), - (277, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 65, 20, '', ''), - (278, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 66, 20, '', ''), - (279, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 67, 20, '', ''), - (280, 5, 2, 'PRIVATE', 1, 'AVAILABLE', '', 68, 20, '', ''), - (281, 6, 2, 'PRIVATE', 1, 'AVAILABLE', '', 69, 20, '', ''), - (282, 7, 2, 'PRIVATE', 1, 'AVAILABLE', '', 70, 20, '', ''), - (283, 8, 2, 'PRIVATE', 1, 'AVAILABLE', '', 71, 20, '', ''), - (284, 9, 2, 'PRIVATE', 1, 'AVAILABLE', '', 72, 20, '', ''), - (285, 10, 2, 'PRIVATE', 1, 'AVAILABLE', '', 73, 20, '', ''), - (286, 11, 2, 'PRIVATE', 1, 'AVAILABLE', '', 74, 20, '', ''), - (287, 12, 2, 'PRIVATE', 1, 'AVAILABLE', '', 75, 20, '', ''), - (288, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 76, 20, '', ''), - (289, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 77, 20, '', ''), - (290, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 78, 20, '', ''), - (291, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 79, 20, '', ''), - (292, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 80, 20, '', ''), - (293, 5, 3, 'PRIVATE', 1, 'AVAILABLE', '', 81, 20, '', ''), - (294, 6, 3, 'PRIVATE', 1, 'AVAILABLE', '', 82, 20, '', ''), - (295, 7, 3, 'PRIVATE', 1, 'AVAILABLE', '', 83, 20, '', ''), - (296, 8, 3, 'PRIVATE', 1, 'AVAILABLE', '', 84, 20, '', ''), - (297, 9, 3, 'PRIVATE', 1, 'AVAILABLE', '', 85, 20, '', ''), - (298, 10, 3, 'PRIVATE', 1, 'AVAILABLE', '', 86, 20, '', ''), - (299, 11, 3, 'PRIVATE', 1, 'AVAILABLE', '', 87, 20, '', ''), - (300, 12, 3, 'PRIVATE', 1, 'AVAILABLE', '', 88, 20, '', ''), - (301, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 17, 19, '', ''), - (302, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 18, 19, '', ''), - (303, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 19, 19, '', ''), - (304, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 20, 19, '', ''), - (305, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 21, 19, '', ''), - (306, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 22, 19, '', ''), - (307, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 23, 19, '', ''), - (308, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 24, 19, '', ''), - (309, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 25, 19, '', ''), - (310, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 26, 19, '', ''), - (311, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 27, 19, '', ''), - (312, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 28, 19, '', ''), - (313, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 29, 19, '', ''), - (314, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 30, 19, '', ''), - (315, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 31, 19, '', ''), - (316, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 32, 19, '', ''), - (317, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 33, 19, '', ''), - (318, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 34, 19, '', ''), - (319, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 35, 19, '', ''), - (320, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 36, 19, '', ''), - (321, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 1, 18, '', ''), - (322, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 2, 18, '', ''), - (323, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 3, 18, '', ''), - (324, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 4, 18, '', ''), - (325, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 5, 18, '', ''), - (326, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 6, 18, '', ''), - (327, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 7, 18, '', ''), - (328, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 8, 18, '', ''), - (329, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 9, 18, '', ''), - (330, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 10, 18, '', ''), - (331, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 11, 18, '', ''), - (332, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 12, 18, '', ''), - (333, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 13, 18, '', ''), - (334, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 14, 18, '', ''), - (335, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 15, 18, '', ''), - (336, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 16, 18, '', ''), - (337, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 21, '', ''), - (338, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 90, 21, '', ''), - (339, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 91, 21, '', ''), - (340, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 92, 21, '', ''), - (341, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 93, 21, '', ''), - (342, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 94, 21, '', ''), - (343, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 95, 21, '', ''), - (344, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 96, 21, '', ''), - (345, 0, 0, 'CLUB', 1, 'AVAILABLE', '', 1, 7, '', ''), - (346, 1, 0, 'CLUB', 1, 'AVAILABLE', '', 2, 7, '', ''), - (347, 2, 0, 'CLUB', 1, 'AVAILABLE', '', 3, 7, '', ''), - (348, 3, 0, 'CLUB', 1, 'AVAILABLE', '', 4, 7, '', ''), - (349, 0, 1, 'CLUB', 1, 'AVAILABLE', '', 5, 7, '', ''), - (350, 1, 1, 'CLUB', 1, 'AVAILABLE', '', 6, 7, '', ''), - (351, 2, 1, 'CLUB', 1, 'AVAILABLE', '', 7, 7, '', ''), - (352, 3, 1, 'CLUB', 1, 'AVAILABLE', '', 8, 7, '', ''), - (353, 0, 0, 'CLUB', 1, 'AVAILABLE', '', 9, 8, '', ''), - (354, 1, 0, 'CLUB', 1, 'AVAILABLE', '', 10, 8, '', ''), - (355, 2, 0, 'CLUB', 1, 'AVAILABLE', '', 11, 8, '', ''), - (356, 3, 0, 'CLUB', 1, 'AVAILABLE', '', 12, 8, '', ''), - (357, 0, 1, 'CLUB', 1, 'AVAILABLE', '', 13, 8, '', ''), - (358, 1, 1, 'CLUB', 1, 'AVAILABLE', '', 14, 8, '', ''), - (359, 2, 1, 'CLUB', 1, 'AVAILABLE', '', 15, 8, '', ''), - (360, 3, 1, 'CLUB', 1, 'AVAILABLE', '', 16, 8, '', ''), - (361, 0, 0, 'CLUB', 1, 'AVAILABLE', '', 17, 9, '', ''), - (362, 1, 0, 'CLUB', 1, 'AVAILABLE', '', 18, 9, '', ''), - (363, 2, 0, 'CLUB', 1, 'AVAILABLE', '', 19, 9, '', ''), - (364, 3, 0, 'CLUB', 1, 'AVAILABLE', '', 20, 9, '', ''), - (365, 0, 1, 'CLUB', 1, 'AVAILABLE', '', 21, 9, '', ''), - (366, 1, 1, 'CLUB', 1, 'AVAILABLE', '', 22, 9, '', ''), - (367, 2, 1, 'CLUB', 1, 'AVAILABLE', '', 23, 9, '', ''), - (368, 3, 1, 'CLUB', 1, 'AVAILABLE', '', 24, 9, '', ''), - (369, 0, 0, 'CLUB', 1, 'AVAILABLE', '', 25, 10, '', ''), - (370, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 26, 10, '', ''), - (371, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 27, 10, '', ''), - (372, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 28, 10, '', ''), - (373, 0, 1, 'CLUB', 1, 'AVAILABLE', '', 29, 10, '', ''), - (374, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 30, 10, '', ''), - (375, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 31, 10, '', ''), - (376, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 32, 10, '', ''), - (377, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 33, 11, '', ''), - (378, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 34, 11, '', ''), - (379, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 35, 11, '', ''), - (380, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 36, 11, '', ''), - (381, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 37, 11, '', ''), - (382, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 38, 11, '', ''), - (383, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 39, 11, '', ''), - (384, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 40, 11, '', ''), - (385, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 12, '', ''), - (386, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 12, '', ''), - (387, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 12, '', ''), - (388, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 12, '', ''), - (389, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 45, 12, '', ''), - (390, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 46, 12, '', ''), - (391, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 47, 12, '', ''), - (392, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 48, 12, '', ''), - (393, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 49, 13, '', ''), - (394, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 50, 13, '', ''), - (395, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 51, 13, '', ''), - (396, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 52, 13, '', ''), - (397, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 53, 13, '', ''), - (398, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 54, 13, '', ''); +VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), + (2, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 90, 5, '', '', 1), + (3, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 91, 5, '', '', 1), + (4, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 92, 5, '', '', 1), + (5, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 93, 5, '', '', 1), + (6, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 94, 5, '', '', 1), + (7, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 95, 5, '', '', 1), + (8, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 96, 5, '', '', 1), + (9, 8, 0, 'SHARE', 3, 'AVAILABLE', '', 97, 5, '', '', 1), + (10, 9, 0, 'SHARE', 3, 'AVAILABLE', '', 98, 5, '', '', 1), + (11, 10, 0, 'SHARE', 3, 'AVAILABLE', '', 99, 5, '', '', 1), + (12, 11, 0, 'SHARE', 3, 'AVAILABLE', '', 100, 5, '', '', 1), + (13, 12, 0, 'SHARE', 3, 'AVAILABLE', '', 101, 5, '', '', 1), + (14, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 102, 5, '', '', 1), + (15, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 103, 5, '', '', 1), + (16, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 104, 5, '', '', 1), + (17, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 105, 5, '', '', 1), + (18, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 106, 5, '', '', 1), + (19, 5, 1, 'PRIVATE', 1, 'AVAILABLE', '', 107, 5, '', '', 1), + (20, 6, 1, 'PRIVATE', 1, 'AVAILABLE', '', 108, 5, '', '', 1), + (21, 7, 1, 'PRIVATE', 1, 'AVAILABLE', '', 109, 5, '', '', 1), + (22, 8, 1, 'PRIVATE', 1, 'AVAILABLE', '', 110, 5, '', '', 1), + (23, 9, 1, 'PRIVATE', 1, 'AVAILABLE', '', 111, 5, '', '', 1), + (24, 10, 1, 'PRIVATE', 1, 'AVAILABLE', '', 112, 5, '', '', 1), + (25, 11, 1, 'PRIVATE', 1, 'AVAILABLE', '', 113, 5, '', '', 1), + (26, 12, 1, 'PRIVATE', 1, 'AVAILABLE', '', 114, 5, '', '', 1), + (27, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 115, 5, '', '', 1), + (28, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 116, 5, '', '', 1), + (29, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 117, 5, '', '', 1), + (30, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 118, 5, '', '', 1), + (31, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 119, 5, '', '', 1), + (32, 5, 2, 'PRIVATE', 1, 'AVAILABLE', '', 120, 5, '', '', 1), + (33, 6, 2, 'PRIVATE', 1, 'AVAILABLE', '', 121, 5, '', '', 1), + (34, 7, 2, 'PRIVATE', 1, 'AVAILABLE', '', 122, 5, '', '', 1), + (35, 8, 2, 'PRIVATE', 1, 'AVAILABLE', '', 123, 5, '', '', 1), + (36, 9, 2, 'PRIVATE', 1, 'AVAILABLE', '', 124, 5, '', '', 1), + (37, 10, 2, 'PRIVATE', 1, 'AVAILABLE', '', 125, 5, '', '', 1), + (38, 11, 2, 'PRIVATE', 1, 'AVAILABLE', '', 126, 5, '', '', 1), + (39, 12, 2, 'PRIVATE', 1, 'AVAILABLE', '', 127, 5, '', '', 1), + (40, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 128, 5, '', '', 1), + (41, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 129, 5, '', '', 1), + (42, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 130, 5, '', '', 1), + (43, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 131, 5, '', '', 1), + (44, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 132, 5, '', '', 1), + (45, 5, 3, 'PRIVATE', 1, 'AVAILABLE', '', 133, 5, '', '', 1), + (46, 6, 3, 'PRIVATE', 1, 'AVAILABLE', '', 134, 5, '', '', 1), + (47, 7, 3, 'PRIVATE', 1, 'AVAILABLE', '', 135, 5, '', '', 1), + (48, 8, 3, 'PRIVATE', 1, 'AVAILABLE', '', 136, 5, '', '', 1), + (49, 9, 3, 'PRIVATE', 1, 'AVAILABLE', '', 137, 5, '', '', 1), + (50, 10, 3, 'PRIVATE', 1, 'AVAILABLE', '', 138, 5, '', '', 1), + (51, 11, 3, 'PRIVATE', 1, 'AVAILABLE', '', 139, 5, '', '', 1), + (52, 12, 3, 'PRIVATE', 1, 'AVAILABLE', '', 140, 5, '', '', 1), + (53, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 141, 6, '', '', 1), + (54, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 142, 6, '', '', 1), + (55, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 143, 6, '', '', 1), + (56, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 144, 6, '', '', 1), + (57, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 145, 6, '', '', 1), + (58, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 146, 6, '', '', 1), + (59, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 147, 6, '', '', 1), + (60, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 148, 6, '', '', 1), + (61, 0, 0, 'PRIVATE', 1, 'AVAILABLE', '', 17, 2, '', '', 1), + (62, 1, 0, 'PRIVATE', 1, 'AVAILABLE', '', 18, 2, '', '', 1), + (63, 2, 0, 'PRIVATE', 1, 'AVAILABLE', '', 19, 2, '', '', 1), + (64, 3, 0, 'PRIVATE', 1, 'AVAILABLE', '', 20, 2, '', '', 1), + (65, 4, 0, 'PRIVATE', 1, 'AVAILABLE', '', 21, 2, '', '', 1), + (66, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 22, 2, '', '', 1), + (67, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 23, 2, '', '', 1), + (68, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 24, 2, '', '', 1), + (69, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 25, 2, '', '', 1), + (70, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 26, 2, '', '', 1), + (71, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 27, 2, '', '', 1), + (72, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 28, 2, '', '', 1), + (73, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 29, 2, '', '', 1), + (74, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 30, 2, '', '', 1), + (75, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 31, 2, '', '', 1), + (76, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 32, 2, '', '', 1), + (77, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 33, 2, '', '', 1), + (78, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 34, 2, '', '', 1), + (79, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 35, 2, '', '', 1), + (80, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 36, 2, '', '', 1), + (81, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 1, 1, '', '', 1), + (82, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 2, 1, '', '', 1), + (83, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 3, 1, '', '', 1), + (84, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 4, 1, '', '', 1), + (85, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 5, 1, '', '', 1), + (86, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 6, 1, '', '', 1), + (87, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 7, 1, '', '', 1), + (88, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 8, 1, '', '', 1), + (89, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 9, 1, '', '', 1), + (90, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 10, 1, '', '', 1), + (91, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 11, 1, '', '', 1), + (92, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 12, 1, '', '', 1), + (93, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 13, 1, '', '', 1), + (94, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 14, 1, '', '', 1), + (95, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 15, 1, '', '', 1), + (96, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 16, 1, '', '', 1), + (97, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 37, 3, '', '', 1), + (98, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 38, 3, '', '', 1), + (99, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 39, 3, '', '', 1), + (100, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 40, 3, '', '', 1), + (101, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 3, '', '', 1), + (102, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 3, '', '', 1), + (103, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 3, '', '', 1), + (104, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 3, '', '', 1), + (105, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 45, 3, '', '', 1), + (106, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 46, 3, '', '', 1), + (107, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 47, 3, '', '', 1), + (108, 3, 1, 'SHARE', 3, 'AVAILABLE', '', 48, 3, '', '', 1), + (109, 4, 1, 'SHARE', 3, 'AVAILABLE', '', 49, 3, '', '', 1), + (110, 5, 1, 'SHARE', 3, 'AVAILABLE', '', 50, 3, '', '', 1), + (111, 6, 1, 'SHARE', 3, 'AVAILABLE', '', 51, 3, '', '', 1), + (112, 7, 1, 'SHARE', 3, 'AVAILABLE', '', 52, 3, '', '', 1), + (113, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 53, 3, '', '', 1), + (114, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 54, 3, '', '', 1), + (115, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 55, 3, '', '', 1), + (116, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 56, 3, '', '', 1), + (117, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 57, 3, '', '', 1), + (118, 5, 2, 'PRIVATE', 1, 'AVAILABLE', '', 58, 3, '', '', 1), + (119, 6, 2, 'PRIVATE', 1, 'AVAILABLE', '', 59, 3, '', '', 1), + (120, 7, 2, 'PRIVATE', 1, 'AVAILABLE', '', 60, 3, '', '', 1), + (121, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 61, 3, '', '', 1), + (122, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 62, 3, '', '', 1), + (123, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 63, 3, '', '', 1), + (124, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 64, 3, '', '', 1), + (125, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 65, 3, '', '', 1), + (126, 5, 3, 'PRIVATE', 1, 'AVAILABLE', '', 66, 3, '', '', 1), + (127, 6, 3, 'PRIVATE', 1, 'AVAILABLE', '', 67, 3, '', '', 1), + (128, 7, 3, 'PRIVATE', 1, 'AVAILABLE', '', 68, 3, '', '', 1), + (129, 0, 0, 'PRIVATE', 1, 'AVAILABLE', '', 69, 4, '', '', 1), + (130, 1, 0, 'PRIVATE', 1, 'AVAILABLE', '', 70, 4, '', '', 1), + (131, 2, 0, 'PRIVATE', 1, 'AVAILABLE', '', 71, 4, '', '', 1), + (132, 3, 0, 'PRIVATE', 1, 'AVAILABLE', '', 72, 4, '', '', 1), + (133, 4, 0, 'PRIVATE', 1, 'AVAILABLE', '', 73, 4, '', '', 1), + (134, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 74, 4, '', '', 1), + (135, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 75, 4, '', '', 1), + (136, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 76, 4, '', '', 1), + (137, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 77, 4, '', '', 1), + (138, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 78, 4, '', '', 1), + (139, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 79, 4, '', '', 1), + (140, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 80, 4, '', '', 1), + (141, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 81, 4, '', '', 1), + (142, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 82, 4, '', '', 1), + (143, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 83, 4, '', '', 1), + (144, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 84, 4, '', '', 1), + (145, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 85, 4, '', '', 1), + (146, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 86, 4, '', '', 1), + (147, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 87, 4, '', '', 1), + (148, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 88, 4, '', '', 1), + (149, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 37, 16, '', '', 1), + (150, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 38, 16, '', '', 1), + (151, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 39, 16, '', '', 1), + (152, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 40, 16, '', '', 1), + (153, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 16, '', '', 1), + (154, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 16, '', '', 1), + (155, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 16, '', '', 1), + (156, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 16, '', '', 1), + (157, 8, 0, 'SHARE', 3, 'AVAILABLE', '', 45, 16, '', '', 1), + (158, 9, 0, 'SHARE', 3, 'AVAILABLE', '', 46, 16, '', '', 1), + (159, 10, 0, 'SHARE', 3, 'AVAILABLE', '', 47, 16, '', '', 1), + (160, 11, 0, 'SHARE', 3, 'AVAILABLE', '', 48, 16, '', '', 1), + (161, 12, 0, 'SHARE', 3, 'AVAILABLE', '', 49, 16, '', '', 1), + (162, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 50, 16, '', '', 1), + (163, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 51, 16, '', '', 1), + (164, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 52, 16, '', '', 1), + (165, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 53, 16, '', '', 1), + (166, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 54, 16, '', '', 1), + (167, 5, 1, 'PRIVATE', 1, 'AVAILABLE', '', 55, 16, '', '', 1), + (168, 6, 1, 'PRIVATE', 1, 'AVAILABLE', '', 56, 16, '', '', 1), + (169, 7, 1, 'PRIVATE', 1, 'AVAILABLE', '', 57, 16, '', '', 1), + (170, 8, 1, 'PRIVATE', 1, 'AVAILABLE', '', 58, 16, '', '', 1), + (171, 9, 1, 'PRIVATE', 1, 'AVAILABLE', '', 59, 16, '', '', 1), + (172, 10, 1, 'PRIVATE', 1, 'AVAILABLE', '', 60, 16, '', '', 1), + (173, 11, 1, 'PRIVATE', 1, 'AVAILABLE', '', 61, 16, '', '', 1), + (174, 12, 1, 'PRIVATE', 1, 'AVAILABLE', '', 62, 16, '', '', 1), + (175, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 63, 16, '', '', 1), + (176, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 64, 16, '', '', 1), + (177, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 65, 16, '', '', 1), + (178, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 66, 16, '', '', 1), + (179, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 67, 16, '', '', 1), + (180, 5, 2, 'PRIVATE', 1, 'AVAILABLE', '', 68, 16, '', '', 1), + (181, 6, 2, 'PRIVATE', 1, 'AVAILABLE', '', 69, 16, '', '', 1), + (182, 7, 2, 'PRIVATE', 1, 'AVAILABLE', '', 70, 16, '', '', 1), + (183, 8, 2, 'PRIVATE', 1, 'AVAILABLE', '', 71, 16, '', '', 1), + (184, 9, 2, 'PRIVATE', 1, 'AVAILABLE', '', 72, 16, '', '', 1), + (185, 10, 2, 'PRIVATE', 1, 'AVAILABLE', '', 73, 16, '', '', 1), + (186, 11, 2, 'PRIVATE', 1, 'AVAILABLE', '', 74, 16, '', '', 1), + (187, 12, 2, 'PRIVATE', 1, 'AVAILABLE', '', 75, 16, '', '', 1), + (188, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 76, 16, '', '', 1), + (189, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 77, 16, '', '', 1), + (190, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 78, 16, '', '', 1), + (191, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 79, 16, '', '', 1), + (192, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 80, 16, '', '', 1), + (193, 5, 3, 'PRIVATE', 1, 'AVAILABLE', '', 81, 16, '', '', 1), + (194, 6, 3, 'PRIVATE', 1, 'AVAILABLE', '', 82, 16, '', '', 1), + (195, 7, 3, 'PRIVATE', 1, 'AVAILABLE', '', 83, 16, '', '', 1), + (196, 8, 3, 'PRIVATE', 1, 'AVAILABLE', '', 84, 16, '', '', 1), + (197, 9, 3, 'PRIVATE', 1, 'AVAILABLE', '', 85, 16, '', '', 1), + (198, 10, 3, 'PRIVATE', 1, 'AVAILABLE', '', 86, 16, '', '', 1), + (199, 11, 3, 'PRIVATE', 1, 'AVAILABLE', '', 87, 16, '', '', 1), + (200, 12, 3, 'PRIVATE', 1, 'AVAILABLE', '', 88, 16, '', '', 1), + (201, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 17, '', '', 1), + (202, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 90, 17, '', '', 1), + (203, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 91, 17, '', '', 1), + (204, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 92, 17, '', '', 1), + (205, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 93, 17, '', '', 1), + (206, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 94, 17, '', '', 1), + (207, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 95, 17, '', '', 1), + (208, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 96, 17, '', '', 1), + (209, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 97, 17, '', '', 1), + (210, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 98, 17, '', '', 1), + (211, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 99, 17, '', '', 1), + (212, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 100, 17, '', '', 1), + (213, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 17, 15, '', '', 1), + (214, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 18, 15, '', '', 1), + (215, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 19, 15, '', '', 1), + (216, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 20, 15, '', '', 1), + (217, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 21, 15, '', '', 1), + (218, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 22, 15, '', '', 1), + (219, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 23, 15, '', '', 1), + (220, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 24, 15, '', '', 1), + (221, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 25, 15, '', '', 1), + (222, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 26, 15, '', '', 1), + (223, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 27, 15, '', '', 1), + (224, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 28, 15, '', '', 1), + (225, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 29, 15, '', '', 1), + (226, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 30, 15, '', '', 1), + (227, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 31, 15, '', '', 1), + (228, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 32, 15, '', '', 1), + (229, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 33, 15, '', '', 1), + (230, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 34, 15, '', '', 1), + (231, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 35, 15, '', '', 1), + (232, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 36, 15, '', '', 1), + (233, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 1, 14, '', '', 1), + (234, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 2, 14, '', '', 1), + (235, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 3, 14, '', '', 1), + (236, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 4, 14, '', '', 1), + (237, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 5, 14, '', '', 1), + (238, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 6, 14, '', '', 1), + (239, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 7, 14, '', '', 1), + (240, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 8, 14, '', '', 1), + (241, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 9, 14, '', '', 1), + (242, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 10, 14, '', '', 1), + (243, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 11, 14, '', '', 1), + (244, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 12, 14, '', '', 1), + (245, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 13, 14, '', '', 1), + (246, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 14, 14, '', '', 1), + (247, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 15, 14, '', '', 1), + (248, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 16, 14, '', '', 1), + (249, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 37, 20, '', '', 1), + (250, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 38, 20, '', '', 1), + (251, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 39, 20, '', '', 1), + (252, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 40, 20, '', '', 1), + (253, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 20, '', '', 1), + (254, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 20, '', '', 1), + (255, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 20, '', '', 1), + (256, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 20, '', '', 1), + (257, 8, 0, 'SHARE', 3, 'AVAILABLE', '', 45, 20, '', '', 1), + (258, 9, 0, 'SHARE', 3, 'AVAILABLE', '', 46, 20, '', '', 1), + (259, 10, 0, 'SHARE', 3, 'AVAILABLE', '', 47, 20, '', '', 1), + (260, 11, 0, 'SHARE', 3, 'AVAILABLE', '', 48, 20, '', '', 1), + (261, 12, 0, 'SHARE', 3, 'AVAILABLE', '', 49, 20, '', '', 1), + (262, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 50, 20, '', '', 1), + (263, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 51, 20, '', '', 1), + (264, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 52, 20, '', '', 1), + (265, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 53, 20, '', '', 1), + (266, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 54, 20, '', '', 1), + (267, 5, 1, 'PRIVATE', 1, 'AVAILABLE', '', 55, 20, '', '', 1), + (268, 6, 1, 'PRIVATE', 1, 'AVAILABLE', '', 56, 20, '', '', 1), + (269, 7, 1, 'PRIVATE', 1, 'AVAILABLE', '', 57, 20, '', '', 1), + (270, 8, 1, 'PRIVATE', 1, 'AVAILABLE', '', 58, 20, '', '', 1), + (271, 9, 1, 'PRIVATE', 1, 'AVAILABLE', '', 59, 20, '', '', 1), + (272, 10, 1, 'PRIVATE', 1, 'AVAILABLE', '', 60, 20, '', '', 1), + (273, 11, 1, 'PRIVATE', 1, 'AVAILABLE', '', 61, 20, '', '', 1), + (274, 12, 1, 'PRIVATE', 1, 'AVAILABLE', '', 62, 20, '', '', 1), + (275, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 63, 20, '', '', 1), + (276, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 64, 20, '', '', 1), + (277, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 65, 20, '', '', 1), + (278, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 66, 20, '', '', 1), + (279, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 67, 20, '', '', 1), + (280, 5, 2, 'PRIVATE', 1, 'AVAILABLE', '', 68, 20, '', '', 1), + (281, 6, 2, 'PRIVATE', 1, 'AVAILABLE', '', 69, 20, '', '', 1), + (282, 7, 2, 'PRIVATE', 1, 'AVAILABLE', '', 70, 20, '', '', 1), + (283, 8, 2, 'PRIVATE', 1, 'AVAILABLE', '', 71, 20, '', '', 1), + (284, 9, 2, 'PRIVATE', 1, 'AVAILABLE', '', 72, 20, '', '', 1), + (285, 10, 2, 'PRIVATE', 1, 'AVAILABLE', '', 73, 20, '', '', 1), + (286, 11, 2, 'PRIVATE', 1, 'AVAILABLE', '', 74, 20, '', '', 1), + (287, 12, 2, 'PRIVATE', 1, 'AVAILABLE', '', 75, 20, '', '', 1), + (288, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 76, 20, '', '', 1), + (289, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 77, 20, '', '', 1), + (290, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 78, 20, '', '', 1), + (291, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 79, 20, '', '', 1), + (292, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 80, 20, '', '', 1), + (293, 5, 3, 'PRIVATE', 1, 'AVAILABLE', '', 81, 20, '', '', 1), + (294, 6, 3, 'PRIVATE', 1, 'AVAILABLE', '', 82, 20, '', '', 1), + (295, 7, 3, 'PRIVATE', 1, 'AVAILABLE', '', 83, 20, '', '', 1), + (296, 8, 3, 'PRIVATE', 1, 'AVAILABLE', '', 84, 20, '', '', 1), + (297, 9, 3, 'PRIVATE', 1, 'AVAILABLE', '', 85, 20, '', '', 1), + (298, 10, 3, 'PRIVATE', 1, 'AVAILABLE', '', 86, 20, '', '', 1), + (299, 11, 3, 'PRIVATE', 1, 'AVAILABLE', '', 87, 20, '', '', 1), + (300, 12, 3, 'PRIVATE', 1, 'AVAILABLE', '', 88, 20, '', '', 1), + (301, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 17, 19, '', '', 1), + (302, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 18, 19, '', '', 1), + (303, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 19, 19, '', '', 1), + (304, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 20, 19, '', '', 1), + (305, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 21, 19, '', '', 1), + (306, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 22, 19, '', '', 1), + (307, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 23, 19, '', '', 1), + (308, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 24, 19, '', '', 1), + (309, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 25, 19, '', '', 1), + (310, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 26, 19, '', '', 1), + (311, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 27, 19, '', '', 1), + (312, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 28, 19, '', '', 1), + (313, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 29, 19, '', '', 1), + (314, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 30, 19, '', '', 1), + (315, 4, 2, 'PRIVATE', 1, 'AVAILABLE', '', 31, 19, '', '', 1), + (316, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 32, 19, '', '', 1), + (317, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 33, 19, '', '', 1), + (318, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 34, 19, '', '', 1), + (319, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 35, 19, '', '', 1), + (320, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 36, 19, '', '', 1), + (321, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 1, 18, '', '', 1), + (322, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 2, 18, '', '', 1), + (323, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 3, 18, '', '', 1), + (324, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 4, 18, '', '', 1), + (325, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 5, 18, '', '', 1), + (326, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 6, 18, '', '', 1), + (327, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 7, 18, '', '', 1), + (328, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 8, 18, '', '', 1), + (329, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 9, 18, '', '', 1), + (330, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 10, 18, '', '', 1), + (331, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 11, 18, '', '', 1), + (332, 3, 2, 'PRIVATE', 1, 'AVAILABLE', '', 12, 18, '', '', 1), + (333, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 13, 18, '', '', 1), + (334, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 14, 18, '', '', 1), + (335, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 15, 18, '', '', 1), + (336, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 16, 18, '', '', 1), + (337, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 21, '', '', 1), + (338, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 90, 21, '', '', 1), + (339, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 91, 21, '', '', 1), + (340, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 92, 21, '', '', 1), + (341, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 93, 21, '', '', 1), + (342, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 94, 21, '', '', 1), + (343, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 95, 21, '', '', 1), + (344, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 96, 21, '', '', 1), + (345, 0, 0, 'CLUB', 1, 'AVAILABLE', '', 1, 7, '', '', 1), + (346, 1, 0, 'CLUB', 1, 'AVAILABLE', '', 2, 7, '', '', 1), + (347, 2, 0, 'CLUB', 1, 'AVAILABLE', '', 3, 7, '', '', 1), + (348, 3, 0, 'CLUB', 1, 'AVAILABLE', '', 4, 7, '', '', 1), + (349, 0, 1, 'CLUB', 1, 'AVAILABLE', '', 5, 7, '', '', 1), + (350, 1, 1, 'CLUB', 1, 'AVAILABLE', '', 6, 7, '', '', 1), + (351, 2, 1, 'CLUB', 1, 'AVAILABLE', '', 7, 7, '', '', 1), + (352, 3, 1, 'CLUB', 1, 'AVAILABLE', '', 8, 7, '', '', 1), + (353, 0, 0, 'CLUB', 1, 'AVAILABLE', '', 9, 8, '', '', 1), + (354, 1, 0, 'CLUB', 1, 'AVAILABLE', '', 10, 8, '', '', 1), + (355, 2, 0, 'CLUB', 1, 'AVAILABLE', '', 11, 8, '', '', 1), + (356, 3, 0, 'CLUB', 1, 'AVAILABLE', '', 12, 8, '', '', 1), + (357, 0, 1, 'CLUB', 1, 'AVAILABLE', '', 13, 8, '', '', 1), + (358, 1, 1, 'CLUB', 1, 'AVAILABLE', '', 14, 8, '', '', 1), + (359, 2, 1, 'CLUB', 1, 'AVAILABLE', '', 15, 8, '', '', 1), + (360, 3, 1, 'CLUB', 1, 'AVAILABLE', '', 16, 8, '', '', 1), + (361, 0, 0, 'CLUB', 1, 'AVAILABLE', '', 17, 9, '', '', 1), + (362, 1, 0, 'CLUB', 1, 'AVAILABLE', '', 18, 9, '', '', 1), + (363, 2, 0, 'CLUB', 1, 'AVAILABLE', '', 19, 9, '', '', 1), + (364, 3, 0, 'CLUB', 1, 'AVAILABLE', '', 20, 9, '', '', 1), + (365, 0, 1, 'CLUB', 1, 'AVAILABLE', '', 21, 9, '', '', 1), + (366, 1, 1, 'CLUB', 1, 'AVAILABLE', '', 22, 9, '', '', 1), + (367, 2, 1, 'CLUB', 1, 'AVAILABLE', '', 23, 9, '', '', 1), + (368, 3, 1, 'CLUB', 1, 'AVAILABLE', '', 24, 9, '', '', 1), + (369, 0, 0, 'CLUB', 1, 'AVAILABLE', '', 25, 10, '', '', 1), + (370, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 26, 10, '', '', 1), + (371, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 27, 10, '', '', 1), + (372, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 28, 10, '', '', 1), + (373, 0, 1, 'CLUB', 1, 'AVAILABLE', '', 29, 10, '', '', 1), + (374, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 30, 10, '', '', 1), + (375, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 31, 10, '', '', 1), + (376, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 32, 10, '', '', 1), + (377, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 33, 11, '', '', 1), + (378, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 34, 11, '', '', 1), + (379, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 35, 11, '', '', 1), + (380, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 36, 11, '', '', 1), + (381, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 37, 11, '', '', 1), + (382, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 38, 11, '', '', 1), + (383, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 39, 11, '', '', 1), + (384, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 40, 11, '', '', 1), + (385, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 12, '', '', 1), + (386, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 12, '', '', 1), + (387, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 12, '', '', 1), + (388, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 12, '', '', 1), + (389, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 45, 12, '', '', 1), + (390, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 46, 12, '', '', 1), + (391, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 47, 12, '', '', 1), + (392, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 48, 12, '', '', 1), + (393, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 49, 13, '', '', 1), + (394, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 50, 13, '', '', 1), + (395, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 51, 13, '', '', 1), + (396, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 52, 13, '', '', 1), + (397, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 53, 13, '', '', 1), + (398, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 54, 13, '', '', 1); +/*!40000 ALTER TABLE `cabinet` + ENABLE KEYS */; UNLOCK TABLES; -- @@ -506,23 +528,26 @@ UNLOCK TABLES; -- DROP TABLE IF EXISTS `cabinet_place`; -create table cabinet_place +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `cabinet_place` ( - id bigint auto_increment - primary key, - height int null, - width int null, - building varchar(255) null, - floor int null, - section varchar(255) null, - end_x int null, - end_y int null, - start_x int null, - start_y int null + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `height` int(11) DEFAULT NULL, + `width` int(11) DEFAULT NULL, + `building` varchar(255) DEFAULT NULL, + `floor` int(11) DEFAULT NULL, + `section` varchar(255) DEFAULT NULL, + `end_x` int(11) DEFAULT NULL, + `end_y` int(11) DEFAULT NULL, + `start_x` int(11) DEFAULT NULL, + `start_y` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) ) ENGINE = InnoDB AUTO_INCREMENT = 22 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `cabinet_place` @@ -557,101 +582,128 @@ VALUES (1, 4, 4, '새롬관', 2, 'End of Cluster 1', 0, 0, 0, 0), ENABLE KEYS */; UNLOCK TABLES; - -- --- Table structure for table `user` +-- Table structure for table `lent_history` -- -DROP TABLE IF EXISTS `user`; - -create table user +DROP TABLE IF EXISTS `lent_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `lent_history` ( - id bigint auto_increment - primary key, - blackholed_at datetime(6) null, - deleted_at datetime(6) null, - email varchar(255) null, - name varchar(32) not null, - role varchar(32) not null, - slack_alarm boolean default true null, - email_alarm boolean default true null, - push_alarm boolean default false null, - - constraint UK_gj2fy3dcix7ph7k8684gka40c - unique (name), - constraint UK_ob8kqyqqgmefl0aco34akdtpe - unique (email) + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `ended_at` datetime(6) DEFAULT NULL, + `expired_at` datetime(6) DEFAULT NULL, + `started_at` datetime(6) NOT NULL, + `cabinet_id` bigint(20) NOT NULL, + `user_id` bigint(20) NOT NULL, + `version` bigint(20) NOT NULL DEFAULT 1, + PRIMARY KEY (`id`), + CONSTRAINT `lent_history_cabinet_id` FOREIGN KEY (`cabinet_id`) REFERENCES `cabinet` (`id`), + CONSTRAINT `lent_history_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE = InnoDB - AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; --- Dumping data for table `user` --- - -LOCK TABLES `user` WRITE; -/*!40000 ALTER TABLE `user` - DISABLE KEYS */; -/*!40000 ALTER TABLE `user` - ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `lent_history` --- - -DROP TABLE IF EXISTS `lent_history`; -create table lent_history +DROP TABLE IF EXISTS `user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user` ( - id bigint auto_increment - primary key, - cabinet_id bigint not null, - ended_at datetime(6) null, - expired_at datetime(6) null, - started_at datetime(6) not null, - user_id bigint not null, - constraint FK1xkpq0vetx7k0g7x02lpc1yxf - foreign key (cabinet_id) references cabinet (id), - constraint FK379daj8r09scml2fagnjnablm - foreign key (user_id) references user (id) + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `blackholed_at` datetime(6) DEFAULT NULL, + `deleted_at` datetime(6) DEFAULT NULL, + `email` varchar(255) DEFAULT NULL, + `name` varchar(32) NOT NULL, + `slack_alarm` tinyint(1) DEFAULT TRUE NOT NULL, + `email_alarm` tinyint(1) DEFAULT TRUE NOT NULL, + `push_alarm` tinyint(1) DEFAULT FALSE NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `user_name_unique_key` (`name`), + UNIQUE KEY `user_email_unique_key` (`email`) ) ENGINE = InnoDB + AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; --- --- Dumping data for table `lent_history` --- -LOCK TABLES `lent_history` WRITE; -/*!40000 ALTER TABLE `lent_history` - DISABLE KEYS */; -/*!40000 ALTER TABLE `lent_history` - ENABLE KEYS */; -UNLOCK TABLES; +DROP TABLE IF EXISTS `lent_extension`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `lent_extension` +( + `id` bigint(20) AUTO_INCREMENT, + `user_id` bigint(20) NOT NULL, + `name` varchar(64) NOT NULL, + `extension_period` int NOT NULL, + `type` varchar(32) NOT NULL, + `expired_at` datetime(6) NOT NULL, + `used_at` datetime(6) DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT lent_extension_user_id FOREIGN KEY (`user_id`) REFERENCES user (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; -DROP TABLE IF EXISTS `lent_extension`; +DROP TABLE IF EXISTS `club`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `club` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `created_at` datetime(6) NOT NULL, + `deleted_at` datetime(6) DEFAULT NULL, + `name` varchar(255) NOT NULL, + `notice` longtext NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `club_name_unique_key` (`name`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; -create table lent_extension +DROP TABLE IF EXISTS `club_lent_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `club_lent_history` ( - id bigint auto_increment - primary key, - user_id bigint not null, - name varchar(255) not null, - extension_period int not null, - type varchar(255) not null, - expired_at datetime(6) not null, - used_at datetime(6) null, - constraint FKc63p20x0ypp1j99gougq63mq7 - foreign key (user_id) references user (id) + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `cabinet_id` bigint(20) NOT NULL, + `club_id` bigint(20) NOT NULL, + `ended_at` datetime(6) DEFAULT NULL, + `expired_at` datetime(6) NOT NULL, + `started_at` datetime(6) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `club_lent_history_cabinet_id` FOREIGN KEY (`cabinet_id`) REFERENCES `cabinet` (`id`), + CONSTRAINT `club_lent_history_club_id` FOREIGN KEY (`club_id`) REFERENCES `club` (`id`) ) ENGINE = InnoDB - AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; + + +DROP TABLE IF EXISTS `club_registration`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `club_registration` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `club_id` bigint(20) NOT NULL, + `registered_at` datetime(6) NOT NULL, + `deleted_at` datetime(6) DEFAULT NULL, + `user_id` bigint(20) NOT NULL, + `user_role` varchar(32) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `club_registration_club_id` FOREIGN KEY (`club_id`) REFERENCES `club` (`id`), + CONSTRAINT `club_registration_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + /*!40103 SET TIME_ZONE = @OLD_TIME_ZONE */; diff --git a/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java b/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java index 1197b65d8..87f1d3fdf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java +++ b/backend/src/main/java/org/ftclub/cabinet/CabinetApplication.java @@ -6,11 +6,13 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Import; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.scheduling.annotation.EnableAsync; @EnableAsync @EnableFeignClients @SpringBootApplication(exclude = SecurityAutoConfiguration.class) +@EnableJpaAuditing @Import({CorsConfig.class}) // @CrossOrigin(origins = {"*"}, allowedHeaders = {"*"}, originPatterns = {"*"}) public class CabinetApplication { diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java deleted file mode 100644 index abf74cc1b..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/controller/AdminController.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.ftclub.cabinet.admin.admin.controller; - -import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.admin.admin.service.AdminFacadeService; -import org.ftclub.cabinet.auth.domain.AuthGuard; -import org.ftclub.cabinet.auth.domain.AuthLevel; -import org.ftclub.cabinet.log.LogLevel; -import org.ftclub.cabinet.log.Logging; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 관리자가 유저를 관리할 때 사용하는 컨트롤러입니다. - */ -@RestController -@RequiredArgsConstructor -@RequestMapping("/v4/admin/admins") -@Logging(level = LogLevel.DEBUG) -public class AdminController { - - private final AdminFacadeService adminFacadeService; - - /** - * 유저를 관리자로 승격시킵니다. - * - * @param email 유저의 이메일 - */ - @AuthGuard(level = AuthLevel.MASTER_ONLY) - @PostMapping("/{email}/promote") - public void promoteUserToAdmin(@PathVariable String email) { - adminFacadeService.promoteAdminByEmail(email); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java index 2b895df99..a76f8420a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminCommandService.java @@ -31,14 +31,4 @@ public Admin createAdminByEmail(String email) { }); return adminRepository.save(Admin.of(email, AdminRole.NONE)); } - - /** - * 관리자 권한을 변경합니다. - * - * @param admin 관리자 - */ - public void changeAdminRole(Admin admin, AdminRole adminRole) { - admin.changeAdminRole(adminRole); - adminRepository.save(admin); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java deleted file mode 100644 index 2b6ec81b0..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/admin/admin/service/AdminFacadeService.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.ftclub.cabinet.admin.admin.service; - -import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.admin.admin.domain.Admin; -import org.ftclub.cabinet.admin.admin.domain.AdminRole; -import org.ftclub.cabinet.exception.ExceptionStatus; -import org.ftclub.cabinet.log.LogLevel; -import org.ftclub.cabinet.log.Logging; -import org.springframework.stereotype.Service; - -/** - * 관리자(Admin)와 관련한 비즈니스 로직을 처리하는 서비스 클래스입니다. - */ -@Service -@RequiredArgsConstructor -@Logging(level = LogLevel.DEBUG) -public class AdminFacadeService { - - private final AdminQueryService adminQueryService; - private final AdminCommandService adminCommandService; - - /** - * 관리자({@link AdminRole#ADMIN})로 해당 Admin의 권한을 변경합니다. - * - * @param email 유저의 이메일 - */ - public void promoteAdminByEmail(String email) { - Admin admin = adminQueryService.findByEmail(email) - .orElseThrow(ExceptionStatus.NOT_FOUND_ADMIN::asServiceException); - adminCommandService.changeAdminRole(admin, AdminRole.ADMIN); - } -} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java index 11a7fca0d..69e6f2cc1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/cabinet/controller/AdminCabinetController.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.admin.cabinet.controller; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.admin.dto.AdminCabinetGridUpdateRequestDto; import org.ftclub.cabinet.admin.dto.AdminCabinetStatusNoteUpdateRequestDto; @@ -8,16 +9,19 @@ import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.CabinetPaginationDto; +import org.ftclub.cabinet.dto.CabinetStatusRequestDto; +import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ControllerException; -import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.log.Logging; import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; /** * 관리자가 사물함을 관리할 때 사용하는 컨트롤러입니다. @@ -29,21 +33,7 @@ public class AdminCabinetController { private final CabinetFacadeService cabinetFacadeService; - private final LentFacadeService lentFacadeService; - /** - * 사물함의 정보와 그 사물함의 대여 정보들을 가져옵니다. - * - * @param cabinetId 사물함 아이디 - * @return 사물함 정보와 그 사물함의 대여 정보를 반환합니다. - * @throws ControllerException 인자가 null이거나 빈 값일 경우 발생시킵니다. - */ - @GetMapping("/{cabinetId}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public CabinetInfoResponseDto getCabinetInfo( - @PathVariable("cabinetId") Long cabinetId) { - return cabinetFacadeService.getCabinetInfo(cabinetId); - } /** * 사물함의 고장 사유를 업데이트합니다. @@ -81,29 +71,14 @@ public void updateCabinetTitle( * @param cabinetStatusRequestDto 사물함 상태 변경 요청 DTO * @throws ControllerException cabinetIds가 null인 경우. */ - @PatchMapping("/") + @PatchMapping("") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void updateCabinetBundleStatus( @Valid @RequestBody CabinetStatusRequestDto cabinetStatusRequestDto) { - cabinetFacadeService.updateCabinetBundleStatus(cabinetStatusRequestDto); - } - - /** - * 사물함의 대여 타입을 업데이트합니다. - * - * @param cabinetClubStatusRequestDto 사물함 상태 변경 요청 DTO - * @throws ControllerException cabinetIds가 null인 경우. - */ - @PatchMapping("/club") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void updateCabinetClubStatus( - @Valid @RequestBody CabinetClubStatusRequestDto cabinetClubStatusRequestDto) { - cabinetFacadeService.updateClub(cabinetClubStatusRequestDto); - lentFacadeService.startLentClubCabinet(cabinetClubStatusRequestDto.getUserId(), - cabinetClubStatusRequestDto.getCabinetId()); + cabinetFacadeService.updateCabinetBundleStatus(cabinetStatusRequestDto.getCabinetIds(), + cabinetStatusRequestDto.getStatus(), cabinetStatusRequestDto.getLentType()); } - /** * 사물함의 행과 열을 업데이트합니다. * @@ -134,21 +109,6 @@ public void updateCabinetVisibleNum( cabinetFacadeService.updateCabinetVisibleNum(cabinetId, dto.getVisibleNum()); } - /** - * 사물함 대여 타입에 따른 사물함의 정보를 페이지네이션으로 가져옵니다. - * - * @param lentType 사물함 대여 타입 - * @param pageable 페이지네이션 정보 - * @return 사물함 정보 페이지네이션 - */ - @GetMapping("/lent-types/{lentType}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public CabinetPaginationDto getCabinetsByLentType( - @PathVariable("lentType") LentType lentType, - @Valid Pageable pageable) { - return cabinetFacadeService.getCabinetPaginationByLentType(lentType, pageable); - } - /** * 사물함 상태에 따른 사물함의 정보를 페이지네이션으로 가져옵니다. * @@ -164,21 +124,6 @@ public CabinetPaginationDto getCabinetsByStatus( return cabinetFacadeService.getCabinetPaginationByStatus(status, pageable); } - /** - * 사물함 표시 번호에 따른 사물함의 정보를 페이지네이션으로 가져옵니다. - * - * @param visibleNum 사물함 표시 번호 - * @param pageable 페이지네이션 정보 - * @return 사물함 정보 페이지네이션 - */ - @GetMapping("/visible-num/{visibleNum}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public CabinetPaginationDto getCabinetsByVisibleNum( - @PathVariable("visibleNum") Integer visibleNum, - @Valid Pageable pageable) { - return cabinetFacadeService.getCabinetPaginationByVisibleNum(visibleNum, pageable); - } - /** * 사물함의 대여 기록을 페이지네이션으로 가져옵니다. * diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/club/controller/AdminClubController.java b/backend/src/main/java/org/ftclub/cabinet/admin/club/controller/AdminClubController.java new file mode 100644 index 000000000..e83175bc7 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/club/controller/AdminClubController.java @@ -0,0 +1,112 @@ +package org.ftclub.cabinet.admin.club.controller; + + +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.admin.club.service.AdminClubFacadeService; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.dto.ClubCreateDto; +import org.ftclub.cabinet.dto.ClubInfoPaginationDto; +import org.ftclub.cabinet.dto.ClubUpdateRequestDto; +import org.ftclub.cabinet.dto.LentEndMemoDto; +import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.log.Logging; +import org.springframework.data.domain.Pageable; +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; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 관리자가 동아리를 관리할 때 사용하는 컨트롤러입니다. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/v4/admin/clubs") +@Logging +public class AdminClubController { + + private final AdminClubFacadeService adminClubFacadeService; + private final LentFacadeService lentFacadeService; + + /** + * 모든 동아리 정보를 가져옵니다. + * + * @param pageable 페이징 정보 + * @return 모든 동아리 정보 + */ + @GetMapping("") + @AuthGuard(level = ADMIN_ONLY) + public ClubInfoPaginationDto getAllClubsInfoDto(@Valid Pageable pageable) { + return adminClubFacadeService.findAllActiveClubsInfo(pageable); + } + + /** + * 동아리 생성 + * + * @param clubCreateDto 생성할 동아리 정보 + */ + @PostMapping("") + @AuthGuard(level = ADMIN_ONLY) + public void createNewClub(@Valid @RequestBody ClubCreateDto clubCreateDto) { + adminClubFacadeService.createNewClub(clubCreateDto.getClubName(), + clubCreateDto.getClubMaster()); + } + + /** + * 동아리 삭제 + * + * @param cabinetId 삭제할 동아리 아이디 + */ + @DeleteMapping("/{cabinetId}") + @AuthGuard(level = ADMIN_ONLY) + public void deleteClub(@PathVariable Long cabinetId) { + adminClubFacadeService.deleteClub(cabinetId); + } + + /** + * 동아리 정보 수정 + * + * @param clubId 동아리 아이디 + * @param clubUpdateRequestDto 수정할 동아리 정보 + */ + @PatchMapping("/{clubId}") + @AuthGuard(level = ADMIN_ONLY) + public void updateClubUser(@PathVariable("clubId") Long clubId, + @Valid @RequestBody ClubUpdateRequestDto clubUpdateRequestDto) { + adminClubFacadeService.updateClub(clubId, clubUpdateRequestDto.getClubName(), + clubUpdateRequestDto.getClubMaster()); + } + + /** + * 동아리의 사물함에 동아리 대여를 설정합니다. + * + * @param clubId 대여시킬 동아리 ID + * @param cabinetId 대여시킬 사물함 ID + */ + @PostMapping("/{clubId}/cabinets/{cabinetId}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void startClubLent(@PathVariable Long clubId, @PathVariable Long cabinetId) { + lentFacadeService.startLentClubCabinet(clubId, cabinetId); + } + + /** + * 동아리의 사물함에 대여 중인 동아리를 반납 시킵니다. + * + * @param clubId 반납할 동아리 ID + * @param cabinetId 반납할 사물함 ID + */ + @DeleteMapping("/{clubId}/cabinets/{cabinetId}") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public void endClubLent(@PathVariable Long clubId, @PathVariable Long cabinetId, + @Valid @RequestBody LentEndMemoDto lentEndMemoDto) { + lentFacadeService.endLentClub(clubId, cabinetId, lentEndMemoDto.getCabinetMemo()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/club/service/AdminClubFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/club/service/AdminClubFacadeService.java new file mode 100644 index 000000000..cf2bced7d --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/club/service/AdminClubFacadeService.java @@ -0,0 +1,121 @@ +package org.ftclub.cabinet.admin.club.service; + +import static org.ftclub.cabinet.user.domain.UserRole.CLUB_ADMIN; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.club.domain.Club; +import org.ftclub.cabinet.club.domain.ClubRegistration; +import org.ftclub.cabinet.club.service.ClubCommandService; +import org.ftclub.cabinet.club.service.ClubQueryService; +import org.ftclub.cabinet.club.service.ClubRegistrationCommandService; +import org.ftclub.cabinet.club.service.ClubRegistrationQueryService; +import org.ftclub.cabinet.dto.ClubInfoDto; +import org.ftclub.cabinet.dto.ClubInfoPaginationDto; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.mapper.ClubMapper; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserRole; +import org.ftclub.cabinet.user.service.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +@Transactional +public class AdminClubFacadeService { + + private final ClubMapper clubMapper; + private final ClubQueryService clubQueryService; + private final ClubCommandService clubCommandService; + private final ClubRegistrationCommandService clubRegistrationCommandService; + private final ClubRegistrationQueryService clubRegistrationQueryService; + private final UserQueryService userQueryService; + + + /** + * 현재 사물함을 대여 중인 모든 동아리 정보를 가져옵니다. + * + * @param pageable 페이징 정보 + * @return 현재 사물함을 대여 중인 모든 동아리 정보 + */ + public ClubInfoPaginationDto findAllActiveClubsInfo(Pageable pageable) { + Page clubs = clubQueryService.findAllActiveClubs(pageable); + List clubIds = clubs.stream().map(Club::getId) + .collect(Collectors.toList()); + Map> clubMasterMap = + clubRegistrationQueryService.findClubUsersByClubs(clubIds) + .stream().collect(Collectors.groupingBy(ClubRegistration::getClubId)); + + List result = clubs.stream().map(club -> { + Long clubId = club.getId(); + String clubName = club.getName(); + String clubMasterName = clubMasterMap.get(clubId).stream() + .filter(c -> c.getUserRole().equals(CLUB_ADMIN)) + .map(c -> c.getUser().getName()).findFirst().orElse(null); + return clubMapper.toClubInfoDto(clubId, clubName, clubMasterName); + }).collect(Collectors.toList()); + return clubMapper.toClubInfoPaginationDto(result, clubs.getTotalElements()); + } + + /** + * 동아리를 생성합니다. + * + * @param clubName 동아리 이름 + * @param clubMasterName 동아리장 이름 + */ + public void createNewClub(String clubName, String clubMasterName) { + User clubMaster = userQueryService.getUserByName(clubMasterName); + Club club = clubCommandService.createClub(clubName); + clubRegistrationCommandService.addNewClubUser( + ClubRegistration.of(clubMaster.getId(), club.getId(), UserRole.CLUB_ADMIN)); + } + + /** + * 동아리를 삭제합니다. + * + * @param clubId 동아리 ID + */ + public void deleteClub(Long clubId) { + Club club = clubQueryService.getClub(clubId); + clubCommandService.deleteClub(club); + List clubUsersByClub = clubRegistrationQueryService.findClubUsersByClub( + clubId); + clubUsersByClub.forEach(clubRegistrationCommandService::deleteClubUser); + } + + /** + * 동아리 정보를 수정합니다. + * + * @param clubId 동아리 ID + * @param clubName 변경할 동아리 이름 + * @param clubMasterName 변경할 동아리장 이름 + */ + public void updateClub(Long clubId, String clubName, String clubMasterName) { + Club club = clubQueryService.getClub(clubId); + if (!clubName.equals(club.getName())) { + clubCommandService.updateName(club, clubName); + } + + ClubRegistration clubMasterRegistration = + clubRegistrationQueryService.getClubMasterByClubWithUser(clubId); + if (!clubMasterName.equals(clubMasterRegistration.getUser().getName())) { + User newClubMaster = userQueryService.getUserByName(clubMasterName); + ClubRegistration newClubMasterRegistration = + clubRegistrationQueryService.getClubUser(newClubMaster.getId(), clubId); + + if (newClubMasterRegistration == null) { + newClubMasterRegistration = clubRegistrationCommandService.addNewClubUser( + ClubRegistration.of(clubId, newClubMaster.getId(), UserRole.CLUB_ADMIN)); + } + clubRegistrationCommandService.mandateClubMaster(clubMasterRegistration, + newClubMasterRegistration); + } + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminClubUserRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminClubUserRequestDto.java index 6ee2b597d..fe1b16b0c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminClubUserRequestDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminClubUserRequestDto.java @@ -1,10 +1,11 @@ package org.ftclub.cabinet.admin.dto; +import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; -import javax.validation.constraints.NotNull; - +@NoArgsConstructor @AllArgsConstructor @Getter public class AdminClubUserRequestDto { diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java index 1882e9e0d..c79fd6d4d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java @@ -12,10 +12,13 @@ import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.service.CabinetCommandService; import org.ftclub.cabinet.cabinet.service.CabinetQueryService; +import org.ftclub.cabinet.club.domain.ClubLentHistory; import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.ClubLentCommandService; +import org.ftclub.cabinet.lent.service.ClubLentQueryService; import org.ftclub.cabinet.lent.service.LentCommandService; import org.ftclub.cabinet.lent.service.LentPolicyService; import org.ftclub.cabinet.lent.service.LentQueryService; @@ -46,6 +49,9 @@ public class AdminLentFacadeService { private final LentCommandService lentCommandService; private final LentRedisService lentRedisService; + private final ClubLentQueryService clubLentQueryService; + private final ClubLentCommandService clubLentCommandService; + private final LentPolicyService lentPolicyService; private final BanPolicyService banPolicyService; @@ -107,12 +113,19 @@ public void endUserLent(List userIds) { @Transactional public void endCabinetLent(List cabinetIds) { - LocalDateTime now = LocalDateTime.now(); List cabinets = cabinetQueryService.findCabinetsForUpdate(cabinetIds); List lentHistories = lentQueryService.findCabinetsActiveLentHistories(cabinetIds); Map> lentHistoriesByCabinetId = lentHistories.stream() .collect(Collectors.groupingBy(LentHistory::getCabinetId)); + + // is club cabinet + if (lentHistories.isEmpty()) { + endClubCabinetLent(cabinetIds, cabinets); + return; + } + + LocalDateTime now = LocalDateTime.now(); cabinets.forEach(cabinet -> { List cabinetLentHistories = lentHistoriesByCabinetId.get(cabinet.getId()); @@ -125,4 +138,21 @@ public void endCabinetLent(List cabinetIds) { lentCommandService.endLent(lentHistories, now); cabinetCommandService.changeUserCount(cabinets, 0); } + + private void endClubCabinetLent(List cabinetIds, List cabinets) { + List allActiveLentHistoriesWithClub = clubLentQueryService.getAllActiveClubLentHistoriesWithCabinets( + cabinetIds); + cabinets.forEach(cabinet -> { + List clubLentHistories = + allActiveLentHistoriesWithClub.stream() + .filter(clh -> clh.getCabinetId().equals(cabinet.getId())) + .collect(Collectors.toList()); + clubLentHistories.forEach( + clh -> clubLentCommandService.endLent(clh, LocalDateTime.now())); + cabinetCommandService.changeUserCount(cabinet, 0); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); + lentRedisService.setPreviousUserName( + cabinet.getId(), clubLentHistories.get(0).getClub().getName()); + }); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java index 595cd10f6..0e948a0ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/search/service/AdminSearchFacadeService.java @@ -1,9 +1,28 @@ package org.ftclub.cabinet.admin.search.service; +import static java.util.stream.Collectors.toList; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.IN_SESSION; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.service.CabinetQueryService; -import org.ftclub.cabinet.dto.*; +import org.ftclub.cabinet.dto.CabinetDto; +import org.ftclub.cabinet.dto.CabinetInfoPaginationDto; +import org.ftclub.cabinet.dto.CabinetInfoResponseDto; +import org.ftclub.cabinet.dto.CabinetSimpleDto; +import org.ftclub.cabinet.dto.CabinetSimplePaginationDto; +import org.ftclub.cabinet.dto.LentDto; +import org.ftclub.cabinet.dto.UserBlockedInfoDto; +import org.ftclub.cabinet.dto.UserCabinetDto; +import org.ftclub.cabinet.dto.UserCabinetPaginationDto; +import org.ftclub.cabinet.dto.UserProfileDto; +import org.ftclub.cabinet.dto.UserProfilePaginationDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.lent.service.LentRedisService; @@ -21,16 +40,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -import static java.util.stream.Collectors.toList; -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.IN_SESSION; - @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) @@ -49,7 +58,7 @@ public class AdminSearchFacadeService { private final LentMapper lentMapper; public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pageable) { - Page users = userQueryService.getUsers(partialName, pageable); + Page users = userQueryService.findUsers(partialName, pageable); List result = users.stream() .map(userMapper::toUserProfileDto).collect(toList()); return userMapper.toUserProfilePaginationDto(result, users.getTotalElements()); @@ -57,7 +66,7 @@ public UserProfilePaginationDto getUsersProfile(String partialName, Pageable pag public UserCabinetPaginationDto getUserLentCabinetInfo(String partialName, Pageable pageable) { LocalDateTime now = LocalDateTime.now(); - Page users = userQueryService.getUsers(partialName, pageable); + Page users = userQueryService.findUsers(partialName, pageable); List userIds = users.stream().map(User::getId).collect(toList()); List activeBanHistories = @@ -109,7 +118,7 @@ public CabinetInfoPaginationDto getCabinetInfo(Integer visibleNum) { } else if (cabinet.isStatus(IN_SESSION)) { List usersInCabinet = lentRedisService.findUsersInCabinet(cabinet.getId()); - List users = userQueryService.getUsers(usersInCabinet); + List users = userQueryService.findUsers(usersInCabinet); lents = users.stream().map(user -> lentMapper.toLentDto(user, null)) .collect(toList()); } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java index 1b2967ca0..80972df35 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/controller/AdminUserController.java @@ -1,20 +1,20 @@ package org.ftclub.cabinet.admin.user.controller; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.admin.dto.AdminClubUserRequestDto; import org.ftclub.cabinet.admin.lent.service.AdminLentFacadeService; import org.ftclub.cabinet.admin.user.service.AdminLentExtensionFacadeService; import org.ftclub.cabinet.admin.user.service.AdminUserFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; -import org.ftclub.cabinet.dto.ClubUserListDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.log.Logging; import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import java.time.LocalDateTime; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor @@ -37,58 +37,6 @@ public void deleteBanHistoryByUserId(@PathVariable("userId") Long userId) { adminUserFacadeService.deleteRecentBanHistory(userId, LocalDateTime.now()); } - /** - * 동아리 유저를 생성합니다. - * - * @param dto 동아리 이름 - */ - @PostMapping("/club") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void createClubUser(@RequestBody AdminClubUserRequestDto dto) { - adminUserFacadeService.createClubUser(dto.getClubName()); - } - - /** - * 동아리 유저를 삭제합니다. - * - * @param clubId 동아리 고유 아이디 - */ - @DeleteMapping("/club/{clubId}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void deleteClubUser(@PathVariable("clubId") Long clubId) { - adminUserFacadeService.deleteClubUser(clubId); - } - - @GetMapping("/clubs") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public ClubUserListDto findClubs(@Valid Pageable pageable) { - return adminUserFacadeService.findAllClubUsers(pageable); - } - - @PatchMapping("/club/{clubId}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void updateClubUser(@PathVariable("clubId") Long clubId, - @Valid @RequestBody AdminClubUserRequestDto dto) { - adminUserFacadeService.updateClubUser(clubId, dto.getClubName()); - } - - // TODO : 안 쓰는 부분인 것 확정 되면 삭제 -// @GetMapping("/lent-extensions") -// @AuthGuard(level = AuthLevel.ADMIN_ONLY) -// public LentExtensionPaginationDto getAllLentExtension(@RequestParam("page") Integer page, -// @RequestParam("size") Integer size) { -// log.info("Called getAllLentExtension"); -// return adminUserFacadeService.getAllLentExtension(page, size); -// } -// -// @GetMapping("/lent-extensions/active") -// @AuthGuard(level = AuthLevel.ADMIN_ONLY) -// public LentExtensionPaginationDto getAllActiveLentExtension(@RequestParam("page") Integer page, -// @RequestParam("size") Integer size) { -// log.info("Called getAllActiveLentExtension"); -// return adminUserFacadeService.getAllActiveLentExtension(page, size); -// } - /** * 유저의 대여 기록을 반환합니다. * @@ -102,10 +50,4 @@ public LentHistoryPaginationDto getLentHistoriesByUserId( @PathVariable("userId") Long userId, Pageable pageable) { return adminLentFacadeService.getUserLentHistories(userId, pageable); } - - @PostMapping("/lent-extensions/{user}") - @AuthGuard(level = AuthLevel.ADMIN_ONLY) - public void issueLentExtension(@PathVariable("user") String username) { - adminLentExtensionFacadeService.assignLentExtension(username); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java index ec3c84b7c..a309bb9f7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminLentExtensionFacadeService.java @@ -3,26 +3,11 @@ import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; -import org.ftclub.cabinet.user.domain.LentExtensionType; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.service.LentExtensionCommandService; -import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; - @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) public class AdminLentExtensionFacadeService { - private final UserQueryService userQueryService; - private final LentExtensionCommandService lentExtensionCommandService; - - // TODO : 더 세부적으로 구현해야함 - public void assignLentExtension(String username) { - LocalDateTime now = LocalDateTime.now(); - User user = userQueryService.getUserByName(username); - lentExtensionCommandService.createLentExtension(user.getId(), LentExtensionType.ALL, now); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java index 512ed1194..b3f6674a9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/user/service/AdminUserFacadeService.java @@ -1,23 +1,13 @@ package org.ftclub.cabinet.admin.user.service; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.dto.ClubUserListDto; -import org.ftclub.cabinet.dto.UserProfileDto; import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; -import org.ftclub.cabinet.mapper.UserMapper; -import org.ftclub.cabinet.user.domain.User; import org.ftclub.cabinet.user.service.BanHistoryCommandService; import org.ftclub.cabinet.user.service.BanHistoryQueryService; -import org.ftclub.cabinet.user.service.UserCommandService; -import org.ftclub.cabinet.user.service.UserQueryService; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.List; - /** * 관리자 페이지에서 사용되는 유저 서비스 */ @@ -26,62 +16,19 @@ @Logging(level = LogLevel.DEBUG) public class AdminUserFacadeService { - private final UserQueryService userQueryService; - private final UserCommandService userCommandService; private final BanHistoryQueryService banHistoryQueryService; private final BanHistoryCommandService banHistoryCommandService; - private final UserMapper userMapper; /** - * 가장 최근의 밴 기록을 제거합니다. + * 유저의 가장 최근의 밴 기록을 제거합니다. + * + * @param userId 유저 고유 아이디 + * @param now 현재 시간 */ public void deleteRecentBanHistory(Long userId, LocalDateTime now) { banHistoryQueryService.findRecentActiveBanHistory(userId, now) - .ifPresent(banHistory -> { - banHistoryCommandService.deleteRecentBanHistory(banHistory, now); - }); - } - - /** - * 동아리 사용자를 생성합니다. - * - * @param clubName 동아리 이름 - * @return 생성된 동아리 사용자 - */ - public User createClubUser(String clubName) { - return userCommandService.createClubUser(clubName); - } - - /** - * 동아리 사용자를 삭제합니다. - * - * @param userId 동아리 사용자 id - */ - public void deleteClubUser(Long userId) { - User clubUser = userQueryService.getClubUser(userId); - userCommandService.deleteClubUser(clubUser); - } - - /** - * 동아리 사용자를 조회합니다. - * - * @return 동아리 사용자 - */ - public ClubUserListDto findAllClubUsers(Pageable pageable) { - Page clubUsers = userQueryService.findClubUsers(pageable); - List result = clubUsers.map(userMapper::toUserProfileDto).toList(); - return userMapper.toClubUserListDto(result, clubUsers.getTotalElements()); - } - - /** - * 동아리 사용자의 동아리 이름을 변경합니다. - * - * @param userId 동아리 사용자 id - * @param clubName 변경할 동아리 이름 - */ - public void updateClubUser(Long userId, String clubName) { - User clubUser = userQueryService.getUser(userId); - userCommandService.updateClubName(clubUser, clubName); + .ifPresent(banHistory -> + banHistoryCommandService.deleteRecentBanHistory(banHistory, now)); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java index a1b9be2ae..10898d56a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenProvider.java @@ -3,6 +3,8 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.sql.Timestamp; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.config.JwtProperties; @@ -10,9 +12,6 @@ import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Component; -import java.sql.Timestamp; -import java.time.LocalDateTime; - /** * API 제공자에 따라 JWT 토큰을 생성하는 클래스입니다. */ @@ -37,7 +36,7 @@ public String createUserToken(User user, LocalDateTime now) { claims.put("email", user.getEmail()); claims.put("name", user.getName()); claims.put("blackholedAt", user.getBlackholedAtString()); - claims.put("role", user.getRole()); +// claims.put("role", user.getRole()); return Jwts.builder() .setClaims(claims) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java index a66ad87bb..39059a418 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/controller/CabinetController.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.cabinet.controller; +import java.util.List; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; @@ -15,8 +16,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - /** * 일반 사용자가 사물함 서비스를 사용하기 위한 컨트롤러입니다. */ @@ -26,57 +25,58 @@ @Logging public class CabinetController { - private final CabinetFacadeService cabinetFacadeService; + private final CabinetFacadeService cabinetFacadeService; - /** - * 모든 건물과 층에 대한 정보를 가져옵니다. - * - * @return 모든 건물과 층에 대한 정보를 반환합니다. - */ - @GetMapping("/buildings/floors") - @AuthGuard(level = AuthLevel.USER_OR_ADMIN) - public List getBuildingFloorsResponse() { - return cabinetFacadeService.getBuildingFloorsResponse(); - } + /** + * 모든 건물과 층에 대한 정보를 가져옵니다. + * + * @return 모든 건물과 층에 대한 정보를 반환합니다. + */ + @GetMapping("/buildings/floors") + @AuthGuard(level = AuthLevel.USER_OR_ADMIN) + public List getBuildingFloorsResponse() { + return cabinetFacadeService.getBuildingFloorsResponse(); + } - /** - * 건물과 층에 해당하는 섹션별 사물함들의 정보를 가져옵니다. - * - * @param building 건물 이름 - * @param floor 층 - * @return 건물과 층에 해당하는 섹션별 사물함 정보를 반환합니다. - * @throws ControllerException 인자가 null이거나 빈 값일 경우 발생시킵니다. - */ - @GetMapping("/buildings/{building}/floors/{floor}") - @AuthGuard(level = AuthLevel.USER_OR_ADMIN) - public List getCabinetsPerSection( - @PathVariable("building") String building, - @PathVariable("floor") Integer floor) { - return cabinetFacadeService.getCabinetsPerSection(building, floor); - } + /** + * 건물과 층에 해당하는 섹션별 사물함들의 정보를 가져옵니다. + * + * @param building 건물 이름 + * @param floor 층 + * @return 건물과 층에 해당하는 섹션별 사물함 정보를 반환합니다. + * @throws ControllerException 인자가 null이거나 빈 값일 경우 발생시킵니다. + */ + @GetMapping("/buildings/{building}/floors/{floor}") + @AuthGuard(level = AuthLevel.USER_OR_ADMIN) + public List getCabinetsPerSection( + @PathVariable("building") String building, + @PathVariable("floor") Integer floor) { + return cabinetFacadeService.getCabinetsPerSection(building, floor); + } - /** - * 사물함의 정보를 가져옵니다. - * - * @param cabinetId 사물함 ID - * @return 사물함의 정보를 반환합니다. - * @throws ControllerException 인자가 null이거나 빈 값일 경우 발생시킵니다. - */ - @GetMapping("/{cabinetId}") - @AuthGuard(level = AuthLevel.USER_OR_ADMIN) - public CabinetInfoResponseDto getCabinetInfo( - @PathVariable("cabinetId") Long cabinetId) { - return cabinetFacadeService.getCabinetInfo(cabinetId); - } + /** + * 사물함의 정보를 가져옵니다. + * + * @param cabinetId 사물함 ID + * @return 사물함의 정보를 반환합니다. + * @throws ControllerException 인자가 null이거나 빈 값일 경우 발생시킵니다. + */ + @GetMapping("/{cabinetId}") + @AuthGuard(level = AuthLevel.USER_OR_ADMIN) + public CabinetInfoResponseDto getCabinetInfo( + @PathVariable("cabinetId") Long cabinetId) { + return cabinetFacadeService.getCabinetInfo(cabinetId); + } - /** - * 오픈 예정인 사물함들의 정보를 가져옵니다. - * - * @return 오픈 예정인 사물함들의 정보를 반환합니다. - */ - @GetMapping("/buildings/{building}/pending") - @AuthGuard(level = AuthLevel.USER_OR_ADMIN) - public CabinetPendingResponseDto getPendingCabinets(@PathVariable("building") String building) { - return cabinetFacadeService.getPendingCabinets(building); - } + /** + * 오픈 예정인 사물함들의 정보를 가져옵니다. + * + * @return 오픈 예정인 사물함들의 정보를 반환합니다. + */ + @GetMapping("/buildings/{building}/available") + @AuthGuard(level = AuthLevel.USER_OR_ADMIN) + public CabinetPendingResponseDto getAvailableCabinets( + @PathVariable("building") String building) { + return cabinetFacadeService.getAvailableCabinets(building); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 230b49b68..817907ad2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -127,15 +127,6 @@ public interface CabinetRepository extends JpaRepository, Cabinet Optional findByUserIdAndLentHistoryEndedAtIsNullWithXLock( @Param("userId") Long userId); - /** - * lentType 으로 사물함을 조회한다. - * - * @param lentType 사물함 대여 타입(LentType) - * @param pageable 페이지 정보 - * @return - */ - Page findPaginationByLentType(@Param("lentType") LentType lentType, Pageable pageable); - /** * status로 사물함을 조회한다. * @@ -146,16 +137,6 @@ Optional findByUserIdAndLentHistoryEndedAtIsNullWithXLock( @EntityGraph(attributePaths = {"cabinetPlace"}) Page findPaginationByStatus(@Param("status") CabinetStatus status, Pageable pageable); - /** - * visibleNum으로 사물함을 조회한다. - * - * @param visibleNum 사물함 번호 - * @param pageable 페이지 정보 - * @return - */ - Page findPaginationByVisibleNum(@Param("visibleNum") Integer visibleNum, - Pageable pageable); - /** * 사물함 번호에 해당하는 모든 사물함을 조회한다. * diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetCommandService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetCommandService.java index 59a7e2ad8..538582412 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetCommandService.java @@ -1,5 +1,12 @@ package org.ftclub.cabinet.cabinet.service; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.BROKEN; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.FULL; +import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.PENDING; +import static org.ftclub.cabinet.exception.ExceptionStatus.INVALID_STATUS; + +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; @@ -12,16 +19,11 @@ import org.ftclub.cabinet.utils.ExceptionUtil; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.stream.Collectors; - -import static org.ftclub.cabinet.cabinet.domain.CabinetStatus.*; -import static org.ftclub.cabinet.exception.ExceptionStatus.INVALID_STATUS; - @Service @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) public class CabinetCommandService { + private static final String EMPTY_STRING = ""; private final CabinetRepository cabinetRepository; @@ -34,7 +36,6 @@ public class CabinetCommandService { */ public void changeStatus(Cabinet cabinet, CabinetStatus cabinetStatus) { cabinet.specifyStatus(cabinetStatus); - cabinetRepository.save(cabinet); } /** @@ -55,7 +56,6 @@ public void changeUserCount(Cabinet cabinet, int userCount) { if (userCount == cabinet.getMaxUser()) { cabinet.specifyStatus(FULL); } - cabinetRepository.save(cabinet); } /** @@ -66,7 +66,6 @@ public void changeUserCount(Cabinet cabinet, int userCount) { */ public void updateTitle(Cabinet cabinet, String title) { cabinet.writeTitle(title); - cabinetRepository.save(cabinet); } /** @@ -77,7 +76,6 @@ public void updateTitle(Cabinet cabinet, String title) { */ public void updateMemo(Cabinet cabinet, String memo) { cabinet.writeMemo(memo); - cabinetRepository.save(cabinet); } /** @@ -92,7 +90,8 @@ public void changeUserCount(List cabinets, int userCount) { List cabinetIds = cabinets.stream() .map(Cabinet::getId).collect(Collectors.toList()); if (userCount == 0) { - cabinetRepository.updateStatusAndTitleAndMemoByCabinetIdsIn(cabinetIds, PENDING, EMPTY_STRING, EMPTY_STRING); + cabinetRepository.updateStatusAndTitleAndMemoByCabinetIdsIn(cabinetIds, PENDING, + EMPTY_STRING, EMPTY_STRING); } else { cabinetRepository.updateStatusByCabinetIdsIn(cabinetIds, FULL); } @@ -138,6 +137,10 @@ public void updateStatus(Cabinet cabinet, CabinetStatus status) { cabinet.specifyStatus(status); } + public void updateStatusBulk(List cabinetIds, CabinetStatus status) { + cabinetRepository.updateStatusByCabinetIdsIn(cabinetIds, status); + } + /** * 사물함의 대여타입을 변경합니다. * @@ -148,17 +151,4 @@ public void updateStatus(Cabinet cabinet, CabinetStatus status) { public void updateLentType(Cabinet cabinet, LentType lentType) { cabinet.specifyLentType(lentType); } - - /** - * 동아리 사물함으로 업데이트 합니다 - * - * @param cabinet 변경될 사물함 - * @param clubName 동아리 이름 - * @param statusNote 상태 메모(동아리 장의 intra id) - */ - public void updateClubStatus(Cabinet cabinet, String clubName, String statusNote) { - cabinet.writeTitle(clubName); - cabinet.writeStatusNote(statusNote); - cabinet.specifyLentType(LentType.CLUB); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index b59159168..96ecdaddb 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -19,21 +19,23 @@ import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.domain.Grid; import org.ftclub.cabinet.cabinet.domain.LentType; +import org.ftclub.cabinet.club.domain.Club; +import org.ftclub.cabinet.club.domain.ClubLentHistory; +import org.ftclub.cabinet.club.service.ClubQueryService; import org.ftclub.cabinet.dto.ActiveCabinetInfoEntities; import org.ftclub.cabinet.dto.BuildingFloorsDto; -import org.ftclub.cabinet.dto.CabinetClubStatusRequestDto; import org.ftclub.cabinet.dto.CabinetDto; import org.ftclub.cabinet.dto.CabinetInfoResponseDto; import org.ftclub.cabinet.dto.CabinetPaginationDto; import org.ftclub.cabinet.dto.CabinetPendingResponseDto; import org.ftclub.cabinet.dto.CabinetPreviewDto; -import org.ftclub.cabinet.dto.CabinetStatusRequestDto; import org.ftclub.cabinet.dto.CabinetsPerSectionResponseDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.dto.LentHistoryPaginationDto; import org.ftclub.cabinet.exception.ExceptionStatus; import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.ClubLentQueryService; import org.ftclub.cabinet.lent.service.LentQueryService; import org.ftclub.cabinet.lent.service.LentRedisService; import org.ftclub.cabinet.log.LogLevel; @@ -54,10 +56,11 @@ public class CabinetFacadeService { private final CabinetCommandService cabinetCommandService; private final CabinetQueryService cabinetQueryService; - private final LentQueryService lentQueryService; private final LentRedisService lentRedisService; private final UserQueryService userQueryService; + private final ClubQueryService clubQueryService; + private final ClubLentQueryService clubLentQueryService; private final CabinetMapper cabinetMapper; private final LentMapper lentMapper; @@ -86,6 +89,16 @@ public List getBuildingFloorsResponse() { @Transactional(readOnly = true) public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { Cabinet cabinet = cabinetQueryService.getCabinet(cabinetId); + if (cabinet.getLentType().equals(LentType.CLUB)) { + ClubLentHistory activeClubLentHistory = + clubLentQueryService.findActiveLentHistoryWithClub(cabinetId); + List lentDtos = new ArrayList<>(); + if (activeClubLentHistory != null) { + lentDtos.add(lentMapper.toLentDto(activeClubLentHistory)); + } + LocalDateTime sessionExpiredAt = lentRedisService.getSessionExpired(cabinetId); + return cabinetMapper.toCabinetInfoResponseDto(cabinet, lentDtos, sessionExpiredAt); + } List cabinetActiveLentHistories = lentQueryService.findCabinetActiveLentHistories( cabinetId); List lentDtos = cabinetActiveLentHistories.stream() @@ -94,7 +107,7 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { if (lentDtos.isEmpty()) { List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); - List users = userQueryService.getUsers(usersInCabinet); + List users = userQueryService.findUsers(usersInCabinet); users.forEach(user -> lentDtos.add( LentDto.builder().userId(user.getId()).name(user.getName()).build() )); @@ -115,20 +128,36 @@ public CabinetInfoResponseDto getCabinetInfo(Long cabinetId) { @Transactional(readOnly = true) public List getCabinetsPerSection(String building, Integer floor) { - List activeCabinetInfos = cabinetQueryService.findActiveCabinetInfoEntities( - building, floor); + List activeCabinetInfos = + cabinetQueryService.findActiveCabinetInfoEntities(building, floor); Map> cabinetLentHistories = activeCabinetInfos.stream(). collect(groupingBy(ActiveCabinetInfoEntities::getCabinet, - mapping(ActiveCabinetInfoEntities::getLentHistory, - Collectors.toList()))); + mapping(ActiveCabinetInfoEntities::getLentHistory, Collectors.toList()))); List allCabinetsOnSection = cabinetQueryService.findAllCabinetsByBuildingAndFloor(building, floor); + Map> clubLentMap = + clubLentQueryService.findAllActiveLentHistoriesWithClub().stream() + .collect(groupingBy(ClubLentHistory::getCabinetId)); Map> cabinetPreviewsBySection = new LinkedHashMap<>(); allCabinetsOnSection.stream() .sorted(Comparator.comparing(Cabinet::getVisibleNum)) .forEach(cabinet -> { String section = cabinet.getCabinetPlace().getLocation().getSection(); + if (cabinet.getLentType().equals(LentType.CLUB)) { + if (!clubLentMap.containsKey(cabinet.getId())) { + cabinetPreviewsBySection.computeIfAbsent(section, + k -> new ArrayList<>()) + .add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); + } else { + clubLentMap.get(cabinet.getId()).stream() + .map(c -> c.getClub().getName()) + .findFirst().ifPresent(clubName -> cabinetPreviewsBySection + .computeIfAbsent(section, k -> new ArrayList<>()) + .add(cabinetMapper.toCabinetPreviewDto(cabinet, 1, clubName))); + } + return; + } List lentHistories = cabinetLentHistories.getOrDefault(cabinet, Collections.emptyList()); String title = getCabinetTitle(cabinet, lentHistories); @@ -166,18 +195,18 @@ private String getCabinetTitle(Cabinet cabinet, List lentHistories) * @return 빌딩에 있는 모든 PENDING 상태의 사물함 */ @Transactional - public CabinetPendingResponseDto getPendingCabinets(String building) { + public CabinetPendingResponseDto getAvailableCabinets(String building) { final LocalDateTime now = LocalDateTime.now(); - List pendingCabinets = - cabinetQueryService.findPendingCabinetsNotLentTypeAndStatus( - building, LentType.CLUB, List.of(AVAILABLE, PENDING)); - List cabinetIds = pendingCabinets.stream() + final LocalDateTime yesterday = now.minusDays(1).withHour(13).withMinute(0).withSecond(0); + List availableCabinets = cabinetQueryService.findCabinetsNotLentTypeAndStatus( + building, LentType.CLUB, List.of(AVAILABLE, PENDING)); + List cabinetIds = availableCabinets.stream() .filter(cabinet -> cabinet.isStatus(PENDING)) .map(Cabinet::getId).collect(Collectors.toList()); Map> lentHistoriesMap; if (now.getHour() < 13) { lentHistoriesMap = lentQueryService.findPendingLentHistoriesOnDate( - now.minusDays(1).toLocalDate(), cabinetIds) + yesterday.toLocalDate(), cabinetIds) .stream().collect(groupingBy(LentHistory::getCabinetId)); } else { lentHistoriesMap = lentQueryService.findCabinetLentHistories(cabinetIds) @@ -186,17 +215,13 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { Map> cabinetFloorMap = cabinetQueryService.findAllFloorsByBuilding(building).stream() .collect(toMap(key -> key, value -> new ArrayList<>())); - pendingCabinets.forEach(cabinet -> { + availableCabinets.forEach(cabinet -> { Integer floor = cabinet.getCabinetPlace().getLocation().getFloor(); if (cabinet.isStatus(AVAILABLE)) { cabinetFloorMap.get(floor).add(cabinetMapper.toCabinetPreviewDto(cabinet, 0, null)); } if (cabinet.isStatus(PENDING)) { - List lentHistories = lentHistoriesMap.get(cabinet.getId()); - if (lentHistories == null || lentHistories.isEmpty()) { - return; - } - lentHistories.stream() + lentHistoriesMap.get(cabinet.getId()).stream() .map(LentHistory::getEndedAt) .max(LocalDateTime::compareTo) .ifPresent(latestEndedAt -> cabinetFloorMap.get(floor) @@ -206,22 +231,6 @@ public CabinetPendingResponseDto getPendingCabinets(String building) { return cabinetMapper.toCabinetPendingResponseDto(cabinetFloorMap); } - /** - * LentType에 해당하는 캐비넷 정보를 모두 가져옵니다. - * - * @param lentType 대여 타입 - * @param pageable 페이징 정보 - * @return 캐비넷 정보 - */ - @Transactional(readOnly = true) - public CabinetPaginationDto getCabinetPaginationByLentType(LentType lentType, - Pageable pageable) { - Page cabinets = cabinetQueryService.findAllByLentType(lentType, pageable); - List result = cabinets.stream() - .map(cabinetMapper::toCabinetDto).collect(Collectors.toList()); - return cabinetMapper.toCabinetPaginationDtoList(result, cabinets.getTotalElements()); - } - /** * CabinetStatus에 해당하는 캐비넷 정보를 모두 가져옵니다. * @@ -237,21 +246,6 @@ public CabinetPaginationDto getCabinetPaginationByStatus(CabinetStatus status, return cabinetMapper.toCabinetPaginationDtoList(result, cabinets.getTotalElements()); } - /** - * visibleNum에 해당하는 캐비넷 정보를 모두 가져옵니다. - * - * @param visibleNum 사물함 번호 - * @param pageable 페이징 정보 - * @return - */ - public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, - Pageable pageable) { - Page cabinets = cabinetQueryService.findAllByVisibleNum(visibleNum, pageable); - List result = cabinets.stream() - .map(cabinetMapper::toCabinetDto).collect(Collectors.toList()); - return cabinetMapper.toCabinetPaginationDtoList(result, cabinets.getTotalElements()); - } - /** * cabinetId로 해당 사물함의 대여기록을 모두 가져옵니다. ( 대여시작 날짜 내림차순 정렬) * @@ -261,9 +255,10 @@ public CabinetPaginationDto getCabinetPaginationByVisibleNum(Integer visibleNum, */ @Transactional(readOnly = true) public LentHistoryPaginationDto getLentHistoryPagination(Long cabinetId, Pageable pageable) { - Page lentHistories = - lentQueryService.findCabinetLentHistoriesWithUserAndCabinet(cabinetId, pageable); + Page lentHistories = lentQueryService.findCabinetLentHistoriesWithUserAndCabinet( + cabinetId, pageable); List result = lentHistories.stream() + .sorted(Comparator.comparing(LentHistory::getStartedAt).reversed()) .map(lh -> lentMapper.toLentHistoryDto(lh, lh.getUser(), lh.getCabinet())) .collect(Collectors.toList()); return lentMapper.toLentHistoryPaginationDto(result, lentHistories.getTotalElements()); @@ -324,23 +319,22 @@ public void updateCabinetVisibleNum(Long cabinetId, Integer visibleNum) { /** * [ADMIN] 사물함의 상태를 변경합니다. * - * @param cabinetStatusRequestDto 변경할 사물함 ID 리스트, 변경할 상태 + * @param cabinetIds 변경할 사물함 ID 리스트 + * @param status 변경할 상태 + * @param lentType 변경할 대여 타입 */ @Transactional - public void updateCabinetBundleStatus(CabinetStatusRequestDto cabinetStatusRequestDto) { - CabinetStatus status = cabinetStatusRequestDto.getStatus(); - LentType lentType = cabinetStatusRequestDto.getLentType(); + public void updateCabinetBundleStatus(List cabinetIds, CabinetStatus status, + LentType lentType) { - List cabinetsWithLock = cabinetQueryService.findCabinetsForUpdate( - cabinetStatusRequestDto.getCabinetIds()); + List cabinetsWithLock = cabinetQueryService.findCabinetsForUpdate(cabinetIds); for (Cabinet cabinet : cabinetsWithLock) { if (status != null) { - cabinetCommandService.updateStatus(cabinet, cabinetStatusRequestDto.getStatus()); + cabinetCommandService.updateStatus(cabinet, status); } if (lentType != null) { - cabinetCommandService.updateLentType(cabinet, - cabinetStatusRequestDto.getLentType()); + cabinetCommandService.updateLentType(cabinet, lentType); } } } @@ -348,24 +342,14 @@ public void updateCabinetBundleStatus(CabinetStatusRequestDto cabinetStatusReque /** * [ADMIN] 사물함에 동아리 유저를 대여 시킵니다. {inheritDoc} * - * @param dto 변경하려는 동아리 정보 dto + * @param clubId 대여할 유저 ID + * @param cabinetId 대여할 사물함 ID + * @param statusNote 상태 메모 */ @Transactional - public void updateClub(CabinetClubStatusRequestDto dto) { - Cabinet cabinet = cabinetQueryService.getUserActiveCabinetForUpdate(dto.getCabinetId()); - - Cabinet activeCabinetByUserId = cabinetQueryService.findActiveCabinetByUserId( - dto.getUserId()); - if (activeCabinetByUserId != null) { - throw ExceptionStatus.LENT_ALREADY_EXISTED.asServiceException(); - } - - String clubName = ""; - if (dto.getUserId() != null) { - clubName = userQueryService.getUser(dto.getUserId()).getName(); - } - - cabinetCommandService.updateClubStatus(cabinet, clubName, dto.getStatusNote()); + public void updateClub(Long clubId, Long cabinetId, String statusNote) { + Cabinet cabinet = cabinetQueryService.getUserActiveCabinetForUpdate(cabinetId); + Club club = clubQueryService.getClub(clubId); } /** @@ -383,4 +367,8 @@ public void updateStatus(Long cabinetId, CabinetStatus status) { cabinet.specifyStatus(status); } + @Transactional + public void updateStatus(List cabinetId, CabinetStatus status) { + cabinetCommandService.updateStatusBulk(cabinetId, status); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java index 7be8e8df6..bad86ed17 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java @@ -36,6 +36,10 @@ public int countCabinets(CabinetStatus status, Integer floor) { return cabinetRepository.countByStatusAndFloor(status, floor); } + public long countCabinets() { + return cabinetRepository.count(); + } + /*------------------------------------------ FIND -------------------------------------------*/ /** @@ -170,7 +174,7 @@ public List findActiveCabinetInfoEntities(String buil * @param cabinetStatuses 사물함 상태 * @return 사물함 */ - public List findPendingCabinetsNotLentTypeAndStatus( + public List findCabinetsNotLentTypeAndStatus( String building, LentType lentType, List cabinetStatuses) { return cabinetRepository.findAllByBuildingAndLentTypeNotAndStatusIn(building, lentType, cabinetStatuses); @@ -186,17 +190,6 @@ public Cabinet findActiveCabinetByUserId(Long userId) { return cabinetRepository.findByUserIdAndLentHistoryEndedAtIsNull(userId).orElse(null); } - /** - * LentType에 해당하는 사물함을 page로 가져옵니다. - * - * @param lentType 대여 타입 - * @param pageable 페이지 정보 - * @return 사물함 - */ - public Page findAllByLentType(LentType lentType, Pageable pageable) { - return cabinetRepository.findPaginationByLentType(lentType, pageable); - } - /** * status에 해당하는 사물함을 page로 가져옵니다. * @@ -208,17 +201,6 @@ public Page findAllByStatus(CabinetStatus status, Pageable pageable) { return cabinetRepository.findPaginationByStatus(status, pageable); } - /** - * 사물함 번호에 해당하는 사물함을 page로 가져옵니다. - * - * @param visibleNum 사물함 번호 - * @param pageable 페이지 정보 - * @return 사물함 - */ - public Page findAllByVisibleNum(Integer visibleNum, Pageable pageable) { - return cabinetRepository.findPaginationByVisibleNum(visibleNum, pageable); - } - /** * 사물함 상태에 맞는 사물함들 중 from 이전에 대여기간이 만료되는 사물함을 가져옵니다. * diff --git a/backend/src/main/java/org/ftclub/cabinet/club/controller/ClubController.java b/backend/src/main/java/org/ftclub/cabinet/club/controller/ClubController.java new file mode 100644 index 000000000..573aaba04 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/controller/ClubController.java @@ -0,0 +1,122 @@ +package org.ftclub.cabinet.club.controller; + +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.club.service.ClubFacadeService; +import org.ftclub.cabinet.dto.AddClubUserRequestDto; +import org.ftclub.cabinet.dto.ClubInfoPaginationDto; +import org.ftclub.cabinet.dto.ClubInfoResponseDto; +import org.ftclub.cabinet.dto.ClubMemoUpdateDto; +import org.ftclub.cabinet.dto.ClubNoticeUpdateDto; +import org.ftclub.cabinet.dto.MandateClubMasterRequestDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.user.domain.UserSession; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/v4/clubs") +@Logging +public class ClubController { + + private final ClubFacadeService clubFacadeService; + + /** + * 내가 속한 동아리 목록을 조회한다. + * + * @param user 사용자 세션 + * @return 내가 속한 동아리 목록 + */ + @AuthGuard(level = AuthLevel.USER_ONLY) + @GetMapping("") + public ClubInfoPaginationDto getMyClubs(@UserSession UserSessionDto user) { + return clubFacadeService.getMyClubs(user.getUserId()); + } + + /** + * 동아리 정보를 조회한다. + * + * @param user 사용자 세션 + * @param clubId 동아리 ID + * @param pageable 페이징 정보 + * @return 동아리 정보 + */ + @AuthGuard(level = AuthLevel.USER_ONLY) + @GetMapping("/{clubId}") + public ClubInfoResponseDto getClubInfo(@UserSession UserSessionDto user, + @PathVariable Long clubId, + @Valid Pageable pageable) { + return clubFacadeService.getClubInfo(user.getUserId(), clubId, pageable); + } + + /** + * 동아리에 사용자를 추가한다. + * + * @param user 사용자 세션 + * @param clubId 동아리 ID + * @param addClubUserRequestDto 사용자 추가 요청 정보 + */ + @AuthGuard(level = AuthLevel.USER_ONLY) + @PostMapping("/{clubId}/users") + public void addClubUser(@UserSession UserSessionDto user, + @PathVariable Long clubId, + @Valid @RequestBody AddClubUserRequestDto addClubUserRequestDto) { + clubFacadeService.addClubUser(user.getUserId(), clubId, addClubUserRequestDto.getName()); + } + + /** + * 동아리에서 사용자를 제거한다. + * + * @param user 사용자 세션 + * @param clubId 동아리 ID + * @param userId 사용자 ID + */ + @AuthGuard(level = AuthLevel.USER_ONLY) + @DeleteMapping("/{clubId}/users/{userId}") + public void deleteClubUser(@UserSession UserSessionDto user, + @PathVariable Long clubId, + @PathVariable Long userId) { + clubFacadeService.deleteClubUser(user.getUserId(), clubId, userId); + } + + /** + * 동아리장을 위임한다. + * + * @param user 사용자 세션 + * @param clubId 동아리 ID + * @param mandateClubMasterRequestDto 동아리장 위임 요청 정보 + */ + @AuthGuard(level = AuthLevel.USER_ONLY) + @PostMapping("/{clubId}/mandate") + public void mandateClubUser(@UserSession UserSessionDto user, + @PathVariable Long clubId, + @Valid @RequestBody MandateClubMasterRequestDto mandateClubMasterRequestDto) { + clubFacadeService.mandateClubUser(user.getUserId(), clubId, + mandateClubMasterRequestDto.getClubMaster()); + } + + @AuthGuard(level = AuthLevel.USER_ONLY) + @PostMapping("/{clubId}/notice") + public void updateClubNotice(@UserSession UserSessionDto user, @PathVariable Long clubId, + @Valid @RequestBody ClubNoticeUpdateDto clubNoticeUpdateDto) { + clubFacadeService.updateClubNotice(user.getUserId(), clubId, + clubNoticeUpdateDto.getNotice()); + } + + @AuthGuard(level = AuthLevel.USER_ONLY) + @PostMapping("/{clubId}/memo") + public void updateClubMemo(@UserSession UserSessionDto user, @PathVariable Long clubId, + @Valid @RequestBody ClubMemoUpdateDto clubMemoUpdateDto) { + clubFacadeService.updateClubMemo(user.getUserId(), clubId, clubMemoUpdateDto.getMemo()); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/domain/Club.java b/backend/src/main/java/org/ftclub/cabinet/club/domain/Club.java new file mode 100644 index 000000000..f833afba0 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/domain/Club.java @@ -0,0 +1,93 @@ +package org.ftclub.cabinet.club.domain; + +import java.time.LocalDateTime; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.utils.ExceptionUtil; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Entity +@Table(name = "CLUB") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@EntityListeners(AuditingEntityListener.class) +@ToString(exclude = {"clubRegistrations", "clubLentHistories"}) +public class Club { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private long id; + + @NotNull + @Column(name = "NAME", unique = true, nullable = false) + private String name; + + @CreatedDate + @Column(name = "CREATED_AT", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "DELETED_AT", length = 32) + private LocalDateTime deletedAt; + + @Lob + @Column(name = "NOTICE", nullable = false) + private String notice = ""; + + + @OneToMany(mappedBy = "club", fetch = FetchType.LAZY) + private List clubRegistrations; + + @OneToMany(mappedBy = "club", fetch = FetchType.LAZY) + private List clubLentHistories; + + protected Club(String name) { + this.name = name; + } + + public static Club of(String name) { + Club club = new Club(name); + ExceptionUtil.throwIfFalse(club.isValid(), + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + return club; + } + + private boolean isValid() { + return name != null; + } + + public void delete() { + this.deletedAt = LocalDateTime.now(); + } + + public void changeClubName(String clubName) { + if (clubName == null || clubName.isEmpty()) { + throw ExceptionStatus.INVALID_ARGUMENT.asDomainException(); + } + this.name = clubName; + } + + public void changeClubNotice(String notice) { + if (notice == null) { + throw ExceptionStatus.INVALID_ARGUMENT.asDomainException(); + } + this.notice = notice; + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubLentHistory.java b/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubLentHistory.java new file mode 100644 index 000000000..a17593ef7 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubLentHistory.java @@ -0,0 +1,98 @@ +package org.ftclub.cabinet.club.domain; + +import java.time.LocalDateTime; +import javax.annotation.Nullable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.utils.DateUtil; +import org.ftclub.cabinet.utils.ExceptionUtil; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Log4j2 +@ToString(exclude = {"club", "cabinet"}) +@Getter +@Table(name = "CLUB_LENT_HISTORY") +@EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class ClubLentHistory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private long id; + + @Column(name = "CLUB_ID", nullable = false, updatable = false) + private Long clubId; + + @Column(name = "CABINET_ID", nullable = false, updatable = false) + private Long cabinetId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CLUB_ID", insertable = false, updatable = false) + private Club club; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CABINET_ID", insertable = false, updatable = false) + private Cabinet cabinet; + + @CreatedDate + @Column(name = "STARTED_AT", nullable = false, updatable = false) + private LocalDateTime startedAt; + + @Nullable + @Column(name = "ENDED_AT") + private LocalDateTime endedAt; + + @Column(name = "EXPIRED_AT") + private LocalDateTime expiredAt; + + protected ClubLentHistory(Long clubId, Long cabinetId, LocalDateTime expiredAt) { + this.clubId = clubId; + this.cabinetId = cabinetId; + this.expiredAt = expiredAt; + } + + public static ClubLentHistory of(Long clubId, Long cabinetId, LocalDateTime expiredAt) { + ClubLentHistory clubLentHistory = new ClubLentHistory(clubId, cabinetId, expiredAt); + if (!clubLentHistory.isValid()) { + throw ExceptionStatus.INVALID_ARGUMENT.asDomainException(); + } + return clubLentHistory; + } + + private boolean isValid() { + return clubId != null && cabinetId != null && expiredAt != null; + } + + @Logging(level = LogLevel.DEBUG) + public void endLent(LocalDateTime now) { + ExceptionUtil.throwIfFalse((this.isEndLentValid(now)), + ExceptionStatus.INVALID_ARGUMENT.asDomainException()); + this.endedAt = now; + ExceptionUtil.throwIfFalse((this.isValid()), + ExceptionStatus.INVALID_ARGUMENT.asDomainException()); + } + + private boolean isEndLentValid(LocalDateTime endedAt) { + return endedAt != null && DateUtil.calculateTwoDateDiff(endedAt, this.startedAt) >= 0; + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubRegistration.java b/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubRegistration.java new file mode 100644 index 000000000..333800b7a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/domain/ClubRegistration.java @@ -0,0 +1,94 @@ +package org.ftclub.cabinet.club.domain; + +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.exception.DomainException; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserRole; +import org.ftclub.cabinet.utils.ExceptionUtil; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Entity +@Table(name = "CLUB_REGISTRATION") +@Getter +@ToString(exclude = {"user", "club"}) +@Log4j2 +@EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor +public class ClubRegistration { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private long id; + + @Column(name = "USER_ID", nullable = false) + private Long userId; + + @Column(name = "CLUB_ID", nullable = false) + private Long clubId; + + @Enumerated(value = EnumType.STRING) + @Column(name = "USER_ROLE", length = 32, nullable = false) + private UserRole userRole; + + @CreatedDate + @Column(name = "REGISTERED_AT", nullable = false, updatable = false) + private LocalDateTime registeredAt; + + @Column(name = "DELETED_AT") + private LocalDateTime deletedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "USER_ID", insertable = false, updatable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CLUB_ID", insertable = false, updatable = false) + private Club club; + + + protected ClubRegistration(Long userId, Long clubId, UserRole userRole) { + this.userId = userId; + this.clubId = clubId; + this.userRole = userRole; + } + + public static ClubRegistration of(Long userId, Long clubId, UserRole userRole) { + ClubRegistration clubRegistration = new ClubRegistration(userId, clubId, userRole); + ExceptionUtil.throwIfFalse(clubRegistration.isValid(), + new DomainException(ExceptionStatus.INVALID_ARGUMENT)); + return clubRegistration; + } + + private boolean isValid() { + return this.userId != null && this.clubId != null + && userRole.isValid(); + } + + public ClubRegistration changeUserRole(UserRole userRole) { + this.userRole = userRole; + return this; + } + + public void delete() { + this.deletedAt = LocalDateTime.now(); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRegistrationRepoitory.java b/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRegistrationRepoitory.java new file mode 100644 index 000000000..695a083ab --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRegistrationRepoitory.java @@ -0,0 +1,81 @@ +package org.ftclub.cabinet.club.repository; + +import io.lettuce.core.dynamic.annotation.Param; +import java.util.List; +import java.util.Optional; +import org.ftclub.cabinet.club.domain.ClubRegistration; +import org.ftclub.cabinet.user.domain.UserRole; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface ClubRegistrationRepoitory extends JpaRepository { + + List findAllByClubIdAndDeletedAtIsNull(@Param("clubId") Long clubId); + + Optional findByUserIdAndClubIdAndDeletedAtIsNull(@Param("userId") Long userId, + @Param("clubId") Long clubId); + + /** + * 특정 유저가 속한 동아리 목록을 조회한다. + *

+ * Club도 Join 연산으로 함께 조회한다. + *

+ * + * @param userId + * @return + */ + @Query("SELECT cr " + + "FROM ClubRegistration cr " + + "LEFT JOIN FETCH cr.club " + + "WHERE cr.userId = :userId " + + "AND cr.deletedAt IS NULL") + List findAllByUserIdJoinClub(@Param("userId") Long userId); + + /** + * 특정 동아리 목록에 속한 동아리 회원 목록을 조회한다. + *

+ * User도 Join 연산으로 함께 조회한다. + *

+ * + * @param clubIds 동아리 ID 목록 + * @return 동아리 회원 목록 + */ + @Query("SELECT cr " + + "FROM ClubRegistration cr " + + "LEFT JOIN FETCH cr.user " + + "WHERE cr.clubId IN :clubIds " + + "AND cr.deletedAt IS NULL") + List findAllByClubIdInJoinUser(List clubIds); + + /** + * 특정 동아리의 특정 유저의 동아리 회원 정보를 조회한다. + * + * @param clubId 동아리 ID + * @param userId 유저 ID + * @return 동아리 회원 정보 + */ + @Query("SELECT cr " + + "FROM ClubRegistration cr " + + "WHERE cr.clubId = :clubId AND cr.userId = :userId") + Optional findByClubIdAndUserId(@Param("clubId") Long clubId, + @Param("userId") Long userId); + + /** + * 특정 동아리의 동아리장 정보를 조회한다. + *

+ * User도 Join 연산으로 함께 조회한다. + *

+ * + * @param clubId 동아리 ID + * @param userRole 유저 동아리 권한 + * @return 동아리 회원 정보 + */ + @Query("SELECT cr " + + "FROM ClubRegistration cr " + + "LEFT JOIN FETCH cr.user " + + "WHERE cr.clubId = :clubId AND cr.userRole = :userRole") + Optional findByClubIdAndUserRoleJoinUser( + @Param("clubId") Long clubId, @Param("userRole") UserRole userRole); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRepository.java b/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRepository.java new file mode 100644 index 000000000..1910e6fcd --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/repository/ClubRepository.java @@ -0,0 +1,39 @@ +package org.ftclub.cabinet.club.repository; + +import io.lettuce.core.dynamic.annotation.Param; +import java.util.Optional; +import org.ftclub.cabinet.club.domain.Club; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface ClubRepository extends JpaRepository { + + /** + * 삭제되지 않은 모든 동아리 목록을 조회한다. + * + * @param pageable 페이징 정보 + * @return 모든 동아리 목록 + */ + @Query("SELECT c FROM Club c WHERE c.deletedAt IS NULL") + Page findPaginationByDeletedAtIsNull(Pageable pageable); + + /** + * 특정 동아리 정보를 조회한다. + *

+ * ClubLentHistory도 Join 연산으로 함께 조회한다. + *

+ * + * @param clubId 동아리 ID + * @return 동아리 정보 + */ + @Query("SELECT DISTINCT c " + + "FROM Club c " + + "LEFT JOIN FETCH c.clubLentHistories " + + "WHERE c.id = :clubId " + + "AND c.deletedAt IS NULL") + Optional findByIdAndDeletedAtIsNull(@Param("clubId") Long clubId); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubCommandService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubCommandService.java new file mode 100644 index 000000000..089b410f5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubCommandService.java @@ -0,0 +1,53 @@ +package org.ftclub.cabinet.club.service; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.club.domain.Club; +import org.ftclub.cabinet.club.repository.ClubRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ClubCommandService { + + private final ClubRepository clubRepository; + + /** + * 동아리를 생성한다. + * + * @param clubName 동아리 이름 + * @return 생성된 동아리 + */ + public Club createClub(String clubName) { + Club club = Club.of(clubName); + return clubRepository.save(club); + } + + /** + * 동아리를 삭제한다. + * + * @param club 동아리 + */ + public void deleteClub(Club club) { + club.delete(); + clubRepository.save(club); + } + + /** + * 동아리 이름을 변경한다. + * + * @param club 동아리 + * @param clubName 동아리 이름 + */ + public void updateName(Club club, String clubName) { + club.changeClubName(clubName); + clubRepository.save(club); + } + + public void changeClubNotice(Club club, String notice) { + club.changeClubNotice(notice); + clubRepository.save(club); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java new file mode 100644 index 000000000..bdbef3cc5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java @@ -0,0 +1,214 @@ +package org.ftclub.cabinet.club.service; + +import static org.ftclub.cabinet.user.domain.UserRole.CLUB_ADMIN; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.cabinet.service.CabinetCommandService; +import org.ftclub.cabinet.club.domain.Club; +import org.ftclub.cabinet.club.domain.ClubLentHistory; +import org.ftclub.cabinet.club.domain.ClubRegistration; +import org.ftclub.cabinet.dto.ClubInfoDto; +import org.ftclub.cabinet.dto.ClubInfoPaginationDto; +import org.ftclub.cabinet.dto.ClubInfoResponseDto; +import org.ftclub.cabinet.dto.ClubUserResponseDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.lent.service.ClubLentQueryService; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.mapper.ClubMapper; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.domain.UserRole; +import org.ftclub.cabinet.user.service.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +@Transactional +public class ClubFacadeService { + + private final ClubQueryService clubQueryService; + private final ClubCommandService clubCommandService; + private final ClubRegistrationQueryService clubRegistrationQueryService; + private final ClubRegistrationCommandService clubRegistrationCommandService; + private final ClubLentQueryService clubLentQueryService; + private final UserQueryService userQueryService; + private final CabinetCommandService cabinetCommandService; + + private final ClubPolicyService clubPolicyService; + + private final ClubMapper clubMapper; + + + /** + * 동아리 정보를 조회한다. + * + * @param userId 사용자 ID + * @param clubId 동아리 ID + * @param pageable 페이징 정보 + * @return 동아리 정보 + */ + @Transactional(readOnly = true) + public ClubInfoResponseDto getClubInfo(Long userId, Long clubId, Pageable pageable) { + Club club = clubQueryService.getClubWithClubRegistration(clubId); + List clubUserIds = club.getClubRegistrations().stream() + .filter(cr -> cr.getDeletedAt() == null) + .map(ClubRegistration::getUserId).collect(Collectors.toList()); + Long clubMasterId = club.getClubRegistrations().stream() + .filter(cr -> cr.getUserRole().equals(CLUB_ADMIN)) + .map(ClubRegistration::getUserId).findFirst() + .orElseThrow(ExceptionStatus.INVALID_CLUB_MASTER::asServiceException); + + clubPolicyService.verifyClubUserIn(clubUserIds, userId); + + Page userMap = userQueryService.findUsers(clubUserIds, pageable); + User clubMaster = + clubRegistrationQueryService.getClubUserByUser(clubMasterId, clubId).getUser(); + ClubUserResponseDto clubMasterDto = + clubMapper.toClubUserResponseDto(clubMaster.getId(), clubMaster.getName()); + Cabinet clubCabinet = + clubLentQueryService.getActiveLentHistoryWithCabinet(clubId).getCabinet(); + + List clubUsers = userMap.stream() + .map(user -> clubMapper.toClubUserResponseDto(user.getId(), user.getName())) + .collect(Collectors.toList()); + return clubMapper.toClubInfoResponseDto(club.getName(), clubMasterDto, + club.getNotice(), clubCabinet, clubUsers, userMap.getTotalElements()); + } + + /** + * 내가 속한 동아리 목록을 조회한다. + * + * @param userId 사용자 ID + * @return 내가 속한 동아리 목록 + */ + @Transactional(readOnly = true) + public ClubInfoPaginationDto getMyClubs(Long userId) { + List usersWithClub = + clubRegistrationQueryService.findClubUsersWithClubByUser(userId); + List clubIds = usersWithClub.stream().map(ClubRegistration::getClubId) + .collect(Collectors.toList()); + Map> clubMasterMap = + clubRegistrationQueryService.findClubUsersByClubs(clubIds) + .stream().collect(Collectors.groupingBy(ClubRegistration::getClubId)); + + List result = usersWithClub.stream().map(cr -> { + Long clubId = cr.getClubId(); + String clubName = cr.getClub().getName(); + String clubMasterName = clubMasterMap.get(clubId).stream() + .filter(c -> c.getUserRole().equals(CLUB_ADMIN)) + .map(c -> c.getUser().getName()).findFirst().orElse(null); + return clubMapper.toClubInfoDto(clubId, clubName, clubMasterName); + }).collect(Collectors.toList()); + return clubMapper.toClubInfoPaginationDto(result, (long) result.size()); + } + + /** + * 동아리에 사용자를 추가한다. + * + * @param masterId 동아리 마스터 ID + * @param clubId 동아리 ID + * @param name 추가할 사용자 이름 + */ + public void addClubUser(Long masterId, Long clubId, String name) { + User clubMaster = userQueryService.getUser(masterId); + ClubRegistration clubMasterRegistration = + clubRegistrationQueryService.getClubUserByUser(masterId, clubId); + User newClubUser = userQueryService.getUserByName(name); + List clubUserIds = clubRegistrationQueryService.findClubUsersByClub(clubId) + .stream().map(ClubRegistration::getUserId).collect(Collectors.toList()); + + clubPolicyService.verifyClubUserIn(clubUserIds, clubMaster.getId()); + clubPolicyService.verifyClubUserNotIn(clubUserIds, newClubUser.getId()); + clubPolicyService.verifyClubMaster(clubMasterRegistration.getUserRole(), + clubMasterRegistration.getClubId(), clubId); + + ClubRegistration clubRegistration = ClubRegistration.of(newClubUser.getId(), clubId, + UserRole.CLUB); + clubRegistrationCommandService.addNewClubUser(clubRegistration); + } + + /** + * 동아리에서 사용자를 제거한다. + * + * @param masterId 동아리 마스터 ID + * @param clubId 동아리 ID + * @param deletedUserId 제거할 사용자 ID + */ + public void deleteClubUser(Long masterId, Long clubId, Long deletedUserId) { + User clubMaster = userQueryService.getUser(masterId); + userQueryService.getUser(deletedUserId); + ClubRegistration clubMasterRegistration = + clubRegistrationQueryService.getClubUserByUser(masterId, clubId); + ClubRegistration deletedUserRegistration = + clubRegistrationQueryService.getClubUserByUser(deletedUserId, clubId); + List clubUserIds = clubRegistrationQueryService.findClubUsersByClub(clubId) + .stream().map(ClubRegistration::getUserId).collect(Collectors.toList()); + + clubPolicyService.verifyClubUserIn(clubUserIds, clubMaster.getId()); + clubPolicyService.verifyClubUserIn(clubUserIds, deletedUserId); + clubPolicyService.verifyClubMaster(clubMasterRegistration.getUserRole(), + clubMasterRegistration.getClubId(), clubId); + + clubRegistrationCommandService.deleteClubUser(deletedUserRegistration); + } + + /** + * 동아리 마스터를 위임한다. + * + * @param clubMasterId 동아리 마스터 ID + * @param clubId 동아리 ID + * @param newClubMasterName 새로운 동아리 마스터 이름 + */ + public void mandateClubUser(Long clubMasterId, Long clubId, String newClubMasterName) { + User newClubMaster = userQueryService.getUserByName(newClubMasterName); + + ClubRegistration oldClubMasterRegistration = + clubRegistrationQueryService.getClubUserByUser(clubMasterId, clubId); + ClubRegistration newClubMasterRegistration = + clubRegistrationQueryService.getClubUserByUser(newClubMaster.getId(), clubId); + List clubUserIds = clubRegistrationQueryService.findClubUsersByClub(clubId) + .stream().map(ClubRegistration::getUserId).collect(Collectors.toList()); + + clubPolicyService.verifyClubUserIn(clubUserIds, newClubMaster.getId()); + clubPolicyService.verifyClubUserIn(clubUserIds, clubMasterId); + clubPolicyService.verifyClubMaster(oldClubMasterRegistration.getUserRole(), + oldClubMasterRegistration.getClubId(), clubId); + + clubRegistrationCommandService.mandateClubMaster(oldClubMasterRegistration, + newClubMasterRegistration); + } + + @Transactional + public void updateClubNotice(Long userId, Long clubId, String notice) { + ClubRegistration clubMasterRegistration + = clubRegistrationQueryService.getClubUserByUser(userId, clubId); + Club club = clubQueryService.getClub(clubId); + + clubPolicyService.verifyClubMaster(clubMasterRegistration.getUserRole(), + clubMasterRegistration.getClubId(), clubId); + + clubCommandService.changeClubNotice(club, notice); + } + + @Transactional + public void updateClubMemo(Long userId, Long clubId, String memo) { + ClubRegistration clubMasterRegistration + = clubRegistrationQueryService.getClubUserByUser(userId, clubId); + Club club = clubQueryService.getClub(clubId); + + clubPolicyService.verifyClubMaster(clubMasterRegistration.getUserRole(), + clubMasterRegistration.getClubId(), club.getId()); + + ClubLentHistory clubLentHistory = + clubLentQueryService.getActiveLentHistoryWithCabinet(clubId); + cabinetCommandService.updateMemo(clubLentHistory.getCabinet(), memo); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubPolicyService.java new file mode 100644 index 000000000..0b28c52a1 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubPolicyService.java @@ -0,0 +1,55 @@ +package org.ftclub.cabinet.club.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.user.domain.UserRole; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ClubPolicyService { + + /** + * 동아리 마스터 권한을 검증한다. + * + * @param masterRole 동아리 마스터 권한 + * @param clubId 마스터가 속한 동아리 ID + * @param inputClubId 입력받은 동아리 ID + */ + public void verifyClubMaster(UserRole masterRole, Long clubId, Long inputClubId) { + if (!masterRole.equals(UserRole.CLUB_ADMIN)) { + throw ExceptionStatus.NOT_CLUB_MASTER.asServiceException(); + } + if (!clubId.equals(inputClubId)) { + throw ExceptionStatus.INVALID_CLUB.asServiceException(); + } + } + + /** + * 동아리에 속한 사용자인지 검증한다. + * + * @param clubUserIds 동아리에 속한 사용자 ID 목록 + * @param userId 사용자 ID + */ + public void verifyClubUserIn(List clubUserIds, Long userId) { + if (!clubUserIds.contains(userId)) { + throw ExceptionStatus.NOT_CLUB_USER.asServiceException(); + } + } + + /** + * 동아리에 속해있지 않은 사용자인지 검증한다. + * + * @param clubUserIds 동아리에 속한 사용자 ID 목록 + * @param userId 사용자 ID + */ + public void verifyClubUserNotIn(List clubUserIds, Long userId) { + if (clubUserIds.contains(userId)) { + throw ExceptionStatus.USER_ALREADY_EXISTED.asServiceException(); + } + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubQueryService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubQueryService.java new file mode 100644 index 000000000..50553e94d --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubQueryService.java @@ -0,0 +1,55 @@ +package org.ftclub.cabinet.club.service; + +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.club.domain.Club; +import org.ftclub.cabinet.club.repository.ClubRepository; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ClubQueryService { + + private final ClubRepository clubRepository; + + /** + * 모든 동아리 목록을 조회한다. + * + * @param pageable 페이징 정보 + * @return 모든 동아리 목록 + */ + public Page findAllActiveClubs(Pageable pageable) { + return clubRepository.findPaginationByDeletedAtIsNull(pageable); + } + + /** + * 특정 동아리 정보를 조회한다. + * + * @param clubId 동아리 ID + * @return 동아리 정보 + */ + public Club getClub(Long clubId) { + Optional club = clubRepository.findById(clubId); + return club.orElseThrow(ExceptionStatus.NOT_FOUND_CLUB::asServiceException); + } + + /** + * 특정 동아리 정보를 조회한다. + *

+ * ClubRegistration도 Join 연산으로 함께 조회한다. + *

+ * + * @param clubId 동아리 ID + * @return 동아리 정보 + */ + public Club getClubWithClubRegistration(Long clubId) { + Optional club = clubRepository.findByIdAndDeletedAtIsNull(clubId); + return club.orElseThrow(ExceptionStatus.NOT_FOUND_CLUB::asServiceException); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationCommandService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationCommandService.java new file mode 100644 index 000000000..e5c272555 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationCommandService.java @@ -0,0 +1,48 @@ +package org.ftclub.cabinet.club.service; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.club.domain.ClubRegistration; +import org.ftclub.cabinet.club.repository.ClubRegistrationRepoitory; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.user.domain.UserRole; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ClubRegistrationCommandService { + + private final ClubRegistrationRepoitory clubRegistrationRepoitory; + + /** + * 동아리 회원을 추가한다. + * + * @param clubRegistration 동아리 회원 정보 + * @return 동아리 회원 정보 + */ + public ClubRegistration addNewClubUser(ClubRegistration clubRegistration) { + return clubRegistrationRepoitory.save(clubRegistration); + } + + /** + * 동아리 회원을 삭제한다. + * + * @param clubRegistration 동아리 회원 정보 + */ + public void deleteClubUser(ClubRegistration clubRegistration) { + clubRegistration.delete(); + } + + /** + * 동아리장을 위임한다. + * + * @param oldClubRegistration 기존 동아리장 정보 + * @param newClubRegistration 새 동아리장 정보 + */ + public void mandateClubMaster(ClubRegistration oldClubRegistration, + ClubRegistration newClubRegistration) { + oldClubRegistration.changeUserRole(UserRole.CLUB); + newClubRegistration.changeUserRole(UserRole.CLUB_ADMIN); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationQueryService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationQueryService.java new file mode 100644 index 000000000..3bc38ec8f --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubRegistrationQueryService.java @@ -0,0 +1,96 @@ +package org.ftclub.cabinet.club.service; + + +import static org.ftclub.cabinet.user.domain.UserRole.CLUB_ADMIN; + +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.club.domain.ClubRegistration; +import org.ftclub.cabinet.club.repository.ClubRegistrationRepoitory; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ClubRegistrationQueryService { + + private final ClubRegistrationRepoitory clubRegistrationRepoitory; + + /** + * 특정 동아리에 속한 동아리 회원 목록을 조회한다. + * + * @param clubId 동아리 ID + * @return 동아리 회원 목록 + */ + public List findClubUsersByClub(Long clubId) { + return clubRegistrationRepoitory.findAllByClubIdAndDeletedAtIsNull(clubId); + } + + /** + * 특정 유저가 속한 동아리 목록을 조회한다. + * + * @param userId 유저 ID + * @return 동아리 회원 목록 + */ + public ClubRegistration getClubUserByUser(Long userId, Long clubId) { + Optional clubRegistration = + clubRegistrationRepoitory.findByUserIdAndClubIdAndDeletedAtIsNull(userId, clubId); + return clubRegistration.orElseThrow(ExceptionStatus.NOT_CLUB_USER::asServiceException); + } + + /** + * 특정 유저가 속한 동아리 목록을 조회한다. + *

+ * Club도 Join 연산으로 함께 조회한다. + *

+ * + * @param userId 유저 ID + * @return 동아리 회원 목록 + */ + public List findClubUsersWithClubByUser(Long userId) { + return clubRegistrationRepoitory.findAllByUserIdJoinClub(userId); + } + + /** + * 특정 동아리에 속한 동아리 회원 목록을 조회한다. + *

+ * User도 Join 연산으로 함께 조회한다. + *

+ * + * @param clubIds 동아리 ID 목록 + * @return 동아리 회원 목록 + */ + public List findClubUsersByClubs(List clubIds) { + return clubRegistrationRepoitory.findAllByClubIdInJoinUser(clubIds); + } + + /** + * 특정 유저가 속한 특정 동아리의 동아리 회원 정보를 조회한다. + * + * @param userId 유저 ID + * @param clubId 동아리 ID + * @return 동아리 회원 목록 + */ + public ClubRegistration getClubUser(Long userId, Long clubId) { + return clubRegistrationRepoitory.findByClubIdAndUserId(clubId, userId) + .orElseThrow(ExceptionStatus.NOT_CLUB_USER::asServiceException); + } + + /** + * 특정 동아리의 동아리장 회원 정보를 조회한다. + *

+ * User도 Join 연산으로 함께 조회한다. + *

+ * + * @param clubId 동아리 ID + * @return 동아리 회원 목록 + */ + public ClubRegistration getClubMasterByClubWithUser(Long clubId) { + return clubRegistrationRepoitory.findByClubIdAndUserRoleJoinUser(clubId, CLUB_ADMIN) + .orElseThrow(ExceptionStatus.NOT_CLUB_MASTER::asServiceException); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/AddClubUserRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/AddClubUserRequestDto.java new file mode 100644 index 000000000..d8b9c60a5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/AddClubUserRequestDto.java @@ -0,0 +1,17 @@ +package org.ftclub.cabinet.dto; + +import javax.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class AddClubUserRequestDto { + + @NotBlank + private String name; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ClubCreateDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ClubCreateDto.java new file mode 100644 index 000000000..86ab4b519 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ClubCreateDto.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.dto; + +import javax.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.hibernate.validator.constraints.Length; + +@ToString +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ClubCreateDto { + + @NotBlank + @Length(min = 1, max = 30) + private String clubName; + @NotBlank + private String clubMaster; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetClubStatusRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ClubDeleteDto.java similarity index 58% rename from backend/src/main/java/org/ftclub/cabinet/dto/CabinetClubStatusRequestDto.java rename to backend/src/main/java/org/ftclub/cabinet/dto/ClubDeleteDto.java index 92848ff63..5c5e3fd66 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/CabinetClubStatusRequestDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ClubDeleteDto.java @@ -1,18 +1,18 @@ package org.ftclub.cabinet.dto; + import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.ToString; -@AllArgsConstructor -@ToString @Getter -public class CabinetClubStatusRequestDto { +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class ClubDeleteDto { - private final Long userId; - @NotNull - private final Long cabinetId; @NotNull - private final String statusNote; + private Long clubId; } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoDto.java new file mode 100644 index 000000000..d5322e8d8 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoDto.java @@ -0,0 +1,14 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class ClubInfoDto { + + private final Long clubId; + private final String clubName; + private final String clubMaster; +} + diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoPaginationDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoPaginationDto.java new file mode 100644 index 000000000..d136f560d --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoPaginationDto.java @@ -0,0 +1,13 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class ClubInfoPaginationDto { + + private final List result; //클럽 정보 + private final Long totalLength; //총 개수 +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoResponseDto.java new file mode 100644 index 000000000..edd7db7e6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ClubInfoResponseDto.java @@ -0,0 +1,24 @@ +package org.ftclub.cabinet.dto; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; +import org.ftclub.cabinet.cabinet.domain.Location; + +@Getter +@ToString +@AllArgsConstructor +public class ClubInfoResponseDto { + + private final String clubName; + private final ClubUserResponseDto clubMaster; + private final String clubMemo; // cabinet password + private final String clubNotice; + @JsonUnwrapped + private final Location location; + private final Long visibleNum; + private final List clubUsers; + private final Long clubUserCount; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ClubMemoUpdateDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ClubMemoUpdateDto.java new file mode 100644 index 000000000..7cb148fe4 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ClubMemoUpdateDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class ClubMemoUpdateDto { + + String memo; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ClubNoticeUpdateDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ClubNoticeUpdateDto.java new file mode 100644 index 000000000..8709cafe1 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ClubNoticeUpdateDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class ClubNoticeUpdateDto { + + private String notice; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ClubUpdateRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ClubUpdateRequestDto.java new file mode 100644 index 000000000..b311e92aa --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ClubUpdateRequestDto.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.dto; + +import javax.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.hibernate.validator.constraints.Length; + +@Getter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class ClubUpdateRequestDto { + + @NotBlank + @Length(min = 1, max = 30) + private String clubName; + @NotBlank + private String clubMaster; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ClubUserResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ClubUserResponseDto.java new file mode 100644 index 000000000..cc1cac524 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ClubUserResponseDto.java @@ -0,0 +1,14 @@ +package org.ftclub.cabinet.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +public class ClubUserResponseDto { + + private final Long userId; + private final String userName; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java index 1896b95ec..13081a585 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/LentExtensionResponseDto.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.dto; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -15,7 +16,6 @@ public class LentExtensionResponseDto { private long lentExtensionId; private String name; private int extensionPeriod; - // 추후에 프론트랑 의논 후 expiredAt의 타입을 다시 LocalDateTime으로 변경해야 함 - private String expiredAt; + private LocalDateTime expiredAt; private LentExtensionType lentExtensionType; } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/MandateClubMasterRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/MandateClubMasterRequestDto.java new file mode 100644 index 000000000..22018a8d5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/MandateClubMasterRequestDto.java @@ -0,0 +1,17 @@ +package org.ftclub.cabinet.dto; + +import javax.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class MandateClubMasterRequestDto { + + @NotBlank + private String clubMaster; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java index 36e50e8f3..b8606da4c 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/UserVerifyRequestDto.java @@ -6,13 +6,12 @@ import lombok.Getter; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.UserRole; @Getter @AllArgsConstructor public class UserVerifyRequestDto { - private UserRole userRole; + // private UserRole userRole; private LocalDateTime blackholedAt; private int lentCount; private Long cabinetId; diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index c5fad8886..6cf1251a9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -16,7 +16,9 @@ public enum ExceptionStatus { NOT_FOUND_ADMIN(HttpStatus.NOT_FOUND, "어드민이 존재하지 않습니다"), NOT_FOUND_CABINET(HttpStatus.NOT_FOUND, "사물함이 존재하지 않습니다."), NOT_FOUND_LENT_HISTORY(HttpStatus.NOT_FOUND, "대여한 사물함이 존재하지 않습니다."), + NOT_FOUND_CLUB(HttpStatus.NOT_FOUND, "동아리가 존재하지 않습니다."), LENT_CLUB(HttpStatus.I_AM_A_TEAPOT, "동아리 전용 사물함입니다"), + LENT_NOT_CLUB(HttpStatus.I_AM_A_TEAPOT, "동아리 전용 사물함이 아닙니다"), LENT_EXPIRE_IMMINENT(HttpStatus.I_AM_A_TEAPOT, "만료가 임박한 공유 사물함입니다\n해당 사물함은 대여할 수 없습니다"), LENT_FULL(HttpStatus.CONFLICT, "사물함에 잔여 자리가 없습니다"), LENT_EXPIRED(HttpStatus.FORBIDDEN, "연체된 사물함은 대여할 수 없습니다"), @@ -63,7 +65,12 @@ public enum ExceptionStatus { SWAP_EXPIRE_IMMINENT(HttpStatus.I_AM_A_TEAPOT, "현재 사물함의 대여 기간의 만료가 임박해 사물함을 이동 할 수 없습니다."), SWAP_LIMIT_EXCEEDED(HttpStatus.I_AM_A_TEAPOT, "사물함 이사 횟수 제한을 초과했습니다.\n 일주일에 1회만 이사할 수 있습니다."), SWAP_RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, "이사하기 기능을 사용한 기록이 없습니다."), - SWAP_SAME_CABINET(HttpStatus.BAD_REQUEST, "같은 사물함으로 이사할 수 없습니다."); + SWAP_SAME_CABINET(HttpStatus.BAD_REQUEST, "같은 사물함으로 이사할 수 없습니다."), + INVALID_CLUB(HttpStatus.BAD_REQUEST, "동아리가 맞지 않습니다."), + NOT_CLUB_MASTER(HttpStatus.BAD_REQUEST, "동아리 장이 아닙니다."), + INVALID_CLUB_MASTER(HttpStatus.BAD_REQUEST, "동아리에 동아리 장이 없습니다."), + NOT_FOUND_CLUB_LENT_HISTORY(HttpStatus.NOT_FOUND, "동아리가 대여한 사물함이 없습니다."), + ; final private int statusCode; final private String message; diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyStatus.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyStatus.java index 85122a7c9..2cc8af857 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentPolicyStatus.java @@ -56,6 +56,6 @@ BLACKHOLED_USER, SWAP_EXPIREDAT_IMMINENT, INVALID_LENT_TYPE, INVALID_ARGUMENT, - INVALID_EXPIREDAT, SWAP_SAME_CABINET, SWAP_LIMIT_EXCEEDED, + INVALID_EXPIREDAT, SWAP_SAME_CABINET, SWAP_LIMIT_EXCEEDED, LENT_NOT_CLUB, } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/ClubLentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/ClubLentRepository.java new file mode 100644 index 000000000..761b71e79 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/ClubLentRepository.java @@ -0,0 +1,42 @@ +package org.ftclub.cabinet.lent.repository; + +import java.util.List; +import java.util.Optional; +import org.ftclub.cabinet.club.domain.ClubLentHistory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface ClubLentRepository extends JpaRepository { + + @Query("SELECT clh " + + "FROM ClubLentHistory clh " + + "LEFT JOIN FETCH clh.club " + + "WHERE clh.endedAt IS NULL ") + List findAllByEndedAtIsNullJoinClub(); + + @Query("SELECT clh " + + "FROM ClubLentHistory clh " + + "LEFT JOIN FETCH clh.club " + + "WHERE clh.cabinetId = :cabinetId " + + "AND clh.endedAt IS NULL ") + Optional findByCabinetIdAndEndedAtIsNullJoinClub(Long cabinetId); + + @Query("SELECT clh " + + "FROM ClubLentHistory clh " + + "LEFT JOIN FETCH clh.cabinet c " + + "LEFT JOIN FETCH c.cabinetPlace cp " + + "WHERE clh.clubId = :clubId " + + "AND clh.endedAt IS NULL ") + Optional findByEndedAtIsNullJoinCabinet(Long clubId); + + Optional findByClubIdAndCabinetIdAndEndedAtIsNull(Long clubId, Long cabinetId); + + @Query("SELECT clh " + + "FROM ClubLentHistory clh " + + "LEFT JOIN FETCH clh.club " + + "WHERE clh.cabinetId IN :cabinetIds " + + "AND clh.endedAt IS NULL ") + Optional> findByEndedAtIsNullJoinCabinets(List cabinetIds); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java index 092c70ed0..c335511ca 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRedis.java @@ -24,8 +24,10 @@ public class LentRedis { private static final String USER_ENTERED = "entered"; private static final String USER_SWAPPED = "swapped"; + private static final String SHADOW_KEY_SUFFIX = ":shadow"; - private static final String VALUE_KEY_SUFFIX = ":user"; + private static final String CABINET_KEY_SUFFIX = ":cabinetSession"; + private static final String VALUE_KEY_SUFFIX = ":userSession"; private static final String PREVIOUS_USER_SUFFIX = ":previousUser"; private static final String SWAP_KEY_SUFFIX = ":swap"; @@ -65,14 +67,15 @@ public LentRedis(RedisTemplate valueHashRedisTemplate, */ public void attemptJoinCabinet(String cabinetId, String userId, String shareCode) { String savedCode = shadowKeyTemplate.opsForValue().get(cabinetId + SHADOW_KEY_SUFFIX); + String cabinetKey = cabinetId + CABINET_KEY_SUFFIX; if (Objects.equals(savedCode, shareCode)) { - shareCabinetTemplate.put(cabinetId, userId, USER_ENTERED); + shareCabinetTemplate.put(cabinetKey, userId, USER_ENTERED); userCabinetTemplate.set(userId + VALUE_KEY_SUFFIX, cabinetId); } else { - if (shareCabinetTemplate.hasKey(cabinetId, userId)) { - shareCabinetTemplate.increment(cabinetId, userId, 1L); + if (shareCabinetTemplate.hasKey(cabinetKey, userId)) { + shareCabinetTemplate.increment(cabinetKey, userId, 1L); } else { - shareCabinetTemplate.put(cabinetId, userId, "1"); + shareCabinetTemplate.put(cabinetKey, userId, "1"); } throw ExceptionStatus.WRONG_SHARE_CODE.asServiceException(); } @@ -86,7 +89,7 @@ public void attemptJoinCabinet(String cabinetId, String userId, String shareCode * @return 유저가 공유 사물함 세션에 있는지 여부 */ public boolean isUserInCabinet(String cabinetId, String userId) { - return shareCabinetTemplate.hasKey(cabinetId, userId); + return shareCabinetTemplate.hasKey(cabinetId + CABINET_KEY_SUFFIX, userId); } /** @@ -96,8 +99,9 @@ public boolean isUserInCabinet(String cabinetId, String userId) { * @return 공유 사물함 세션에 참여 중인 유저 수 */ public Long countUserInCabinet(String cabinetId) { - Collection joinUsers = shareCabinetTemplate.entries(cabinetId).values(); - return joinUsers.parallelStream() + String cabinetKey = cabinetId + CABINET_KEY_SUFFIX; + Collection joinUsers = shareCabinetTemplate.entries(cabinetKey).values(); + return joinUsers.stream() .filter(value -> Objects.nonNull(value) && value.equals(USER_ENTERED)).count(); } @@ -109,7 +113,7 @@ public Long countUserInCabinet(String cabinetId) { * @return 대여 시도 횟수 */ public String getAttemptCountInCabinet(String cabinetId, String userId) { - return shareCabinetTemplate.get(cabinetId, userId); + return shareCabinetTemplate.get(cabinetId + CABINET_KEY_SUFFIX, userId); } /** @@ -164,7 +168,7 @@ public void deleteShadowKey(String cabinetId) { * @param userId 삭제할 user id */ public void deleteUserInCabinet(String cabinetId, String userId) { - shareCabinetTemplate.delete(cabinetId, userId); + shareCabinetTemplate.delete(cabinetId + CABINET_KEY_SUFFIX, userId); userCabinetTemplate.getOperations().delete(userId + VALUE_KEY_SUFFIX); } @@ -174,7 +178,7 @@ public void deleteUserInCabinet(String cabinetId, String userId) { * @param cabinetId 삭제할 공유 사물함 cabinet id */ public void deleteCabinet(String cabinetId) { - shareCabinetTemplate.getOperations().delete(cabinetId); + shareCabinetTemplate.getOperations().delete(cabinetId + CABINET_KEY_SUFFIX); } /** @@ -203,7 +207,7 @@ public String findCabinetByUser(String userId) { * @return 공유 사물함에 참여 중인 유저 id */ public List getAllUserInCabinet(String cabinetId) { - Map entries = shareCabinetTemplate.entries(cabinetId); + Map entries = shareCabinetTemplate.entries(cabinetId + CABINET_KEY_SUFFIX); return entries.entrySet().stream() .filter(entry -> entry.getValue().equals(USER_ENTERED)) .map(Map.Entry::getKey).collect(Collectors.toList()); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java index 21105e2f4..b4ba806bc 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/LentRepository.java @@ -173,8 +173,8 @@ int countReturnFromStartDateToEndDate(@Param("startDate") LocalDateTime startDat + "FROM LentHistory lh " + "LEFT JOIN FETCH lh.user u " + "LEFT JOIN FETCH lh.cabinet c " - + "WHERE lh.cabinetId = :cabinetId " - + "ORDER BY lh.startedAt DESC", + + "LEFT JOIN FETCH c.cabinetPlace cp " + + "WHERE lh.cabinetId = :cabinetId ", countQuery = "SELECT count(lh) " + "FROM LentHistory lh " + "WHERE lh.cabinetId = :cabinetId ") @@ -290,6 +290,22 @@ List findByUserIdsAndEndedAtIsNullJoinCabinet( */ List findAllByCabinetIdIn(List cabinetIds); + /** + * 특정 사물함들의 대여 기록을 가져옵니다. + *

+ * cabinet 정보를 Join하여 가져옵니다. + *

+ * + * @param cabinetId 찾으려는 cabinet id + * @return {@link LentHistory}의 {@link List} + */ + @Query("SELECT lh " + + "FROM LentHistory lh " + + "LEFT JOIN FETCH lh.cabinet c " + + "LEFT JOIN FETCH c.cabinetPlace cp " + + "WHERE lh.cabinetId = :cabinetId") + List findAllByCabinetIdJoinCabinet(Long cabinetId); + /** * 연체되어 있는 사물함을 모두 가져옵니다. *

diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentCommandService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentCommandService.java new file mode 100644 index 000000000..12216021e --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentCommandService.java @@ -0,0 +1,26 @@ +package org.ftclub.cabinet.lent.service; + +import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.club.domain.ClubLentHistory; +import org.ftclub.cabinet.lent.repository.ClubLentRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ClubLentCommandService { + + private final ClubLentRepository clubLentRepository; + + public void startLent(Long clubId, Long cabinetId, LocalDateTime expiredAt) { + ClubLentHistory clubLent = ClubLentHistory.of(clubId, cabinetId, expiredAt); + clubLentRepository.save(clubLent); + } + + public void endLent(ClubLentHistory clubLentHistory, LocalDateTime now) { + clubLentHistory.endLent(now); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentQueryService.java new file mode 100644 index 000000000..231d02a94 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentQueryService.java @@ -0,0 +1,42 @@ +package org.ftclub.cabinet.lent.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.club.domain.ClubLentHistory; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.lent.repository.ClubLentRepository; +import org.ftclub.cabinet.log.LogLevel; +import org.ftclub.cabinet.log.Logging; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Logging(level = LogLevel.DEBUG) +public class ClubLentQueryService { + + private final ClubLentRepository clubLentRepository; + + public ClubLentHistory findActiveLentHistoryWithClub(Long cabinetId) { + return clubLentRepository.findByCabinetIdAndEndedAtIsNullJoinClub(cabinetId) + .orElse(null); + } + + public ClubLentHistory getActiveLentHistoryWithCabinet(Long clubId) { + return clubLentRepository.findByEndedAtIsNullJoinCabinet(clubId) + .orElseThrow(ExceptionStatus.NOT_FOUND_CLUB_LENT_HISTORY::asServiceException); + } + + public List findAllActiveLentHistoriesWithClub() { + return clubLentRepository.findAllByEndedAtIsNullJoinClub(); + } + + public List getAllActiveClubLentHistoriesWithCabinets(List cabinetIds) { + return clubLentRepository.findByEndedAtIsNullJoinCabinets(cabinetIds) + .orElseThrow(ExceptionStatus.NOT_FOUND_CLUB_LENT_HISTORY::asServiceException); + } + + public ClubLentHistory getClubActiveLentHistory(Long clubId, Long cabinetId) { + return clubLentRepository.findByClubIdAndCabinetIdAndEndedAtIsNull(clubId, cabinetId) + .orElseThrow(ExceptionStatus.NOT_FOUND_CLUB_LENT_HISTORY::asServiceException); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index b9f4549f4..11796f6df 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -12,6 +12,9 @@ import org.ftclub.cabinet.cabinet.domain.LentType; import org.ftclub.cabinet.cabinet.service.CabinetCommandService; import org.ftclub.cabinet.cabinet.service.CabinetQueryService; +import org.ftclub.cabinet.club.domain.Club; +import org.ftclub.cabinet.club.domain.ClubLentHistory; +import org.ftclub.cabinet.club.service.ClubQueryService; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.dto.LentHistoryDto; @@ -47,6 +50,9 @@ public class LentFacadeService { private final LentRedisService lentRedisService; private final LentQueryService lentQueryService; private final LentCommandService lentCommandService; + private final ClubQueryService clubQueryService; + private final ClubLentCommandService clubLentCommandService; + private final ClubLentQueryService clubLentQueryService; private final UserQueryService userQueryService; private final CabinetQueryService cabinetQueryService; private final CabinetCommandService cabinetCommandService; @@ -97,7 +103,7 @@ public MyCabinetResponseDto getMyLentInfo(UserSessionDto user) { return null; } List usersInCabinet = lentRedisService.findUsersInCabinet(cabinetId); - List userList = userQueryService.getUsers(usersInCabinet); + List userList = userQueryService.findUsers(usersInCabinet); userActiveCabinet = cabinetQueryService.getCabinet(cabinetId); lentDtoList = userList.stream().map(u -> lentMapper.toLentDto(u, null)) .collect(Collectors.toList()); @@ -156,8 +162,10 @@ public void startLentCabinet(Long userId, Long cabinetId) { userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.PRIVATE); lentPolicyService.verifyUserForLent( - new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, + new UserVerifyRequestDto(user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); +// new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, +// cabinetId, cabinet.getStatus(), banHistories)); lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, LentType.PRIVATE, @@ -194,7 +202,7 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) } lentPolicyService.verifyUserForLent( - new UserVerifyRequestDto(user.getRole(), user.getBlackholedAt(), lentCount, + new UserVerifyRequestDto(user.getBlackholedAt(), lentCount, cabinetId, cabinet.getStatus(), banHistories)); Long attemptCount = lentRedisService.getAttemptCountOnShareCabinet(cabinetId, userId); @@ -225,23 +233,25 @@ public void startLentShareCabinet(Long userId, Long cabinetId, String shareCode) /** * 동아리 사물함 대여 시작 * - * @param userId 사용자 ID + * @param clubId 사용자 ID * @param cabinetId 사물함 ID */ @Transactional - public void startLentClubCabinet(Long userId, Long cabinetId) { + public void startLentClubCabinet(Long clubId, Long cabinetId) { LocalDateTime now = LocalDateTime.now(); - // TODO : userId로 ClubUser 검증 로직 필요(Policy) Cabinet cabinet = cabinetQueryService.getCabinet(cabinetId); + Club club = clubQueryService.getClub(clubId); int userCount = lentQueryService.countCabinetUser(cabinetId); lentPolicyService.verifyCabinetLentCount(cabinet.getLentType(), cabinet.getMaxUser(), userCount); lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.CLUB); - lentPolicyService.verifyCabinetForLent(cabinet.getStatus(), cabinet.getLentType()); + lentPolicyService.verifyCabinetForClubLent(cabinet.getStatus(), cabinet.getLentType()); + LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, cabinet.getLentType(), 1); - lentCommandService.startLent(userId, cabinetId, now, expiredAt); + clubLentCommandService.startLent(clubId, cabinetId, expiredAt); + cabinetCommandService.updateTitle(cabinet, club.getName()); cabinetCommandService.changeUserCount(cabinet, userCount + 1); } @@ -256,8 +266,8 @@ public void startLentClubCabinet(Long userId, Long cabinetId) { @Transactional public void endUserLent(Long userId, String memo) { LocalDateTime now = LocalDateTime.now(); - List lentHistories = lentQueryService.findUserActiveLentHistoriesInCabinetForUpdate( - userId); + List lentHistories = + lentQueryService.findUserActiveLentHistoriesInCabinetForUpdate(userId); if (lentHistories.isEmpty()) { throw ExceptionStatus.NOT_FOUND_LENT_HISTORY.asServiceException(); } @@ -291,18 +301,22 @@ public void endUserLent(Long userId, String memo) { } /** - * 사물함 정보 수정 - *

- * 사물함 제목과 메모를 수정할 수 있습니다. + * 동아리 사물함 대여 종료 * - * @param userId 사용자 ID - * @param title 사물함 제목 - * @param memo 사물함 메모 + * @param clubId 사용자 ID + * @param cabinetId 사물함 ID */ @Transactional - public void updateLentCabinetInfo(Long userId, String title, String memo) { - Cabinet cabinet = cabinetQueryService.getUserActiveCabinetForUpdate(userId); - cabinetCommandService.updateTitle(cabinet, title); + public void endLentClub(Long clubId, Long cabinetId, String memo) { + LocalDateTime now = LocalDateTime.now(); + Cabinet cabinet = cabinetQueryService.getCabinet(cabinetId); + ClubLentHistory clubLentHistory = + clubLentQueryService.getClubActiveLentHistory(clubId, cabinetId); + lentPolicyService.verifyCabinetType(cabinet.getLentType(), LentType.CLUB); + + clubLentCommandService.endLent(clubLentHistory, now); + cabinetCommandService.changeUserCount(cabinet, 0); + cabinetCommandService.changeStatus(cabinet, CabinetStatus.AVAILABLE); cabinetCommandService.updateMemo(cabinet, memo); } @@ -343,6 +357,22 @@ public void shareCabinetSessionExpired(Long cabinetId) { lentRedisService.confirmCabinetSession(cabinetId, usersInCabinetSession); } + /** + * 사물함 정보 수정 + *

+ * 사물함 제목과 메모를 수정할 수 있습니다. + * + * @param userId 사용자 ID + * @param title 사물함 제목 + * @param memo 사물함 메모 + */ + @Transactional + public void updateLentCabinetInfo(Long userId, String title, String memo) { + Cabinet cabinet = cabinetQueryService.getUserActiveCabinetForUpdate(userId); + cabinetCommandService.updateTitle(cabinet, title); + cabinetCommandService.updateMemo(cabinet, memo); + } + /** * 개인 사물함을 스왑합니다. *

@@ -356,7 +386,7 @@ public void swapPrivateCabinet(Long userId, Long newCabinetId) { boolean existSwapRecord = lentRedisService.isExistSwapRecord(userId); LocalDateTime swapExpiredAt = lentRedisService.getSwapExpiredAt(userId); - lentPolicyService.verifySwapable(existSwapRecord, swapExpiredAt); + lentPolicyService.verifySwappable(existSwapRecord, swapExpiredAt); LocalDateTime now = LocalDateTime.now(); LentHistory oldLentHistory = lentQueryService.getUserActiveLentHistoryWithCabinet(userId); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java index d10a69e2e..eeaa438f5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentPolicyService.java @@ -15,7 +15,6 @@ import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.BanType; -import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.utils.DateUtil; import org.springframework.stereotype.Service; @@ -46,6 +45,8 @@ private void handlePolicyStatus(LentPolicyStatus status, LocalDateTime policyDat throw ExceptionStatus.LENT_EXPIRED.asServiceException(); case LENT_CLUB: throw ExceptionStatus.LENT_CLUB.asServiceException(); + case LENT_NOT_CLUB: + throw ExceptionStatus.LENT_NOT_CLUB.asServiceException(); case IMMINENT_EXPIRATION: throw ExceptionStatus.LENT_EXPIRE_IMMINENT.asServiceException(); case INVALID_EXPIREDAT: @@ -94,9 +95,9 @@ private void handlePolicyStatus(LentPolicyStatus status, LocalDateTime policyDat public void verifyUserForLent(UserVerifyRequestDto requestDto) { LocalDateTime now = LocalDateTime.now(); LentPolicyStatus status = LentPolicyStatus.FINE; - if (!requestDto.getUserRole().equals(UserRole.USER)) { - status = LentPolicyStatus.NOT_USER; - } +// if (requestDto.getUserRole().equals(UserRole.CLUB)) { +// status = LentPolicyStatus.NOT_USER; +// } if (requestDto.getLentCount() != 0) { status = LentPolicyStatus.ALREADY_LENT_USER; } @@ -130,21 +131,33 @@ public void verifyUserForLent(UserVerifyRequestDto requestDto) { * @param lentType 대여 타입 */ public void verifyCabinetForLent(CabinetStatus cabinetStatus, LentType lentType) { - LentPolicyStatus status = LentPolicyStatus.FINE; + LentPolicyStatus status = checkCabinetStatus(cabinetStatus); if (lentType.equals(LentType.CLUB)) { status = LentPolicyStatus.LENT_CLUB; } + handlePolicyStatus(status, null); + } + + public void verifyCabinetForClubLent(CabinetStatus cabinetStatus, LentType lentType) { + LentPolicyStatus status = checkCabinetStatus(cabinetStatus); + if (!lentType.equals(LentType.CLUB)) { + status = LentPolicyStatus.LENT_CLUB; + } + handlePolicyStatus(status, null); + } + + private LentPolicyStatus checkCabinetStatus(CabinetStatus cabinetStatus) { switch (cabinetStatus) { case FULL: - status = LentPolicyStatus.FULL_CABINET; + return LentPolicyStatus.FULL_CABINET; case BROKEN: - status = LentPolicyStatus.BROKEN_CABINET; + return LentPolicyStatus.BROKEN_CABINET; case OVERDUE: - status = LentPolicyStatus.OVERDUE_CABINET; + return LentPolicyStatus.OVERDUE_CABINET; case PENDING: - status = LentPolicyStatus.PENDING_CABINET; + return LentPolicyStatus.PENDING_CABINET; } - handlePolicyStatus(status, null); + return LentPolicyStatus.FINE; } /** @@ -197,14 +210,23 @@ public LocalDateTime generateExpirationDate(LocalDateTime now, LentType lentType if (!DateUtil.isToday(now)) { status = LentPolicyStatus.INVALID_ARGUMENT; } - int lentTerm = 0; - if (lentType.equals(LentType.PRIVATE)) { - lentTerm = cabinetProperties.getLentTermPrivate(); - } else if (lentType.equals(LentType.SHARE)) { - lentTerm = cabinetProperties.getLentTermShareBasic() - + cabinetProperties.getLentTermShare() * lentUserCount; + LocalDateTime expiredAt = now; + switch (lentType) { + case PRIVATE: + expiredAt = DateUtil.setLastTime( + now.plusDays(cabinetProperties.getLentTermPrivate())); + break; + case SHARE: + expiredAt = DateUtil.setLastTime( + now.plusDays(cabinetProperties.getLentTermShareBasic() + + (long) cabinetProperties.getLentTermShare() * lentUserCount)); + break; + case CLUB: + expiredAt = DateUtil.getInfinityDate(); + break; + default: + status = LentPolicyStatus.INVALID_ARGUMENT; } - LocalDateTime expiredAt = DateUtil.setLastTime(now.plusDays(lentTerm)); if (DateUtil.isPast(expiredAt)) { status = LentPolicyStatus.INVALID_EXPIREDAT; } @@ -283,7 +305,7 @@ public void verifySelfSwap(Long oldCabinetId, Long newCabinetId) { handlePolicyStatus(status, null); } - public void verifySwapable(boolean existSwapRecord, LocalDateTime swapExpiredAt) { + public void verifySwappable(boolean existSwapRecord, LocalDateTime swapExpiredAt) { if (existSwapRecord) { handlePolicyStatus(LentPolicyStatus.SWAP_LIMIT_EXCEEDED, swapExpiredAt); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java index 696a857c2..442d123a2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentQueryService.java @@ -80,6 +80,10 @@ public List findCabinetLentHistories(List cabinetIds) { return lentRepository.findAllByCabinetIdIn(cabinetIds); } + public List findCabinetLentHistoriesWithCabinet(Long cabinetId) { + return lentRepository.findAllByCabinetIdJoinCabinet(cabinetId); + } + /** * 유저가 지금 빌리고 있는 사물함의 개수를 가져옵니다. * diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/ClubMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/ClubMapper.java new file mode 100644 index 000000000..b890dc2f2 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/ClubMapper.java @@ -0,0 +1,35 @@ +package org.ftclub.cabinet.mapper; + +import java.util.List; +import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.dto.ClubInfoDto; +import org.ftclub.cabinet.dto.ClubInfoPaginationDto; +import org.ftclub.cabinet.dto.ClubInfoResponseDto; +import org.ftclub.cabinet.dto.ClubUserResponseDto; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; +import org.springframework.stereotype.Component; + +@Mapper(componentModel = "spring", + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL, + nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT +) +@Component +public interface ClubMapper { + + ClubMapper INSTANCE = org.mapstruct.factory.Mappers.getMapper(ClubMapper.class); + + ClubInfoDto toClubInfoDto(Long clubId, String clubName, String clubMaster); + + ClubInfoPaginationDto toClubInfoPaginationDto(List result, Long totalLength); + + ClubUserResponseDto toClubUserResponseDto(Long userId, String userName); + + @Mapping(target = "clubMemo", source = "cabinet.memo") + @Mapping(target = "location", source = "cabinet.cabinetPlace.location") + ClubInfoResponseDto toClubInfoResponseDto(String clubName, ClubUserResponseDto clubMaster, + String clubNotice, Cabinet cabinet, + List clubUsers, Long clubUserCount); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java index cb06315ec..3f26df680 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java @@ -4,6 +4,7 @@ import java.util.List; import org.ftclub.cabinet.cabinet.domain.Cabinet; +import org.ftclub.cabinet.club.domain.ClubLentHistory; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.LentDto; import org.ftclub.cabinet.dto.LentHistoryDto; @@ -31,6 +32,11 @@ public interface LentMapper { @Mapping(target = "userId", source = "user.id") LentDto toLentDto(User user, LentHistory lentHistory); + @Mapping(target = "lentHistoryId", source = "clubLentHistory.id") + @Mapping(target = "userId", source = "clubLentHistory.clubId") + @Mapping(target = "name", source = "clubLentHistory.club.name") + LentDto toLentDto(ClubLentHistory clubLentHistory); + @Mapping(target = "userId", source = "lentHistory.userId") @Mapping(target = "cabinetId", source = "cabinet.id") @Mapping(target = "location", source = "cabinet.cabinetPlace.location") @@ -49,5 +55,6 @@ ActiveLentHistoryDto toActiveLentHistoryDto(LentHistory lentHistory, User user, Cabinet cabinet, Boolean isExpired, - Long daysFromExpireDate); + Long daysLeftFromExpireDate + ); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java index 967bfa658..a8742d922 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/controller/UserController.java @@ -11,7 +11,12 @@ import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.UserSession; import org.ftclub.cabinet.user.service.UserFacadeService; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; /** * 유저가 자신의 정보를 확인할 때 사용하는 컨트롤러입니다. @@ -36,19 +41,6 @@ public MyProfileResponseDto getMyProfile(@UserSession UserSessionDto userSession return userFacadeService.getProfile(userSessionDto); } - /** - * 현재 로그인한 유저의 모든 연장권 정보를 리턴합니다. - * - * @param userSessionDto 현재 로그인한 유저의 세션 정보 - * @return {@link LentExtensionPaginationDto} 현재 로그인한 유저의 연장권 정보 - */ - @GetMapping("/me/lent-extensions") - @AuthGuard(level = AuthLevel.USER_ONLY) - public LentExtensionPaginationDto getMyLentExtension( - @UserSession UserSessionDto userSessionDto) { - return userFacadeService.getLentExtensions(userSessionDto); - } - /** * 현재 로그인한 유저의 사용가능한 연장권 정보를 리턴합니다. * @@ -59,7 +51,7 @@ public LentExtensionPaginationDto getMyLentExtension( @AuthGuard(level = AuthLevel.USER_ONLY) public LentExtensionPaginationDto getMyActiveLentExtension( @UserSession UserSessionDto userSessionDto) { - return userFacadeService.getActiveLentExtensionsPage(userSessionDto); + return userFacadeService.getActiveLentExtensions(userSessionDto); } /** @@ -67,7 +59,7 @@ public LentExtensionPaginationDto getMyActiveLentExtension( * * @param userSessionDto 현재 로그인한 유저의 세션 정보 */ - @GetMapping("/me/lent-extensions/use") + @PostMapping("/me/lent-extensions") @AuthGuard(level = AuthLevel.USER_ONLY) public void useLentExtension( @UserSession UserSessionDto userSessionDto) { diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java index 326602b70..0ef8e67d0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/LentExtensions.java @@ -1,17 +1,13 @@ package org.ftclub.cabinet.user.domain; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Getter; import lombok.ToString; import lombok.extern.log4j.Log4j2; -import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; - -@Getter @ToString @Log4j2 @AllArgsConstructor @@ -20,38 +16,39 @@ public class LentExtensions { private final List lentExtensions; - private void filterActiveLentExtensions() { + public boolean isEmpty() { + return lentExtensions == null || lentExtensions.isEmpty(); + } + + public LentExtensions sortLentExtensions() { + lentExtensions.sort(Comparator.comparing(LentExtension::getExpiredAt)); + return this; + } + + public LentExtensions filterActiveLentExtensions() { LocalDateTime currentTime = LocalDateTime.now(); lentExtensions.removeIf(lentExtension -> lentExtension.getUsedAt() != null || lentExtension.isExpiredBefore(currentTime) ); + return this; } - private void sortImminentASC() { - lentExtensions.sort(Comparator.comparing(LentExtension::getExpiredAt)); - } - - public boolean isEmpty() { - return lentExtensions == null || lentExtensions.isEmpty(); - } - - public Optional findImminentActiveLentExtension() { - filterActiveLentExtensions(); - sortImminentASC(); - return Optional.ofNullable(!lentExtensions.isEmpty() ? - lentExtensions.get(0) : null); + public boolean hasActiveLentExtension() { + return !this.isEmpty() + && lentExtensions.parallelStream() + .anyMatch(e -> !e.isExpiredBefore(LocalDateTime.now()) && !e.isUsed()); } - public List getActiveLentExtensions() { - filterActiveLentExtensions(); + public List get() { return lentExtensions; } - public boolean hasActiveLentExtension() { - return !this.isEmpty() - && lentExtensions.parallelStream() - .anyMatch(e -> !e.isExpiredBefore(LocalDateTime.now()) && !e.isUsed()); + public LentExtension getOne() { + if (lentExtensions == null || lentExtensions.isEmpty() || lentExtensions.get(0) == null) { + return null; + } + return lentExtensions.get(0); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java index 0c81acd1f..f0870a694 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/User.java @@ -1,23 +1,26 @@ package org.ftclub.cabinet.user.domain; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.regex.Pattern; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.OneToMany; import javax.persistence.Table; import javax.validation.constraints.Email; import javax.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.ToString; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; +import org.ftclub.cabinet.club.domain.ClubRegistration; import org.ftclub.cabinet.dto.UpdateAlarmRequestDto; import org.ftclub.cabinet.exception.DomainException; import org.ftclub.cabinet.exception.ExceptionStatus; @@ -27,58 +30,54 @@ @Table(name = "USER") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter +@ToString(exclude = {"joinedClubs"}) @Log4j2 public class User { + + @OneToMany(mappedBy = "user") + private final List joinedClubs = new ArrayList<>(); @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ID") private Long id; - @NotNull @Column(name = "NAME", length = 32, unique = true, nullable = false) private String name; - @Email @Column(name = "EMAIL", unique = true) private String email; - @Column(name = "BLACKHOLED_AT") private LocalDateTime blackholedAt = null; - @Column(name = "DELETED_AT", length = 32) private LocalDateTime deletedAt = null; - - @Enumerated(value = EnumType.STRING) - @Column(name = "ROLE", length = 32, nullable = false) - private UserRole role; - + // @Enumerated(value = EnumType.STRING) +// @Column(name = "ROLE", length = 32, nullable = false) +// private UserRole role; @Column(name = "SLACK_ALARM", columnDefinition = "boolean default true") private boolean slackAlarm; - @Column(name = "EMAIL_ALARM", columnDefinition = "boolean default true") private boolean emailAlarm; - @Column(name = "PUSH_ALARM", columnDefinition = "boolean default false") private boolean pushAlarm; - protected User(String name, String email, LocalDateTime blackholedAt, UserRole userRole) { + protected User(String name, String email, LocalDateTime blackholedAt) { this.name = name; this.email = email; this.blackholedAt = blackholedAt; - this.role = userRole; +// this.role = userRole; setDefaultAlarmStatus(); } - public static User of(String name, String email, LocalDateTime blackholedAt, - UserRole userRole) { - User user = new User(name, email, blackholedAt, userRole); + public static User of(String name, String email, LocalDateTime blackholedAt) { + User user = new User(name, email, blackholedAt); ExceptionUtil.throwIfFalse(user.isValid(), new DomainException(ExceptionStatus.INVALID_ARGUMENT)); return user; } + private void setDefaultAlarmStatus() { this.slackAlarm = true; this.emailAlarm = true; @@ -87,8 +86,8 @@ private void setDefaultAlarmStatus() { private boolean isValid() { return name != null && email != null && Pattern.matches( - "^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\\.[A-Za-z0-9\\-]+\\.*[A-Za-z0-9\\-]*", email) - && role != null && role.isValid(); + "^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\\.[A-Za-z0-9\\-]+\\.*[A-Za-z0-9\\-]*", email); +// && role != null && role.isValid(); } @Override @@ -103,9 +102,9 @@ public boolean equals(Object o) { return Objects.equals(id, user.id); } - public boolean isUserRole(UserRole role) { - return role.equals(this.role); - } +// public boolean isUserRole(UserRole role) { +// return role.equals(this.role); +// } public void changeBlackholedAt(LocalDateTime blackholedAt) { log.info("Called changeBlackholedAt - form {} to {}", this.blackholedAt, blackholedAt); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserRole.java b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserRole.java index cda26cc35..f377b3669 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/domain/UserRole.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/domain/UserRole.java @@ -2,9 +2,10 @@ public enum UserRole { - USER, CLUB; - public boolean isValid() { - return this.equals(USER) || this.equals(CLUB); - } + CLUB, CLUB_ADMIN; + + public boolean isValid() { + return this.equals(CLUB) || this.equals(CLUB_ADMIN); + } } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java index aee4045f3..9c22c44dd 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/LentExtensionRepository.java @@ -9,7 +9,11 @@ @Repository public interface LentExtensionRepository extends JpaRepository { - List findAllByUserId(@Param("userId") Long userId); - + /** + * 특정 유저의 사용 가능한 연장권을 모두 가져옵니다. + * + * @param userId 유저의 아이디 + * @return 사용 가능한 연장권 {@link List}. + */ List findAllByUserIdAndUsedAtIsNull(@Param("userId") Long userId); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index c46208836..11e7da9bf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Optional; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -25,8 +24,8 @@ public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.id = :userId AND u.deletedAt IS NULL") Optional findById(@Param("userId") Long userId); - @Query("SELECT u FROM User u WHERE u.id = :userId AND u.role = :role AND u.deletedAt IS NULL") - Optional findByIdAndRole(Long userId, UserRole role); +// @Query("SELECT u FROM User u WHERE u.id = :userId AND u.role = :role AND u.deletedAt IS NULL") +// Optional findByIdAndRole(Long userId, UserRole role); /** * 소프트 딜리트 사용으로 인한 Deprecated @@ -82,7 +81,7 @@ public interface UserRepository extends JpaRepository { /** * */ - Page findAllByRoleAndDeletedAtIsNull(@Param("role") UserRole role, Pageable pageable); +// Page findAllByRoleAndDeletedAtIsNull(@Param("role") UserRole role, Pageable pageable); /** * 블랙홀에 빠질 위험이 있는 유저들의 정보를 조회합니다. blackholedAt이 현재 시간보다 과거인 유저들을 블랙홀에 빠질 위험이 있는 유저로 판단합니다. @@ -123,4 +122,16 @@ public interface UserRepository extends JpaRepository { */ @Query("SELECT u FROM User u WHERE u.name IN :userNames AND u.deletedAt IS NULL") List findAllUsersInNames(List userNames); + + /** + * 유저의 ID 리스트로 유저들을 가져옵니다. + *

+ * 페이지 정보를 포함합니다. + * + * @param userIds 유저 ID 리스트 + * @param pageable 페이지 정보 + * @return {@link User} 리스트 + */ + @Query("SELECT u FROM User u WHERE u.id IN :userIds AND u.deletedAt IS NULL") + Page findPaginationByIds(@Param("userIds") List userIds, Pageable pageable); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java index 347236414..ddebce293 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionQueryService.java @@ -1,8 +1,6 @@ package org.ftclub.cabinet.user.service; -import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.log.LogLevel; @@ -23,42 +21,34 @@ public class LentExtensionQueryService { /** * 유저의 사용 가능한 연장권 중 사용 기한이 가장 임박한 연장권을 가져옵니다. * - * @param userId + * @param userId 유저의 아이디 * @return 사용 기한이 가장 임박한 연장권을 반환합니다. */ public LentExtension findActiveLentExtension(Long userId) { - List lentExtensions = lentExtensionRepository.findAllByUserId(userId); + List activeLentExtensions = + lentExtensionRepository.findAllByUserIdAndUsedAtIsNull(userId); return LentExtensions.builder() - .lentExtensions(lentExtensions) + .lentExtensions(activeLentExtensions) .build() - .findImminentActiveLentExtension() - .orElse(null); + .filterActiveLentExtensions() + .sortLentExtensions() + .getOne(); } /** * 유저의 사용 가능한 연장권을 모두 가져옵니다. * - * @param userId + * @param userId 유저의 아이디 * @return 사용 가능한 연장권을 모두 반환합니다. */ - public LentExtensions findActiveLentExtensions(Long userId) { + public List findActiveLentExtensions(Long userId) { + List activeLentExtensions = + lentExtensionRepository.findAllByUserIdAndUsedAtIsNull(userId); return LentExtensions.builder() - .lentExtensions(lentExtensionRepository.findAllByUserIdAndUsedAtIsNull(userId)) - .build(); - } - - /** - * 유저의 모든 연장권을 사용 기한 기준 최신 순서로 가져옵니다. - * - * @param userId - * @return 사용 기한을 기준으로 최신 순서로 정렬된 모든 연장권을 반환합니다. - */ - public List findLentExtensionsInLatestOrder(Long userId) { - return lentExtensionRepository.findAllByUserId(userId) - .stream() - .sorted(Comparator.comparing(LentExtension::getExpiredAt, - Comparator.reverseOrder())) - .collect(Collectors.toList()); + .lentExtensions(activeLentExtensions) + .build() + .sortLentExtensions() + .get(); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java index de2aafe6e..5da70b657 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java @@ -9,7 +9,6 @@ import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.stereotype.Service; @@ -31,8 +30,9 @@ public User createUserByFtProfile(FtProfile profile) { if (userRepository.existsByNameAndEmail(profile.getIntraName(), profile.getEmail())) { throw ExceptionStatus.USER_ALREADY_EXISTED.asServiceException(); } - User user = User.of(profile.getIntraName(), profile.getEmail(), profile.getBlackHoledAt(), - UserRole.USER); +// User user = User.of(profile.getIntraName(), profile.getEmail(), profile.getBlackHoledAt(), +// UserRole.USER); + User user = User.of(profile.getIntraName(), profile.getEmail(), profile.getBlackHoledAt()); return userRepository.save(user); } @@ -46,7 +46,8 @@ public User createClubUser(String clubName) { if (userRepository.existsByNameAndEmail(clubName, clubName + "@ftclub.org")) { throw ExceptionStatus.EXISTED_CLUB_USER.asServiceException(); } - User user = User.of(clubName, clubName + "@ftclub.org", null, UserRole.CLUB); +// User user = User.of(clubName, clubName + "@ftclub.org", null, UserRole.CLUB); + User user = User.of(clubName, clubName + "@ftclub.org", null); return userRepository.save(user); } @@ -57,9 +58,9 @@ public User createClubUser(String clubName) { * @param clubName 변경할 유저 이름 */ public void updateClubName(User user, String clubName) { - if (!user.isUserRole(UserRole.CLUB)) { - throw ExceptionStatus.NOT_CLUB_USER.asServiceException(); - } +// if (!user.isUserRole(UserRole.CLUB)) { +// throw ExceptionStatus.NOT_CLUB_USER.asServiceException(); +// } user.changeName(clubName); userRepository.save(user); } @@ -80,9 +81,9 @@ public void deleteById(Long userId, LocalDateTime deletedAt) { * @param clubUser 삭제할 동아리 유저 객체 */ public void deleteClubUser(User clubUser) { - if (!clubUser.isUserRole(UserRole.CLUB)) { - throw ExceptionStatus.NOT_CLUB_USER.asServiceException(); - } +// if (!clubUser.isUserRole(UserRole.CLUB)) { +// throw ExceptionStatus.NOT_CLUB_USER.asServiceException(); +// } userRepository.deleteById(clubUser.getId(), LocalDateTime.now()); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java index 3dea61d63..8aa3e22aa 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserFacadeService.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.alarm.dto.AlarmTypeResponseDto; import org.ftclub.cabinet.alarm.fcm.config.FirebaseConfig; import org.ftclub.cabinet.alarm.fcm.service.FCMTokenRedisService; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -24,7 +25,6 @@ import org.ftclub.cabinet.user.domain.BanHistory; import org.ftclub.cabinet.user.domain.LentExtension; import org.ftclub.cabinet.user.domain.LentExtensionPolicy; -import org.ftclub.cabinet.user.domain.LentExtensions; import org.ftclub.cabinet.user.domain.User; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -62,27 +62,11 @@ public MyProfileResponseDto getProfile(UserSessionDto user) { LentExtensionResponseDto lentExtensionResponseDto = userMapper.toLentExtensionResponseDto( lentExtension); User currentUser = userQueryService.getUser(user.getUserId()); - boolean isDeviceTokenExpired = currentUser.getAlarmTypes().isPush() + AlarmTypeResponseDto userAlarmTypes = currentUser.getAlarmTypes(); + boolean isDeviceTokenExpired = userAlarmTypes.isPush() && fcmTokenRedisService.findByUserName(user.getName()).isEmpty(); return userMapper.toMyProfileResponseDto(user, cabinet, banHistory, - lentExtensionResponseDto, currentUser.getAlarmTypes(), isDeviceTokenExpired); - } - - /** - * 유저의 모든 연장권 정보를 가져옵니다. - * - * @param user 유저의 세션 정보 - * @return 유저의 모든 연장권 정보를 반환합니다. - */ - @Transactional(readOnly = true) - public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { - List lentExtensionResponseDtos = lentExtensionQueryService.findLentExtensionsInLatestOrder( - user.getUserId()) - .stream() - .map(userMapper::toLentExtensionResponseDto) - .collect(Collectors.toList()); - return userMapper.toLentExtensionPaginationDto(lentExtensionResponseDtos, - (long) lentExtensionResponseDtos.size()); + lentExtensionResponseDto, userAlarmTypes, isDeviceTokenExpired); } /** @@ -92,16 +76,14 @@ public LentExtensionPaginationDto getLentExtensions(UserSessionDto user) { * @return 유저의 사용 가능한 연장권 정보를 반환합니다. */ @Transactional(readOnly = true) - public LentExtensionPaginationDto getActiveLentExtensionsPage(UserSessionDto user) { - LentExtensions lentExtensions = lentExtensionQueryService.findActiveLentExtensions( - user.getUserId()); - List LentExtensionResponseDtos = lentExtensions.getLentExtensions() - .stream() + public LentExtensionPaginationDto getActiveLentExtensions(UserSessionDto user) { + List lentExtensions = + lentExtensionQueryService.findActiveLentExtensions(user.getUserId()); + List result = lentExtensions.stream() .map(userMapper::toLentExtensionResponseDto) .collect(Collectors.toList()); - return userMapper.toLentExtensionPaginationDto(LentExtensionResponseDtos, - (long) LentExtensionResponseDtos.size()); + return userMapper.toLentExtensionPaginationDto(result, (long) lentExtensions.size()); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java index 8f78f8551..0d4fdec40 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserQueryService.java @@ -8,7 +8,6 @@ import org.ftclub.cabinet.log.LogLevel; import org.ftclub.cabinet.log.Logging; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.user.repository.UserRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -38,10 +37,10 @@ public User getUser(Long userId) { * @param userId 동아리 유저의 ID * @return 동아리 유저 객체를 반환합니다. */ - public User getClubUser(Long userId) { - Optional user = userRepository.findByIdAndRole(userId, UserRole.CLUB); - return user.orElseThrow(ExceptionStatus.NOT_FOUND_USER::asServiceException); - } +// public User getClubUser(Long userId) { +// Optional user = userRepository.findByIdAndRole(userId, UserRole.CLUB); +// return user.orElseThrow(ExceptionStatus.NOT_FOUND_USER::asServiceException); +// } /** * 유저를 가져옵니다. @@ -57,11 +56,22 @@ public User getUserByName(String name) { /** * 유저들을 가져옵니다. * - * @param userIdsInCabinet 유저들의 ID + * @param userIds 유저들의 ID * @return 유저 객체들을 반환합니다. */ - public List getUsers(List userIdsInCabinet) { - return userRepository.findAllByIds(userIdsInCabinet); + public List findUsers(List userIds) { + return userRepository.findAllByIds(userIds); + } + + /** + * 유저들을 가져옵니다. + * + * @param userIds 유저들의 ID + * @param pageable 페이징 정보 + * @return 유저 객체들을 페이지 형식으로 반환합니다. + */ + public Page findUsers(List userIds, Pageable pageable) { + return userRepository.findPaginationByIds(userIds, pageable); } /** @@ -71,7 +81,7 @@ public List getUsers(List userIdsInCabinet) { * @param pageable 페이징 정보 * @return 유저 객체들을 페이지 형식으로 반환합니다. */ - public Page getUsers(String partialName, Pageable pageable) { + public Page findUsers(String partialName, Pageable pageable) { return userRepository.findPaginationByPartialName(partialName, pageable); } @@ -111,9 +121,9 @@ public Optional findUserByName(String name) { * @param pageable 페이징 정보 * @return 동아리 유저 객체들을 페이지 형식으로 반환합니다. */ - public Page findClubUsers(Pageable pageable) { - return userRepository.findAllByRoleAndDeletedAtIsNull(UserRole.CLUB, pageable); - } +// public Page findClubUsers(Pageable pageable) { +// return userRepository.findAllByRoleAndDeletedAtIsNull(UserRole.CLUB, pageable); +// } /** * 블랙홀에 빠질 위험이 있는 유저들을 가져옵니다. blackholedAt이 일주일 이하인 유저들을 블랙홀에 빠질 위험이 있는 유저로 판단합니다. diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/cqrs/DatabaseSynchronizer.java b/backend/src/main/java/org/ftclub/cabinet/utils/cqrs/DatabaseSynchronizer.java new file mode 100644 index 000000000..92b056fd4 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/utils/cqrs/DatabaseSynchronizer.java @@ -0,0 +1,21 @@ +package org.ftclub.cabinet.utils.cqrs; + +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; +import org.ftclub.cabinet.club.service.ClubFacadeService; +import org.ftclub.cabinet.lent.service.LentFacadeService; +import org.ftclub.cabinet.user.service.UserFacadeService; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@EnableScheduling +public class DatabaseSynchronizer { + + private final CabinetFacadeService cabinetFacadeService; + private final LentFacadeService lentFacadeService; + private final UserFacadeService userFacadeService; + private final ClubFacadeService clubFacadeService; + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java index 1ae1b1856..f20bd45a0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java @@ -1,5 +1,9 @@ package org.ftclub.cabinet.utils.release; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; @@ -8,36 +12,22 @@ import org.ftclub.cabinet.cabinet.service.CabinetQueryService; import org.springframework.stereotype.Component; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - @Component @RequiredArgsConstructor @Log4j2 public class ReleaseManager { - private final CabinetQueryService cabinetQueryService; - private final CabinetFacadeService cabinetFacadeService; - - private List getAllPendedYesterdayCabinet() { - return cabinetQueryService.findAllPendingCabinetsByCabinetStatusAndBeforeEndedAt( - CabinetStatus.PENDING, LocalDateTime.from(LocalDate.now().atStartOfDay())); - } - - private void releaseCabinets(List cabinets) { - for (Cabinet cabinet : cabinets) { - releaseCabinet(cabinet); - } - } - - private void releaseCabinet(Cabinet cabinet) { - cabinetFacadeService.updateStatus(cabinet.getId(), CabinetStatus.AVAILABLE); - } + private final CabinetQueryService cabinetQueryService; + private final CabinetFacadeService cabinetFacadeService; + private List getAllPendedYesterdayCabinet() { + return cabinetQueryService.findAllPendingCabinetsByCabinetStatusAndBeforeEndedAt( + CabinetStatus.PENDING, LocalDateTime.from(LocalDate.now().atStartOfDay())); + } - public void releasingCabinets() { - List cabinets = getAllPendedYesterdayCabinet(); - releaseCabinets(cabinets); - } + public void releasingCabinets() { + List cabinets = getAllPendedYesterdayCabinet(); + List cabinetIds = cabinets.stream().map(Cabinet::getId).collect(Collectors.toList()); + cabinetFacadeService.updateStatus(cabinetIds, CabinetStatus.AVAILABLE); + } } diff --git a/backend/src/test/java/org/ftclub/cabinet/mapper/CabinetMapperTest2.java b/backend/src/test/java/org/ftclub/cabinet/mapper/CabinetMapperTest2.java index b9fd37f2b..fc6799fb0 100644 --- a/backend/src/test/java/org/ftclub/cabinet/mapper/CabinetMapperTest2.java +++ b/backend/src/test/java/org/ftclub/cabinet/mapper/CabinetMapperTest2.java @@ -1,19 +1,16 @@ package org.ftclub.cabinet.mapper; -import org.ftclub.cabinet.cabinet.domain.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.dto.CabinetDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; -import org.ftclub.cabinet.utils.DateUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; - -import static org.junit.jupiter.api.Assertions.assertEquals; - class CabinetMapperTest2 { CabinetMapper mapper = CabinetMapper.INSTANCE; @@ -26,17 +23,17 @@ class CabinetMapperTest2 { @BeforeEach void setUp() { // 사물함 - cabinet = Cabinet.of(1, CabinetStatus.AVAILABLE, LentType.PRIVATE, 1, - Grid.of(1, 1), - CabinetPlace.of( - Location.of("building", 1, "section"), - SectionFormation.of(1, 1), - MapArea.of(1, 1, 1, 1))); - // 사용자 - user = User.of("user", "email@email.com", DateUtil.getInfinityDate(), UserRole.USER); - // 대여 기록 - lentHistory = LentHistory.of(LocalDateTime.now(), DateUtil.getInfinityDate(), 1L, - 1L); +// cabinet = Cabinet.of(1, CabinetStatus.AVAILABLE, LentType.PRIVATE, 1, +// Grid.of(1, 1), +// CabinetPlace.of( +// Location.of("building", 1, "section"), +// SectionFormation.of(1, 1), +// MapArea.of(1, 1, 1, 1))); +// // 사용자 +// user = User.of("user", "email@email.com", DateUtil.getInfinityDate(), UserRole.USER); +// // 대여 기록 +// lentHistory = LentHistory.of(LocalDateTime.now(), DateUtil.getInfinityDate(), 1L, +// 1L); } @@ -58,14 +55,14 @@ void setUp() { @DisplayName("CabinetDto null 매핑 성공 - null 프로퍼티를 갖는 Dto 반환") void toCabinetDto_성공_null_값() { CabinetDto dto = mapper.toCabinetDto(null); - assertEquals(null, dto.getCabinetId()); - assertEquals(null, dto.getVisibleNum()); - assertEquals(null, dto.getLocation()); - assertEquals(null, dto.getLentType()); - assertEquals(null, dto.getMaxUser()); - assertEquals(null, dto.getTitle()); - assertEquals(null, dto.getStatus()); - assertEquals(null, dto.getStatusNote()); + assertNull(dto.getCabinetId()); + assertNull(dto.getVisibleNum()); + assertNull(dto.getLocation()); + assertNull(dto.getLentType()); + assertNull(dto.getMaxUser()); + assertNull(dto.getTitle()); + assertNull(dto.getStatus()); + assertNull(dto.getStatusNote()); } @Test diff --git a/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java b/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java index 386f2dd03..c4658aaa3 100644 --- a/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/mapper/LentMapperTest.java @@ -6,19 +6,9 @@ import java.time.LocalDateTime; import org.ftclub.cabinet.cabinet.domain.Cabinet; -import org.ftclub.cabinet.cabinet.domain.CabinetPlace; -import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.ftclub.cabinet.cabinet.domain.Grid; -import org.ftclub.cabinet.cabinet.domain.LentType; -import org.ftclub.cabinet.cabinet.domain.Location; -import org.ftclub.cabinet.cabinet.domain.MapArea; -import org.ftclub.cabinet.cabinet.domain.SectionFormation; -import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.LentDto; -import org.ftclub.cabinet.dto.LentHistoryDto; import org.ftclub.cabinet.lent.domain.LentHistory; import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; @@ -50,55 +40,55 @@ void toLentDto() { @Test void toLentHistoryDto() { - Location location = Location.of("testBuilding", 9, "testSection"); - LentHistory lentHistory = LentHistory.of(LocalDateTime.now(), LocalDateTime.now(), 19L, - 99L); - User user = User.of("testName", "testEmail@testmail.com", LocalDateTime.now(), - UserRole.USER); - Cabinet cabinet = Cabinet.of(1, CabinetStatus.AVAILABLE, LentType.SHARE, 10, Grid.of(1, 2), - CabinetPlace.of(location, SectionFormation.of(1, 2), MapArea.of(1, 1, 1, 1))); - - System.out.println(user); - System.out.println(cabinet); - - LentHistoryDto lentHistoryDto = lentMapper.toLentHistoryDto(lentHistory, user, cabinet); - assertEquals(cabinet.getId(), lentHistoryDto.getCabinetId()); - assertEquals(lentHistory.getUserId(), lentHistoryDto.getUserId()); - assertEquals(cabinet.getVisibleNum(), lentHistoryDto.getVisibleNum()); - assertEquals(user.getName(), lentHistoryDto.getName()); - assertEquals(lentHistory.getStartedAt(), lentHistoryDto.getStartedAt()); - assertEquals(lentHistory.getEndedAt(), lentHistoryDto.getEndedAt()); - assertEquals(location, lentHistoryDto.getLocation()); +// Location location = Location.of("testBuilding", 9, "testSection"); +// LentHistory lentHistory = LentHistory.of(LocalDateTime.now(), LocalDateTime.now(), 19L, +// 99L); +// User user = User.of("testName", "testEmail@testmail.com", LocalDateTime.now(), +// UserRole.USER); +// Cabinet cabinet = Cabinet.of(1, CabinetStatus.AVAILABLE, LentType.SHARE, 10, Grid.of(1, 2), +// CabinetPlace.of(location, SectionFormation.of(1, 2), MapArea.of(1, 1, 1, 1))); +// +// System.out.println(user); +// System.out.println(cabinet); +// +// LentHistoryDto lentHistoryDto = lentMapper.toLentHistoryDto(lentHistory, user, cabinet); +// assertEquals(cabinet.getId(), lentHistoryDto.getCabinetId()); +// assertEquals(lentHistory.getUserId(), lentHistoryDto.getUserId()); +// assertEquals(cabinet.getVisibleNum(), lentHistoryDto.getVisibleNum()); +// assertEquals(user.getName(), lentHistoryDto.getName()); +// assertEquals(lentHistory.getStartedAt(), lentHistoryDto.getStartedAt()); +// assertEquals(lentHistory.getEndedAt(), lentHistoryDto.getEndedAt()); +// assertEquals(location, lentHistoryDto.getLocation()); } @Test void toActiveLentHistoryDto() { - LocalDateTime now = LocalDateTime.now(); - Location location = Location.of("testBuilding", 9, "testSection"); - - LentHistory lentHistory = LentHistory.of(now, now, 19L, - 99L); - User user = User.of("testName", "testEmail@testmail.com", now, - UserRole.USER); - Cabinet cabinet = Cabinet.of(1, CabinetStatus.AVAILABLE, LentType.SHARE, 10, Grid.of(1, 2), - CabinetPlace.of(location, SectionFormation.of(1, 2), MapArea.of(1, 1, 1, 1))); - - System.out.println(user); - System.out.println(cabinet); - - ActiveLentHistoryDto activeLentHistoryDto = lentMapper.toActiveLentHistoryDto( - lentHistory, - user, - cabinet, - lentHistory.isExpired(now), - lentHistory.getDaysUntilExpiration(now)); - - assertEquals(activeLentHistoryDto.getUserId(), lentHistory.getUserId()); - assertEquals(activeLentHistoryDto.getName(), user.getName()); - assertEquals(activeLentHistoryDto.getEmail(), user.getEmail()); - assertEquals(activeLentHistoryDto.getCabinetId(), cabinet.getId()); - assertEquals(activeLentHistoryDto.getIsExpired(), lentHistory.isExpired(now)); - assertEquals(activeLentHistoryDto.getDaysFromExpireDate(), - lentHistory.getDaysUntilExpiration(now)); +// LocalDateTime now = LocalDateTime.now(); +// Location location = Location.of("testBuilding", 9, "testSection"); +// +// LentHistory lentHistory = LentHistory.of(now, now, 19L, +// 99L); +// User user = User.of("testName", "testEmail@testmail.com", now, +// UserRole.USER); +// Cabinet cabinet = Cabinet.of(1, CabinetStatus.AVAILABLE, LentType.SHARE, 10, Grid.of(1, 2), +// CabinetPlace.of(location, SectionFormation.of(1, 2), MapArea.of(1, 1, 1, 1))); +// +// System.out.println(user); +// System.out.println(cabinet); +// +// ActiveLentHistoryDto activeLentHistoryDto = lentMapper.toActiveLentHistoryDto( +// lentHistory, +// user, +// cabinet, +// lentHistory.isExpired(now), +// lentHistory.getDaysUntilExpiration(now)); +// +// assertEquals(activeLentHistoryDto.getUserId(), lentHistory.getUserId()); +// assertEquals(activeLentHistoryDto.getName(), user.getName()); +// assertEquals(activeLentHistoryDto.getEmail(), user.getEmail()); +// assertEquals(activeLentHistoryDto.getCabinetId(), cabinet.getId()); +// assertEquals(activeLentHistoryDto.getIsExpired(), lentHistory.isExpired(now)); +// assertEquals(activeLentHistoryDto.getDaysLeftFromExpireDate(), +// lentHistory.getDaysUntilExpiration(now)); } } \ No newline at end of file diff --git a/backend/src/test/java/org/ftclub/cabinet/mapper/UserSessionMapperTest.java b/backend/src/test/java/org/ftclub/cabinet/mapper/UserSessionMapperTest.java index 23fc53570..e813e074a 100644 --- a/backend/src/test/java/org/ftclub/cabinet/mapper/UserSessionMapperTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/mapper/UserSessionMapperTest.java @@ -1,17 +1,5 @@ package org.ftclub.cabinet.mapper; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.time.LocalDateTime; - -import org.ftclub.cabinet.dto.UserBlockedInfoDto; -import org.ftclub.cabinet.dto.UserProfileDto; -import org.ftclub.cabinet.user.domain.BanHistory; -import org.ftclub.cabinet.user.domain.BanType; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -24,26 +12,26 @@ class UserSessionMapperTest { @Test void toUserBlockedInfoDto() throws Exception { - LocalDateTime now = LocalDateTime.now(); - User user = mock(User.class); - when(user.getId()).thenReturn(3L); - when(user.getName()).thenReturn("intraId"); - when(user.getEmail()).thenReturn("email@test.com"); - when(user.getBlackholedAt()).thenReturn(now); - when(user.getRole()).thenReturn(UserRole.USER); - BanHistory banHistory = BanHistory.of(now, now, BanType.PRIVATE, 3L); - UserBlockedInfoDto userBlockedInfoDto = userMapper.toUserBlockedInfoDto(banHistory, user); - assertEquals(banHistory.getUserId(), userBlockedInfoDto.getUserId()); - assertEquals(user.getName(), userBlockedInfoDto.getName()); - assertEquals(banHistory.getBannedAt(), userBlockedInfoDto.getBannedAt()); - assertEquals(banHistory.getUnbannedAt(), userBlockedInfoDto.getUnbannedAt()); +// LocalDateTime now = LocalDateTime.now(); +// User user = mock(User.class); +// when(user.getId()).thenReturn(3L); +// when(user.getName()).thenReturn("intraId"); +// when(user.getEmail()).thenReturn("email@test.com"); +// when(user.getBlackholedAt()).thenReturn(now); +// when(user.getRole()).thenReturn(UserRole.USER); +// BanHistory banHistory = BanHistory.of(now, now, BanType.PRIVATE, 3L); +// UserBlockedInfoDto userBlockedInfoDto = userMapper.toUserBlockedInfoDto(banHistory, user); +// assertEquals(banHistory.getUserId(), userBlockedInfoDto.getUserId()); +// assertEquals(user.getName(), userBlockedInfoDto.getName()); +// assertEquals(banHistory.getBannedAt(), userBlockedInfoDto.getBannedAt()); +// assertEquals(banHistory.getUnbannedAt(), userBlockedInfoDto.getUnbannedAt()); } @Test void toUserProfileDto() { - User user = User.of("intraId", "user@email.com", LocalDateTime.now(), UserRole.USER); - UserProfileDto userProfileDto = userMapper.toUserProfileDto(user); - assertEquals(user.getId(), userProfileDto.getUserId()); - assertEquals(user.getName(), userProfileDto.getName()); +// User user = User.of("intraId", "user@email.com", LocalDateTime.now(), UserRole.USER); +// UserProfileDto userProfileDto = userMapper.toUserProfileDto(user); +// assertEquals(user.getId(), userProfileDto.getUserId()); +// assertEquals(user.getName(), userProfileDto.getName()); } } diff --git a/backend/src/test/java/org/ftclub/cabinet/user/domain/UserTest.java b/backend/src/test/java/org/ftclub/cabinet/user/domain/UserTest.java index 7e05bea0d..bc2fed3da 100644 --- a/backend/src/test/java/org/ftclub/cabinet/user/domain/UserTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/user/domain/UserTest.java @@ -1,115 +1,104 @@ package org.ftclub.cabinet.user.domain; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.exception.DomainException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; @RequiredArgsConstructor public class UserTest { - @Test - @DisplayName("유저 생성 성공 - 일반적인 이메일 형식") - void of_성공_일반적인_이메일_형식() { - assertDoesNotThrow(() -> - User.of("test1", "test-test@test.com", null, UserRole.USER)); - } - - @Test - @DisplayName("유저 생성 성공 - 아이디에 점과 하이픈이 들어간 이메일 형식") - void of_성공_아이디에_점과_하이픈_이메일_형식() { - assertDoesNotThrow(() -> - User.of("test2", "test.test_123@test.com", null, UserRole.CLUB)); - } - - @Test - @DisplayName("유저 생성 성공 - 점이 두 개 들어간 이메일 형식") - void of_성공_점_두개_이메일_형식() { - assertDoesNotThrow(() -> - User.of("test3", "test@student.42seoul.kr", null, UserRole.USER)); - } - - @Test - @DisplayName("유저 생성 실패 - 유저 이름 null") - void of_실패_유저_이름_null() { - assertThrows(DomainException.class, - () -> User.of(null, "test@test.com", null, UserRole.USER)); - } - - @Test - @DisplayName("유저 생성 실패 - 잘못된 이메일 형식") - void of_실패_잘못된_이메일_형식() { - assertThrows(DomainException.class, - () -> User.of("test1", "test@test", null, UserRole.USER)); - } - - @Test - @DisplayName("유저 생성 실패 - 이메일 비허용 특수문자 포함") - void of_실패_이메일_비허용_특수문자_포함() { - assertThrows(DomainException.class, - () -> User.of("test2", "!test@test.com", null, UserRole.CLUB)); - } - - @Test - @DisplayName("유저 생성 실패 - 이메일 null") - void of_실패_이메일_null() { - assertThrows(DomainException.class, - () -> User.of("test3", null, null, UserRole.CLUB)); - } - - @Test - @DisplayName("유저 생성 실패 - 유저 역할 null") - void of_실패_유저_role_null() { - assertThrows(DomainException.class, - () -> User.of("test4", "test@test.com", null, null)); - } - - @Test - @DisplayName("유저 역할 확인 성공 - 일반 유저") - void isUserRole_성공_일반유저() { - User user = User.of("test", "test@test.com", null, UserRole.USER); - - assertTrue(user.isUserRole(UserRole.USER)); - assertFalse(user.isUserRole(UserRole.CLUB)); - } - - @Test - @DisplayName("유저 역할 확인 성공 - 동아리") - void isUserRole_성공_동아리() { - User user = User.of("test", "test@test.com", null, UserRole.CLUB); - - assertFalse(user.isUserRole(UserRole.USER)); - assertTrue(user.isUserRole(UserRole.CLUB)); - } - - @Test - @DisplayName("유저 블랙홀 날짜 변경 성공 - 블랙홀 날짜 변경") - void changeBlackholedAt_성공() { - LocalDateTime blackholedAt = LocalDateTime.now().plusDays(10); - LocalDateTime changedBlackholedAt = LocalDateTime.now().plusDays(20); - User user = User.of("test", "test@test.com", blackholedAt, UserRole.CLUB); - user.changeBlackholedAt(changedBlackholedAt); - - assertNotEquals(blackholedAt, user.getBlackholedAt()); - assertEquals(changedBlackholedAt, user.getBlackholedAt()); - } - - @Test - @DisplayName("유저 삭제 날짜 변경 성공 - 삭제 날짜 변경") - void setDeletedAt_성공() { - LocalDateTime deletedAt = LocalDateTime.now().plusDays(10); - User user = User.of("test", "test@test.com", null, UserRole.CLUB); - user.setDeletedAt(deletedAt); - - assertNotEquals(null, user.getDeletedAt()); - assertEquals(deletedAt, user.getDeletedAt()); - } +// @Test +// @DisplayName("유저 생성 성공 - 일반적인 이메일 형식") +// void of_성공_일반적인_이메일_형식() { +//// assertDoesNotThrow(() -> +//// User.of("test1", "test-test@test.com", null, UserRole.USER)); +// } +// +// @Test +// @DisplayName("유저 생성 성공 - 아이디에 점과 하이픈이 들어간 이메일 형식") +// void of_성공_아이디에_점과_하이픈_이메일_형식() { +//// assertDoesNotThrow(() -> +//// User.of("test2", "test.test_123@test.com", null, UserRole.CLUB)); +// } +// +// @Test +// @DisplayName("유저 생성 성공 - 점이 두 개 들어간 이메일 형식") +// void of_성공_점_두개_이메일_형식() { +// assertDoesNotThrow(() -> +// User.of("test3", "test@student.42seoul.kr", null, UserRole.USER)); +// } +// +// @Test +// @DisplayName("유저 생성 실패 - 유저 이름 null") +// void of_실패_유저_이름_null() { +// assertThrows(DomainException.class, +// () -> User.of(null, "test@test.com", null, UserRole.USER)); +// } +// +// @Test +// @DisplayName("유저 생성 실패 - 잘못된 이메일 형식") +// void of_실패_잘못된_이메일_형식() { +// assertThrows(DomainException.class, +// () -> User.of("test1", "test@test", null, UserRole.USER)); +// } +// +// @Test +// @DisplayName("유저 생성 실패 - 이메일 비허용 특수문자 포함") +// void of_실패_이메일_비허용_특수문자_포함() { +// assertThrows(DomainException.class, +// () -> User.of("test2", "!test@test.com", null, UserRole.CLUB)); +// } +// +// @Test +// @DisplayName("유저 생성 실패 - 이메일 null") +// void of_실패_이메일_null() { +// assertThrows(DomainException.class, +// () -> User.of("test3", null, null, UserRole.CLUB)); +// } +// +// @Test +// @DisplayName("유저 생성 실패 - 유저 역할 null") +// void of_실패_유저_role_null() { +// assertThrows(DomainException.class, +// () -> User.of("test4", "test@test.com", null, null)); +// } +// +// @Test +// @DisplayName("유저 역할 확인 성공 - 일반 유저") +// void isUserRole_성공_일반유저() { +// User user = User.of("test", "test@test.com", null, UserRole.USER); +// +// assertTrue(user.isUserRole(UserRole.USER)); +// assertFalse(user.isUserRole(UserRole.CLUB)); +// } +// +// @Test +// @DisplayName("유저 역할 확인 성공 - 동아리") +// void isUserRole_성공_동아리() { +// User user = User.of("test", "test@test.com", null, UserRole.CLUB); +// +// assertFalse(user.isUserRole(UserRole.USER)); +// assertTrue(user.isUserRole(UserRole.CLUB)); +// } +// +// @Test +// @DisplayName("유저 블랙홀 날짜 변경 성공 - 블랙홀 날짜 변경") +// void changeBlackholedAt_성공() { +// LocalDateTime blackholedAt = LocalDateTime.now().plusDays(10); +// LocalDateTime changedBlackholedAt = LocalDateTime.now().plusDays(20); +// User user = User.of("test", "test@test.com", blackholedAt, UserRole.CLUB); +// user.changeBlackholedAt(changedBlackholedAt); +// +// assertNotEquals(blackholedAt, user.getBlackholedAt()); +// assertEquals(changedBlackholedAt, user.getBlackholedAt()); +// } +// +// @Test +// @DisplayName("유저 삭제 날짜 변경 성공 - 삭제 날짜 변경") +// void setDeletedAt_성공() { +// LocalDateTime deletedAt = LocalDateTime.now().plusDays(10); +// User user = User.of("test", "test@test.com", null, UserRole.CLUB); +// user.setDeletedAt(deletedAt); +// +// assertNotEquals(null, user.getDeletedAt()); +// assertEquals(deletedAt, user.getDeletedAt()); +// } } diff --git a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java index df73e4a53..14f466e53 100644 --- a/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/utils/mail/EmailServiceUnitTest.java @@ -1,11 +1,11 @@ package org.ftclub.cabinet.utils.mail; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import javax.mail.MessagingException; import org.ftclub.cabinet.alarm.config.GmailProperties; -import org.ftclub.cabinet.alarm.domain.AlarmEvent; -import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; import org.ftclub.cabinet.alarm.handler.EmailAlarmSender; -import org.ftclub.cabinet.user.domain.User; -import org.ftclub.cabinet.user.domain.UserRole; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -17,21 +17,12 @@ import org.springframework.mail.javamail.JavaMailSender; import org.thymeleaf.ITemplateEngine; -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; - @ExtendWith(MockitoExtension.class) public class EmailServiceUnitTest { - private String name = "testUser"; - private String email = "testEamil@test.com"; - private JavaMailSender javaMailSender = mock(JavaMailSender.class); + private final String name = "testUser"; + private final String email = "testEamil@test.com"; + private final JavaMailSender javaMailSender = mock(JavaMailSender.class); @Mock private ITemplateEngine templateEngine; @Mock(lenient = true) @@ -55,12 +46,12 @@ void setupBeforeEach() { @Test @DisplayName("실패 - 개발 환경에서는 메일을 보내지 않음") void 실패_sendMail_개발환경() throws MessagingException, MailException { - given(gmailProperties.getIsProduction()).willReturn(false); - - emailAlarmSender.send(User.of(name, email, null, UserRole.USER), - AlarmEvent.of(1L, new LentExpirationAlarm(1L))); - - then(javaMailSender).should(never()).send(any(MimeMessage.class)); +// given(gmailProperties.getIsProduction()).willReturn(false); +// +// emailAlarmSender.send(User.of(name, email, null, UserRole.USER), +// AlarmEvent.of(1L, new LentExpirationAlarm(1L))); +// +// then(javaMailSender).should(never()).send(any(MimeMessage.class)); } // @Test diff --git a/backend/src/test/java/org/ftclub/testutils/TestUtils.java b/backend/src/test/java/org/ftclub/testutils/TestUtils.java index c3d5470fa..9073f9513 100644 --- a/backend/src/test/java/org/ftclub/testutils/TestUtils.java +++ b/backend/src/test/java/org/ftclub/testutils/TestUtils.java @@ -4,31 +4,27 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; - +import java.security.Key; +import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; - +import java.util.Map; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.Cookie; +import javax.xml.bind.DatatypeConverter; import org.ftclub.cabinet.admin.admin.domain.AdminRole; -import org.ftclub.cabinet.user.domain.UserRole; import org.ftclub.cabinet.utils.DateUtil; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import javax.crypto.spec.SecretKeySpec; -import javax.servlet.http.Cookie; -import javax.xml.bind.DatatypeConverter; -import java.security.Key; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - public class TestUtils { public static String getTestAdminToken(Key signingKey, LocalDateTime now, String emailName, - String emailDomain) { + String emailDomain) { Map claim = new HashMap<>(); claim.put("email", emailName + "@" + emailDomain); claim.put("role", AdminRole.ADMIN); @@ -40,7 +36,7 @@ public static String getTestAdminToken(Key signingKey, LocalDateTime now, String } public static String getTestMasterToken(Key signingKey, LocalDateTime now, String emailName, - String emailDomain) { + String emailDomain) { Map claim = new HashMap<>(); claim.put("email", emailName + "@" + emailDomain); claim.put("role", AdminRole.MASTER); @@ -52,12 +48,12 @@ public static String getTestMasterToken(Key signingKey, LocalDateTime now, Strin } public static String getTestUserTokenByName(Key signingKey, LocalDateTime now, - LocalDateTime blackholedAt, String name, String emailDomain) { + LocalDateTime blackholedAt, String name, String emailDomain) { Map claim = new HashMap<>(); claim.put("name", name); claim.put("email", name + "@" + emailDomain); claim.put("blackholedAt", DateUtil.toDate(blackholedAt)); - claim.put("role", UserRole.USER); + claim.put("role", "USER"); return Jwts.builder() .setClaims(claim) .signWith(signingKey, SignatureAlgorithm.HS256) @@ -83,7 +79,7 @@ public static Key getSigningKey(String secretKey) { } public static MockHttpServletRequestBuilder mockRequest(HttpMethod method, Cookie cookie, - String url, Object... uriVars) { + String url, Object... uriVars) { if (method.equals(HttpMethod.GET)) { return MockMvcRequestBuilders.get(url, uriVars) .cookie(cookie) diff --git a/config b/config index c31aa887c..71add80f8 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit c31aa887c99ee2cb15a4f730c08ec1d60e6c9f79 +Subproject commit 71add80f85a59a4f395c8813507bdbcbffe6ce23 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b812f2015..fcb618f61 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,15 +1,15 @@ import React, { Suspense, lazy } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; +import AvailablePage from "@/pages/AvailablePage"; +import ClubPage from "@/pages/ClubPage"; import HomePage from "@/pages/HomePage"; import Layout from "@/pages/Layout"; import LogPage from "@/pages/LogPage"; import LoginPage from "@/pages/LoginPage"; import MainPage from "@/pages/MainPage"; -import PendingPage from "@/pages/PendingPage/PendingPage"; +import ProfilePage from "@/pages/ProfilePage"; import AdminMainPage from "@/pages/admin/AdminMainPage"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import PostLogin from "./pages/PostLogin"; -import ProfilePage from "./pages/ProfilePage"; const NotFoundPage = lazy(() => import("@/pages/NotFoundPage")); const LoginFailurePage = lazy(() => import("@/pages/LoginFailurePage")); @@ -27,14 +27,14 @@ function App(): React.ReactElement { }> - } /> }> } /> } /> } /> - } /> + } /> } /> - } /> + } /> + } /> {/* admin용 라우터 */} }> @@ -43,7 +43,7 @@ function App(): React.ReactElement { } /> } /> } /> - } /> + } /> } /> => { + try { + const response = await instance.get(axiosMyClubListURL); + return response; + } catch (error) { + throw error; + } +}; + +const axiosGetClubInfoURL = "/v4/clubs/"; +export const axiosGetClubInfo = async ( + clubId: number, + page: number, + size: number +): Promise => { + try { + const response = await instance.get( + axiosGetClubInfoURL + clubId.toString(), + { + params: { page: page, size: size }, + } + ); + return response; + } catch (error) { + throw error; + } +}; + +const axiosAddClubMemberURL = "/v4/clubs"; +export const axiosAddClubMember = async ( + clubId: number, + name: String +): Promise => { + // TODO : 예외처리? + try { + const response = await instance.post( + `${axiosAddClubMemberURL}/${clubId}/users`, + { + name, + } + ); + return response; + } catch (error) { + throw error; + } +}; + +const axiosMandateClubMemURL = "/v4/clubs"; +export const axiosMandateClubMember = async ( + clubId: number, + clubMaster: string +): Promise => { + // TODO : 예외처리? + try { + const response = await instance.post( + `${axiosMandateClubMemURL}/${clubId}/mandate`, + { clubMaster } + ); + return response; + } catch (error) { + throw error; + } +}; + +const axiosDeleteClubMemberURL = "/v4/clubs/"; +export const axiosDeleteClubMember = async ( + clubId: number, + userId: number +): Promise => { + try { + const response = await instance.delete( + `${axiosDeleteClubMemberURL}${clubId}/users/${userId}` + ); + return response; + } catch (error) { + throw error; + } +}; + +const axiosUpdateClubMemoURL = "/v4/clubs/"; +export const axiosUpdateClubMemo = async ( + clubId: number, + clubMemo: string +): Promise => { + try { + const response = await instance.post( + `${axiosUpdateClubMemoURL}${clubId}/memo`, + { + memo: clubMemo, + } + ); + return response; + } catch (error) { + throw error; + } +}; + +const axiosUpdateClubNoticeURL = "/v4/clubs/"; +export const axiosUpdateClubNotice = async ( + clubId: number, + notice: string +): Promise => { + try { + const response = await instance.post( + `${axiosUpdateClubNoticeURL}${clubId}/notice`, + { + notice, + } + ); + return response; + } catch (error) { + throw error; + } +}; + // V3 API const axiosBuildingFloorURL = "/v4/cabinets/buildings/floors"; export const axiosBuildingFloor = async (): Promise => { @@ -450,7 +566,7 @@ export const axiosGetUserLentLog = async ( } }; -const axiosGetClubUserLogURL = "/v4/admin/users/clubs"; +const axiosGetClubUserLogURL = "/v4/admin/clubs"; export const axiosGetClubUserLog = async ( page: number, size: number @@ -465,29 +581,33 @@ export const axiosGetClubUserLog = async ( } }; -const axiosCreateClubUserURL = "/v4/admin/users/club"; +const axiosCreateClubUserURL = "/v4/admin/clubs"; export const axiosCreateClubUser = async ( - clubName: string | null + clubName: string | null, + clubMaster: string | null ): Promise => { - if (clubName === null) return; + if (clubName === null || clubMaster === null) return; try { - const response = await instance.post(axiosCreateClubUserURL, { clubName }); + const response = await instance.post(axiosCreateClubUserURL, { + clubName, + clubMaster, + }); return response; } catch (error) { throw error; } }; -const axiosEditClubUserURL = "/v4/admin/users/club/"; +const axiosEditClubUserURL = "/v4/admin/clubs/"; export const axiosEditClubUser = async ( - clubInfo: ClubUserDto | null + clubInfo: ClubUserDto ): Promise => { - if (clubInfo === null || clubInfo.name === null) return; try { const response = await instance.patch( - axiosEditClubUserURL + clubInfo.userId.toString(), + axiosEditClubUserURL + clubInfo.clubId.toString(), { - clubName: clubInfo.name, + clubName: clubInfo.clubName, + clubMaster: clubInfo.clubMaster, } ); return response; @@ -496,7 +616,7 @@ export const axiosEditClubUser = async ( } }; -const axiosDeleteClubUserURL = "/v4/admin/users/club/"; +const axiosDeleteClubUserURL = "/v4/admin/clubs/"; export const axiosDeleteClubUser = async ( clubId: number | null ): Promise => { @@ -511,18 +631,39 @@ export const axiosDeleteClubUser = async ( } }; -const axiosLentClubUserURL = "/v4/admin/cabinets/club"; -export const axiosLentClubUser = async ( - userId: number, - cabinetId: number, - statusNote: string | null +// const axiosLentClubUserURL = "/v4/admin/cabinets/club"; +// export const axiosLentClubUser = async ( +// userId: number, +// cabinetId: number, +// statusNote: string | null +// ): Promise => { +// try { +// const response = await instance.patch(axiosLentClubUserURL, { +// userId, +// cabinetId, +// statusNote, +// }); +// return response; +// } catch (error) { +// throw error; +// } +// }; + +// Lent Cabinet to Club {cabinetId} to {clubId} +// /v4/admin/clubs/{clubId}/cabinets/{cabinetId} + +const axiosLentClubCabinetURL = "/v4/admin/clubs/"; +export const axiosLentClubCabinet = async ( + clubId: number, + cabinetId: number ): Promise => { try { - const response = await instance.patch(axiosLentClubUserURL, { - userId, - cabinetId, - statusNote, - }); + const response = await instance.post( + axiosLentClubCabinetURL + + clubId.toString() + + "/cabinets/" + + cabinetId.toString() + ); return response; } catch (error) { throw error; @@ -558,10 +699,10 @@ export const axiosCancel = async (cabinetId: number | null): Promise => { } }; -const axiosGetPendingCabinetsURL = "/v4/cabinets/buildings/새롬관/pending"; -export const axiosGetPendingCabinets = async (): Promise => { +const axiosGetAvailableCabinetsURL = "/v4/cabinets/buildings/새롬관/available"; +export const axiosGetAvailableCabinets = async (): Promise => { try { - const response = await instance.get(axiosGetPendingCabinetsURL); + const response = await instance.get(axiosGetAvailableCabinetsURL); return response; } catch (error) { throw error; diff --git a/frontend/src/assets/css/media.css b/frontend/src/assets/css/media.css index 788765743..612f68ee4 100644 --- a/frontend/src/assets/css/media.css +++ b/frontend/src/assets/css/media.css @@ -51,6 +51,21 @@ transform: translateX(0%); } +/* ClubMemberInfoArea(rightSection) */ +/* #clubMemberInfoArea { + position: fixed; + top: 80px; + right: 0; + height: calc(100% - 80px); + z-index: 9; + transform: translateX(120%); + transition: transform 0.3s ease-in-out; + box-shadow: 0 0 40px 0 var(--bg-shadow); + } */ +#clubMemberInfoArea.on { + transform: translateX(0%); +} + /* search */ @media (max-width: 768px) { #searchBar { diff --git a/frontend/src/assets/data/maps.ts b/frontend/src/assets/data/maps.ts index 3d6906540..450756f56 100644 --- a/frontend/src/assets/data/maps.ts +++ b/frontend/src/assets/data/maps.ts @@ -156,6 +156,31 @@ export const modalPropsMap = { title: "이사하기", confirmMessage: "네, 이사할게요", }, + MODAL_CLUB_ADD_MEM: { + type: "confirm", + title: "동아리 멤버 추가", + confirmMessage: "저장", + }, + MODAL_CLUB_DEL_MEM: { + type: "confirm", + title: "멤버 내보내기", + confirmMessage: "확인", + }, + MODAL_CLUB_MANDATE_MEM: { + type: "confirm", + title: "동아리장 위임", + confirmMessage: "네, 위임할게요", + }, + MODAL_CLUB_PAGE_SET_PW: { + type: "confirm", + title: "비밀번호 설정", + confirmMessage: "설정", + }, + MODAL_CLUB_MODIFY_PW: { + type: "confirm", + title: "비밀번호 수적", + confirmMessage: "입력", + }, }; export const cabinetFilterMap = { diff --git a/frontend/src/assets/images/close-circle.svg b/frontend/src/assets/images/close-circle.svg new file mode 100644 index 000000000..6ef6eb139 --- /dev/null +++ b/frontend/src/assets/images/close-circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/images/crown.svg b/frontend/src/assets/images/crown.svg new file mode 100644 index 000000000..9bed5a2d3 --- /dev/null +++ b/frontend/src/assets/images/crown.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/edit.svg b/frontend/src/assets/images/edit.svg new file mode 100644 index 000000000..bd3a26936 --- /dev/null +++ b/frontend/src/assets/images/edit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/assets/images/leader.svg b/frontend/src/assets/images/leader.svg new file mode 100644 index 000000000..627d7d003 --- /dev/null +++ b/frontend/src/assets/images/leader.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/lock.svg b/frontend/src/assets/images/lock.svg new file mode 100644 index 000000000..f0bfeb848 --- /dev/null +++ b/frontend/src/assets/images/lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/assets/images/maru.svg b/frontend/src/assets/images/maru.svg new file mode 100644 index 000000000..9f350118e --- /dev/null +++ b/frontend/src/assets/images/maru.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/images/more.svg b/frontend/src/assets/images/more.svg new file mode 100644 index 000000000..290d27643 --- /dev/null +++ b/frontend/src/assets/images/more.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/assets/images/selectMaincolor.svg b/frontend/src/assets/images/selectMaincolor.svg new file mode 100644 index 000000000..05e790359 --- /dev/null +++ b/frontend/src/assets/images/selectMaincolor.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/pages/PendingPage/components/PendingCountdown.tsx b/frontend/src/components/Available/AvailableCountdown.tsx similarity index 76% rename from frontend/src/pages/PendingPage/components/PendingCountdown.tsx rename to frontend/src/components/Available/AvailableCountdown.tsx index 317049b47..25860f213 100644 --- a/frontend/src/pages/PendingPage/components/PendingCountdown.tsx +++ b/frontend/src/components/Available/AvailableCountdown.tsx @@ -9,7 +9,7 @@ openTime.setHours(13, 0, 0, 0); // 오픈 시간인 13:00:00(오후 1시)로 설 const hours24 = 86400000; // 24시간을 밀리초로 표현 -const PendingCountdown = ({ +const AvailableCountdown = ({ observeOpenTime, }: { observeOpenTime: () => void; @@ -20,9 +20,6 @@ const PendingCountdown = ({ const hours = Math.floor(remainingTime / 3600000); const minutes = Math.floor((remainingTime % 3600000) / 60000); const seconds = Math.floor((remainingTime % 60000) / 1000); - const twoDigitsHours = String(hours).padStart(2, "0"); - const twoDigitsMinutes = String(minutes).padStart(2, "0"); - const twoDigitsSeconds = String(seconds).padStart(2, "0"); useEffect(() => { if (serverTime.toLocaleTimeString() === Time.PENDING_OPEN) @@ -43,19 +40,27 @@ const PendingCountdown = ({ return ( <> - + + {remainingTime === 0 ? "OPEN" - : `${twoDigitsHours}:${twoDigitsMinutes}:${twoDigitsSeconds} 남았습니다`} - + : `${hours}시간 ${minutes}분 ${seconds}초`} + ); }; -const PendingCountdownStyled = styled.div` - color: white; - font-size: 1.1rem; +const AvailableCountdownIconStyled = styled.img` + height: 25px; + width: 25px; + margin-top: 50px; +`; + +const AvailableCountdownStyled = styled.div` + margin-top: 5px; + color: var(--main-color); + font-size: 1.8rem; font-weight: 600; `; -export default PendingCountdown; +export default AvailableCountdown; diff --git a/frontend/src/pages/PendingPage/components/FloorContainer.tsx b/frontend/src/components/Available/FloorContainer.tsx similarity index 91% rename from frontend/src/pages/PendingPage/components/FloorContainer.tsx rename to frontend/src/components/Available/FloorContainer.tsx index d3ce4e654..6be4547f2 100644 --- a/frontend/src/pages/PendingPage/components/FloorContainer.tsx +++ b/frontend/src/components/Available/FloorContainer.tsx @@ -38,10 +38,10 @@ const FloorContainer = ({ )} ) : ( - +

해당 층에는 사용 가능한 사물함이 없습니다

- noPending - + noAvailable + )} ); @@ -85,7 +85,7 @@ const FlootCabinetsContainerStyled = styled.div<{ isToggled: boolean }>` } `; -const NoPendingCabinetMessageStyled = styled.div<{ isToggled: boolean }>` +const NoAvailableCabinetMessageStyled = styled.div<{ isToggled: boolean }>` display: ${(props) => (props.isToggled ? "none" : "flex")}; align-items: center; margin-top: 20px; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx index b3c494081..edb31bd47 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx @@ -131,10 +131,7 @@ export const getDetailMessage = ( // 동아리 사물함 else if (lentType === "CLUB") return "동아리 사물함"; // 사용 중 사물함 - else if ( - status === CabinetStatus.FULL || - status === CabinetStatus.OVERDUE - ) + else if (status === CabinetStatus.FULL || status === CabinetStatus.OVERDUE) return getCalcualtedTimeString(new Date(lents[0].expiredAt)); // 빈 사물함 else return null; @@ -262,6 +259,8 @@ const CabinetInfoAreaContainer = (): JSX.Element => { }); }; + const [state, setState] = useState(); + const closeModal = (modalName: TModalState) => { setUserModal({ ...userModal, diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx index ca3cde9ed..0f858bb09 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -14,6 +14,7 @@ import LentModal from "@/components/Modals/LentModal/LentModal"; import MemoModalContainer from "@/components/Modals/MemoModal/MemoModal.container"; import PasswordCheckModalContainer from "@/components/Modals/PasswordCheckModal/PasswordCheckModal.container"; import ReturnModal from "@/components/Modals/ReturnModal/ReturnModal"; +import SwapModal from "@/components/Modals/SwapModal/SwapModal"; import UnavailableModal from "@/components/Modals/UnavailableModal/UnavailableModal"; import { additionalModalType, @@ -26,7 +27,6 @@ import { ReactComponent as ExtensionImg } from "@/assets/images/extensionTicket. import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; import CabinetStatus from "@/types/enum/cabinet.status.enum"; import CabinetType from "@/types/enum/cabinet.type.enum"; -import SwapModal from "../Modals/SwapModal/SwapModal"; const CabinetInfoArea: React.FC<{ selectedCabinetInfo: ISelectedCabinetInfo | null; @@ -164,7 +164,9 @@ const CabinetInfoArea: React.FC<{ )} {selectedCabinetInfo.status == "PENDING" && ( - 매일 13:00 오픈됩니다 + + 매일 13:00 오픈됩니다 + )} )} @@ -423,7 +425,7 @@ const CabinetLentDateInfoStyled = styled.div<{ textColor: string }>` text-align: center; `; -const PendingMessageStyled = styled.p` +const AvailableMessageStyled = styled.p` font-size: 1rem; margin-top: 8px; text-align: center; diff --git a/frontend/src/components/CabinetList/CabinetList.container.tsx b/frontend/src/components/CabinetList/CabinetList.container.tsx index e571fc65b..a099d3b13 100644 --- a/frontend/src/components/CabinetList/CabinetList.container.tsx +++ b/frontend/src/components/CabinetList/CabinetList.container.tsx @@ -9,7 +9,11 @@ import CabinetList from "@/components/CabinetList/CabinetList"; import EmptySection from "@/components/CabinetList/EmptySection/EmptySection"; import RealViewNotification from "@/components/CabinetList/RealViewNotification/RealViewNotification"; import MultiSelectFilterButton from "@/components/Common/MultiSelectFilterButton"; -import { CabinetInfo, CabinetPreview, CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; +import { + CabinetInfo, + CabinetPreview, + CabinetPreviewInfo, +} from "@/types/dto/cabinet.dto"; import useMultiSelect from "@/hooks/useMultiSelect"; interface ICabinetListContainer { diff --git a/frontend/src/components/Card/Card.tsx b/frontend/src/components/Card/Card.tsx index fbfcc5954..30fbc6c12 100644 --- a/frontend/src/components/Card/Card.tsx +++ b/frontend/src/components/Card/Card.tsx @@ -1,17 +1,18 @@ import React from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; export interface IButtonProps { - label: string; + label?: string; // NOTE: icon 이 없을 경우, label 을 표시 onClick?: () => void; backgroundColor?: string; color?: string; + icon?: string; // NOTE: icon 이 있을 경우, icon 을 표시 isClickable: boolean; isExtensible?: boolean; } interface CardProps { - title: string; + title?: string; children: React.ReactElement; buttons?: IButtonProps[]; gridArea: string; @@ -24,28 +25,33 @@ const Card = ({ gridArea, width = "350px", height = "163px", - buttons, + buttons = [], children, }: CardProps) => { return ( - - {title} - - {buttons?.map((button, index) => ( - - {button.label} - - ))} - - + {(title || buttons.length > 0) && ( + + {title && {title}} + {buttons.length > 0 && ( + + {buttons?.map((button, index) => ( + + {button.label} + + ))} + + )} + + )} {children} ); @@ -87,26 +93,38 @@ export const CardButtonWrapper = styled.div` export const CardButtonStyled = styled.div<{ backgroundColor?: string; color?: string; + icon?: string; isClickable?: boolean; isExtensible?: boolean; }>` - background-color: ${(props) => - props.backgroundColor ? props.backgroundColor : "var(--white)"}; - color: ${(props) => - props.color - ? props.color - : props.isExtensible - ? "var(--main-color)" - : "var(--gray-color)"}; - padding: 5px 15px; - border: none; - border-radius: 5px; - font-weight: 350; + ${(props) => + props.icon + ? css` + background-image: url(${props.icon}); + height: 20px; + width: 20px; + background-size: contain; + background-repeat: no-repeat; + ` + : css` + background-color: ${props.backgroundColor + ? props.backgroundColor + : "var(--white)"}; + color: ${props.color + ? props.color + : props.isExtensible + ? "var(--main-color)" + : "var(--gray-color)"}; + padding: 5px 15px; + border: none; + border-radius: 5px; + font-weight: 350; + margin-left: 10px; + &:hover { + font-weight: ${props.isClickable && 400}; + } + `} cursor: ${(props) => (props.isClickable ? "pointer" : "default")}; - margin-left: 10px; - &:hover { - font-weight: ${(props) => props.isClickable && 400}; - } `; export default Card; diff --git a/frontend/src/components/Card/CardStyles.ts b/frontend/src/components/Card/CardStyles.ts index 8dd4d360b..de068c48e 100644 --- a/frontend/src/components/Card/CardStyles.ts +++ b/frontend/src/components/Card/CardStyles.ts @@ -35,7 +35,7 @@ export const ContentInfoStyled = styled.div<{ `} `; -export const ContentDeatilStyled = styled.div<{ +export const ContentDetailStyled = styled.div<{ status?: CabinetStatus; }>` color: ${(props) => diff --git a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx b/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx new file mode 100644 index 000000000..4a3c08a74 --- /dev/null +++ b/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx @@ -0,0 +1,188 @@ +import { useState } from "react"; +import styled from "styled-components"; +import Card from "@/components/Card/Card"; +import { + CardContentStyled, + ContentDetailStyled, +} from "@/components/Card/CardStyles"; +import ClubPasswordModalContainer from "@/components/Modals/ClubModal/ClubPasswordModal.container"; +import { ClubInfoResponseDto } from "@/types/dto/club.dto"; + +const ClubCabinetInfoCard = ({ + clubInfo, + isMaster, +}: { + clubInfo: ClubInfoResponseDto; + isMaster: boolean; +}) => { + const [password, setPassword] = useState(clubInfo.clubMemo || "1111"); + const [showPassword, setShowPassword] = useState(false); + const [showPasswordModal, setShowPasswordModal] = useState(false); + + const handleLockLogoClick = () => { + setShowPasswordModal(true); + }; + + return ( + <> + {}, + icon: "", + isClickable: false, + }, + ] + } + > + <> + + + {clubInfo.visibleNum} + + + {clubInfo.clubName} + + {clubInfo.floor + "층 - " + clubInfo.section} + + + + + {clubInfo.clubMaster.userName} + + + + + + setShowPassword(true)} + onMouseLeave={() => setShowPassword(false)} + > + 비밀번호 + + {showPassword ? password : "****"} + + + + + + {showPasswordModal && ( + + )} + + ); +}; + +const CabinetInfoWrapper = styled.div` + display: flex; + width: 85%; + margin: 9px 0 9px 0; + align-items: center; +`; + +const CabinetRectangleStyled = styled.div` + width: 90px; + height: 90px; + line-height: 90px; + border-radius: 10px; + margin-right: 20px; + background-color: var(--full); + color: var(--black); + font-size: 2rem; + text-align: center; +`; + +const CabinetInfoDetailStyled = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +const ClubNameTextStyled = styled.div` + font-size: 1.2rem; + font-weight: bold; + height: 30px; + line-height: 30px; +`; + +const CabinetInfoTextStyled = styled.div<{ + fontSize: string; + fontColor: string; +}>` + font-size: ${(props) => props.fontSize}; + font-weight: 400; + line-height: 30px; + color: ${(props) => props.fontColor}; + text-align: center; + white-space: pre-line; +`; + +const CabinetUserListWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +`; + +const CabinetIconStyled = styled.div` + width: 22px; + height: 18px; + margin-right: 0.5rem; + background-image: url("/src/assets/images/leader.svg"); + background-size: contain; + background-repeat: no-repeat; +`; + +const ContentInfoStyled = styled.div<{ + isSelected?: boolean; + selectedColor?: string; +}>` + display: flex; + padding: 8px 10px; + + ${(props) => + props.isSelected && + ` + background-color: ${props.selectedColor}; + color: white; + border-radius: 8px; + `} +`; + +const CardContentWrapper = styled.div` + background-color: var(--white); + border-radius: 10px; + padding: 10px 0; + margin: 5px 5px 5px 5px; + width: 90%; + display: flex; + flex-direction: column; + + &:hover { + cursor: pointer; + } +`; + +export default ClubCabinetInfoCard; diff --git a/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx b/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx new file mode 100644 index 000000000..916370820 --- /dev/null +++ b/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx @@ -0,0 +1,92 @@ +import { useState } from "react"; +import styled from "styled-components"; +import Card from "@/components/Card/Card"; +import { CardContentWrapper } from "@/components/Card/CardStyles"; +import ClubMemoModalContainer from "@/components/Modals/ClubModal/ClubMemoModal.container"; + +const ClubNoticeCard = ({ + notice, + isMaster, +}: { + notice: string; + isMaster: boolean; +}) => { + const [showMemoModal, setShowMemoModal] = useState(false); + const [text, setText] = useState(notice); + + const openModal = () => { + setShowMemoModal(true); + }; + + return ( + <> + {}, + icon: "", + isClickable: false, + }, + ] + } + > + <> + + {notice} + + + + {showMemoModal && ( + + )} + + ); +}; + +const ClubNoticeTextStyled = styled.div` + width: 100%; + height: 150px; + font-size: 1rem; + padding: 0.5rem 1rem; + word-break: break-all; + white-space: pre-wrap; + line-height: 1.2rem; + overflow-y: auto; + letter-spacing: 0.8px; + + ::-webkit-scrollbar { + width: 20px; + background-color: transparent; + } + + ::-webkit-scrollbar-thumb { + background: var(--line-color); + border-radius: 50px; + border: 6px solid transparent; + background-clip: padding-box; + display: inline-block; + } + +`; + +export default ClubNoticeCard; diff --git a/frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx b/frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx index 51e694207..8a893b6fe 100644 --- a/frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx +++ b/frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx @@ -2,7 +2,7 @@ import Card, { IButtonProps } from "@/components/Card/Card"; import { CardContentStyled, CardContentWrapper, - ContentDeatilStyled, + ContentDetailStyled, ContentInfoStyled, } from "@/components/Card/CardStyles"; import { LentExtensionDto } from "@/types/dto/lent.dto"; @@ -25,17 +25,17 @@ const ExtensionCard = ({ extensionInfo, button }: ExtensionProps) => { 사용 기한 - + {!!extensionInfo ? formatDate(new Date(extensionInfo.expiredAt), ".") : "-"} - + 연장 기간 - + {!!extensionInfo ? extensionInfo.extensionPeriod + "일" : "-"} - + diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx b/frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx index 499e3d889..57b6bc12f 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx +++ b/frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx @@ -37,6 +37,7 @@ const calculateDateUsed = (startedAt: Date) => { return diffDays; }; +// NOTE: 내 이름을 볼드처리한 사용자 리스트를 반환 const getCabinetUserList = ( selectedCabinetInfo: CabinetInfo, myName: string | null diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx index 4fffdbf07..559e9446d 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx @@ -3,7 +3,7 @@ import Card from "@/components/Card/Card"; import { CardContentStyled, CardContentWrapper, - ContentDeatilStyled, + ContentDetailStyled, ContentInfoStyled, } from "@/components/Card/CardStyles"; import { MyCabinetInfo } from "@/components/Card/LentInfoCard/LentInfoCard.container"; @@ -78,39 +78,39 @@ const LentInfoCard = ({ 사용 기간 - + {cabinetInfo?.isLented && cabinetInfo.status != "IN_SESSION" ? `${cabinetInfo.dateUsed}일` : "-"} - + {cabinetInfo?.status === "OVERDUE" ? "연체 기간" : "남은 기간"} - + {cabinetInfo?.expireDate ? `${cabinetInfo.dateLeft}일` : "-"} - + {!!unbannedAt ? "패널티 종료 일자" : "종료 일자"} - + {!!unbannedAt ? formatDate(new Date(unbannedAt), ".") : cabinetInfo?.expireDate ? formatDate(new Date(cabinetInfo?.expireDate), ".") : "-"} - + 이전 대여자 - + {cabinetInfo?.previousUserName || "-"} - + diff --git a/frontend/src/components/Club/AdminClubLog.tsx b/frontend/src/components/Club/AdminClubLog.tsx index d0dd18094..f9ed15e56 100644 --- a/frontend/src/components/Club/AdminClubLog.tsx +++ b/frontend/src/components/Club/AdminClubLog.tsx @@ -63,7 +63,6 @@ const AdminClubLogStyled = styled.div` border-radius: 10px; overflow: hidden; margin: 0 auto; - box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); `; const SectionPaginationStyled = styled.div` diff --git a/frontend/src/components/Club/ClubInfo.tsx b/frontend/src/components/Club/ClubInfo.tsx new file mode 100644 index 000000000..4814c0a7a --- /dev/null +++ b/frontend/src/components/Club/ClubInfo.tsx @@ -0,0 +1,117 @@ +import { useEffect, useState } from "react"; +import { useRecoilValue } from "recoil"; +import styled from "styled-components"; +import { userState } from "@/recoil/atoms"; +import ClubCabinetInfoCard from "@/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard"; +import ClubNoticeCard from "@/components/Card/ClubNoticeCard/ClubNoticeCard"; +import ClubMemberListContainer from "@/components/Club/ClubMemberList/ClubMemberList.container"; +import LoadingAnimation from "@/components/Common/LoadingAnimation"; +import { ClubInfoResponseDto } from "@/types/dto/club.dto"; +import useClubInfo from "@/hooks/useClubInfo"; +import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; + +const ClubInfo = () => { + const myInfo = useRecoilValue(userState); + const { clubState, clubInfo, setPage } = useClubInfo(); + const [imMaster, setImMaster] = useState(false); + + useEffect(() => { + if (clubInfo && clubInfo !== STATUS_400_BAD_REQUEST) { + let clubInfoTest = clubInfo as ClubInfoResponseDto; + if (clubInfoTest.clubMaster.userName === myInfo.name) setImMaster(true); + } + }, [clubInfo]); + + return ( + <> + {clubInfo === undefined ? ( + + ) : clubInfo === STATUS_400_BAD_REQUEST ? ( + + 동아리 사물함이 없어요 + + + + + ) : ( + + 동아리 정보 + + + + + + + )} + + ); +}; + +const EmptyClubCabinetTextStyled = styled.div` + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.125rem; + color: var(--gray-color); + /* margin-top: 20px; */ +`; + +const SadCcabiStyled = styled.div` + display: flex; + align-items: center; + margin-left: 5px; + + img { + width: 30px; + aspect-ratio: 1 / 1; + margin-left: 8px; + } +`; + +const ClubInfoWrapperStyled = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + width: 100%; + height: 100%; + margin-top: 2rem; +`; + +const TitleStyled = styled.div` + width: 80%; + max-width: 720px; + display: flex; + justify-content: flex-start; + margin-bottom: 1rem; + font-size: 20px; + font-weight: bold; +`; + +const CardGridWrapper = styled.div` + display: grid; + margin: 1rem 0 2rem; + justify-content: center; + align-items: center; + width: 100%; + grid-gap: 20px; + grid-template-columns: 350px 350px; + grid-template-rows: 240px; + grid-template-areas: "clubCabinetInfo clubNotice"; + + @media screen and (max-width: 768px) { + grid-template-columns: 350px; + grid-template-rows: 240px 240px; + grid-template-areas: + "clubCabinetInfo" + "clubNotice"; + } +`; + +export default ClubInfo; diff --git a/frontend/src/components/Club/ClubList.tsx b/frontend/src/components/Club/ClubList.tsx new file mode 100644 index 000000000..d1b15fa0e --- /dev/null +++ b/frontend/src/components/Club/ClubList.tsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from "react"; +import styled from "styled-components"; +import MultiToggleSwitchSeparated, { + toggleItem, +} from "@/components/Common/MultiToggleSwitchSeparated"; +import { ClubListReponseType } from "@/types/dto/club.dto"; +import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; + +interface ClubListProps { + clubList: ClubListReponseType; + toggleType: T; + setToggleType: React.Dispatch>; +} + +const ClubList = ({ + clubList, + toggleType, + setToggleType, +}: ClubListProps) => { + const [toggleList, setToggleList] = useState([]); + + useEffect(() => { + if (clubList && clubList !== STATUS_400_BAD_REQUEST) { + setToggleType(clubList.result[0].clubId as T); + const clubToToggle = clubList.result.map((club) => { + return { + name: club.clubName.toString(), + key: club.clubId, + }; + }); + setToggleList(clubToToggle); + } + }, [clubList]); + + return ( + + + + ); +}; + +const ClubListWrapperStyled = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 80%; + max-width: 720px; + margin: 2rem 0; +`; + +export default ClubList; diff --git a/frontend/src/components/Club/ClubLogTable.tsx b/frontend/src/components/Club/ClubLogTable.tsx index 673f1a7a6..8e661e434 100644 --- a/frontend/src/components/Club/ClubLogTable.tsx +++ b/frontend/src/components/Club/ClubLogTable.tsx @@ -12,85 +12,93 @@ const ClubLogTable = ({ ClubList }: { ClubList: ClubLogResponseType }) => { const handleRowClick = (clubInfo: ClubUserDto) => { setSelectedClubInfo( - selectedClubInfo?.userId === clubInfo.userId ? null : clubInfo + selectedClubInfo?.clubId === clubInfo.clubId ? null : clubInfo ); }; if (ClubList === undefined) return ; return ( - - - -
- - - - {ClubList !== STATUS_400_BAD_REQUEST && ( - - {ClubList.map(({ userId, name }) => ( - handleRowClick({ userId, name })} - className={ - selectedClubInfo?.userId === userId ? "selected" : "" - } - > - + <> + {ClubList !== STATUS_400_BAD_REQUEST && ClubList.length !== 0 ? ( + + + + + + - ))} - - )} - - {ClubList === STATUS_400_BAD_REQUEST || - (ClubList.length === 0 && ( - 등록된 동아리가 없습니다. - ))} - + + + {ClubList.map(({ clubId, clubName, clubMaster }) => ( + + handleRowClick({ clubId, clubName, clubMaster }) + } + className={ + selectedClubInfo?.clubId === clubId ? "selected" : "" + } + > + + + + ))} + + + + ) : ( + 등록된 동아리가 없습니다. + )} + ); }; const LogTableWrapperStyled = styled.div` width: 100%; max-width: 400px; - border-radius: 10px; overflow: hidden; margin: 0 auto; - box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); `; const LogTableStyled = styled.table` width: 100%; - background: var(--white); + background: white; overflow: scroll; + border-spacing: 0 0.3em; + border-collapse: separate; `; const TheadStyled = styled.thead` width: 100%; height: 50px; line-height: 50px; - background-color: var(--main-color); - color: var(--white); + background: var(--white); `; const TbodyStyled = styled.tbody` + color: var(--gray-color); & > tr { text-align: center; height: 50px; cursor: pointer; + background-color: #f9f6ff; + } + & > tr td:first-of-type { + border-radius: 8px 0 0 8px; + } + & > tr td:last-of-type { + border-radius: 0 8px 8px 0; } & > tr > td { height: 50px; line-height: 50px; width: 33.3%; } - & > tr:nth-child(2n) { - background: #f9f6ff; - } + & > tr:hover, & > tr.selected { background-color: var(--sub-color); color: var(--white); - font-weight: 700; } `; diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx new file mode 100644 index 000000000..134e28477 --- /dev/null +++ b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx @@ -0,0 +1,60 @@ +import { useState } from "react"; +import { useRecoilValue } from "recoil"; +import { + targetClubCabinetInfoState, + targetClubInfoState, + targetClubUserInfoState, + userState, +} from "@/recoil/atoms"; +import ClubMemberInfoArea from "@/components/Club/ClubMemberInfoArea/ClubMemberInfoArea"; +import useMenu from "@/hooks/useMenu"; + +export type TClubModalState = "deleteModal" | "mandateModal"; + +export interface ICurrentClubModalStateInfo { + deleteModal: boolean; + mandateModal: boolean; +} + +const ClubMemberInfoAreaContainer = () => { + const myInfo = useRecoilValue(userState); + const targetClubInfo = useRecoilValue(targetClubInfoState); + const targetClubUserInfo = useRecoilValue(targetClubUserInfoState); + const targetClubCabinetInfo = useRecoilValue(targetClubCabinetInfoState); + const [clubModal, setClubModal] = useState({ + deleteModal: false, + mandateModal: false, + }); + const { closeClubMember } = useMenu(); + + const openModal = (modalName: TClubModalState) => { + setClubModal({ + ...clubModal, + [modalName]: true, + }); + }; + + const closeModal = () => { + setClubModal({ + ...clubModal, + deleteModal: false, + mandateModal: false, + }); + }; + + return ( + + ); +}; + +export default ClubMemberInfoAreaContainer; diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx new file mode 100644 index 000000000..43b9d58ca --- /dev/null +++ b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -0,0 +1,222 @@ +import styled from "styled-components"; +import { + ICurrentClubModalStateInfo, + TClubModalState, +} from "@/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; +import Button from "@/components/Common/Button"; +import DeleteClubMemberModal from "@/components/Modals/ClubModal/DeleteClubMemberModal"; +import MandateClubMemberModal from "@/components/Modals/ClubModal/MandateClubMemberModal"; +import { + cabinetIconSrcMap, + cabinetLabelColorMap, + cabinetStatusColorMap, +} from "@/assets/data/maps"; +import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; +import { + ClubCabinetInfo, + ClubResponseDto, + ClubUserResponseDto, +} from "@/types/dto/club.dto"; +import CabinetStatus from "@/types/enum/cabinet.status.enum"; +import CabinetType from "@/types/enum/cabinet.type.enum"; + +interface ClubMemberInfoAreaProps { + selectedClubInfo: ClubResponseDto; + selectedClubMemberInfo: ClubUserResponseDto; + selectedClubCabinetInfo: ClubCabinetInfo | null; + closeClubMemberInfoArea: () => void; + isMaster: boolean; + isMine: boolean; + clubModal: ICurrentClubModalStateInfo; + openModal: (modalName: TClubModalState) => void; + closeModal: (modalName: TClubModalState) => void; +} + +const ClubMemberInfoArea = ({ + selectedClubInfo, + selectedClubMemberInfo, + selectedClubCabinetInfo, + closeClubMemberInfoArea, + isMaster, + isMine, + clubModal, + openModal, + closeModal, +}: ClubMemberInfoAreaProps) => { + return ( + <> + + {selectedClubCabinetInfo === null ? ( + + + + + + 동아리를
+ 선택해주세요 +
+
+ ) : ( + <> + + {/* */} + + {selectedClubInfo!.clubName} + + + {selectedClubMemberInfo!.userName === + selectedClubInfo.clubMaster ? ( + + ) : ( + + )} + + {selectedClubMemberInfo!.userName || "-"} + + +
- - - @@ -106,24 +109,17 @@ - - + + - - - - - - - @@ -134,18 +130,18 @@ - - - - - + + + + +
분야 기술스택 {new Date(startedAt).toLocaleString("ko-KR", dateOptions)} - {endedAt - ? new Date(endedAt).toLocaleString("ko-KR", dateOptions) - : '-'} + + {endedAt + ? new Date(endedAt).toLocaleString("ko-KR", dateOptions) + : "-"}
동아리명
{`${name}`}
동아리명동아리장
{`${clubName}`}{`${clubMaster}`}
컴파일 타임에 에러를 검출하여 서비스 과정에서 발생할 수 있는 오류를 최소화했습니다.
_icon ESLint코딩 컨벤션에 위배되거나 안티 패턴을 미리 검출하여 에러 발생 요소를 줄였습니다.
_icon Prettier 기본적인 코딩룰 적용으로 가독성 향상 및 코드 양식을 통일했습니다.
Back-End_icon NestJSExpress.js 대비 낮은 자유도로 협업에 적합한 프레임워크로 판단했고, IoC, DI, AOP를 통해 유지보수성을 높였습니다._icon Spring FrameworkSpring Framework 기반의 프로젝트로, 다양한 레퍼런스와 라이브러리를 활용하여 안정적인 서비스를 구축했습니다.
_icon MariaDB 활성화된 커뮤니티를 통해 여러 레퍼런스를 이용, 개발 중 발생하는 여러 문제들을 해결했습니다.
_icon TypeORMRaw query로 작성하는 것보다 용이하고 추후 다른 DBMS로 쉽게 전환 가능한 라이브러리로, 이를 이용해 유지보수성을 높였습니다.
_icon Swagger명확한 HTTP API 명세를 통해 프론트엔드/백엔드의 원활한 협업을 이뤘습니다.
_icon PassportOAuth2 인증 방식 적용에 용이한 라이브러리로, 서버 자체 토큰 및 42 인트라 인증에 사용했습니다.
Infra_icon AWS 비용효율적이고 신뢰도가 높은 웹서비스로 판단, EC2/RDS/S3/CloudFront 등의 솔루션들을 사용하여 신속하고 안정적인 서비스 환경을 구성했습니다.
_iconPM2서버 앱 프로세스의 명확한 관리와 무중단 서비스에 사용했습니다.
_icon Docker컨테이너를 통해 통일된 로컬 개발환경을 설정하여 개발의 호환성을 높였습니다.컨테이너를 통해 프러덕션과 로컬 환경의 동일성을 유지하고, 배포과정을 자동화하여 개발환경의 일관성을 유지했습니다.
_icon Github Actions CI/CD를 통해 테스트, 배포를 자동화하여 무중단 서비스를 지원, 효율성과 효과성을 높였습니다.
_icon _iconPrometheus/Grafana애플리케이션 서버의 상태를 모니터링하여 이상 징후를 빠르게 파악하고 대응하여 서비스의 안정성을 높였습니다.
@@ -167,8 +163,8 @@ | [🍎 skim](https://github.com/subin195-09) | [🍪 spark](https://github.com/Hyunja27) | [✏️yooh](https://github.com/oyhoyhk) | [🪀 yoyoo](https://github.com/Yoowatney) | [🎒 yubchoi](https://github.com/yubinquitous) | | ----------------------------------------- | --------------------------------------- | ------------------------------------ | ---------------------------------------- | --------------------------------------------- | -| [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [원 jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seong-hui ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | -| --------------------------------------------- | ---------------------------------------------- | -------------------------------------- | -------------------------------------------- | --------------------------------------------- | -------------------------------------- | +| [ 🌑 daewoole](https://github.com/LeeDaeWook) | [🐝 hyungnoh](https://github.com/YESHYUNGSEOK) | [🐻‍❄️ jpark2](https://github.com/Z1park) | [🎨 jusohn](https://github.com/junyoung2015) | [🤓 seonghmo ](https://github.com/seong-hui) | [🚀 wchae](https://github.com/enaenen) | +| --------------------------------------------- | ---------------------------------------------- | -------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------- | | [ 🐶 surlee](https://github.com/Elineely) | [ 🐣 hyowchoi](https://github.com/chyo1/) | [ 👽 sohyupar](https://github.com/saewoo1) | [🦝 jimchoi](https://github.com/jimchoi9) | [ 🛼 jeekim](https://github.com/jnkeniaem) | [🍾 miyu](https://github.com/Minkyu01) | [🧸 gykoh](https://github.com/gykoh42) | | ----------------------------------------- | ----------------------------------------- | ------------------------------------------ | ----------------------------------------- | ------------------------------------------ | -------------------------------------- | -------------------------------------- | From c27d19eec993b5defa64037d16cca36d31a74262 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 7 Mar 2024 20:29:16 +0900 Subject: [PATCH 0453/1029] =?UTF-8?q?[FE]=20FIX:=20light=20mode=EC=9D=98?= =?UTF-8?q?=20gray=20300=EC=9D=84=20gray=20tmp=203=EB=A1=9C=20=EB=8C=80?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/AdminInfo/Chart/BarChart.tsx | 8 +++++++- .../src/components/AdminInfo/Chart/PieChart.tsx | 1 + .../src/components/Available/FloorContainer.tsx | 2 +- frontend/src/components/Club/AdminClubLog.tsx | 4 +++- frontend/src/components/Home/ServiceManual.tsx | 7 ++----- .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 2 +- .../LeftSectionNav/LeftSectionNavClubs.tsx | 2 +- frontend/src/components/Login/LoginTemplate.tsx | 16 ++++++++++++++-- frontend/src/index.css | 2 ++ frontend/src/pages/admin/AdminHomePage.tsx | 4 ++-- 10 files changed, 34 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/AdminInfo/Chart/BarChart.tsx b/frontend/src/components/AdminInfo/Chart/BarChart.tsx index 74824662e..272ea160f 100644 --- a/frontend/src/components/AdminInfo/Chart/BarChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/BarChart.tsx @@ -54,7 +54,13 @@ const BarChart = ({ data }: { data: IRentInfo[] }) => ( padding={0.3} valueScale={{ type: "linear" }} indexScale={{ type: "band", round: true }} - colors={["var(--gray-600)", "var(--expired)", "var(--gray-300)", "var(--main-color)"]} + colors={[ + "var(--gray-600)", + "var(--expired)", + "var(--gray-300)", + "var(--main-color)", + ]} + // 사용불가와 사용 중은 헷갈릴 수 있으니 색 그대로 borderColor={{ from: "color", modifiers: [["darker", 1.6]], diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/components/AdminInfo/Chart/PieChart.tsx index 0c556184d..851aa65eb 100644 --- a/frontend/src/components/AdminInfo/Chart/PieChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/PieChart.tsx @@ -67,6 +67,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { "var(--main-color)", "var(--gray-600)", ]} + // 사용불가와 사용 중은 헷갈릴 수 있으니 색 그대로 innerRadius={0.5} padAngle={0.7} cornerRadius={3} diff --git a/frontend/src/components/Available/FloorContainer.tsx b/frontend/src/components/Available/FloorContainer.tsx index e5c74094d..b6455cfda 100644 --- a/frontend/src/components/Available/FloorContainer.tsx +++ b/frontend/src/components/Available/FloorContainer.tsx @@ -59,7 +59,7 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` color: var(--color-text-normal); padding-left: 5px; padding-right: 5px; - border-bottom: 1.5px solid var(--gray-300); + border-bottom: 1.5px solid var(--gray-tmp-3); cursor: pointer; button { all: initial; diff --git a/frontend/src/components/Club/AdminClubLog.tsx b/frontend/src/components/Club/AdminClubLog.tsx index 25f990dd0..a9196933f 100644 --- a/frontend/src/components/Club/AdminClubLog.tsx +++ b/frontend/src/components/Club/AdminClubLog.tsx @@ -20,7 +20,9 @@ const AdminClubLog = ({ indexButtons.push( changePageOnClickIndexButton(i)} className="cabiButton" /> diff --git a/frontend/src/components/Home/ServiceManual.tsx b/frontend/src/components/Home/ServiceManual.tsx index 3a6fc502f..50f4d8f9c 100644 --- a/frontend/src/components/Home/ServiceManual.tsx +++ b/frontend/src/components/Home/ServiceManual.tsx @@ -121,10 +121,7 @@ const TitleContainerStyled = styled.div` display: flex; justify-content: space-between; align-items: center; - border-bottom: 2px solid var(--gray-300); - /* light */ - border-bottom: 2px solid var(--gray-500); - /* dark */ + border-bottom: 2px solid var(--gray-tmp-3); margin-bottom: 70px; color: var(--main-color); font-weight: 700; @@ -153,7 +150,7 @@ const NotionBtn = styled.button` color: var(--gray-300); /* dark */ background: var(--color-background); - border: 1px solid var(--gray-300); + border: 1px solid var(--gray-tmp-3); :hover { color: var(--color-text-normal); font-weight: 400; diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index 2434a5038..76149c817 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -116,7 +116,7 @@ const ProfileLeftNavOptionStyled = styled.div<{ & hr { width: 80%; height: 1px; - background-color: var(--gray-300); + background-color: var(--gray-tmp-3); border: 0; margin-top: 20px; margin-bottom: 20px; diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx index 3a040447d..3254c3ed2 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx +++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx @@ -52,7 +52,7 @@ const ClubLeftNavOptionStyled = styled.div` & hr { width: 80%; height: 1px; - background-color: var(--gray-300); + background-color: var(--gray-tmp-3); border: 0; margin-top: 20px; margin-bottom: 20px; diff --git a/frontend/src/components/Login/LoginTemplate.tsx b/frontend/src/components/Login/LoginTemplate.tsx index ff55945b2..e3b30af32 100644 --- a/frontend/src/components/Login/LoginTemplate.tsx +++ b/frontend/src/components/Login/LoginTemplate.tsx @@ -17,7 +17,7 @@ const LoginTemplate = (props: { - + 42서울 캐비닛 서비스 @@ -124,7 +124,7 @@ const LoginCardStyled = styled.div` align-items: center; flex-direction: column; padding: 85px 0; - background-color: var(--white); + background-color: var(--color-background); `; const CardLogoStyled = styled.div` @@ -134,6 +134,18 @@ const CardLogoStyled = styled.div` .logo_svg__currentPath { fill: var(--main-color); } + .logo_svg__top { + fill: var(--color-background); + } + .logo_svg__left { + fill: var(--color-background); + } + .logo_svg__line { + stroke: var(--color-background); + } + .logo_svg__border { + stroke: var(--color-text-normal); + } } `; diff --git a/frontend/src/index.css b/frontend/src/index.css index 2174c3d15..06f05db8e 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -109,6 +109,7 @@ --gray-tmp-1: var(--gray-100); --gray-tmp-2: var(--gray-200); + --gray-tmp-3: var(--gray-300); --gray-tmp-4: var(--gray-400); } @@ -144,6 +145,7 @@ --gray-tmp-1: var(--gray-700); --gray-tmp-2: var(--gray-700); + --gray-tmp-3: var(--gray-600); --gray-tmp-4: var(--gray-500); } } diff --git a/frontend/src/pages/admin/AdminHomePage.tsx b/frontend/src/pages/admin/AdminHomePage.tsx index 188c2a1f2..bb8b335a0 100644 --- a/frontend/src/pages/admin/AdminHomePage.tsx +++ b/frontend/src/pages/admin/AdminHomePage.tsx @@ -173,7 +173,7 @@ const ContainerStyled = styled.div` width: 100%; min-width: 0; min-height: 0; - background: var(--white); + background: var(--color-background); display: flex; justify-content: center; align-items: center; @@ -210,7 +210,7 @@ const ContainerStyled = styled.div` `; const AdminHomeStyled = styled.div` - background: var(--white); + background: var(--color-background); width: 100%; height: 100%; display: grid; From e0df5c6323edc7afec4ea08de37d4fe0529e9f1c Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 7 Mar 2024 20:54:44 +0900 Subject: [PATCH 0454/1029] =?UTF-8?q?[FE]=20FIX:=20light=20mode=EC=9D=98?= =?UTF-8?q?=20gray=20500=EC=9D=84=20gray=20tmp=205=EB=A1=9C=20=EB=8C=80?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AdminInfo/Chart/PieChart.tsx | 2 +- .../components/AdminInfo/Table/Pagination.tsx | 4 ++-- .../components/Available/FloorContainer.tsx | 2 +- .../CabinetInfoArea/AdminCabinetInfoArea.tsx | 18 +++++++++++++++--- .../CabinetInfoArea/CabinetInfoArea.tsx | 4 ++-- frontend/src/components/Card/Card.tsx | 2 +- .../ClubCabinetInfoCard.tsx | 2 +- .../Card/LentInfoCard/LentInfoCard.tsx | 5 ++--- .../Card/ProfileCard/ProfileCard.tsx | 2 +- frontend/src/components/Club/ClubInfo.tsx | 3 +-- frontend/src/components/Club/ClubLogTable.tsx | 2 +- .../ClubMemberInfoArea/ClubMemberInfoArea.tsx | 2 +- frontend/src/components/Common/Button.tsx | 8 ++++---- .../CabinetColorTable/CabinetColorTable.tsx | 2 +- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 18 +++++++++--------- .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 8 ++++---- .../LeftSectionNav/LeftSectionNavClubs.tsx | 2 +- frontend/src/components/Search/NoSearch.tsx | 2 +- .../src/components/Search/SearchDefault.tsx | 2 +- .../components/Search/SearchItemByIntraId.tsx | 2 +- .../src/components/Search/SearchItemByNum.tsx | 2 +- .../SectionPagination/SectionPagination.tsx | 2 +- .../components/TopNav/SearchBar/SearchBar.tsx | 6 +++--- .../SearchBar/SearchBarList/SearchBarList.tsx | 2 +- frontend/src/components/TopNav/TopNav.tsx | 2 +- .../components/UserInfoArea/UserInfoArea.tsx | 4 ++-- frontend/src/index.css | 2 ++ frontend/src/pages/ClubPage.tsx | 2 +- frontend/src/pages/admin/AdminPagination.tsx | 6 +++--- 29 files changed, 66 insertions(+), 54 deletions(-) diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/components/AdminInfo/Chart/PieChart.tsx index 851aa65eb..4a50cbacb 100644 --- a/frontend/src/components/AdminInfo/Chart/PieChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/PieChart.tsx @@ -96,7 +96,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { itemsSpacing: 5, itemWidth: 70, itemHeight: 18, - itemTextColor: "var(--gray-500)", + itemTextColor: "var(--gray-tmp-5)", itemDirection: "top-to-bottom", itemOpacity: 1, symbolSize: 12, diff --git a/frontend/src/components/AdminInfo/Table/Pagination.tsx b/frontend/src/components/AdminInfo/Table/Pagination.tsx index af239ed14..5f09aa0e0 100644 --- a/frontend/src/components/AdminInfo/Table/Pagination.tsx +++ b/frontend/src/components/AdminInfo/Table/Pagination.tsx @@ -40,8 +40,8 @@ const PageButtonStyled = styled.div` `; const ActivaPageButtonStyled = styled(PageButtonStyled)` - background: var(--gray-500); - border: 2px solid var(--gray-500); + background: var(--gray-tmp-5); + border: 2px solid var(--gray-tmp-5); `; const ButtonContainerStyled = styled.div` diff --git a/frontend/src/components/Available/FloorContainer.tsx b/frontend/src/components/Available/FloorContainer.tsx index b6455cfda..d55713790 100644 --- a/frontend/src/components/Available/FloorContainer.tsx +++ b/frontend/src/components/Available/FloorContainer.tsx @@ -91,7 +91,7 @@ const NoAvailableCabinetMessageStyled = styled.div<{ isToggled: boolean }>` margin-top: 20px; margin-left: 5px; p { - color: var(--gray-500); + color: var(--gray-tmp-5); line-height: 1.5; word-break: keep-all; } diff --git a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 20cfcc349..73606567c 100644 --- a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -67,7 +67,7 @@ const AdminCabinetInfoArea: React.FC<{ - + 사물함/유저를
선택해주세요
@@ -77,7 +77,7 @@ const AdminCabinetInfoArea: React.FC<{ if (multiSelectTargetInfo) { return ( - + {currentFloor + "F - " + currentSection} @@ -129,7 +129,7 @@ const AdminCabinetInfoArea: React.FC<{ return ( 대여기록 - + {selectedCabinetInfo!.floor + "F - " + selectedCabinetInfo!.section} - + 사물함을
선택해주세요
) : ( - + {selectedCabinetInfo!.floor !== 0 ? selectedCabinetInfo!.floor + "F - " + selectedCabinetInfo!.section : "-"} diff --git a/frontend/src/components/Card/Card.tsx b/frontend/src/components/Card/Card.tsx index b2dc65019..c0c1c85d7 100644 --- a/frontend/src/components/Card/Card.tsx +++ b/frontend/src/components/Card/Card.tsx @@ -114,7 +114,7 @@ export const CardButtonStyled = styled.div<{ ? props.color : props.isExtensible ? "var(--main-color)" - : "var(--gray-500)"}; + : "var(--gray-tmp-5)"}; padding: 5px 15px; border: none; border-radius: 5px; diff --git a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx b/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx index 152cfaa90..3642b59f3 100644 --- a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx +++ b/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx @@ -59,7 +59,7 @@ const ClubCabinetInfoCard = ({ {clubInfo.clubName} {clubInfo.floor + "층 - " + clubInfo.section} diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx index d66f0412a..fabc3dfbf 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx @@ -54,7 +54,7 @@ const LentInfoCard = ({ {cabinetInfo.floor !== 0 ? cabinetInfo.floor + "층 - " + cabinetInfo.section @@ -149,8 +149,7 @@ const CabinetRectangleStyled = styled.div<{ ? "var(--color-background)" : props.status === "IN_SESSION" ? "var(--main-color)" - : "var(--color-text-normal)" - }; + : "var(--color-text-normal)"}; font-size: 2rem; text-align: center; `; diff --git a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx b/frontend/src/components/Card/ProfileCard/ProfileCard.tsx index 062e344ea..c9deca6ce 100644 --- a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx +++ b/frontend/src/components/Card/ProfileCard/ProfileCard.tsx @@ -75,7 +75,7 @@ const ProfileDetail = styled.div` const EmailDetail = styled(ProfileDetail)` font-size: 0.9rem; - color: var(--gray-500); + color: var(--gray-tmp-5); `; export default ProfileCard; diff --git a/frontend/src/components/Club/ClubInfo.tsx b/frontend/src/components/Club/ClubInfo.tsx index 624a1ca17..8a65a3f14 100644 --- a/frontend/src/components/Club/ClubInfo.tsx +++ b/frontend/src/components/Club/ClubInfo.tsx @@ -58,8 +58,7 @@ const EmptyClubCabinetTextStyled = styled.div` justify-content: center; align-items: center; font-size: 1.125rem; - color: var(--gray-500); - /* margin-top: 20px; */ + color: var(--gray-tmp-5); `; const SadCcabiStyled = styled.div` diff --git a/frontend/src/components/Club/ClubLogTable.tsx b/frontend/src/components/Club/ClubLogTable.tsx index 59e0686f5..795ec372f 100644 --- a/frontend/src/components/Club/ClubLogTable.tsx +++ b/frontend/src/components/Club/ClubLogTable.tsx @@ -77,7 +77,7 @@ const TheadStyled = styled.thead` `; const TbodyStyled = styled.tbody` - color: var(--gray-500); + color: var(--gray-tmp-5); & > tr { text-align: center; height: 50px; diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx index 7672a39d6..fedf3a1ad 100644 --- a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx +++ b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -51,7 +51,7 @@ const ClubMemberInfoArea = ({ - + 동아리를
선택해주세요
diff --git a/frontend/src/components/Common/Button.tsx b/frontend/src/components/Common/Button.tsx index df3161094..e79c38b0b 100644 --- a/frontend/src/components/Common/Button.tsx +++ b/frontend/src/components/Common/Button.tsx @@ -66,8 +66,8 @@ const ButtonContainerStyled = styled.button` props.theme === "grayLine" && css` background: var(--color-background); - color: var(--gray-500); - border: 1px solid var(--gray-500); + color: var(--gray-tmp-5); + border: 1px solid var(--gray-tmp-5); `} ${(props) => props.theme === "smallGrayLine" && @@ -75,9 +75,9 @@ const ButtonContainerStyled = styled.button` max-width: 200px; height: 40px; background: var(--color-background); - color: var(--gray-500); + color: var(--gray-tmp-5); font-size: 0.875rem; - border: 1px solid var(--gray-500); + border: 1px solid var(--gray-tmp-5); `} @media (max-height: 745px) { diff --git a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx index cd090f741..393184752 100644 --- a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx +++ b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx @@ -36,7 +36,7 @@ const ColorTableStyled = styled.div` left: 30px; width: auto; overflow: hidden; - color: var(--gray-500); + color: var(--gray-tmp-5); `; const ColorTableItemStyled = styled.div<{ color: string }>` diff --git a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx index 60bee56ed..fabde5094 100644 --- a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx +++ b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -88,7 +88,7 @@ const LeftMainNav = ({ } onClick={onClickSearchButton} > - + Search @@ -97,7 +97,7 @@ const LeftMainNav = ({ target="_blank" title="슬랙 캐비닛 채널 새창으로 열기" > - + Contact @@ -109,14 +109,14 @@ const LeftMainNav = ({ } onClick={onClickAdminClubButton} > - + Club - + Logout @@ -131,7 +131,7 @@ const LeftMainNav = ({ } onClick={onClickMainClubButton} > - + Clubs - + Profile @@ -182,7 +182,7 @@ const TopBtnStyled = styled.li` font-weight: 300; margin-bottom: 2.5vh; border-radius: 10px; - color: var(--gray-500); + color: var(--gray-tmp-5); cursor: pointer; &:last-child { margin-bottom: 0; @@ -223,7 +223,7 @@ const BottomBtnStyled = styled.li` font-weight: 300; margin-top: 2.5vh; border-radius: 10px; - color: var(--gray-500); + color: var(--gray-tmp-5); cursor: pointer; display: flex; flex-direction: column; @@ -232,7 +232,7 @@ const BottomBtnStyled = styled.li` margin-top: 0; } & a { - color: var(--gray-500); + color: var(--gray-tmp-5); } & div { width: 24px; diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index 76149c817..a6ea55f50 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -76,14 +76,14 @@ const LeftSectionNav = ({ title="슬랙 캐비닛 채널 새창으로 열기" > 문의하기 - + onClickClubForm()} title="동아리 사물함 사용 신청서 새창으로 열기" > 동아리 신청서 - + {isClub && } @@ -129,7 +129,7 @@ export const FloorSectionStyled = styled.div` line-height: 40px; border-radius: 10px; text-indent: 20px; - color: var(--gray-500); + color: var(--gray-tmp-5); margin: 2px 0; cursor: pointer; @media (hover: hover) and (pointer: fine) { @@ -150,7 +150,7 @@ const SectionLinkStyled = styled.div` cursor: pointer; display: flex; align-items: center; - color: var(--gray-500); + color: var(--gray-tmp-5); #linknImg { width: 15px; diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx index 3254c3ed2..700a74635 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx +++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx @@ -60,7 +60,7 @@ const ClubLeftNavOptionStyled = styled.div` `; const ListTitleStyled = styled.div` - color: var(--gray-500); + color: var(--gray-tmp-5); font-size: 0.9rem; margin: 0.5rem 0.75rem; `; diff --git a/frontend/src/components/Search/NoSearch.tsx b/frontend/src/components/Search/NoSearch.tsx index 6a7fd4ae6..8fe98057d 100644 --- a/frontend/src/components/Search/NoSearch.tsx +++ b/frontend/src/components/Search/NoSearch.tsx @@ -28,7 +28,7 @@ const NoSearchStyled = styled.div` & > p { margin-top: 10px; font-size: 1.125rem; - color: var(--gray-500); + color: var(--gray-tmp-5); } `; diff --git a/frontend/src/components/Search/SearchDefault.tsx b/frontend/src/components/Search/SearchDefault.tsx index a94058bb6..3527eed44 100644 --- a/frontend/src/components/Search/SearchDefault.tsx +++ b/frontend/src/components/Search/SearchDefault.tsx @@ -29,7 +29,7 @@ const SearchDefaultStyled = styled.div` & > p { margin-top: 10px; font-size: 1.125rem; - color: var(--gray-500); + color: var(--gray-tmp-5); text-align: center; line-height: 1.75rem; } diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/components/Search/SearchItemByIntraId.tsx index 66d83e481..1ef5b4cf4 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/components/Search/SearchItemByIntraId.tsx @@ -187,7 +187,7 @@ const TextWrapper = styled.div` const LocationStyled = styled.p` font-size: 0.875rem; line-height: 28px; - color: var(--gray-500); + color: var(--gray-tmp-5); `; const NameWrapperStyled = styled.div` diff --git a/frontend/src/components/Search/SearchItemByNum.tsx b/frontend/src/components/Search/SearchItemByNum.tsx index 2fe96c139..a70f39e19 100644 --- a/frontend/src/components/Search/SearchItemByNum.tsx +++ b/frontend/src/components/Search/SearchItemByNum.tsx @@ -126,7 +126,7 @@ const TextWrapper = styled.div` const LocationStyled = styled.p` font-size: 0.875rem; line-height: 28px; - color: var(--gray-500); + color: var(--gray-tmp-5); `; const NameWrapperStyled = styled.div` diff --git a/frontend/src/components/SectionPagination/SectionPagination.tsx b/frontend/src/components/SectionPagination/SectionPagination.tsx index 13db5f22c..c4dc3466d 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.tsx +++ b/frontend/src/components/SectionPagination/SectionPagination.tsx @@ -94,7 +94,7 @@ const SectionNameTextStyled = styled.div` min-width: 220px; font-size: 1rem; text-align: center; - color: var(--gray-500); + color: var(--gray-tmp-5); `; const SectionIndexStyled = styled.div` diff --git a/frontend/src/components/TopNav/SearchBar/SearchBar.tsx b/frontend/src/components/TopNav/SearchBar/SearchBar.tsx index 24a7066e4..98ee529bb 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchBar.tsx +++ b/frontend/src/components/TopNav/SearchBar/SearchBar.tsx @@ -206,14 +206,14 @@ const SearchBarStyled = styled.div` const SearchBarInputStyled = styled.input` width: 300px; height: 40px; - border: 1px solid var(--gray-500); + border: 1px solid var(--gray-tmp-5); border-radius: 10px; text-align: left; padding: 0 20px; - color: var(--gray-500); + color: var(--gray-tmp-5); background-color: rgba(255, 255, 255, 0.2); &::placeholder { - color: var(--gray-500); + color: var(--gray-tmp-5); } `; diff --git a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx b/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx index 7de075045..436498851 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx +++ b/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx @@ -74,7 +74,7 @@ const UlStyled = styled.ul` const TotalStyled = styled.li` font-size: 0.875rem; - color: var(--gray-500); + color: var(--gray-tmp-5); text-align: right; padding: 10px; `; diff --git a/frontend/src/components/TopNav/TopNav.tsx b/frontend/src/components/TopNav/TopNav.tsx index 5aff019a6..f21989405 100644 --- a/frontend/src/components/TopNav/TopNav.tsx +++ b/frontend/src/components/TopNav/TopNav.tsx @@ -98,7 +98,7 @@ const TopNavContainerStyled = styled.nav` background-color: var(--color-background); border-bottom: 1px solid var(--color-line); padding: 0 28px; - color: var(--gray-500); + color: var(--gray-tmp-5); z-index: 10; `; diff --git a/frontend/src/components/UserInfoArea/UserInfoArea.tsx b/frontend/src/components/UserInfoArea/UserInfoArea.tsx index 148aec51b..1563c17ff 100644 --- a/frontend/src/components/UserInfoArea/UserInfoArea.tsx +++ b/frontend/src/components/UserInfoArea/UserInfoArea.tsx @@ -39,7 +39,7 @@ const UserInfoArea: React.FC<{ return ( - + 사물함/유저를
선택해주세요
@@ -49,7 +49,7 @@ const UserInfoArea: React.FC<{ return ( 대여기록 - + 대여 중이 아닌 사용자 ` width: 23px; height: 23px; border-radius: 100%; - border: 3px solid var(--gray-500); + border: 3px solid var(--gray-tmp-5); cursor: pointer; transition: 0.5s; &:hover { - background: var(--gray-500); + background: var(--gray-tmp-5); } - background: ${(props) => (props.active ? "var(--gray-500)" : "none")}; + background: ${(props) => (props.active ? "var(--gray-tmp-5)" : "none")}; `; const AdminPaginationStyled = styled.div<{ page: number }>` From 607dd2d3d24aaef1e1652408a8f299b7c9e39317 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 7 Mar 2024 21:08:05 +0900 Subject: [PATCH 0455/1029] =?UTF-8?q?[FE]=20FEAT:=20gray=20800=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20&=20gray=20tmp=201=EC=97=90=20700=EB=8C=80=EC=8B=A0?= =?UTF-8?q?=20=EB=84=A3=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/index.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index 78fbb3bbd..2b4c3088d 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -10,6 +10,7 @@ --gray-500: #7b7b7b; --gray-600: #3c3c3c; --gray-700: #262626; + --gray-800: #1f1f1f; --black: #181818; /* red color variable */ @@ -144,7 +145,7 @@ color: var(--color-text-normal); background-color: var(--color-background); - --gray-tmp-1: var(--gray-700); + --gray-tmp-1: var(--gray-800); --gray-tmp-2: var(--gray-700); --gray-tmp-3: var(--gray-600); --gray-tmp-4: var(--gray-500); From e629a3b6af4c570be36442e2a4126a2b0faaf090 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 7 Mar 2024 21:37:22 +0900 Subject: [PATCH 0456/1029] =?UTF-8?q?[FE]=20FIX:=20light=20mode=EC=9D=98?= =?UTF-8?q?=20gray=20600=EC=9D=84=20gray=20tmp=206=EB=A1=9C=20=EB=8C=80?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/AdminInfo/Chart/BarChart.tsx | 4 ++-- frontend/src/components/AdminInfo/Chart/PieChart.tsx | 6 +++--- frontend/src/components/Home/ServiceManual.tsx | 5 +---- frontend/src/index.css | 2 ++ 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/AdminInfo/Chart/BarChart.tsx b/frontend/src/components/AdminInfo/Chart/BarChart.tsx index 272ea160f..a2113d40d 100644 --- a/frontend/src/components/AdminInfo/Chart/BarChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/BarChart.tsx @@ -55,9 +55,9 @@ const BarChart = ({ data }: { data: IRentInfo[] }) => ( valueScale={{ type: "linear" }} indexScale={{ type: "band", round: true }} colors={[ - "var(--gray-600)", + "var(--banned)", "var(--expired)", - "var(--gray-300)", + "var(--full)", "var(--main-color)", ]} // 사용불가와 사용 중은 헷갈릴 수 있으니 색 그대로 diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/components/AdminInfo/Chart/PieChart.tsx index 4a50cbacb..7773afe74 100644 --- a/frontend/src/components/AdminInfo/Chart/PieChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/PieChart.tsx @@ -62,10 +62,10 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { }} margin={{ top: 40, right: 80, bottom: 80, left: 80 }} colors={[ - "var(--gray-300)", + "var(--full)", "var(--expired)", "var(--main-color)", - "var(--gray-600)", + "var(--banned)", ]} // 사용불가와 사용 중은 헷갈릴 수 있으니 색 그대로 innerRadius={0.5} @@ -78,7 +78,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { modifiers: [["darker", 0.2]], }} arcLinkLabelsSkipAngle={10} - arcLinkLabelsTextColor="var(--gray-600)" + arcLinkLabelsTextColor="var(--gray-tmp-6)" arcLinkLabelsThickness={2} arcLinkLabelsColor={{ from: "color" }} arcLabelsSkipAngle={10} diff --git a/frontend/src/components/Home/ServiceManual.tsx b/frontend/src/components/Home/ServiceManual.tsx index 50f4d8f9c..333c575cf 100644 --- a/frontend/src/components/Home/ServiceManual.tsx +++ b/frontend/src/components/Home/ServiceManual.tsx @@ -145,10 +145,7 @@ const NotionBtn = styled.button` height: 40px; border-radius: 8px; font-size: 0.875rem; - color: var(--gray-600); - /* light */ - color: var(--gray-300); - /* dark */ + color: var(--gray-tmp-6); background: var(--color-background); border: 1px solid var(--gray-tmp-3); :hover { diff --git a/frontend/src/index.css b/frontend/src/index.css index 2b4c3088d..a9cbc9a14 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -113,6 +113,7 @@ --gray-tmp-3: var(--gray-300); --gray-tmp-4: var(--gray-400); --gray-tmp-5: var(--gray-500); + --gray-tmp-6: var(--gray-600); } /* main color variable */ @@ -150,6 +151,7 @@ --gray-tmp-3: var(--gray-600); --gray-tmp-4: var(--gray-500); --gray-tmp-5: var(--gray-500); + --gray-tmp-6: var(--gray-300); } } From 767e44eea13bc9c9d395fda96653d6f08460ac86 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 7 Mar 2024 21:38:30 +0900 Subject: [PATCH 0457/1029] =?UTF-8?q?[FE]=20FIX:=20backgroundstyled=20?= =?UTF-8?q?=EB=B0=B0=EA=B2=BD=20=EA=B7=B8=EB=A6=BC=EC=9E=90=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Modals/ClubModal/ClubPasswordModal.tsx | 6 ++++-- frontend/src/components/Modals/Modal.tsx | 5 ++--- .../Modals/PasswordCheckModal/PasswordCheckModal.tsx | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx b/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx index 825de55e4..6bb724897 100644 --- a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx +++ b/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx @@ -166,8 +166,10 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--black); - opacity: 0.4; + /* background: var(--bg-black-shadow-300); */ + /* light */ + background: var(--bg-white-shadow-100); + /* dark */ animation: fadeInBg 0.5s; @keyframes fadeInBg { 0% { diff --git a/frontend/src/components/Modals/Modal.tsx b/frontend/src/components/Modals/Modal.tsx index de3ce339c..62abe587f 100644 --- a/frontend/src/components/Modals/Modal.tsx +++ b/frontend/src/components/Modals/Modal.tsx @@ -185,11 +185,10 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--color-text-normal); + /* background: var(--bg-black-shadow-300); */ /* light */ - background: var(--gray-600); + background: var(--bg-white-shadow-100); /* dark */ - opacity: 0.4; animation: fadeInBg 0.5s; @keyframes fadeInBg { 0% { diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx b/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx index 7b9b90ec8..f685a99b5 100644 --- a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx +++ b/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx @@ -152,8 +152,10 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--black); - opacity: 0.4; + /* background: var(--bg-black-shadow-300); */ + /* light */ + background: var(--bg-white-shadow-100); + /* dark */ animation: fadeInBg 0.5s; @keyframes fadeInBg { 0% { From 9e3992f8a1998fa768d4927bd78d0022e7a80cad Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 7 Mar 2024 21:40:05 +0900 Subject: [PATCH 0458/1029] =?UTF-8?q?[FE]=20FIX:=20=EB=8C=80=EC=97=AC?= =?UTF-8?q?=EC=A4=91=EC=9D=B8=20=EC=82=AC=EB=AC=BC=ED=95=A8=EC=9D=B4=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=EB=95=8C=20visible=20number=20=EC=82=AC?= =?UTF-8?q?=EA=B0=81=ED=98=95=20=EB=B0=B0=EA=B2=BD=EC=83=89=20=EB=B0=94?= =?UTF-8?q?=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx index fabc3dfbf..dda39a47b 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx @@ -68,8 +68,7 @@ const LentInfoCard = ({ /> {cabinetInfo.userNameList} @@ -143,7 +142,7 @@ const CabinetRectangleStyled = styled.div<{ ? "var(--expired)" : props.isLented ? "var(--mine)" - : "var(--full)"}; + : "var(--gray-tmp-2)"}; color: ${(props) => props.banned ? "var(--color-background)" From 6c58ff80c1e782c9c6e281af85bc0cbc0a25c078 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 7 Mar 2024 21:40:40 +0900 Subject: [PATCH 0459/1029] =?UTF-8?q?[FE]=20FIX:=20paginationIndexBar=20?= =?UTF-8?q?=EC=82=AC=EA=B0=81=ED=98=95=20=EC=83=89=20gray=20tmp=203?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/SectionPagination/SectionPagination.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/SectionPagination/SectionPagination.tsx b/frontend/src/components/SectionPagination/SectionPagination.tsx index c4dc3466d..67970637e 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.tsx +++ b/frontend/src/components/SectionPagination/SectionPagination.tsx @@ -24,7 +24,7 @@ const SectionPagination: React.FC<{ filledColor={ sectionName === currentSectionName ? "var(--main-color)" - : "var(--gray-600)" + : "var(--gray-tmp-3)" } onClick={() => changeSectionOnClickIndexButton(index)} className="cabiButton" From 74a62f8f58c3521d3c9b63434760f73974fdb989 Mon Sep 17 00:00:00 2001 From: SpaceChae <13278955+enaenen@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:26:30 +0900 Subject: [PATCH 0460/1029] =?UTF-8?q?[BE]=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=82=AC=EB=AC=BC=ED=95=A8=20=EC=97=AC=EB=9F=AC=EA=B0=9C=20?= =?UTF-8?q?=EB=B0=B0=EC=A0=95=20=EC=98=A4=EB=A5=98=20#1560=20(#1561)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BE] FIX: ADMIN 동아리 대여(할당) 전 이미 대여중인 사물함이 있는지 확인 * [BE]FIX: config change --- .../cabinet/admin/lent/service/AdminLentFacadeService.java | 2 +- .../ftclub/cabinet/lent/repository/ClubLentRepository.java | 2 ++ .../ftclub/cabinet/lent/service/ClubLentQueryService.java | 6 ++++++ .../org/ftclub/cabinet/lent/service/LentFacadeService.java | 1 + config | 2 +- 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java index c79fd6d4d..dbaf15cc5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/lent/service/AdminLentFacadeService.java @@ -118,7 +118,7 @@ public void endCabinetLent(List cabinetIds) { lentQueryService.findCabinetsActiveLentHistories(cabinetIds); Map> lentHistoriesByCabinetId = lentHistories.stream() .collect(Collectors.groupingBy(LentHistory::getCabinetId)); - + // is club cabinet if (lentHistories.isEmpty()) { endClubCabinetLent(cabinetIds, cabinets); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/repository/ClubLentRepository.java b/backend/src/main/java/org/ftclub/cabinet/lent/repository/ClubLentRepository.java index 761b71e79..dfe98ac9b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/repository/ClubLentRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/repository/ClubLentRepository.java @@ -39,4 +39,6 @@ public interface ClubLentRepository extends JpaRepository + "WHERE clh.cabinetId IN :cabinetIds " + "AND clh.endedAt IS NULL ") Optional> findByEndedAtIsNullJoinCabinets(List cabinetIds); + + Optional findByClubIdAndEndedAtIsNull(Long clubId); } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentQueryService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentQueryService.java index 231d02a94..75de20ce5 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/ClubLentQueryService.java @@ -16,6 +16,12 @@ public class ClubLentQueryService { private final ClubLentRepository clubLentRepository; + public void findAlreadyExistsClubLentHistory(Long clubId) { + clubLentRepository.findByClubIdAndEndedAtIsNull(clubId).ifPresent(clubLentHistory -> { + throw ExceptionStatus.LENT_ALREADY_EXISTED.asServiceException(); + }); + } + public ClubLentHistory findActiveLentHistoryWithClub(Long cabinetId) { return clubLentRepository.findByCabinetIdAndEndedAtIsNullJoinClub(cabinetId) .orElse(null); diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java index 11796f6df..9050ea7a7 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/service/LentFacadeService.java @@ -250,6 +250,7 @@ public void startLentClubCabinet(Long clubId, Long cabinetId) { LocalDateTime expiredAt = lentPolicyService.generateExpirationDate(now, cabinet.getLentType(), 1); + clubLentQueryService.findAlreadyExistsClubLentHistory(clubId); clubLentCommandService.startLent(clubId, cabinetId, expiredAt); cabinetCommandService.updateTitle(cabinet, club.getName()); cabinetCommandService.changeUserCount(cabinet, userCount + 1); diff --git a/config b/config index 71add80f8..d1eba89e0 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 71add80f85a59a4f395c8813507bdbcbffe6ce23 +Subproject commit d1eba89e00f99a59d4a11d503dce83b59ec666e8 From e17a5dc4096096d264d240ada1c46f5b54457981 Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Fri, 8 Mar 2024 20:58:11 +0900 Subject: [PATCH 0461/1029] [BE] FEAT: anonymous login --- .../auth/service/AuthFacadeService.java | 33 ++++++++++++++----- .../controller/LoginController.java | 30 +++++++++++++++++ 2 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/openpublic/controller/LoginController.java diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java index f65483994..bd4dce194 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AuthFacadeService.java @@ -1,5 +1,11 @@ package org.ftclub.cabinet.auth.service; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.concurrent.ExecutionException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.admin.admin.domain.Admin; import org.ftclub.cabinet.admin.admin.service.AdminCommandService; @@ -14,13 +20,6 @@ import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.stereotype.Service; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.concurrent.ExecutionException; - /** * 인증 관련 비즈니스 로직을 처리하는 서비스입니다. */ @@ -28,6 +27,7 @@ @RequiredArgsConstructor public class AuthFacadeService { + private static final String REDIRECT_COOKIE_NAME = "redirect"; private final UserQueryService userQueryService; private final UserCommandService userCommandService; private final AdminQueryService adminQueryService; @@ -35,10 +35,8 @@ public class AuthFacadeService { private final UserOauthService userOauthService; private final AdminOauthService adminOauthService; private final AuthPolicyService authPolicyService; - private final TokenProvider tokenProvider; private final CookieManager cookieManager; - private static final String REDIRECT_COOKIE_NAME = "redirect"; /** * 유저 로그인 페이지로 리다이렉트합니다. @@ -95,6 +93,23 @@ public void handleUserLogin(HttpServletRequest req, HttpServletResponse res, Str res.sendRedirect(authPolicyService.getMainHomeUrl()); } + public void handlePublicLogin(HttpServletRequest req, HttpServletResponse res, String name) + throws IOException { + + User user = userQueryService.findUser(name).orElseThrow( + ExceptionStatus.NOT_FOUND_USER::asServiceException); + String token = tokenProvider.createUserToken(user, LocalDateTime.now()); + Cookie cookie = cookieManager.cookieOf(TokenProvider.USER_TOKEN_NAME, token); + cookieManager.setCookieToClient(res, cookie, "/", req.getServerName()); + if (cookieManager.getCookieValue(req, REDIRECT_COOKIE_NAME) != null) { + String redirect = cookieManager.getCookieValue(req, REDIRECT_COOKIE_NAME); + cookieManager.deleteCookie(res, REDIRECT_COOKIE_NAME); + res.sendRedirect(redirect); + return; + } + res.sendRedirect(authPolicyService.getMainHomeUrl()); + } + /** * 관리자 로그인 콜백으로 받은 authorization_code로 관리자 프로필 정보를 가져오고, 반환합니다. *

diff --git a/backend/src/main/java/org/ftclub/cabinet/openpublic/controller/LoginController.java b/backend/src/main/java/org/ftclub/cabinet/openpublic/controller/LoginController.java new file mode 100644 index 000000000..0e52032d6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/openpublic/controller/LoginController.java @@ -0,0 +1,30 @@ +package org.ftclub.cabinet.openpublic.controller; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.alarm.config.AlarmProperties; +import org.ftclub.cabinet.auth.service.AuthFacadeService; +import org.ftclub.cabinet.log.Logging; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Logging +@RequiredArgsConstructor +@RequestMapping("/public") +@RestController +public class LoginController { + + private final AuthFacadeService authFacadeService; + private final AlarmProperties alarmProperties; + + @GetMapping("/login") + public void login(HttpServletRequest request, HttpServletResponse response) throws IOException { + final String username = "anonymous"; + if (!alarmProperties.getIsProduction()) { + authFacadeService.handlePublicLogin(request, response, username); + } + } +} From 25dfbd3f363c1e0cc0c0828cecae7d0f691ef3ea Mon Sep 17 00:00:00 2001 From: Woo Joo Chae Date: Sun, 10 Mar 2024 16:27:16 +0900 Subject: [PATCH 0462/1029] [BE] REFACTOR: anonymous login endpoint fix --- .../ftclub/cabinet/openpublic/controller/LoginController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/openpublic/controller/LoginController.java b/backend/src/main/java/org/ftclub/cabinet/openpublic/controller/LoginController.java index 0e52032d6..bd3b45164 100644 --- a/backend/src/main/java/org/ftclub/cabinet/openpublic/controller/LoginController.java +++ b/backend/src/main/java/org/ftclub/cabinet/openpublic/controller/LoginController.java @@ -13,14 +13,14 @@ @Logging @RequiredArgsConstructor -@RequestMapping("/public") +@RequestMapping("/demo") @RestController public class LoginController { private final AuthFacadeService authFacadeService; private final AlarmProperties alarmProperties; - @GetMapping("/login") + @GetMapping("") public void login(HttpServletRequest request, HttpServletResponse response) throws IOException { final String username = "anonymous"; if (!alarmProperties.getIsProduction()) { From 2407deb630f413cd4083f36a063e75c4aac2bac0 Mon Sep 17 00:00:00 2001 From: jusohn Date: Thu, 14 Mar 2024 14:40:11 +0900 Subject: [PATCH 0463/1029] =?UTF-8?q?[FE]=20HOTFIX:=20=ED=95=9C=EB=B2=88?= =?UTF-8?q?=EC=97=90=20=EC=9A=94=EC=B2=AD=ED=95=98=EB=8A=94=20=EB=8F=99?= =?UTF-8?q?=EC=95=84=EB=A6=AC=20=EB=A9=A4=EB=B2=84=20=EC=88=98=EB=A5=BC=20?= =?UTF-8?q?100=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useClubInfo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/hooks/useClubInfo.ts b/frontend/src/hooks/useClubInfo.ts index 5e21043c2..32081ba21 100644 --- a/frontend/src/hooks/useClubInfo.ts +++ b/frontend/src/hooks/useClubInfo.ts @@ -36,7 +36,7 @@ const useClubInfo = () => { const { data }: { data: ClubInfoResponseDto } = await axiosGetClubInfo( clubId, page, - 2 + 100 ); setTargetClubInfo({ clubId, From 6a575bffc39588359dcdbdb98dec59f311765866 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 19 Mar 2024 17:22:16 +0900 Subject: [PATCH 0464/1029] =?UTF-8?q?[FE]=20FEAT:=20--bg-shadow-100=20&=20?= =?UTF-8?q?200=20=EB=8B=A4=20=EB=8B=A4=EB=A3=B8=20#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- frontend/src/assets/css/media.css | 34 ++----------------- .../components/Announce/AnnounceTemplate.tsx | 3 +- .../ClubMemberInfoArea/ClubMemberInfoArea.tsx | 11 +++--- .../ClubMemberList.container.tsx | 4 +++ .../ClubMemberListItem/ClubMemberListItem.tsx | 5 +-- frontend/src/components/LentLog/LentLog.tsx | 1 + .../MapFloorSelectOption.tsx | 2 +- frontend/src/index.css | 3 +- frontend/src/pages/Layout.tsx | 3 +- 10 files changed, 25 insertions(+), 43 deletions(-) diff --git a/config b/config index 4346701b6..d1eba89e0 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 4346701b6f540b1385096a26fc839950fdffa39a +Subproject commit d1eba89e00f99a59d4a11d503dce83b59ec666e8 diff --git a/frontend/src/assets/css/media.css b/frontend/src/assets/css/media.css index 025b67695..e55c20147 100644 --- a/frontend/src/assets/css/media.css +++ b/frontend/src/assets/css/media.css @@ -15,7 +15,7 @@ box-shadow: 0 0 40px 0 var(--bg-shadow-200); } - #menuBg { + /* #menuBg { display: block; position: fixed; left: 0; @@ -25,25 +25,10 @@ background-color: var(--bg-shadow-200); transform: translateX(-100%); z-index: 8; - } + } */ #menuBg.on { transform: translateX(0%); } - - /* cabinetDetailArea(rightSection) */ - #cabinetDetailArea { - position: fixed; - top: 80px; - right: 0; - height: calc(100% - 80px); - z-index: 9; - transform: translateX(120%); - transition: transform 0.3s ease-in-out; - box-shadow: 0 0 40px 0 var(--bg-shadow-200); - } - #cabinetDetailArea.on { - transform: translateX(0%); - } } /* mapInfo */ @@ -51,21 +36,6 @@ transform: translateX(0%); } -/* ClubMemberInfoArea(rightSection) */ -/* #clubMemberInfoArea { - position: fixed; - top: 80px; - right: 0; - height: calc(100% - 80px); - z-index: 9; - transform: translateX(120%); - transition: transform 0.3s ease-in-out; - box-shadow: 0 0 40px 0 var(--bg-shadow-200); - } */ -#clubMemberInfoArea.on { - transform: translateX(0%); -} - /* search */ @media (max-width: 768px) { #searchBar { diff --git a/frontend/src/components/Announce/AnnounceTemplate.tsx b/frontend/src/components/Announce/AnnounceTemplate.tsx index 3716d26e5..815128970 100644 --- a/frontend/src/components/Announce/AnnounceTemplate.tsx +++ b/frontend/src/components/Announce/AnnounceTemplate.tsx @@ -122,7 +122,8 @@ const CabiImgStyled = styled.div` `; const ButtonStyled = styled.button` - box-shadow: 10px 10px 40px 0px var(--bg-shadow-200); + box-shadow: 10px 10px 40px 0px var(--bg-black-shadow-200); + /* black shadow 여야함! */ `; export default AnnounceTemplate; diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx index fedf3a1ad..21c0f6636 100644 --- a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx +++ b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -134,18 +134,21 @@ const ClubMemberInfoAreaStyled = styled.div` position: fixed; top: 80px; right: 0; - min-width: 330px; - width: 330px; height: calc(100% - 80px); - padding: 40px; z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; - box-shadow: 0 0 40px 0 var(--bg-shadow-200); + min-width: 330px; + width: 330px; + padding: 40px; display: flex; flex-direction: column; align-items: center; background: var(--color-background); + box-shadow: 0 0 40px 0 var(--bg-shadow-200); + &.on { + transform: translateX(0%); + } `; const TextStyled = styled.p<{ diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx b/frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx index cbb7ad10a..8ed8ea2b7 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx +++ b/frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx @@ -43,6 +43,10 @@ const ClubMemberListContainer = ({ const selectClubMemberOnClick = (member: ClubUserResponseDto) => { if (targetClubUser.userId === member.userId) { closeClubMember(); + setTargetClubUser({ + userId: 0, + userName: "", + }); return; } setTargetClubUser(member); diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx b/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx index e3dcdc11a..f3692cd70 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx +++ b/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx @@ -57,8 +57,9 @@ const MemberListItemContainerStyled = styled.div<{ css` opacity: 0.9; transform: scale(1.05); - box-shadow: inset 5px 5px 5px var(--bg-shadow-200), - 0px 4px 4px var(--bg-shadow-200); + box-shadow: inset 5px 5px 5px var(--bg-black-shadow-200), + 0px 4px 4px var(--bg-black-shadow-200); + /* black shadow 여야함! */ `} @media (hover: hover) and (pointer: fine) { diff --git a/frontend/src/components/LentLog/LentLog.tsx b/frontend/src/components/LentLog/LentLog.tsx index 8dc904944..c1b5934de 100644 --- a/frontend/src/components/LentLog/LentLog.tsx +++ b/frontend/src/components/LentLog/LentLog.tsx @@ -2,6 +2,7 @@ import styled, { css } from "styled-components"; import LogTable from "@/components/LentLog/LogTable/LogTable"; import { ILentLog } from "@/types/dto/lent.dto"; +// 더 이상 안쓰는 컴포넌트 const LentLog = ({ closeLent, logs, diff --git a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx b/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx index 4452d16ef..fa4d31e31 100644 --- a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx +++ b/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx @@ -25,7 +25,7 @@ const OptionWrapperStyled = styled.div` top: 75px; background: var(--color-background); border-radius: 10px; - box-shadow: 0 0 10px 0 var(--bg-shadow-200); + box-shadow: 0 0 10px 0 var(--bg-shadow-100); overflow: hidden; z-index: 99; display: none; diff --git a/frontend/src/index.css b/frontend/src/index.css index a9cbc9a14..3ca8d1d6f 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -241,7 +241,8 @@ input { .modal { border-radius: 10px; - box-shadow: 10px 10px 40px 0px var(--bg-shadow-200); + box-shadow: 10px 10px 40px 0px var(--bg-black-shadow-200); + /* black shadow 여야함! */ } .textNowrap { diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index 480f6346b..97ea73090 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -136,7 +136,8 @@ const Layout = (): JSX.Element => { - + {/* */} + {/* TODO : 없어져도 되지 않을까? */} Date: Tue, 19 Mar 2024 18:57:16 +0900 Subject: [PATCH 0465/1029] =?UTF-8?q?[FE]=20FIX:=20--bg-shadow-300=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=B2=98=20=ED=99=95=EC=9D=B8=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20100=EC=9C=BC=EB=A1=9C=20=EC=A1=B0=EC=A0=95#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/css/media.css | 5 ----- frontend/src/components/AdminInfo/Table/AdminTable.tsx | 5 +++-- frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx | 2 +- .../src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx | 1 + .../src/components/Card/ThemeColorCard/ThemeColorCard.tsx | 2 +- .../Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx | 1 + frontend/src/components/MapInfo/MapInfo.tsx | 4 ++++ .../src/components/Modals/ClubModal/AddClubMemberModal.tsx | 2 +- frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx | 2 +- frontend/src/components/Modals/ClubModal/ClubModal.tsx | 2 +- frontend/src/components/Modals/MemoModal/MemoModal.tsx | 2 +- 11 files changed, 15 insertions(+), 13 deletions(-) diff --git a/frontend/src/assets/css/media.css b/frontend/src/assets/css/media.css index e55c20147..da444ba20 100644 --- a/frontend/src/assets/css/media.css +++ b/frontend/src/assets/css/media.css @@ -31,11 +31,6 @@ } } -/* mapInfo */ -#mapInfo.on { - transform: translateX(0%); -} - /* search */ @media (max-width: 768px) { #searchBar { diff --git a/frontend/src/components/AdminInfo/Table/AdminTable.tsx b/frontend/src/components/AdminInfo/Table/AdminTable.tsx index 6d8735ad0..f95a46cb6 100644 --- a/frontend/src/components/AdminInfo/Table/AdminTable.tsx +++ b/frontend/src/components/AdminInfo/Table/AdminTable.tsx @@ -80,7 +80,7 @@ const TableWrapperStyled = styled.div` width: 80%; height: 100%; margin: 0 auto; - background: var(--white); + background: var(--color-background); `; const TableBorderStyled = styled.div` @@ -91,6 +91,7 @@ const TableBorderStyled = styled.div` const TableStyled = styled.table` width: 100%; + background: var(--color-background); background: var(--white); overflow: scroll; @@ -104,7 +105,7 @@ const TheadStyled = styled.thead` height: 45px; line-height: 45px; background-color: var(--main-color); - color: var(--white); + color: var(--color-background); `; const TbodyStyled = styled.tbody` diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx index 790a0627e..286b3c105 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -391,9 +391,9 @@ const HoverBox = styled.div<{ background-color: rgba(73, 73, 73, 0.99); border-radius: 10px; box-shadow: 4px 4px 20px 0px var(--bg-shadow-300); + /* TODO : HoverBox 안쓰면 지우고, 쓰면 바꾸기 */ font-size: 0.875rem; text-align: center; - color: var(--color-background); display: flex; flex-direction: column; justify-content: space-around; diff --git a/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx b/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx index 87a8f3ce7..1456f757e 100644 --- a/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx +++ b/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx @@ -61,6 +61,7 @@ const HoverBox = styled.div` background-color: rgba(73, 73, 73, 0.99); border-radius: 10px; box-shadow: 4px 4px 20px 0px var(--bg-shadow-300); + /* TODO : HoverBox 안쓰면 지우고, 쓰면 바꾸기 */ font-size: 0.75rem; color: var(--color-background); display: flex; diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx index 10af9cab0..864fe8278 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -110,7 +110,7 @@ const BackgroundOverlayStyled = styled.div` left: 0; width: 100%; height: 100%; - background: var(--bg-shadow-300); + background: var(--bg-shadow-100); z-index: 1; `; diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx index 21c0f6636..4ab3dce57 100644 --- a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx +++ b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -146,6 +146,7 @@ const ClubMemberInfoAreaStyled = styled.div` align-items: center; background: var(--color-background); box-shadow: 0 0 40px 0 var(--bg-shadow-200); + border-left: 1px solid var(--color-line); &.on { transform: translateX(0%); } diff --git a/frontend/src/components/MapInfo/MapInfo.tsx b/frontend/src/components/MapInfo/MapInfo.tsx index 7e51b2099..0922e0c26 100644 --- a/frontend/src/components/MapInfo/MapInfo.tsx +++ b/frontend/src/components/MapInfo/MapInfo.tsx @@ -68,6 +68,10 @@ const MapInfoContainerStyled = styled.div` flex-direction: column; align-items: center; background: var(--color-background); + border-left: 1px solid var(--color-line); + &.on { + transform: translateX(0%); + } `; export default MapInfo; diff --git a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx b/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx index 878bd1c92..20f286dd4 100644 --- a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx +++ b/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx @@ -123,7 +123,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-300); + background: var(--bg-shadow-100); z-index: 1000; `; diff --git a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx b/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx index 8a2d241af..d3b7b6189 100644 --- a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx +++ b/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx @@ -147,7 +147,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-300); + background: var(--bg-shadow-100); z-index: 1000; `; diff --git a/frontend/src/components/Modals/ClubModal/ClubModal.tsx b/frontend/src/components/Modals/ClubModal/ClubModal.tsx index 5369105a7..435585b42 100644 --- a/frontend/src/components/Modals/ClubModal/ClubModal.tsx +++ b/frontend/src/components/Modals/ClubModal/ClubModal.tsx @@ -292,7 +292,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-300); + background: var(--bg-shadow-100); z-index: 1000; `; diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/components/Modals/MemoModal/MemoModal.tsx index 1f5094a0f..e2a8a26ee 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/components/Modals/MemoModal/MemoModal.tsx @@ -193,7 +193,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-300); + background: var(--bg-shadow-100); z-index: 1000; `; From 21ec4a2af689ff0a3229901bb2b13ad3782ac644 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 19 Mar 2024 19:38:23 +0900 Subject: [PATCH 0466/1029] =?UTF-8?q?[FE]=20FIX:=20media.css=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=EA=B1=B0=20=EB=B3=B5=EA=B5=AC#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/css/media.css | 20 +++++++++++++++++--- frontend/src/pages/Layout.tsx | 3 +-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/src/assets/css/media.css b/frontend/src/assets/css/media.css index da444ba20..b0f22e802 100644 --- a/frontend/src/assets/css/media.css +++ b/frontend/src/assets/css/media.css @@ -15,20 +15,34 @@ box-shadow: 0 0 40px 0 var(--bg-shadow-200); } - /* #menuBg { + #menuBg { display: block; position: fixed; left: 0; top: 0; width: 100%; height: 100%; - background-color: var(--bg-shadow-200); + background-color: var(--bg-shadow-100); transform: translateX(-100%); z-index: 8; - } */ + } #menuBg.on { transform: translateX(0%); } + + #cabinetDetailArea { + position: fixed; + top: 80px; + right: 0; + height: calc(100% - 80px); + z-index: 9; + transform: translateX(120%); + transition: transform 0.3s ease-in-out; + box-shadow: 0 0 40px 0 var(--bg-shadow-200); + } + #cabinetDetailArea.on { + transform: translateX(0%); + } } /* search */ diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index 97ea73090..480f6346b 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -136,8 +136,7 @@ const Layout = (): JSX.Element => { - {/* */} - {/* TODO : 없어져도 되지 않을까? */} + Date: Thu, 21 Mar 2024 16:16:38 +0900 Subject: [PATCH 0467/1029] =?UTF-8?q?[FE]=20FIX:=20react-ga=20=EC=95=88?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20import=20=EC=82=AD=EC=A0=9C=20#1565?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/NotificationCard/NotificationCard.container.tsx | 1 - frontend/src/components/Search/SearchItemByIntraId.tsx | 1 - frontend/src/pages/Layout.tsx | 1 - 3 files changed, 3 deletions(-) diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx index d10d3bb62..6539a4dcb 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx @@ -3,7 +3,6 @@ import { requestFcmAndGetDeviceToken, } from "@/firebase/firebase-messaging-sw"; import { useEffect, useMemo, useState } from "react"; -import { set } from "react-ga"; import NotificationCard from "@/components/Card/NotificationCard/NotificationCard"; import ModalPortal from "@/components/Modals/ModalPortal"; import { diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/components/Search/SearchItemByIntraId.tsx index c09a3c399..a061463d0 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/components/Search/SearchItemByIntraId.tsx @@ -1,4 +1,3 @@ -import { set } from "react-ga"; import { useRecoilState, useResetRecoilState, useSetRecoilState } from "recoil"; import styled, { css } from "styled-components"; import { diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index df5a72a58..5b6e6c440 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from "react"; -import { set } from "react-ga"; import { Outlet } from "react-router"; import { useLocation, useNavigate } from "react-router-dom"; import { useSetRecoilState } from "recoil"; From 5d94d29279c7f091af6f3b930ff7235f13cb3001 Mon Sep 17 00:00:00 2001 From: 42inshin Date: Thu, 21 Mar 2024 17:43:34 +0900 Subject: [PATCH 0468/1029] =?UTF-8?q?[FE]=20FEAT:=20react-ga4=EC=A0=81?= =?UTF-8?q?=EC=9A=A9,=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#1565?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 14 -------------- frontend/package-lock.json | 23 +++++++++-------------- frontend/package.json | 2 +- frontend/src/App.tsx | 4 ++++ frontend/src/TrackPageView.tsx | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 29 deletions(-) create mode 100644 frontend/src/TrackPageView.tsx diff --git a/frontend/index.html b/frontend/index.html index bd6807355..5406b186c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,20 +1,6 @@ - - - diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f5163359f..ddf1b4764 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,7 +19,7 @@ "react-color": "^2.19.3", "react-cookie": "^4.1.1", "react-dom": "^18.2.0", - "react-ga": "^3.3.1", + "react-ga4": "^2.1.0", "react-router-dom": "^6.5.0", "recoil": "^0.7.6", "recoil-persist": "^4.2.0", @@ -9223,14 +9223,10 @@ "react": "^18.2.0" } }, - "node_modules/react-ga": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/react-ga/-/react-ga-3.3.1.tgz", - "integrity": "sha512-4Vc0W5EvXAXUN/wWyxvsAKDLLgtJ3oLmhYYssx+YzphJpejtOst6cbIHCIyF50Fdxuf5DDKqRYny24yJ2y7GFQ==", - "peerDependencies": { - "prop-types": "^15.6.0", - "react": "^15.6.2 || ^16.0 || ^17 || ^18" - } + "node_modules/react-ga4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", + "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" }, "node_modules/react-is": { "version": "18.2.0", @@ -17588,11 +17584,10 @@ "scheduler": "^0.23.0" } }, - "react-ga": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/react-ga/-/react-ga-3.3.1.tgz", - "integrity": "sha512-4Vc0W5EvXAXUN/wWyxvsAKDLLgtJ3oLmhYYssx+YzphJpejtOst6cbIHCIyF50Fdxuf5DDKqRYny24yJ2y7GFQ==", - "requires": {} + "react-ga4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", + "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" }, "react-is": { "version": "18.2.0", diff --git a/frontend/package.json b/frontend/package.json index 0b46a3213..ba68dfef1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,7 +25,7 @@ "react-color": "^2.19.3", "react-cookie": "^4.1.1", "react-dom": "^18.2.0", - "react-ga": "^3.3.1", + "react-ga4": "^2.1.0", "react-router-dom": "^6.5.0", "recoil": "^0.7.6", "recoil-persist": "^4.2.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 54c569ef9..9294984ed 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,6 @@ +import TrackPageView from "@/TrackPageView"; import React, { Suspense, lazy } from "react"; +import ReactGA from "react-ga4"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import AvailablePage from "@/pages/AvailablePage"; import ClubPage from "@/pages/ClubPage"; @@ -26,6 +28,8 @@ const AdminHomePage = lazy(() => import("@/pages/admin/AdminHomePage")); function App(): React.ReactElement { return ( + {/* GA4 Page Traking */} + }> } /> diff --git a/frontend/src/TrackPageView.tsx b/frontend/src/TrackPageView.tsx new file mode 100644 index 000000000..ada252527 --- /dev/null +++ b/frontend/src/TrackPageView.tsx @@ -0,0 +1,32 @@ +import { useEffect, useState } from "react"; +import ReactGA from "react-ga4"; +import { useLocation } from "react-router-dom"; + +const TrackPageView = () => { + const location = useLocation(); + const [initialized, setInitialized] = useState(false); + + useEffect(() => { + if ( + !initialized && + import.meta.env.VITE_GA_TRACKING_ID && + window.location.hostname.includes("localhost") + ) { + ReactGA.initialize(`${import.meta.env.VITE_GA_TRACKING_ID}`); + setInitialized(true); + } + }, []); + + useEffect(() => { + if (!initialized) return; + ReactGA.send({ + hitType: "pageview", + page: location.pathname, + title: location.pathname, + }); + }, [initialized, location]); + + return null; +}; + +export default TrackPageView; From 4d8900f152698c391a61d75099058ef2bf4310c0 Mon Sep 17 00:00:00 2001 From: 42inshin Date: Thu, 21 Mar 2024 17:48:48 +0900 Subject: [PATCH 0469/1029] =?UTF-8?q?[FE]=20FIX:=20env=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20#1565?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index d1eba89e0..0a56e746e 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit d1eba89e00f99a59d4a11d503dce83b59ec666e8 +Subproject commit 0a56e746e0ce51ca4b312e4f0610a3882377e7db From 83bc3554c8aca5f369121ff98d2ca9b12135cfec Mon Sep 17 00:00:00 2001 From: 42inshin Date: Thu, 21 Mar 2024 18:04:36 +0900 Subject: [PATCH 0470/1029] =?UTF-8?q?[FE]=20FIX:=20GA=20dev=EC=9A=A9=20env?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95=20#1565?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 0a56e746e..84179c6b6 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 0a56e746e0ce51ca4b312e4f0610a3882377e7db +Subproject commit 84179c6b63f2b47d062e00329f723dd6f0026020 From 3e4a5dd7ffa01f86e98bb111b3771f1df922333c Mon Sep 17 00:00:00 2001 From: 42inshin Date: Thu, 21 Mar 2024 18:12:49 +0900 Subject: [PATCH 0471/1029] =?UTF-8?q?[FE]=20FIX:=20GA=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EC=A1=B0=EA=B1=B4=EB=AC=B8=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?#1565?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/TrackPageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/TrackPageView.tsx b/frontend/src/TrackPageView.tsx index ada252527..3b8842644 100644 --- a/frontend/src/TrackPageView.tsx +++ b/frontend/src/TrackPageView.tsx @@ -10,7 +10,7 @@ const TrackPageView = () => { if ( !initialized && import.meta.env.VITE_GA_TRACKING_ID && - window.location.hostname.includes("localhost") + !window.location.hostname.includes("localhost") ) { ReactGA.initialize(`${import.meta.env.VITE_GA_TRACKING_ID}`); setInitialized(true); From 52adb9f476d5ab1e0be046f694208deb646ebea6 Mon Sep 17 00:00:00 2001 From: 42inshin Date: Thu, 21 Mar 2024 18:38:56 +0900 Subject: [PATCH 0472/1029] =?UTF-8?q?[FE]=20FIX:=20TrackingPageView=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98,=20=ED=8C=8C=EC=9D=BC=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20#1565?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 7 +++---- .../{TrackPageView.tsx => api/analytics/PageTracker.tsx} | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) rename frontend/src/{TrackPageView.tsx => api/analytics/PageTracker.tsx} (92%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9294984ed..6e725cfb2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,4 @@ -import TrackPageView from "@/TrackPageView"; import React, { Suspense, lazy } from "react"; -import ReactGA from "react-ga4"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import AvailablePage from "@/pages/AvailablePage"; import ClubPage from "@/pages/ClubPage"; @@ -13,6 +11,7 @@ import PostLogin from "@/pages/PostLogin"; import ProfilePage from "@/pages/ProfilePage"; import AdminMainPage from "@/pages/admin/AdminMainPage"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; +import PageTracker from "@/api/analytics/PageTracker"; const NotFoundPage = lazy(() => import("@/pages/NotFoundPage")); const LoginFailurePage = lazy(() => import("@/pages/LoginFailurePage")); @@ -28,8 +27,8 @@ const AdminHomePage = lazy(() => import("@/pages/admin/AdminHomePage")); function App(): React.ReactElement { return ( - {/* GA4 Page Traking */} - + {/* GA4 Page Tracking Component */} + }> } /> diff --git a/frontend/src/TrackPageView.tsx b/frontend/src/api/analytics/PageTracker.tsx similarity index 92% rename from frontend/src/TrackPageView.tsx rename to frontend/src/api/analytics/PageTracker.tsx index 3b8842644..3432292f1 100644 --- a/frontend/src/TrackPageView.tsx +++ b/frontend/src/api/analytics/PageTracker.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import ReactGA from "react-ga4"; import { useLocation } from "react-router-dom"; -const TrackPageView = () => { +const PageTracker = () => { const location = useLocation(); const [initialized, setInitialized] = useState(false); @@ -29,4 +29,4 @@ const TrackPageView = () => { return null; }; -export default TrackPageView; +export default PageTracker; From 8d42066d763c68cd3552a1d53fe8f7a4b299cf62 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 23 Mar 2024 17:05:26 +0900 Subject: [PATCH 0473/1029] =?UTF-8?q?[FE]=20FIX:=20--bg-shadow=20=EC=95=8C?= =?UTF-8?q?=EB=A7=9E=EC=9D=80=20=EC=88=AB=EC=9E=90=20=EB=8B=AC=EC=95=84?= =?UTF-8?q?=EC=A4=8C=20#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/LentLog/AdminLentLog.tsx | 5 +++-- frontend/src/index.css | 2 +- frontend/src/pages/admin/AdminLayout.tsx | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/LentLog/AdminLentLog.tsx b/frontend/src/components/LentLog/AdminLentLog.tsx index 14b93ca70..57efb385f 100644 --- a/frontend/src/components/LentLog/AdminLentLog.tsx +++ b/frontend/src/components/LentLog/AdminLentLog.tsx @@ -96,11 +96,12 @@ const AdminLentLogStyled = styled.div` z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; - box-shadow: 0 0 40px 0 var(--bg-shadow); + box-shadow: 0 0 40px 0 var(--bg-shadow-300); + /* TODO : box-shadow 적용안해도 될듯? */ display: flex; flex-direction: column; align-items: center; - background: var(--white); + background: var(--color-background); &.on { transform: translateX(0); } diff --git a/frontend/src/index.css b/frontend/src/index.css index 3ca8d1d6f..7d4cf0c83 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -198,7 +198,7 @@ button { cursor: pointer; border: none; background-color: var(--main-color); - color: var(--white); + color: var(--color-background); width: 200px; height: 60px; font-size: 1.125rem; diff --git a/frontend/src/pages/admin/AdminLayout.tsx b/frontend/src/pages/admin/AdminLayout.tsx index 27993c108..2afb47731 100644 --- a/frontend/src/pages/admin/AdminLayout.tsx +++ b/frontend/src/pages/admin/AdminLayout.tsx @@ -121,7 +121,7 @@ const DetailInfoContainerStyled = styled.div<{ isFloat: boolean }>` z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; - box-shadow: 0 0 40px 0 var(--bg-shadow); + box-shadow: 0 0 40px 0 var(--bg-shadow-200); &.on { transform: translateX(0%); } From a9805af865ffe105abf683f32d4148fa0190a5f3 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 23 Mar 2024 17:07:54 +0900 Subject: [PATCH 0474/1029] =?UTF-8?q?[FE]=20FIX:=20admin=EC=AA=BD=20--whit?= =?UTF-8?q?e=20>=20--color-background=EB=A1=9C=20=EB=B0=94=EA=BF=94?= =?UTF-8?q?=EC=A4=8C#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/AdminInfo/Table/AdminTable.tsx | 1 - .../CabinetList/CabinetListItem/AdminCabinetListItem.tsx | 2 +- frontend/src/components/Club/ClubLogTable.tsx | 2 +- frontend/src/components/Common/MultiSelectButton.tsx | 2 +- .../components/LentLog/LogTable/AdminCabinetLogTable.tsx | 4 ++-- frontend/src/components/Search/SearchItemByIntraId.tsx | 2 +- .../TopNav/SearchBar/SearchBarList/SearchBarList.tsx | 4 ++-- .../TopNav/SearchBar/SearchListItem/SearchListItem.tsx | 8 ++++---- frontend/src/pages/admin/SearchPage.tsx | 2 +- 9 files changed, 13 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/AdminInfo/Table/AdminTable.tsx b/frontend/src/components/AdminInfo/Table/AdminTable.tsx index f95a46cb6..268ac1607 100644 --- a/frontend/src/components/AdminInfo/Table/AdminTable.tsx +++ b/frontend/src/components/AdminInfo/Table/AdminTable.tsx @@ -92,7 +92,6 @@ const TableBorderStyled = styled.div` const TableStyled = styled.table` width: 100%; background: var(--color-background); - background: var(--white); overflow: scroll; @media screen and (max-width: 1300px) { diff --git a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index 7407c0ca2..5d55966bb 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -168,7 +168,7 @@ const CabinetListItemStyled = styled.div<{ status === "PENDING" && css` border: 2px double var(--main-color); - box-shadow: inset 0px 0px 0px 2px var(--white); + box-shadow: inset 0px 0px 0px 2px var(--color-background); `} ${({ status }) => diff --git a/frontend/src/components/Club/ClubLogTable.tsx b/frontend/src/components/Club/ClubLogTable.tsx index 795ec372f..26876da1f 100644 --- a/frontend/src/components/Club/ClubLogTable.tsx +++ b/frontend/src/components/Club/ClubLogTable.tsx @@ -73,7 +73,7 @@ const TheadStyled = styled.thead` width: 100%; height: 50px; line-height: 50px; - background: var(--white); + background: var(--color-background); `; const TbodyStyled = styled.tbody` diff --git a/frontend/src/components/Common/MultiSelectButton.tsx b/frontend/src/components/Common/MultiSelectButton.tsx index bdd45fd01..5baf39a3b 100644 --- a/frontend/src/components/Common/MultiSelectButton.tsx +++ b/frontend/src/components/Common/MultiSelectButton.tsx @@ -39,7 +39,7 @@ const MultiSelectButtonContainerStyled = styled.button` props.theme === "fill" && css` background: var(--main-color); - color: var(--white); + color: var(--color-background); `} ${(props) => props.theme === "line" && diff --git a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx b/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx index 3f8e881fd..279dae460 100644 --- a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx +++ b/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx @@ -68,7 +68,7 @@ const LogTableWrapperstyled = styled.div` const LogTableStyled = styled.table` width: 100%; - background: var(--white); + background: var(--color-background); overflow: scroll; `; @@ -77,7 +77,7 @@ const TheadStyled = styled.thead` height: 50px; line-height: 50px; background-color: var(--main-color); - color: var(--white); + color: var(--color-background); & > tr > th:first-child { padding-left: 20px; } diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/components/Search/SearchItemByIntraId.tsx index 1ef5b4cf4..70dafb344 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/components/Search/SearchItemByIntraId.tsx @@ -169,7 +169,7 @@ const RectangleStyled = styled.div<{ font-size: 1.625rem; color: ${(props) => props.banned - ? "var(--white)" + ? "var(--color-background)" : props.status ? cabinetLabelColorMap[props.status] : "var(--black)"}; diff --git a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx b/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx index 436498851..a4389412f 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx +++ b/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx @@ -58,12 +58,12 @@ const UlStyled = styled.ul` top: 50px; left: 0; width: 300px; - border: 1px solid var(--white); + border: 1px solid var(--color-background); border-radius: 10px; text-align: left; padding: 10px; color: var(--black); - background-color: var(--white); + background-color: var(--color-background); box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); opacity: 0.9; overflow: hidden; diff --git a/frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx b/frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx index 549ccfa0a..1726f35cc 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx +++ b/frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx @@ -56,10 +56,10 @@ const LiStyled = styled.li` &.active { background-color: var(--main-color); - color: var(--white); + color: var(--color-background); } &.active strong { - color: var(--white); + color: var(--color-background); } &.active img { filter: invert(100%); @@ -68,10 +68,10 @@ const LiStyled = styled.li` @media (hover: hover) and (pointer: fine) { &:hover { background-color: var(--main-color); - color: var(--white); + color: var(--color-background); } &:hover strong { - color: var(--white); + color: var(--color-background); } &:hover img { filter: invert(100%); diff --git a/frontend/src/pages/admin/SearchPage.tsx b/frontend/src/pages/admin/SearchPage.tsx index 7e5e2bdc2..050c933ff 100644 --- a/frontend/src/pages/admin/SearchPage.tsx +++ b/frontend/src/pages/admin/SearchPage.tsx @@ -177,7 +177,7 @@ const MoreButtonStyled = styled.button` border: 1px solid var(--main-color); border-radius: 30px; text-indent: -20px; - background-color: var(--white); + background-color: var(--color-background); color: var(--main-color); position: relative; From 80c5b9fae1e42e43036fd9c5aae558d99bd1c119 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 23 Mar 2024 17:10:38 +0900 Subject: [PATCH 0475/1029] =?UTF-8?q?[FE]=20FIX:=20admin=EC=AA=BD=20--blac?= =?UTF-8?q?k=20>=20--color-text-normal=EB=A1=9C=20=EB=B0=94=EA=BF=94?= =?UTF-8?q?=EC=A4=8C#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/AdminInfo/Chart/PieChart.tsx | 2 +- .../CabinetInfoArea/AdminCabinetInfoArea.tsx | 9 +++------ .../components/CabinetInfoArea/CabinetInfoArea.tsx | 2 +- .../CabinetListItem/AdminCabinetListItem.tsx | 2 +- .../CabinetList/CabinetListItem/CabinetListItem.tsx | 6 +++--- frontend/src/components/Home/ManualContentBox.tsx | 7 +------ .../src/components/Modals/ClubModal/ClubMemoModal.tsx | 4 ++-- .../src/components/Modals/ManualModal/ManualModal.tsx | 11 +++-------- .../src/components/Search/SearchItemByIntraId.tsx | 2 +- frontend/src/components/Search/SearchItemByNum.tsx | 4 +++- .../TopNav/SearchBar/SearchBarList/SearchBarList.tsx | 2 +- frontend/src/components/UserInfoArea/UserInfoArea.tsx | 3 +-- frontend/src/index.css | 2 +- frontend/src/recoil/atoms.ts | 3 +-- 14 files changed, 23 insertions(+), 36 deletions(-) diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/components/AdminInfo/Chart/PieChart.tsx index 7773afe74..11be6186c 100644 --- a/frontend/src/components/AdminInfo/Chart/PieChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/PieChart.tsx @@ -105,7 +105,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { { on: "hover", style: { - itemTextColor: "var(--black)", + itemTextColor: "var(--color-text-normal)", }, }, ], diff --git a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 73606567c..2a88b968e 100644 --- a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -142,8 +142,7 @@ const AdminCabinetInfoArea: React.FC<{ title={selectedCabinetInfo!.lentType} cabinetType={selectedCabinetInfo!.lentType} /> - {/* */} - + {selectedCabinetInfo!.userNameList} @@ -173,7 +172,7 @@ const AdminCabinetInfoArea: React.FC<{ > {selectedCabinetInfo!.detailMessage} - + {expireDate} {adminModal.returnModal && ( @@ -336,10 +335,8 @@ const MultiCabinetIconStyled = styled.div<{ status: CabinetStatus }>` align-items: center; background-color: ${({ status }) => cabinetStatusColorMap[status]}; border-radius: 5px; - /* color: ${({ status }) => - status === CabinetStatus.FULL ? "black" : "white"}; */ color: ${({ status }) => - status === CabinetStatus.FULL ? "var(--black)" : "white"}; + status === CabinetStatus.FULL ? "var(--color-text-normal)" : "var(--color-background)"}; `; export default AdminCabinetInfoArea; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx index 286b3c105..9826553f8 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -352,7 +352,7 @@ const CabinetRectangleStyled = styled.div<{ font-size: 2rem; color: ${(props) => - props.isMine ? "var(--black)" : cabinetLabelColorMap[props.cabinetStatus]}; + props.isMine ? "var(--color-text-normal)" : cabinetLabelColorMap[props.cabinetStatus]}; text-align: center; ${({ cabinetStatus }) => diff --git a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index 5d55966bb..bb9fcbe11 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -232,7 +232,7 @@ const CabinetNumberStyled = styled.p<{ ${({ status }) => status === "IN_SESSION" && css` - color: var(--black); + color: var(--color-text-normal); `} `; diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx index aa4e07dda..abeb4b4e5 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -267,7 +267,7 @@ const CabinetLabelStyled = styled.p<{ ${(props) => props.isMine && css` - color: var(--black); + color: var(--color-text-normal); `} `; @@ -280,7 +280,7 @@ const CabinetNumberStyled = styled.p<{ ${(props) => props.isMine && css` - color: var(--black); + color: var(--color-text-normal); `} ${({ status }) => status === "IN_SESSION" && @@ -302,7 +302,7 @@ const CabinetIconContainerStyled = styled.div<{ ${(props) => props.isMine && css` - color: var(--black); + color: var(--color-text-normal); `}; `; diff --git a/frontend/src/components/Home/ManualContentBox.tsx b/frontend/src/components/Home/ManualContentBox.tsx index f26a17301..2e90663e9 100644 --- a/frontend/src/components/Home/ManualContentBox.tsx +++ b/frontend/src/components/Home/ManualContentBox.tsx @@ -154,14 +154,9 @@ const MaunalContentBoxStyled = styled.div<{ stroke: ${(props) => props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" - : // : props.contentStatus === ContentStatus.EXTENSION - // ? "var(--black)" - // : "white"}; - // light - props.contentStatus === ContentStatus.EXTENSION + : props.contentStatus === ContentStatus.EXTENSION ? "var(--color-text-normal)" : "var(--color-background)"}; - // dark cursor: pointer; } diff --git a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx b/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx index d3b7b6189..78727623a 100644 --- a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx +++ b/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx @@ -112,7 +112,7 @@ const ContentItemTextAreaStyled = styled.textarea<{ height: 100%; border-radius: 10px; font-size: 1.125rem; - color: var(--black); + color: var(--color-text-normal); overflow-y: auto; word-break: break-all; white-space: pre-wrap; @@ -122,7 +122,7 @@ const ContentItemTextAreaStyled = styled.textarea<{ cursor: ${({ mode }) => (mode === "read" ? "default" : "input")}; color: ${({ mode }) => - mode === "read" ? "var(--main-color)" : "var(--black)"}; + mode === "read" ? "var(--main-color)" : "var(--color-text-normal)"}; &::placeholder { color: ${({ mode }) => mode === "read" ? "var(--main-color)" : "var(--color-line)"}; diff --git a/frontend/src/components/Modals/ManualModal/ManualModal.tsx b/frontend/src/components/Modals/ManualModal/ManualModal.tsx index bd23df587..0fa0c4e5c 100644 --- a/frontend/src/components/Modals/ManualModal/ManualModal.tsx +++ b/frontend/src/components/Modals/ManualModal/ManualModal.tsx @@ -204,14 +204,9 @@ const ModalContent = styled.div<{ stroke: ${(props) => props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" - : // : props.contentStatus === ContentStatus.EXTENSION - // ? "var(--black)" - // : "white"}; - // light - props.contentStatus === ContentStatus.EXTENSION + : props.contentStatus === ContentStatus.EXTENSION ? "var(--color-text-normal)" : "var(--color-background)"}; - /* dark */ } `; @@ -232,8 +227,8 @@ const CloseButton = styled.div<{ props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" : props.contentStatus === ContentStatus.EXTENSION - ? "var(--black)" - : "white"}; + ? "var(--color-text-normal)" + : "var(--color-background)"}; } :hover { transform: translateX(-16px); diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/components/Search/SearchItemByIntraId.tsx index 70dafb344..50f272551 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/components/Search/SearchItemByIntraId.tsx @@ -172,7 +172,7 @@ const RectangleStyled = styled.div<{ ? "var(--color-background)" : props.status ? cabinetLabelColorMap[props.status] - : "var(--black)"}; + : "var(--color-text-normal)"}; display: flex; justify-content: center; align-items: center; diff --git a/frontend/src/components/Search/SearchItemByNum.tsx b/frontend/src/components/Search/SearchItemByNum.tsx index a70f39e19..d958978fc 100644 --- a/frontend/src/components/Search/SearchItemByNum.tsx +++ b/frontend/src/components/Search/SearchItemByNum.tsx @@ -111,7 +111,9 @@ const RectangleStyled = styled.div<{ status: CabinetStatus }>` background-color: ${(props) => cabinetStatusColorMap[props.status]}; font-size: 1.625rem; color: ${(props) => - props.status ? cabinetLabelColorMap[props.status] : "var(--black)"}; + props.status + ? cabinetLabelColorMap[props.status] + : "var(--color-text-normal)"}; display: flex; justify-content: center; align-items: center; diff --git a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx b/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx index a4389412f..1961f27cb 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx +++ b/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx @@ -62,7 +62,7 @@ const UlStyled = styled.ul` border-radius: 10px; text-align: left; padding: 10px; - color: var(--black); + color: var(--color-text-normal); background-color: var(--color-background); box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); opacity: 0.9; diff --git a/frontend/src/components/UserInfoArea/UserInfoArea.tsx b/frontend/src/components/UserInfoArea/UserInfoArea.tsx index 1563c17ff..58323f3f0 100644 --- a/frontend/src/components/UserInfoArea/UserInfoArea.tsx +++ b/frontend/src/components/UserInfoArea/UserInfoArea.tsx @@ -60,8 +60,7 @@ const UserInfoArea: React.FC<{ {selectedUserInfo.isBanned ? "!" : "-"} - {/* */} - + {selectedUserInfo.name} diff --git a/frontend/src/index.css b/frontend/src/index.css index 7d4cf0c83..91ad1d3b7 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -230,7 +230,7 @@ img { } input { - color: var(--black); + color: var(--color-text-normal); text-align: center; border: none; font-size: 1rem; diff --git a/frontend/src/recoil/atoms.ts b/frontend/src/recoil/atoms.ts index 4de42e869..7db4200b7 100644 --- a/frontend/src/recoil/atoms.ts +++ b/frontend/src/recoil/atoms.ts @@ -209,6 +209,5 @@ export const targetClubUserInfoState = atom({ export const darkModeState = atom({ key: "darkMode", - // default: "black", - default: "var(--black)", + default: "var(--color-text-normal)", }); From bd2f59597a5c73835288d6c202bf0df8cf2d9e09 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 23 Mar 2024 17:14:04 +0900 Subject: [PATCH 0476/1029] =?UTF-8?q?[FE]=20FIX:=20admin=EC=AA=BD=20white,?= =?UTF-8?q?=20black=20=EA=B0=81=EA=B0=81=20--color-background,--color-text?= =?UTF-8?q?-normal=EB=A1=9C=20=EB=B0=94=EA=BF=94=EC=A4=8C#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx | 2 +- frontend/src/components/Club/ClubLogTable.tsx | 2 +- .../Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx | 8 ++++++-- frontend/src/components/Common/MultiToggleSwitch.tsx | 4 ++-- .../src/components/Common/MultiToggleSwitchSeparated.tsx | 4 ++-- frontend/src/components/TopNav/DarkMode/DarkMode.tsx | 6 +++++- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx b/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx index 3642b59f3..ac705df5a 100644 --- a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx +++ b/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx @@ -65,7 +65,7 @@ const ClubCabinetInfoCard = ({ - + {clubInfo.clubMaster.userName} diff --git a/frontend/src/components/Club/ClubLogTable.tsx b/frontend/src/components/Club/ClubLogTable.tsx index 26876da1f..98c5a43d5 100644 --- a/frontend/src/components/Club/ClubLogTable.tsx +++ b/frontend/src/components/Club/ClubLogTable.tsx @@ -63,7 +63,7 @@ const LogTableWrapperStyled = styled.div` const LogTableStyled = styled.table` width: 100%; - background: white; + background: var(--color-background); overflow: scroll; border-spacing: 0 0.3em; border-collapse: separate; diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx index 4ab3dce57..a3fbebd1f 100644 --- a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx +++ b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -60,7 +60,11 @@ const ClubMemberInfoArea = ({ <> {/* */} - + {selectedClubInfo!.clubName} @@ -70,7 +74,7 @@ const ClubMemberInfoArea = ({ ) : ( )} - + {selectedClubMemberInfo!.userName || "-"} diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx index 30bd82b87..3631c8978 100644 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/components/Common/MultiToggleSwitch.tsx @@ -24,7 +24,7 @@ const MultiToggleSwitch = ({ buttons?.forEach((button) => { if (button.className === initialState) { - button.style.color = "white"; + button.style.color = "var(--color-background)"; button.style.backgroundColor = "var(--main-color)"; } }); @@ -42,7 +42,7 @@ const MultiToggleSwitch = ({ button.style.backgroundColor = "transparent"; }); - target.style.color = "white"; + target.style.color = "var(--color-background)"; target.style.backgroundColor = "var(--main-color)"; setState(target.className as React.SetStateAction); diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx index 90edf3c31..b2c4e5b67 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx @@ -24,7 +24,7 @@ const MultiToggleSwitchSeparated = ({ buttons?.forEach((button) => { if (button.className === `${initialState}`) { - button.style.color = "white"; + button.style.color = "var(--color-background)"; button.style.backgroundColor = "var(--main-color)"; } }); @@ -43,7 +43,7 @@ const MultiToggleSwitchSeparated = ({ button.style.backgroundColor = "var(--gray-tmp-1)"; }); - target.style.color = "white"; + target.style.color = "var(--color-background)"; target.style.backgroundColor = "var(--main-color)"; setState(target.className as React.SetStateAction); diff --git a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx index 864bc1fd0..d964d7014 100644 --- a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx +++ b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx @@ -9,7 +9,11 @@ const DarkMode: React.FC<{}> = () => { <> + + + + ); +}; + +export default SlackAlarmJ; diff --git a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx new file mode 100644 index 000000000..14ea3848e --- /dev/null +++ b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx @@ -0,0 +1,241 @@ +import { useEffect, useRef, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { SlackChannels } from "@/assets/data/SlackAlarm"; +import { axiosSearchByIntraId } from "@/api/axios/axios.custom"; +import useOutsideClick from "@/hooks/useOutsideClick"; +import SlackAlarmSearchBarList from "./SlackAlarmSearchBarList/SlackAlarmSearchBarList"; + +export interface ISlackChannels { + title: string; + channelId: string; +} + +const SlackAlarmSearchBar = () => { + const navigate = useNavigate(); + const searchWrap = useRef(null); + const searchInput = useRef(null); + const [searchListById, setSearchListById] = useState([]); + const [searchListByChannel, setSearchListByChannel] = useState< + ISlackChannels[] + >([]); + const [totalLength, setTotalLength] = useState(0); + const [isFocus, setIsFocus] = useState(true); + const [targetIndex, setTargetIndex] = useState(-1); + const [searchValue, setSearchValue] = useState(""); + const [floor, setFloor] = useState(0); + + const resetSearchState = () => { + setSearchListById([]); + setSearchListByChannel([]); + setTotalLength(0); + setTargetIndex(-1); + setFloor(0); + if (searchInput.current) { + searchInput.current.value = ""; + setSearchValue(""); + } + }; + + const clickSearchButton = () => { + if (searchInput.current) { + const searchValue = searchInput.current.value; + if (searchValue.length <= 0) { + resetSearchState(); + return alert("검색어를 입력해주세요."); + } else if (isNaN(Number(searchValue)) && searchValue.length <= 1) { + resetSearchState(); + return alert("두 글자 이상의 검색어를 입력해주세요."); + } else { + let query = floor + ? `?q=${searchInput.current.value}&floor=${floor}` + : `?q=${searchInput.current.value}`; + navigate({ + pathname: "search", + search: query, + }); + resetSearchState(); + } + } + }; + + const debounce = (func: Function, wait: number) => { + let timeout: NodeJS.Timeout; + + return function executedFunction(...args: any[]) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + }; + + const typeSearchInput = async () => { + if (searchInput.current) { + setSearchValue(searchInput.current.value); + const searchValue = searchInput.current.value; + if (searchValue.length <= 0) { + setSearchListById([]); + setSearchListByChannel([]); + setTotalLength(0); + setTargetIndex(-1); + return; + } + if (searchInput.current!.value[0] === "#") { + // slack channel 검색 + if (searchValue.length <= 1) { + setSearchListByChannel([]); + setTotalLength(0); + setTargetIndex(-1); + } else { + const searchResult = SlackChannels.filter((slackChannel) => { + return slackChannel.title.includes(searchValue); + }); + setSearchListById([]); + setSearchListByChannel(searchResult); + console.log("searchResult : ", searchResult); + setTotalLength(searchResult.length); + } + } else { + // intra_ID 검색 + if (searchValue.length <= 1) { + setSearchListById([]); + setTotalLength(0); + setTargetIndex(-1); + } else { + const searchResult = await axiosSearchByIntraId(searchValue); + setSearchListByChannel([]); + setSearchListById(searchResult.data.result); + setTotalLength(searchResult.data.totalLength); + } + } + } + }; + + const clickCancelButton = () => { + resetSearchState(); + document.getElementById("searchBar")!.classList.remove("on"); + document.getElementById("topNavLogo")!.classList.remove("pushOut"); + document.getElementById("topNavButtonGroup")!.classList.remove("pushOut"); + document.getElementById("topNavWrap")!.classList.remove("pushOut"); + }; + + // outside click + useOutsideClick(searchWrap, () => { + setIsFocus(false); + }); + + const valueChangeHandler = () => { + if (searchInput.current!.value[0] === "#") { + return searchListByChannel[targetIndex].title; + } else return searchListById[targetIndex].name; + }; + + // searchInput value change + useEffect(() => { + if (targetIndex !== -1) { + searchInput.current!.value = valueChangeHandler(); + setSearchValue(searchInput.current!.value); + } + }, [targetIndex]); + + const handleInputKey = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + clickSearchButton(); + } else if (e.key == "ArrowUp") { + if (totalLength > 0) { + setTargetIndex((prev) => + prev > 0 + ? prev - 1 + : Math.max(searchListById.length, searchListByChannel.length) - 1 + ); + } + } else if (e.key == "ArrowDown") { + if (totalLength > 0) { + setTargetIndex((prev) => + prev < Math.max(searchListById.length, searchListByChannel.length) - 1 + ? prev + 1 + : 0 + ); + } + } + }; + + return ( + + + { + setIsFocus(true); + }} + onChange={debounce(typeSearchInput, 300)} + onKeyDown={handleInputKey} + > + + + 취소 + {isFocus && searchInput.current?.value && totalLength > 0 && ( + <> + + + )} + + ); +}; + +const SearchBarWrapperStyled = styled.div` + position: relative; +`; + +const SearchBarStyled = styled.div` + position: relative; + width: 100%; +`; + +const SearchBarInputStyled = styled.input` + width: 300px; + height: 40px; + border: 1px solid #7b7b7b; + border-radius: 10px; + text-align: left; + padding: 0 20px; + color: #7b7b7b; + background-color: rgba(255, 255, 255, 0.2); + &::placeholder { + color: #7b7b7b; + } +`; + +const SearchButtonStyled = styled.button` + background: url("/src/assets/images/searchWhite.svg") no-repeat 50% 50%; + width: 32px; + height: 32px; + position: absolute; + top: 4px; + right: 14px; +`; + +const CancelButtonStyled = styled.button` + min-width: 60px; + width: 60px; + height: 40px; + overflow: hidden; + display: none; + @media screen and (max-width: 768px) { + display: block; + } +`; + +export default SlackAlarmSearchBar; diff --git a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBarList/SlackAlarmSearchBarList.tsx b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBarList/SlackAlarmSearchBarList.tsx new file mode 100644 index 000000000..ef980b503 --- /dev/null +++ b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBarList/SlackAlarmSearchBarList.tsx @@ -0,0 +1,72 @@ +import styled from "styled-components"; +import SearchListItem from "@/components/TopNav/SearchBar/SearchListItem/SearchListItem"; +import { ISlackChannels } from "../SlackAlarmSearchBar"; + +interface ISearchListByIntraId { + name: string; + userId: number; +} + +const SlackAlarmSearchBarList = ({ + searchListById, + searchListByChannel, + searchWord, + resetSearchState, + totalLength, + targetIndex, +}: { + searchListById: ISearchListByIntraId[]; + searchListByChannel: ISlackChannels[]; + resetSearchState: () => void; + searchWord?: string; + totalLength: number; + targetIndex?: number; +}) => { + return ( + + {searchListById.map((item, index: number) => { + return ( + + ); + })} + {searchListByChannel.map((item, index: number) => { + return ( + + ); + })} + + ); +}; + +const UlStyled = styled.ul` + position: absolute; + top: 50px; + left: 0; + width: 300px; + border: 1px solid var(--white); + border-radius: 10px; + text-align: left; + padding: 10px; + color: var(--black); + background-color: var(--white); + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); + opacity: 0.9; + overflow: hidden; + @media (max-width: 768px) { + width: 100%; + } +`; + +export default SlackAlarmSearchBarList; From dfc277818b41b3dda46e3708b696a1e1fa7a9096 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 9 Apr 2024 18:29:35 +0900 Subject: [PATCH 0537/1029] =?UTF-8?q?[FE]=20FEAT:=20textarea=20focus?= =?UTF-8?q?=EC=8B=9C=20=ED=85=8C=EB=91=90=EB=A6=AC=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=83=89=EC=9C=BC=EB=A1=9C=20=EB=B0=94=EB=80=8C=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20&=20=EC=B1=84=EB=84=90=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EB=88=8C=EB=A0=80=EC=9D=84=EB=95=8C=20=EB=B0=9B?= =?UTF-8?q?=EB=8A=94=EC=9D=B4=20input=EC=97=90=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=EC=B1=84=EB=84=90=20=EC=9D=B4=EB=A6=84=20=EB=9C=A8=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/SlackAlarmJ/SlackAlarmJ.tsx | 48 +++++++++++++++---- .../SlackAlarmJ/SlackAlarmSearchBar.tsx | 7 ++- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx b/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx index 08b245cf2..8eed52640 100644 --- a/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx +++ b/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx @@ -1,32 +1,60 @@ -import { useState } from "react"; +import { useRef, useState } from "react"; +import styled from "styled-components"; import { SlackAlarmTemplate, SlackChannels } from "@/assets/data/SlackAlarm"; +import useOutsideClick from "@/hooks/useOutsideClick"; import SlackAlarmSearchBar from "../TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar"; const SlackAlarmJ = () => { const [onFocus, setOnFocus] = useState(false); + const searchTextArea = useRef(null); + const searchInput = useRef(null); + + // outside click + useOutsideClick(searchTextArea, () => { + setOnFocus(false); + }); + + const initialTextrea = () => { + if (searchTextArea.current) searchTextArea.current.value = ""; + }; + + const renderReceiverInput = (title: string) => { + if (searchInput.current) searchInput.current.value = title; + }; - console.log("onFocus : ", onFocus); return ( <>

자주쓰는채널

{SlackChannels.map((channel) => { - return ; + return ( + + ); })}

자주쓰는템플릿

{SlackAlarmTemplate.map((template) => { - return ; + return ( + + ); })}

알림보내기

받는이(intraid/channel)
- +
메시지내용
- - + ref={searchTextArea} + isOnFocus={onFocus} + /> +
@@ -34,3 +62,7 @@ const SlackAlarmJ = () => { }; export default SlackAlarmJ; + +const ContentStyled = styled.textarea<{ isOnFocus: boolean }>` + outline-color: ${(props) => (props.isOnFocus ? "var(--main-color)" : "")}; +`; diff --git a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx index 14ea3848e..c301075bd 100644 --- a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx +++ b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx @@ -11,10 +11,13 @@ export interface ISlackChannels { channelId: string; } -const SlackAlarmSearchBar = () => { +const SlackAlarmSearchBar = ({ + searchInput, +}: { + searchInput: React.RefObject; +}) => { const navigate = useNavigate(); const searchWrap = useRef(null); - const searchInput = useRef(null); const [searchListById, setSearchListById] = useState([]); const [searchListByChannel, setSearchListByChannel] = useState< ISlackChannels[] From 052c25b2c8c604bf4e14cd37a14442ea00519d16 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 9 Apr 2024 18:36:28 +0900 Subject: [PATCH 0538/1029] =?UTF-8?q?[FE]=20FEAT:=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EB=B2=84=ED=8A=BC=20=EB=88=8C=EB=A0=80=EC=9D=84?= =?UTF-8?q?=EB=95=8C=20=EB=A9=94=EC=8B=9C=EC=A7=80=EB=82=B4=EC=9A=A9=20tex?= =?UTF-8?q?tarea=EC=97=90=20=ED=95=B4=EB=8B=B9=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EB=9C=A8=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/SlackAlarm.ts | 4 ++-- .../src/components/SlackAlarmJ/SlackAlarmJ.tsx | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/frontend/src/assets/data/SlackAlarm.ts b/frontend/src/assets/data/SlackAlarm.ts index 49445473e..735c6c36c 100644 --- a/frontend/src/assets/data/SlackAlarm.ts +++ b/frontend/src/assets/data/SlackAlarm.ts @@ -1,6 +1,6 @@ import { ISlackChannels } from "@/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar"; -export const SlackChannels : ISlackChannels[] = [ +export const SlackChannels: ISlackChannels[] = [ { title: "#random", channelId: "CU6MTFBNH", @@ -11,7 +11,7 @@ export const SlackChannels : ISlackChannels[] = [ }, ]; -export const SlackAlarmTemplate = [ +export const SlackAlarmTemplates = [ { title: "점검 시작", content: `:alert::alert::alert::alert::alert::alert: diff --git a/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx b/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx index 8eed52640..d38c19610 100644 --- a/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx +++ b/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx @@ -1,6 +1,6 @@ import { useRef, useState } from "react"; import styled from "styled-components"; -import { SlackAlarmTemplate, SlackChannels } from "@/assets/data/SlackAlarm"; +import { SlackAlarmTemplates, SlackChannels } from "@/assets/data/SlackAlarm"; import useOutsideClick from "@/hooks/useOutsideClick"; import SlackAlarmSearchBar from "../TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar"; @@ -22,6 +22,14 @@ const SlackAlarmJ = () => { if (searchInput.current) searchInput.current.value = title; }; + const renderTemplateTextArea = (title: string) => { + const template = SlackAlarmTemplates.find((template) => { + return template.title === title; + }); + if (searchTextArea.current) + searchTextArea.current.value = template!.content; + }; + return ( <>

자주쓰는채널

@@ -33,11 +41,9 @@ const SlackAlarmJ = () => { ); })}

자주쓰는템플릿

- {SlackAlarmTemplate.map((template) => { + {SlackAlarmTemplates.map((template) => { return ( - ); From 596f7d5ed7f220483a645c2366a907197872628d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EB=AF=BC?= Date: Tue, 9 Apr 2024 19:01:37 +0900 Subject: [PATCH 0539/1029] =?UTF-8?q?[FE]=20FEAT:=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=20=EC=8A=AC=EB=9E=99=20=EC=95=8C=EB=A6=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=ED=98=84=20#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/admin/AdminSlackNotiPage.tsx | 196 +++++++++++++++++- 1 file changed, 195 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index 8848649a8..dd8eeca94 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -1,5 +1,199 @@ +import styled from "styled-components"; +import { SlackAlarmTemplate, SlackChannel } from "@/assets/data/SlackAlarm"; + const AdminSlackNotiPage = () => { - return <>; + return ( + + +

알림

+
+ + + 자주 쓰는 채널 + + {SlackChannel.map((channel: any, idx: number) => { + return ( + + {channel.title} + + ); + })} + + + + + 자주 쓰는 템플릿 + + {SlackAlarmTemplate.map((channel: any, idx: number) => { + return ( + + {channel.title} + + ); + })} + 사물함 대여 + + + + 알림 보내기 + + + + 받는이(Intra ID/ Channel)* + + + + + + 메시지 내용* + + + + + 초기화 + 보내기 + + + +
+ ); }; +const WrapperStyled = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 60px 0; +`; + +const TitleContainerStyled = styled.div` + width: 80%; + max-width: 1000px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 2px solid #d9d9d9; + margin-bottom: 70px; + font-weight: 700; + + .title { + font-size: 1.25rem; + letter-spacing: -0.02rem; + margin-bottom: 20px; + } +`; + +const ContainerStyled = styled.div` + width: 80%; + max-width: 1000px; + margin-bottom: 40px; +`; + +const SubTitleStyled = styled.h2` + font-size: 1.25rem; + font-weight: 700; + margin-bottom: 20px; +`; + +const CapsuleWappingStyled = styled.div` + width: 100%; + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +`; + +const CapsuleButtonStyled = styled.span` + display: flex; + justify-content: center; + align-items: center; + padding: 8px 20px; + background: #f5f5f5; + border: 1px solid #eeeeee; + border-radius: 22px; + cursor: pointer; + + :hover { + background: #b08cff5f; + color: var(--main-color); + border: 1px solid var(--main-color); + } +`; + +const FormWappingStyled = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + /* width: 80%; */ + /* max-width: 1000px; */ + background-color: var(--lightgray-color); + border-radius: 10px; + padding: 30px 20px; + gap: 20px; +`; + +const FormContainerStyled = styled.div` + width: 100%; +`; +const FormSubTitleStyled = styled.h3` + font-size: 0.875rem; + color: var(--gray-color); + margin-bottom: 10px; + span { + color: var(--expired); + } +`; + +const FormInputStyled = styled.input` + width: 100%; + height: 40px; + background-color: #fff; + border-radius: 8px; + border: 1px solid #eee; + :focus { + border: 1px solid var(--main-color); + } + text-align: left; + padding: 0 10px; + ::placeholder { + color: var(--line-color); + } +`; +const FormTextareaStyled = styled.textarea` + box-sizing: border-box; + width: 100%; + min-height: 200px; + background-color: #fff; + border-radius: 8px; + border: 1px solid #eee; + resize: none; + outline: none; + :focus { + border: 1px solid var(--main-color); + } + text-align: left; + padding: 10px; + ::placeholder { + color: var(--line-color); + } +`; + +const FormButtonContainerStyled = styled.div` + width: 100%; + display: flex; + justify-content: space-between; +`; + +const FormButtonStyled = styled.div<{ primary?: boolean }>` + padding: 10px 16px; + font-size: 0.875rem; + background-color: ${(props) => + props.primary ? "var(--main-color)" : "var(--white)"}; + color: ${(props) => (props.primary ? "var(--white)" : "var(--black)")}; + font-weight: 700; + border: 1px solid #eee; + border-radius: 4px; +`; export default AdminSlackNotiPage; From a1906f57f552ec58d02e6d5e8dacc372c365231a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EB=AF=BC?= Date: Tue, 9 Apr 2024 19:08:39 +0900 Subject: [PATCH 0540/1029] =?UTF-8?q?[FE]=20FEAT:=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=20=EC=8A=AC=EB=9E=99=20=EC=95=8C=EB=A6=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/admin/AdminSlackNotiPage.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index dd8eeca94..1928c426e 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -24,7 +24,7 @@ const AdminSlackNotiPage = () => { 자주 쓰는 템플릿 - {SlackAlarmTemplate.map((channel: any, idx: number) => { + {SlackAlarmTemplate.map((channel: any, idx: number) => { return ( {channel.title} @@ -51,7 +51,9 @@ const AdminSlackNotiPage = () => { 초기화 - 보내기 + + 보내기 + @@ -186,7 +188,7 @@ const FormButtonContainerStyled = styled.div` justify-content: space-between; `; -const FormButtonStyled = styled.div<{ primary?: boolean }>` +const FormButtonStyled = styled.div<{ primary?: boolean; disabled?: boolean }>` padding: 10px 16px; font-size: 0.875rem; background-color: ${(props) => @@ -195,5 +197,10 @@ const FormButtonStyled = styled.div<{ primary?: boolean }>` font-weight: 700; border: 1px solid #eee; border-radius: 4px; + opacity: ${(props) => (props.disabled ? "0.3" : "1")}; + cursor: pointer; + :hover { + opacity: 0.85; + } `; export default AdminSlackNotiPage; From d4be04de43991c64fb1a1985b76c1d1da0e06bb5 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 9 Apr 2024 19:30:38 +0900 Subject: [PATCH 0541/1029] =?UTF-8?q?[FE]=20FIX:=20SlackChannel,=20SlackAl?= =?UTF-8?q?armTemplate=EC=97=90=20s=EB=B6=99=EC=97=AC=EC=84=9C=20=EB=B3=B5?= =?UTF-8?q?=EC=88=98=ED=98=95=20=EB=A7=8C=EB=93=A6#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx | 4 ++-- frontend/src/pages/admin/AdminSlackNotiPage.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx index c301075bd..eb15aedd1 100644 --- a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx +++ b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx @@ -93,8 +93,8 @@ const SlackAlarmSearchBar = ({ setTotalLength(0); setTargetIndex(-1); } else { - const searchResult = SlackChannels.filter((slackChannel) => { - return slackChannel.title.includes(searchValue); + const searchResult = SlackChannels.filter((SlackChannels) => { + return SlackChannels.title.includes(searchValue); }); setSearchListById([]); setSearchListByChannel(searchResult); diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index 1928c426e..2b372b182 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import { SlackAlarmTemplate, SlackChannel } from "@/assets/data/SlackAlarm"; +import { SlackAlarmTemplates, SlackChannels } from "@/assets/data/SlackAlarm"; const AdminSlackNotiPage = () => { return ( @@ -11,7 +11,7 @@ const AdminSlackNotiPage = () => { 자주 쓰는 채널 - {SlackChannel.map((channel: any, idx: number) => { + {SlackChannels.map((channel: any, idx: number) => { return ( {channel.title} @@ -24,7 +24,7 @@ const AdminSlackNotiPage = () => { 자주 쓰는 템플릿 - {SlackAlarmTemplate.map((channel: any, idx: number) => { + {SlackAlarmTemplates.map((channel: any, idx: number) => { return ( {channel.title} From d1684bf90f53e6c883ee3f2956d9a2145ef6c854 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 9 Apr 2024 19:52:21 +0900 Subject: [PATCH 0542/1029] =?UTF-8?q?[FE]=20FEAT:=20=EC=8A=AC=EB=9E=99=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20axios=20=EB=A7=8C=EB=93=A4=EA=B3=A0=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/axios/axios.custom.ts | 215 ++++++++++-------- .../components/SlackAlarmJ/SlackAlarmJ.tsx | 42 +++- .../SlackAlarmJ/SlackAlarmSearchBar.tsx | 14 +- 3 files changed, 170 insertions(+), 101 deletions(-) diff --git a/frontend/src/api/axios/axios.custom.ts b/frontend/src/api/axios/axios.custom.ts index 7ebd20000..4101ab7fd 100644 --- a/frontend/src/api/axios/axios.custom.ts +++ b/frontend/src/api/axios/axios.custom.ts @@ -1,5 +1,5 @@ -import {AlarmInfo} from "@/types/dto/alarm.dto"; -import {ClubUserDto} from "@/types/dto/lent.dto"; +import { AlarmInfo } from "@/types/dto/alarm.dto"; +import { ClubUserDto } from "@/types/dto/lent.dto"; import CabinetStatus from "@/types/enum/cabinet.status.enum"; import CabinetType from "@/types/enum/cabinet.type.enum"; import instance from "@/api/axios/axios.instance"; @@ -66,7 +66,7 @@ export const axiosUpdateAlarm = async (alarm: AlarmInfo): Promise => { const axiosUpdateDeviceTokenURL = "/v4/users/me/device-token"; export const axiosUpdateDeviceToken = async ( - deviceToken: string | null + deviceToken: string | null ): Promise => { try { const response = await instance.put(axiosUpdateDeviceTokenURL, { @@ -90,16 +90,16 @@ export const axiosMyClubList = async (): Promise => { const axiosGetClubInfoURL = "/v4/clubs/"; export const axiosGetClubInfo = async ( - clubId: number, - page: number, - size: number + clubId: number, + page: number, + size: number ): Promise => { try { const response = await instance.get( - axiosGetClubInfoURL + clubId.toString(), - { - params: {page: page, size: size}, - } + axiosGetClubInfoURL + clubId.toString(), + { + params: { page: page, size: size }, + } ); return response; } catch (error) { @@ -109,16 +109,16 @@ export const axiosGetClubInfo = async ( const axiosAddClubMemberURL = "/v4/clubs"; export const axiosAddClubMember = async ( - clubId: number, - name: String + clubId: number, + name: String ): Promise => { // TODO : 예외처리? try { const response = await instance.post( - `${axiosAddClubMemberURL}/${clubId}/users`, - { - name, - } + `${axiosAddClubMemberURL}/${clubId}/users`, + { + name, + } ); return response; } catch (error) { @@ -128,14 +128,14 @@ export const axiosAddClubMember = async ( const axiosMandateClubMemURL = "/v4/clubs"; export const axiosMandateClubMember = async ( - clubId: number, - clubMaster: string + clubId: number, + clubMaster: string ): Promise => { // TODO : 예외처리? try { const response = await instance.post( - `${axiosMandateClubMemURL}/${clubId}/mandate`, - {clubMaster} + `${axiosMandateClubMemURL}/${clubId}/mandate`, + { clubMaster } ); return response; } catch (error) { @@ -145,12 +145,12 @@ export const axiosMandateClubMember = async ( const axiosDeleteClubMemberURL = "/v4/clubs/"; export const axiosDeleteClubMember = async ( - clubId: number, - userId: number + clubId: number, + userId: number ): Promise => { try { const response = await instance.delete( - `${axiosDeleteClubMemberURL}${clubId}/users/${userId}` + `${axiosDeleteClubMemberURL}${clubId}/users/${userId}` ); return response; } catch (error) { @@ -160,15 +160,15 @@ export const axiosDeleteClubMember = async ( const axiosUpdateClubMemoURL = "/v4/clubs/"; export const axiosUpdateClubMemo = async ( - clubId: number, - clubMemo: string + clubId: number, + clubMemo: string ): Promise => { try { const response = await instance.post( - `${axiosUpdateClubMemoURL}${clubId}/memo`, - { - memo: clubMemo, - } + `${axiosUpdateClubMemoURL}${clubId}/memo`, + { + memo: clubMemo, + } ); return response; } catch (error) { @@ -178,15 +178,15 @@ export const axiosUpdateClubMemo = async ( const axiosUpdateClubNoticeURL = "/v4/clubs/"; export const axiosUpdateClubNotice = async ( - clubId: number, - notice: string + clubId: number, + notice: string ): Promise => { try { const response = await instance.post( - `${axiosUpdateClubNoticeURL}${clubId}/notice`, - { - notice, - } + `${axiosUpdateClubNoticeURL}${clubId}/notice`, + { + notice, + } ); return response; } catch (error) { @@ -207,12 +207,12 @@ export const axiosBuildingFloor = async (): Promise => { const axiosCabinetByBuildingFloorURL = "/v4/cabinets/buildings/"; export const axiosCabinetByBuildingFloor = async ( - Building: string, - floor: number + Building: string, + floor: number ): Promise => { try { const response = await instance.get( - `${axiosCabinetByBuildingFloorURL}${Building}/floors/${floor}` + `${axiosCabinetByBuildingFloorURL}${Building}/floors/${floor}` ); return response; } catch (error) { @@ -222,7 +222,7 @@ export const axiosCabinetByBuildingFloor = async ( const axiosCabinetByIdURL = "/v4/cabinets/"; export const axiosCabinetById = async ( - cabinetId: number | null + cabinetId: number | null ): Promise => { if (cabinetId === null) return; try { @@ -267,8 +267,8 @@ export const axiosSwapId = async (cabinetId: number | null): Promise => { const axiosUpdateMyCabinetInfoURL = "/v4/lent/me/cabinet"; export const axiosUpdateMyCabinetInfo = async ( - title: string | null, - memo: string | null + title: string | null, + memo: string | null ): Promise => { try { const response = await instance.patch(axiosUpdateMyCabinetInfoURL, { @@ -306,7 +306,7 @@ const axiosMyLentLogURL = "/v4/lent/me/histories"; export const axiosMyLentLog = async (page: number): Promise => { try { const response = await instance.get(axiosMyLentLogURL, { - params: {page: page, size: 10}, + params: { page: page, size: 10 }, }); return response; } catch (error) { @@ -327,8 +327,8 @@ export const axiosExtendLentPeriod = async (): Promise => { // Admin API const axiosAdminAuthLoginURL = "/v4/admin/auth/login"; export const axiosAdminAuthLogin = async ( - id: string, - password: string + id: string, + password: string ): Promise => { try { const response = await instance.post(axiosAdminAuthLoginURL, { @@ -343,12 +343,12 @@ export const axiosAdminAuthLogin = async ( const axiosAdminCabinetInfoByIdURL = "/v4/cabinets/"; export const axiosAdminCabinetInfoByCabinetId = async ( - cabinetId: number | null + cabinetId: number | null ): Promise => { if (cabinetId === null) return; try { const response = await instance.get( - axiosAdminCabinetInfoByIdURL + cabinetId + axiosAdminCabinetInfoByIdURL + cabinetId ); return response; } catch (error) { @@ -370,7 +370,7 @@ export const axiosReturnByUserId = async (userIds: number[]): Promise => { const axiosBundleReturnURL = "/v4/admin/return-cabinets"; export const axiosBundleReturn = async ( - cabinetIdList: number[] + cabinetIdList: number[] ): Promise => { try { const response = await instance.patch(axiosBundleReturnURL, { @@ -384,9 +384,9 @@ export const axiosBundleReturn = async ( const axiosUpdateCabinetsURL = "/v4/admin/cabinets/"; export const axiosUpdateCabinets = async ( - cabinetIds: number[], - lentType: CabinetType | null, - status: CabinetStatus | null + cabinetIds: number[], + lentType: CabinetType | null, + status: CabinetStatus | null ): Promise => { try { const response = await instance.patch(axiosUpdateCabinetsURL, { @@ -404,7 +404,7 @@ const axiosSearchByIntraIdURL = "/v4/admin/search/users-simple"; export const axiosSearchByIntraId = async (intraId: string) => { try { const response = await instance.get(axiosSearchByIntraIdURL, { - params: {name: intraId, page: 0, size: 10}, + params: { name: intraId, page: 0, size: 10 }, }); return response; } catch (error) { @@ -416,7 +416,7 @@ const axiosSearchByCabinetNumURL = "/v4/admin/search/cabinets"; export const axiosSearchByCabinetNum = async (number: number) => { try { const response = await instance.get(axiosSearchByCabinetNumURL, { - params: {visibleNum: number}, + params: { visibleNum: number }, }); return response; } catch (error) { @@ -428,7 +428,7 @@ const axiosSearchByCabinetNumSimpleURL = "/v4/admin/search/cabinets-simple"; export const axiosSearchByCabinetNumSimple = async (number: number) => { try { const response = await instance.get(axiosSearchByCabinetNumSimpleURL, { - params: {visibleNum: number}, + params: { visibleNum: number }, }); return response; } catch (error) { @@ -438,12 +438,12 @@ export const axiosSearchByCabinetNumSimple = async (number: number) => { const axiosSearchDetailByIntraIdURL = "/v4/admin/search/users"; export const axiosSearchDetailByIntraId = async ( - intraId: string, - page: number + intraId: string, + page: number ) => { try { const response = await instance.get(axiosSearchDetailByIntraIdURL, { - params: {name: intraId, page: page, size: 10}, + params: { name: intraId, page: page, size: 10 }, }); return response; } catch (error) { @@ -456,7 +456,7 @@ export const axiosDeleteCurrentBanLog = async (userId: number | null) => { if (userId === null) return; try { const response = await instance.delete( - axiosDeleteCurrentBanLogURL + userId?.toString() + "/ban-history" + axiosDeleteCurrentBanLogURL + userId?.toString() + "/ban-history" ); return response; } catch (error) { @@ -468,7 +468,7 @@ const axiosGetBrokenCabinetListURL = "/v4/admin/cabinets/status/BROKEN"; export const axiosGetBrokenCabinetList = async () => { try { const response = await instance.get(axiosGetBrokenCabinetListURL, { - params: {page: 0, size: 0}, + params: { page: 0, size: 0 }, }); return response.data.result; } catch (error) { @@ -480,7 +480,7 @@ const axiosGetBannedUserListURL = "v4/admin/statistics/users/banned"; export const axiosGetBannedUserList = async () => { try { const response = await instance.get(axiosGetBannedUserListURL, { - params: {page: 0, size: 0}, + params: { page: 0, size: 0 }, }); return response.data.result; } catch (error) { @@ -492,7 +492,7 @@ const axiosGetStatisticsURL = "/v4/admin/statistics/lent-histories"; export const axiosGetStatistics = async (start: Date, end: Date) => { try { const response = await instance.get(axiosGetStatisticsURL, { - params: {startDate: start, endDate: end}, + params: { startDate: start, endDate: end }, }); return response.data; } catch (error) { @@ -501,7 +501,7 @@ export const axiosGetStatistics = async (start: Date, end: Date) => { }; const axiosGetCabinetNumbersPerFloorURL = - "/v4/admin/statistics/buildings/floors/cabinets"; + "/v4/admin/statistics/buildings/floors/cabinets"; export const axiosGetCabinetNumbersPerFloor = async () => { try { const response = await instance.get(axiosGetCabinetNumbersPerFloorURL); @@ -515,7 +515,7 @@ const axiosGetOverdueUserListURL = "/v4/admin/statistics/users/overdue"; export const axiosGetOverdueUserList = async () => { try { const response = await instance.get(axiosGetOverdueUserListURL, { - params: {page: 0, size: 0}, + params: { page: 0, size: 0 }, }); return response.data.result; } catch (error) { @@ -525,16 +525,16 @@ export const axiosGetOverdueUserList = async () => { const axiosGetCabinetLentLogURL = "/v4/admin/cabinets/"; export const axiosGetCabinetLentLog = async ( - cabinetId: number | null, - page: number + cabinetId: number | null, + page: number ): Promise => { if (cabinetId == null) return; try { const response = await instance.get( - axiosGetCabinetLentLogURL + cabinetId.toString() + "/lent-histories", - { - params: {page: page, size: 8}, - } + axiosGetCabinetLentLogURL + cabinetId.toString() + "/lent-histories", + { + params: { page: page, size: 8 }, + } ); return response; } catch (error) { @@ -544,16 +544,16 @@ export const axiosGetCabinetLentLog = async ( const axiosGetUserLentLogURL = "/v4/admin/users/"; export const axiosGetUserLentLog = async ( - userId: number | null, - page: number + userId: number | null, + page: number ): Promise => { if (userId == null) return; try { const response = await instance.get( - axiosGetUserLentLogURL + userId.toString() + "/lent-histories", - { - params: {page: page, size: 8}, - } + axiosGetUserLentLogURL + userId.toString() + "/lent-histories", + { + params: { page: page, size: 8 }, + } ); return response; } catch (error) { @@ -563,12 +563,12 @@ export const axiosGetUserLentLog = async ( const axiosGetClubUserLogURL = "/v4/admin/clubs"; export const axiosGetClubUserLog = async ( - page: number, - size: number + page: number, + size: number ): Promise => { try { const response = await instance.get(axiosGetClubUserLogURL, { - params: {page: page, size: size}, + params: { page: page, size: size }, }); return response; } catch (error) { @@ -578,8 +578,8 @@ export const axiosGetClubUserLog = async ( const axiosCreateClubUserURL = "/v4/admin/clubs"; export const axiosCreateClubUser = async ( - clubName: string | null, - clubMaster: string | null + clubName: string | null, + clubMaster: string | null ): Promise => { if (clubName === null || clubMaster === null) return; try { @@ -595,15 +595,15 @@ export const axiosCreateClubUser = async ( const axiosEditClubUserURL = "/v4/admin/clubs/"; export const axiosEditClubUser = async ( - clubInfo: ClubUserDto + clubInfo: ClubUserDto ): Promise => { try { const response = await instance.patch( - axiosEditClubUserURL + clubInfo.clubId.toString(), - { - clubName: clubInfo.clubName, - clubMaster: clubInfo.clubMaster, - } + axiosEditClubUserURL + clubInfo.clubId.toString(), + { + clubName: clubInfo.clubName, + clubMaster: clubInfo.clubMaster, + } ); return response; } catch (error) { @@ -613,12 +613,12 @@ export const axiosEditClubUser = async ( const axiosDeleteClubUserURL = "/v4/admin/clubs/"; export const axiosDeleteClubUser = async ( - clubId: number | null + clubId: number | null ): Promise => { if (clubId === null) return; try { const response = await instance.delete( - axiosDeleteClubUserURL + clubId?.toString() + axiosDeleteClubUserURL + clubId?.toString() ); return response; } catch (error) { @@ -649,12 +649,12 @@ export const axiosDeleteClubUser = async ( const axiosLentClubCabinetURL = "/v4/admin/clubs/"; export const axiosLentClubCabinet = async ( - clubId: number, - cabinetId: number + clubId: number, + cabinetId: number ): Promise => { try { const response = await instance.post( - axiosLentClubCabinetURL + + axiosLentClubCabinetURL + clubId.toString() + "/cabinets/" + cabinetId.toString() @@ -667,8 +667,8 @@ export const axiosLentClubCabinet = async ( const axiosLentShareIdURL = "/v4/lent/cabinets/share/"; export const axiosLentShareId = async ( - cabinetId: number | null, - shareCode: string + cabinetId: number | null, + shareCode: string ): Promise => { if (cabinetId === null) return; try { @@ -703,3 +703,34 @@ export const axiosGetAvailableCabinets = async (): Promise => { throw error; } }; + +const axiosSendSlackNotificationToUserURL = "/slack/send"; +export const axiosSendSlackNotificationToUser = async ( + receiverName: string, + message: string +): Promise => { + try { + const response = await instance.post(axiosSendSlackNotificationToUserURL, { + receiverName: receiverName, + message: message, + }); + return response; + } catch (error) { + throw error; + } +}; + +export const axiosSendSlackNotificationToChannel = async ( + receiverName: string, + message: string, + channel: string | undefined +): Promise => { + try { + await instance.post(axiosSendSlackNotificationToUserURL + `/${channel}`, { + receiverName: receiverName, + message: message, + }); + } catch (error) { + throw error; + } +}; diff --git a/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx b/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx index d38c19610..944e2fba8 100644 --- a/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx +++ b/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx @@ -1,6 +1,10 @@ import { useRef, useState } from "react"; import styled from "styled-components"; import { SlackAlarmTemplates, SlackChannels } from "@/assets/data/SlackAlarm"; +import { + axiosSendSlackNotificationToChannel, + axiosSendSlackNotificationToUser, +} from "@/api/axios/axios.custom"; import useOutsideClick from "@/hooks/useOutsideClick"; import SlackAlarmSearchBar from "../TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar"; @@ -14,7 +18,8 @@ const SlackAlarmJ = () => { setOnFocus(false); }); - const initialTextrea = () => { + const initializeInputandTextArea = () => { + if (searchInput.current) searchInput.current.value = ""; if (searchTextArea.current) searchTextArea.current.value = ""; }; @@ -30,6 +35,37 @@ const SlackAlarmJ = () => { searchTextArea.current.value = template!.content; }; + const submit = async () => { + if (!searchInput.current?.value) { + // TODO : 보내는이 입력하라고 알리기 + } else if (!searchInput.current.value) { + // TODO : 메세지 입력하라고 알리기 + } else + try { + if (searchInput.current!.value[0] === "#") { + let channelId = SlackChannels.find((channel) => { + return searchInput.current!.value === channel.title; + })?.channelId; + await axiosSendSlackNotificationToChannel( + searchInput.current.value, + searchTextArea.current!.value, + channelId + ); + } else { + await axiosSendSlackNotificationToUser( + searchInput.current.value, + searchTextArea.current!.value + ); + } + } catch (error: any) { + // TODO : response modal? + // setModalTitle(error.response.data.message); + // setModalContent(error.response.data.message); + // setHasErrorOnResponse(true); + } finally { + } + }; + return ( <>

자주쓰는채널

@@ -60,8 +96,8 @@ const SlackAlarmJ = () => { ref={searchTextArea} isOnFocus={onFocus} /> - - + + ); diff --git a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx index eb15aedd1..cfda402c0 100644 --- a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx +++ b/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx @@ -23,7 +23,7 @@ const SlackAlarmSearchBar = ({ ISlackChannels[] >([]); const [totalLength, setTotalLength] = useState(0); - const [isFocus, setIsFocus] = useState(true); + const [isOnFocus, setIsOnFocus] = useState(true); const [targetIndex, setTargetIndex] = useState(-1); const [searchValue, setSearchValue] = useState(""); const [floor, setFloor] = useState(0); @@ -98,7 +98,6 @@ const SlackAlarmSearchBar = ({ }); setSearchListById([]); setSearchListByChannel(searchResult); - console.log("searchResult : ", searchResult); setTotalLength(searchResult.length); } } else { @@ -127,7 +126,7 @@ const SlackAlarmSearchBar = ({ // outside click useOutsideClick(searchWrap, () => { - setIsFocus(false); + setIsOnFocus(false); }); const valueChangeHandler = () => { @@ -174,15 +173,16 @@ const SlackAlarmSearchBar = ({ type="text" placeholder="Search" onFocus={() => { - setIsFocus(true); + setIsOnFocus(true); }} onChange={debounce(typeSearchInput, 300)} onKeyDown={handleInputKey} + isOnFocus={isOnFocus} > 취소 - {isFocus && searchInput.current?.value && totalLength > 0 && ( + {isOnFocus && searchInput.current?.value && totalLength > 0 && ( <> ` width: 300px; height: 40px; border: 1px solid #7b7b7b; @@ -219,6 +219,8 @@ const SearchBarInputStyled = styled.input` &::placeholder { color: #7b7b7b; } + border-color: ${(props) => + props.isOnFocus ? "var(--main-color)" : "#7b7b7b"}; `; const SearchButtonStyled = styled.button` From af94d93493005dda6994ecba58feff545ca2954f Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 9 Apr 2024 20:56:23 +0900 Subject: [PATCH 0543/1029] =?UTF-8?q?[FE]=20ETC:=20=EB=B0=9B=EB=8A=94?= =?UTF-8?q?=EC=9D=B4=20input=20=EA=B5=AC=ED=98=84=EC=A4=91#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 6 +- frontend/src/assets/data/SlackAlarm.ts | 4 +- .../AdminSlackNotiSearchBar.tsx} | 108 ++++++------------ .../AdminSlackNotiSearchBarList.tsx} | 22 ++-- .../AdminSlackNotiSearchListItem.tsx | 51 +++++++++ .../components/SlackAlarmJ/SlackAlarmJ.tsx | 18 +-- .../src/pages/admin/AdminSlackNotiPage.tsx | 67 +++++++---- 7 files changed, 148 insertions(+), 128 deletions(-) rename frontend/src/components/{TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar.tsx => Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar.tsx} (67%) rename frontend/src/components/{TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBarList/SlackAlarmSearchBarList.tsx => Search/AdminSlackNotiSearch/AdminSlackNotiSearchBarList.tsx} (73%) create mode 100644 frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchListItem.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b0151b5af..0085122e9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,7 +12,6 @@ import ProfilePage from "@/pages/ProfilePage"; import AdminMainPage from "@/pages/admin/AdminMainPage"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; import PageTracker from "@/api/analytics/PageTracker"; -import SlackAlarmJ from "./components/SlackAlarmJ/SlackAlarmJ"; const NotFoundPage = lazy(() => import("@/pages/NotFoundPage")); const LoginFailurePage = lazy(() => import("@/pages/LoginFailurePage")); @@ -24,7 +23,9 @@ const AdminLoginFailurePage = lazy( () => import("@/pages/admin/AdminLoginFailurePage") ); const AdminHomePage = lazy(() => import("@/pages/admin/AdminHomePage")); -const AdminSlackNotiPage = lazy(() => import("@/pages/admin/AdminSlackNotiPage")); +const AdminSlackNotiPage = lazy( + () => import("@/pages/admin/AdminSlackNotiPage") +); function App(): React.ReactElement { return ( @@ -52,7 +53,6 @@ function App(): React.ReactElement { } /> } /> } /> - } /> } /> ; + renderReceiverInput: (title: string) => void; }) => { const navigate = useNavigate(); - const searchWrap = useRef(null); const [searchListById, setSearchListById] = useState([]); const [searchListByChannel, setSearchListByChannel] = useState< - ISlackChannels[] + ISlackChannel[] >([]); const [totalLength, setTotalLength] = useState(0); - const [isOnFocus, setIsOnFocus] = useState(true); + const [onFocus, setOnFocus] = useState(true); const [targetIndex, setTargetIndex] = useState(-1); const [searchValue, setSearchValue] = useState(""); const [floor, setFloor] = useState(0); @@ -49,16 +54,8 @@ const SlackAlarmSearchBar = ({ } else if (isNaN(Number(searchValue)) && searchValue.length <= 1) { resetSearchState(); return alert("두 글자 이상의 검색어를 입력해주세요."); - } else { - let query = floor - ? `?q=${searchInput.current.value}&floor=${floor}` - : `?q=${searchInput.current.value}`; - navigate({ - pathname: "search", - search: query, - }); - resetSearchState(); } + // TODO : search bar list item 선택됐을때 엔터눌렀을때 } }; @@ -116,17 +113,9 @@ const SlackAlarmSearchBar = ({ } }; - const clickCancelButton = () => { - resetSearchState(); - document.getElementById("searchBar")!.classList.remove("on"); - document.getElementById("topNavLogo")!.classList.remove("pushOut"); - document.getElementById("topNavButtonGroup")!.classList.remove("pushOut"); - document.getElementById("topNavWrap")!.classList.remove("pushOut"); - }; - // outside click - useOutsideClick(searchWrap, () => { - setIsOnFocus(false); + useOutsideClick(searchInput, () => { + setOnFocus(false); }); const valueChangeHandler = () => { @@ -164,83 +153,54 @@ const SlackAlarmSearchBar = ({ } } }; - return ( - + <> - { - setIsOnFocus(true); + setOnFocus(true); }} onChange={debounce(typeSearchInput, 300)} onKeyDown={handleInputKey} - isOnFocus={isOnFocus} - > - + /> - 취소 - {isOnFocus && searchInput.current?.value && totalLength > 0 && ( + {onFocus && searchInput.current?.value && totalLength > 0 && ( <> )} - + ); }; -const SearchBarWrapperStyled = styled.div` - position: relative; -`; - const SearchBarStyled = styled.div` position: relative; width: 100%; `; -const SearchBarInputStyled = styled.input<{ isOnFocus: boolean }>` - width: 300px; +const FormInputStyled = styled.input` + width: 100%; height: 40px; - border: 1px solid #7b7b7b; - border-radius: 10px; - text-align: left; - padding: 0 20px; - color: #7b7b7b; - background-color: rgba(255, 255, 255, 0.2); - &::placeholder { - color: #7b7b7b; + background-color: #fff; + border-radius: 8px; + border: 1px solid #eee; + :focus { + border: 1px solid var(--main-color); } - border-color: ${(props) => - props.isOnFocus ? "var(--main-color)" : "#7b7b7b"}; -`; - -const SearchButtonStyled = styled.button` - background: url("/src/assets/images/searchWhite.svg") no-repeat 50% 50%; - width: 32px; - height: 32px; - position: absolute; - top: 4px; - right: 14px; -`; - -const CancelButtonStyled = styled.button` - min-width: 60px; - width: 60px; - height: 40px; - overflow: hidden; - display: none; - @media screen and (max-width: 768px) { - display: block; + text-align: left; + padding: 0 10px; + ::placeholder { + color: var(--line-color); } `; -export default SlackAlarmSearchBar; +export default AdminSlackNotiSearchBar; diff --git a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBarList/SlackAlarmSearchBarList.tsx b/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBarList.tsx similarity index 73% rename from frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBarList/SlackAlarmSearchBarList.tsx rename to frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBarList.tsx index ef980b503..8df338e74 100644 --- a/frontend/src/components/TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBarList/SlackAlarmSearchBarList.tsx +++ b/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBarList.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; -import SearchListItem from "@/components/TopNav/SearchBar/SearchListItem/SearchListItem"; -import { ISlackChannels } from "../SlackAlarmSearchBar"; +import { ISlackChannel } from "./AdminSlackNotiSearchBar"; +import AdminSlackNotiSearchListItem from "./AdminSlackNotiSearchListItem"; interface ISearchListByIntraId { name: string; @@ -11,38 +11,36 @@ const SlackAlarmSearchBarList = ({ searchListById, searchListByChannel, searchWord, - resetSearchState, - totalLength, targetIndex, + renderReceiverInput, }: { searchListById: ISearchListByIntraId[]; - searchListByChannel: ISlackChannels[]; - resetSearchState: () => void; - searchWord?: string; - totalLength: number; + searchListByChannel: ISlackChannel[]; + searchWord: string; targetIndex?: number; + renderReceiverInput: (title: string) => void; }) => { return ( {searchListById.map((item, index: number) => { return ( - ); })} {searchListByChannel.map((item, index: number) => { return ( - ); })} diff --git a/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchListItem.tsx b/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchListItem.tsx new file mode 100644 index 000000000..f5c2473b8 --- /dev/null +++ b/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchListItem.tsx @@ -0,0 +1,51 @@ +import styled from "styled-components"; +import ChangeToHTML from "@/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; + +const AdminSlackNotiSearchListItem = (props: { + inputText: string; + resultText: string; + isTargetIndex?: boolean; + renderReceiverInput: (title: string) => void; +}) => { + const { resultText, inputText, isTargetIndex, renderReceiverInput } = props; + + return ( + { + renderReceiverInput(resultText); + }} + > + + + ); +}; + +const LiStyled = styled.li` + padding: 12px; + border-radius: 10px; + cursor: pointer; + & strong { + color: var(--main-color); + } + + &.active { + background-color: var(--main-color); + color: var(--white); + } + &.active strong { + color: var(--white); + } + + @media (hover: hover) and (pointer: fine) { + &:hover { + background-color: var(--main-color); + color: var(--white); + } + &:hover strong { + color: var(--white); + } + } +`; + +export default AdminSlackNotiSearchListItem; diff --git a/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx b/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx index 944e2fba8..80cadf9ca 100644 --- a/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx +++ b/frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx @@ -6,18 +6,12 @@ import { axiosSendSlackNotificationToUser, } from "@/api/axios/axios.custom"; import useOutsideClick from "@/hooks/useOutsideClick"; -import SlackAlarmSearchBar from "../TopNav/SearchBar/SlackAlarmJ/SlackAlarmSearchBar"; +import AdminSlackNotiSearchBar from "../Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar"; const SlackAlarmJ = () => { - const [onFocus, setOnFocus] = useState(false); const searchTextArea = useRef(null); const searchInput = useRef(null); - // outside click - useOutsideClick(searchTextArea, () => { - setOnFocus(false); - }); - const initializeInputandTextArea = () => { if (searchInput.current) searchInput.current.value = ""; if (searchTextArea.current) searchTextArea.current.value = ""; @@ -87,15 +81,15 @@ const SlackAlarmJ = () => {

알림보내기

받는이(intraid/channel)
- + {/* */}
메시지내용
- { setOnFocus(true); }} ref={searchTextArea} isOnFocus={onFocus} - /> + /> */}
@@ -104,7 +98,3 @@ const SlackAlarmJ = () => { }; export default SlackAlarmJ; - -const ContentStyled = styled.textarea<{ isOnFocus: boolean }>` - outline-color: ${(props) => (props.isOnFocus ? "var(--main-color)" : "")}; -`; diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index 2b372b182..d2fc1ef9f 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -1,7 +1,31 @@ +import { useRef } from "react"; import styled from "styled-components"; +import AdminSlackNotiSearchBar, { + ISlackChannel, +} from "@/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar"; import { SlackAlarmTemplates, SlackChannels } from "@/assets/data/SlackAlarm"; const AdminSlackNotiPage = () => { + const searchInput = useRef(null); + const searchTextArea = useRef(null); + + const renderReceiverInput = (title: string) => { + if (searchInput.current) searchInput.current.value = title; + }; + + const renderTemplateTextArea = (title: string) => { + const template = SlackAlarmTemplates.find((template) => { + return template.title === title; + }); + if (searchTextArea.current) + searchTextArea.current.value = template!.content; + }; + + const initializeInputandTextArea = () => { + if (searchInput.current) searchInput.current.value = ""; + if (searchTextArea.current) searchTextArea.current.value = ""; + }; + return ( @@ -11,9 +35,12 @@ const AdminSlackNotiPage = () => { 자주 쓰는 채널 - {SlackChannels.map((channel: any, idx: number) => { + {SlackChannels.map((channel: ISlackChannel, idx: number) => { return ( - + renderReceiverInput(channel.title)} + > {channel.title} ); @@ -24,14 +51,18 @@ const AdminSlackNotiPage = () => { 자주 쓰는 템플릿 - {SlackAlarmTemplates.map((channel: any, idx: number) => { + {SlackAlarmTemplates.map((template: any, idx: number) => { return ( - - {channel.title} + renderTemplateTextArea(template.title)} + > + {template.title} ); })} 사물함 대여 + {/* TODO : 사물함 대여일때 템플릿 */} @@ -41,16 +72,21 @@ const AdminSlackNotiPage = () => { 받는이(Intra ID/ Channel)* - + 메시지 내용* - + - 초기화 + + 초기화 + 보내기 @@ -148,21 +184,6 @@ const FormSubTitleStyled = styled.h3` } `; -const FormInputStyled = styled.input` - width: 100%; - height: 40px; - background-color: #fff; - border-radius: 8px; - border: 1px solid #eee; - :focus { - border: 1px solid var(--main-color); - } - text-align: left; - padding: 0 10px; - ::placeholder { - color: var(--line-color); - } -`; const FormTextareaStyled = styled.textarea` box-sizing: border-box; width: 100%; From 7b092c44ed1ee302c280ca541605fe3e3af6f318 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 10 Apr 2024 14:16:35 +0900 Subject: [PATCH 0544/1029] =?UTF-8?q?[FE]=20FEAT:=20=ED=99=94=EC=82=B4?= =?UTF-8?q?=ED=91=9C=20=EC=9C=84=20=EC=95=84=EB=9E=98=EB=A1=9C=20=EC=84=9C?= =?UTF-8?q?=EC=B9=98=20=ED=95=AD=EB=AA=A9=20=EC=84=A0=ED=83=9D=EB=90=90?= =?UTF-8?q?=EC=9D=84=EB=95=8C=20=EC=97=94=ED=84=B0=20=EB=88=84=EB=A5=B4?= =?UTF-8?q?=EB=A9=B4=20search=20bar=20list=20=EC=95=88=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EA=B2=8C=20=ED=95=A8#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 5461 +---------------- .../AdminSlackNotiSearchBar.tsx | 37 +- 2 files changed, 7 insertions(+), 5491 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ddf1b4764..82e7a5836 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13244,5463 +13244,4 @@ "requires": { "d3-scale": "^3.2.3", "d3-time": "^1.0.11", - "d3-time-format": "^3.0.0", - "lodash": "^4.17.21" - }, - "dependencies": { - "d3-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", - "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" - } - } - }, - "@nivo/tooltip": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.80.0.tgz", - "integrity": "sha512-qGmrreRwnCsYjn/LAuwBtxBn/tvG8y+rwgd4gkANLBAoXd3bzJyvmkSe+QJPhUG64bq57ibDK+lO2pC48a3/fw==", - "requires": { - "@react-spring/web": "9.4.5" - } - }, - "@nivo/voronoi": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.80.0.tgz", - "integrity": "sha512-zaJV3I3cRu1gHpsXCIEvp6GGlGY8P7D9CwAVCjYDGrz3W/+GKN0kA7qGyHTC97zVxJtfefxSPlP/GtOdxac+qw==", - "requires": { - "d3-delaunay": "^5.3.0", - "d3-scale": "^3.2.3" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "@react-spring/animated": { - "version": "9.4.5", - "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.4.5.tgz", - "integrity": "sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA==", - "requires": { - "@react-spring/shared": "~9.4.5", - "@react-spring/types": "~9.4.5" - } - }, - "@react-spring/core": { - "version": "9.4.5", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.4.5.tgz", - "integrity": "sha512-83u3FzfQmGMJFwZLAJSwF24/ZJctwUkWtyPD7KYtNagrFeQKUH1I05ZuhmCmqW+2w1KDW1SFWQ43RawqfXKiiQ==", - "requires": { - "@react-spring/animated": "~9.4.5", - "@react-spring/rafz": "~9.4.5", - "@react-spring/shared": "~9.4.5", - "@react-spring/types": "~9.4.5" - } - }, - "@react-spring/rafz": { - "version": "9.4.5", - "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.4.5.tgz", - "integrity": "sha512-swGsutMwvnoyTRxvqhfJBtGM8Ipx6ks0RkIpNX9F/U7XmyPvBMGd3GgX/mqxZUpdlsuI1zr/jiYw+GXZxAlLcQ==" - }, - "@react-spring/shared": { - "version": "9.4.5", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.4.5.tgz", - "integrity": "sha512-JhMh3nFKsqyag0KM5IIM8BQANGscTdd0mMv3BXsUiMZrcjQTskyfnv5qxEeGWbJGGar52qr5kHuBHtCjQOzniA==", - "requires": { - "@react-spring/rafz": "~9.4.5", - "@react-spring/types": "~9.4.5" - } - }, - "@react-spring/types": { - "version": "9.4.5", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.4.5.tgz", - "integrity": "sha512-mpRIamoHwql0ogxEUh9yr4TP0xU5CWyZxVQeccGkHHF8kPMErtDXJlxyo0lj+telRF35XNihtPTWoflqtyARmg==" - }, - "@react-spring/web": { - "version": "9.4.5", - "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.4.5.tgz", - "integrity": "sha512-NGAkOtKmOzDEctL7MzRlQGv24sRce++0xAY7KlcxmeVkR7LRSGkoXHaIfm9ObzxPMcPHQYQhf3+X9jepIFNHQA==", - "requires": { - "@react-spring/animated": "~9.4.5", - "@react-spring/core": "~9.4.5", - "@react-spring/shared": "~9.4.5", - "@react-spring/types": "~9.4.5" - } - }, - "@remix-run/router": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.1.0.tgz", - "integrity": "sha512-rGl+jH/7x1KBCQScz9p54p0dtPLNeKGb3e0wD2H5/oZj41bwQUnXdzbj2TbUAFhvD7cp9EyEQA4dEgpUFa1O7Q==" - }, - "@rollup/pluginutils": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", - "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - } - }, - "@sinclair/typebox": { - "version": "0.25.21", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.21.tgz", - "integrity": "sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==", - "dev": true - }, - "@svgr/babel-plugin-add-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", - "dev": true, - "requires": {} - }, - "@svgr/babel-plugin-remove-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", - "dev": true, - "requires": {} - }, - "@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", - "dev": true, - "requires": {} - }, - "@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", - "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", - "dev": true, - "requires": {} - }, - "@svgr/babel-plugin-svg-dynamic-title": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", - "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", - "dev": true, - "requires": {} - }, - "@svgr/babel-plugin-svg-em-dimensions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", - "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", - "dev": true, - "requires": {} - }, - "@svgr/babel-plugin-transform-react-native-svg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", - "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", - "dev": true, - "requires": {} - }, - "@svgr/babel-plugin-transform-svg-component": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", - "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", - "dev": true, - "requires": {} - }, - "@svgr/babel-preset": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", - "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", - "dev": true, - "requires": { - "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", - "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", - "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", - "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", - "@svgr/babel-plugin-transform-svg-component": "8.0.0" - } - }, - "@svgr/core": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", - "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", - "dev": true, - "requires": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^8.1.3", - "snake-case": "^3.0.4" - }, - "dependencies": { - "cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "requires": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - } - }, - "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "optional": true, - "peer": true - } - } - }, - "@svgr/hast-util-to-babel-ast": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", - "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", - "dev": true, - "requires": { - "@babel/types": "^7.21.3", - "entities": "^4.4.0" - } - }, - "@svgr/plugin-jsx": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", - "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", - "dev": true, - "requires": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "@svgr/hast-util-to-babel-ast": "8.0.0", - "svg-parser": "^2.0.4" - } - }, - "@svgr/plugin-svgo": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", - "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", - "dev": true, - "requires": { - "cosmiconfig": "^8.1.3", - "deepmerge": "^4.3.1", - "svgo": "^3.0.2" - }, - "dependencies": { - "cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "requires": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - } - }, - "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "optional": true, - "peer": true - } - } - }, - "@svgr/rollup": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/rollup/-/rollup-8.1.0.tgz", - "integrity": "sha512-0XR1poYvPQoPpmfDYLEqUGu5ePAQ4pdgN3VFsZBNAeze7qubVpsIY1o1R6PZpKep/DKu33GSm2NhwpCLkMs2Cw==", - "dev": true, - "requires": { - "@babel/core": "^7.21.3", - "@babel/plugin-transform-react-constant-elements": "^7.21.3", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.0", - "@rollup/pluginutils": "^5.0.2", - "@svgr/core": "8.1.0", - "@svgr/plugin-jsx": "8.1.0", - "@svgr/plugin-svgo": "8.1.0" - } - }, - "@testing-library/dom": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz", - "integrity": "sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "^5.0.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", - "pretty-format": "^27.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "requires": { - "deep-equal": "^2.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/jest-dom": { - "version": "5.16.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", - "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", - "dev": true, - "requires": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "requires": { - "deep-equal": "^2.0.5" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/react": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", - "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.5.0", - "@types/react-dom": "^18.0.0" - } - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true - }, - "@trivago/prettier-plugin-sort-imports": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.1.1.tgz", - "integrity": "sha512-dQ2r2uzNr1x6pJsuh/8x0IRA3CBUB+pWEW3J/7N98axqt7SQSm+2fy0FLNXvXGg77xEDC7KHxJlHfLYyi7PDcw==", - "dev": true, - "requires": { - "@babel/generator": "7.17.7", - "@babel/parser": "^7.20.5", - "@babel/traverse": "7.17.3", - "@babel/types": "7.17.0", - "javascript-natural-sort": "0.7.1", - "lodash": "^4.17.21" - }, - "dependencies": { - "@babel/generator": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", - "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", - "dev": true, - "requires": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true - } - } - }, - "@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true - }, - "@types/aria-query": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", - "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", - "dev": true - }, - "@types/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", - "dev": true - }, - "@types/chai-subset": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", - "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", - "dev": true, - "requires": { - "@types/chai": "*" - } - }, - "@types/cookie": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", - "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" - }, - "@types/estree": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", - "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", - "dev": true - }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.4.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.4.0.tgz", - "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", - "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - } - } - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "@types/node": { - "version": "18.11.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", - "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==" - }, - "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "@types/react": { - "version": "18.0.26", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz", - "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-color": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.7.tgz", - "integrity": "sha512-IGZA7e8Oia0+Sb3/1KP0qTThGelZ9DRspfeLrFWQWv5vXHiYlJJQMC2kgQr75CtP4uL8/kvT8qBgrOVlxVoNTw==", - "requires": { - "@types/react": "*", - "@types/reactcss": "*" - } - }, - "@types/react-dom": { - "version": "18.0.9", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.9.tgz", - "integrity": "sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/reactcss": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.7.tgz", - "integrity": "sha512-MYPuVierMjIo0EDQnNauvBA94IOeB9lfjC619g+26u7ilsTtoFv6X7eQvaw79Fqqpi0yzoSMz0nUazeJlQUZnA==", - "requires": { - "@types/react": "*" - } - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/styled-components": { - "version": "5.1.26", - "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", - "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", - "dev": true, - "requires": { - "@types/hoist-non-react-statics": "*", - "@types/react": "*", - "csstype": "^3.0.2" - } - }, - "@types/testing-library__jest-dom": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", - "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", - "dev": true, - "requires": { - "@types/jest": "*" - } - }, - "@types/yargs": { - "version": "17.0.20", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.20.tgz", - "integrity": "sha512-eknWrTHofQuPk2iuqDm1waA7V6xPlbgBoaaXEgYkClhLOnB0TtbW+srJaOToAgawPxPlHQzwypFA2bhZaUGP5A==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz", - "integrity": "sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.46.1", - "@typescript-eslint/type-utils": "5.46.1", - "@typescript-eslint/utils": "5.46.1", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/parser": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz", - "integrity": "sha512-RelQ5cGypPh4ySAtfIMBzBGyrNerQcmfA1oJvPj5f+H4jI59rl9xxpn4bonC0tQvUKOEN7eGBFWxFLK3Xepneg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.46.1", - "@typescript-eslint/types": "5.46.1", - "@typescript-eslint/typescript-estree": "5.46.1", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz", - "integrity": "sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.46.1", - "@typescript-eslint/visitor-keys": "5.46.1" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz", - "integrity": "sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.46.1", - "@typescript-eslint/utils": "5.46.1", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz", - "integrity": "sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz", - "integrity": "sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.46.1", - "@typescript-eslint/visitor-keys": "5.46.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz", - "integrity": "sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.46.1", - "@typescript-eslint/types": "5.46.1", - "@typescript-eslint/typescript-estree": "5.46.1", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "dependencies": { - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz", - "integrity": "sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.46.1", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@vitejs/plugin-react": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.0.0.tgz", - "integrity": "sha512-1mvyPc0xYW5G8CHQvJIJXLoMjl5Ct3q2g5Y2s6Ccfgwm45y48LBvsla7az+GkkAtYikWQ4Lxqcsq5RHLcZgtNQ==", - "dev": true, - "requires": { - "@babel/core": "^7.20.5", - "@babel/plugin-transform-react-jsx-self": "^7.18.6", - "@babel/plugin-transform-react-jsx-source": "^7.19.6", - "magic-string": "^0.27.0", - "react-refresh": "^0.14.0" - } - }, - "@vitest/expect": { - "version": "0.28.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.3.tgz", - "integrity": "sha512-dnxllhfln88DOvpAK1fuI7/xHwRgTgR4wdxHldPaoTaBu6Rh9zK5b//v/cjTkhOfNP/AJ8evbNO8H7c3biwd1g==", - "dev": true, - "requires": { - "@vitest/spy": "0.28.3", - "@vitest/utils": "0.28.3", - "chai": "^4.3.7" - } - }, - "@vitest/runner": { - "version": "0.28.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.3.tgz", - "integrity": "sha512-P0qYbATaemy1midOLkw7qf8jraJszCoEvjQOSlseiXZyEDaZTZ50J+lolz2hWiWv6RwDu1iNseL9XLsG0Jm2KQ==", - "dev": true, - "requires": { - "@vitest/utils": "0.28.3", - "p-limit": "^4.0.0", - "pathe": "^1.1.0" - }, - "dependencies": { - "p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "requires": { - "yocto-queue": "^1.0.0" - } - }, - "yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true - } - } - }, - "@vitest/spy": { - "version": "0.28.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.3.tgz", - "integrity": "sha512-jULA6suS6CCr9VZfr7/9x97pZ0hC55prnUNHNrg5/q16ARBY38RsjsfhuUXt6QOwvIN3BhSS0QqPzyh5Di8g6w==", - "dev": true, - "requires": { - "tinyspy": "^1.0.2" - } - }, - "@vitest/utils": { - "version": "0.28.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.3.tgz", - "integrity": "sha512-YHiQEHQqXyIbhDqETOJUKx9/psybF7SFFVCNfOvap0FvyUqbzTSDCa3S5lL4C0CLXkwVZttz9xknDoyHMguFRQ==", - "dev": true, - "requires": { - "cli-truncate": "^3.1.0", - "diff": "^5.1.0", - "loupe": "^2.3.6", - "picocolors": "^1.0.0", - "pretty-format": "^27.5.1" - } - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true - }, - "acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "dev": true, - "requires": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - }, - "array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.tosorted": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", - "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.1.3" - } - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true - }, - "axe-core": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.1.tgz", - "integrity": "sha512-lCZN5XRuOnpG4bpMq8v0khrWtUOn+i8lZSb6wHZH56ZfbIEv6XwJV84AAueh9/zi7qPVJ/E4yz6fmsiyOmXR4w==", - "dev": true - }, - "axios": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", - "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", - "dev": true - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", - "semver": "^6.3.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", - "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.32.2" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.3" - } - }, - "babel-plugin-styled-components": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", - "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-module-imports": "^7.16.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11", - "picomatch": "^2.3.0" - } - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" - }, - "caniuse-lite": { - "version": "1.0.30001551", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz", - "integrity": "sha512-vtBAez47BoGMMzlbYhfXrMV1kvRF2WP/lqiMuDu1Sb4EE4LKEgjopFDSRtZfdVnslNRpOqV/woE+Xgrwj6VQlg==", - "dev": true - }, - "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true - }, - "ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true - }, - "cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "requires": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - } - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" - }, - "core-js-compat": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.1.tgz", - "integrity": "sha512-6pYKNOgD/j/bkC5xS5IIg6bncid3rfrI42oBH1SQJbsmYPKF7rhzcFzYCcxYMmNQQ0rCEB8WqpW7QHndOggaeQ==", - "dev": true, - "requires": { - "browserslist": "^4.22.1" - } - }, - "core-js-pure": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz", - "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" - }, - "css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - } - }, - "css-to-react-native": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", - "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", - "requires": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "requires": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true - }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, - "csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "dev": true, - "requires": { - "css-tree": "~2.2.0" - }, - "dependencies": { - "css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dev": true, - "requires": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - } - }, - "mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true - } - } - }, - "cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, - "d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "requires": { - "internmap": "^1.0.0" - } - }, - "d3-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", - "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" - }, - "d3-delaunay": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", - "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", - "requires": { - "delaunator": "4" - } - }, - "d3-format": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", - "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" - }, - "d3-interpolate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", - "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", - "requires": { - "d3-color": "1 - 2" - } - }, - "d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" - }, - "d3-scale": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", - "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", - "requires": { - "d3-array": "^2.3.0", - "d3-format": "1 - 2", - "d3-interpolate": "1.2.0 - 2", - "d3-time": "^2.1.1", - "d3-time-format": "2 - 3" - } - }, - "d3-scale-chromatic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz", - "integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==", - "requires": { - "d3-color": "1 - 2", - "d3-interpolate": "1 - 2" - } - }, - "d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "requires": { - "d3-path": "1" - } - }, - "d3-time": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", - "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", - "requires": { - "d3-array": "2" - } - }, - "d3-time-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", - "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", - "requires": { - "d3-time": "1 - 2" - } - }, - "damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true - }, - "data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, - "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true - }, - "define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delaunator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", - "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "dev": true - }, - "diff-sequences": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true - }, - "dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true - }, - "domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "requires": { - "webidl-conversions": "^7.0.0" - } - }, - "domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dev": true, - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - } - }, - "dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - } - } - }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.559", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.559.tgz", - "integrity": "sha512-iS7KhLYCSJbdo3rUSkhDTVuFNCV34RKs2UaB9Ecr7VlqzjjWW//0nfsFF5dtDmyXlZQaDYYtID5fjtC/6lpRug==", - "dev": true - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", - "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "unbox-primitive": "^1.0.2" - } - }, - "es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - } - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "esbuild": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.9.tgz", - "integrity": "sha512-gkH83yHyijMSZcZFs1IWew342eMdFuWXmQo3zkDPTre25LIPBJsXryg02M3u8OpTwCJdBkdaQwqKkDLnAsAeLQ==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.16.9", - "@esbuild/android-arm64": "0.16.9", - "@esbuild/android-x64": "0.16.9", - "@esbuild/darwin-arm64": "0.16.9", - "@esbuild/darwin-x64": "0.16.9", - "@esbuild/freebsd-arm64": "0.16.9", - "@esbuild/freebsd-x64": "0.16.9", - "@esbuild/linux-arm": "0.16.9", - "@esbuild/linux-arm64": "0.16.9", - "@esbuild/linux-ia32": "0.16.9", - "@esbuild/linux-loong64": "0.16.9", - "@esbuild/linux-mips64el": "0.16.9", - "@esbuild/linux-ppc64": "0.16.9", - "@esbuild/linux-riscv64": "0.16.9", - "@esbuild/linux-s390x": "0.16.9", - "@esbuild/linux-x64": "0.16.9", - "@esbuild/netbsd-x64": "0.16.9", - "@esbuild/openbsd-x64": "0.16.9", - "@esbuild/sunos-x64": "0.16.9", - "@esbuild/win32-arm64": "0.16.9", - "@esbuild/win32-ia32": "0.16.9", - "@esbuild/win32-x64": "0.16.9" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "8.30.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", - "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.4.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "eslint-config-airbnb": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", - "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", - "dev": true, - "requires": { - "eslint-config-airbnb-base": "^15.0.0", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5" - } - }, - "eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, - "requires": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - } - }, - "eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "requires": {} - }, - "eslint-import-resolver-alias": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz", - "integrity": "sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==", - "dev": true, - "requires": {} - }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "dev": true, - "requires": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "eslint-plugin-jsx-a11y": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", - "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.18.9", - "aria-query": "^4.2.2", - "array-includes": "^3.1.5", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.4.3", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.2", - "language-tags": "^1.0.5", - "minimatch": "^3.1.2", - "semver": "^6.3.0" - } - }, - "eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-plugin-react": { - "version": "7.31.11", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.11.tgz", - "integrity": "sha512-TTvq5JsT5v56wPa9OYHzsrOlHzKZKjV+aLgS+55NJP/cuzdiQPC7PfYoUjMoxlffKtvijpk7vA/jmuqRb9nohw==", - "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.8" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - } - } - }, - "eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "expect": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.4.1.tgz", - "integrity": "sha512-OKrGESHOaMxK3b6zxIq9SOW8kEXztKff/Dvg88j4xIJxur1hspEbedVkR3GpHe5LO+WB2Qw7OWN0RMTdp6as5A==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.4.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.4.1", - "jest-message-util": "^29.4.1", - "jest-util": "^29.4.1" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", - "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "firebase": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.4.0.tgz", - "integrity": "sha512-3Z8WsNwA7kbcKGZ+nrTZ/ES518pk0K440ZJYD8nUNKN5hV6ll+unhUw30t1msedN6yIFjhsC/9OwT4Z0ohwO2w==", - "requires": { - "@firebase/analytics": "0.10.0", - "@firebase/analytics-compat": "0.2.6", - "@firebase/app": "0.9.19", - "@firebase/app-check": "0.8.0", - "@firebase/app-check-compat": "0.3.7", - "@firebase/app-compat": "0.2.19", - "@firebase/app-types": "0.9.0", - "@firebase/auth": "1.3.0", - "@firebase/auth-compat": "0.4.6", - "@firebase/database": "1.0.1", - "@firebase/database-compat": "1.0.1", - "@firebase/firestore": "4.2.0", - "@firebase/firestore-compat": "0.3.18", - "@firebase/functions": "0.10.0", - "@firebase/functions-compat": "0.3.5", - "@firebase/installations": "0.6.4", - "@firebase/installations-compat": "0.2.4", - "@firebase/messaging": "0.12.4", - "@firebase/messaging-compat": "0.2.4", - "@firebase/performance": "0.6.4", - "@firebase/performance-compat": "0.2.4", - "@firebase/remote-config": "0.4.4", - "@firebase/remote-config-compat": "0.2.4", - "@firebase/storage": "0.11.2", - "@firebase/storage-compat": "0.3.2", - "@firebase/util": "1.9.3" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "hamt_plus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "requires": { - "whatwg-encoding": "^2.0.0" - } - }, - "http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" - }, - "ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "internal-slot": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", - "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-array-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", - "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-typed-array": "^1.1.10" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "javascript-natural-sort": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", - "dev": true - }, - "jest-diff": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.4.1.tgz", - "integrity": "sha512-uazdl2g331iY56CEyfbNA0Ut7Mn2ulAG5vUaEHXycf1L6IPyuImIxSz4F0VYBKi7LYIuxOwTZzK3wh5jHzASMw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.4.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", - "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true - }, - "jest-matcher-utils": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.4.1.tgz", - "integrity": "sha512-k5h0u8V4nAEy6lSACepxL/rw78FLDkBnXhZVgFneVpnJONhb2DhZj/Gv4eNe+1XqQ5IhgUcqj745UwH0HJmMnA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.4.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.4.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", - "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.4.1.tgz", - "integrity": "sha512-H4/I0cXUaLeCw6FM+i4AwCnOwHRgitdaUFOdm49022YD5nfyr8C/DrbXOBEyJaj+w/y0gGJ57klssOaUiLLQGQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.4.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", - "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.4.1.tgz", - "integrity": "sha512-bQy9FPGxVutgpN4VRc0hk6w7Hx/m6L53QxpDreTZgJd9gfx/AV2MjyPde9tGyZRINAUrSv57p2inGBu2dRLmkQ==", - "dev": true, - "requires": { - "@jest/types": "^29.4.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-sdsl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", - "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsdom": { - "version": "21.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.0.tgz", - "integrity": "sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "dev": true, - "requires": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" - } - }, - "language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true - }, - "language-tags": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.7.tgz", - "integrity": "sha512-bSytju1/657hFjgUzPAPqszxH62ouE8nQFoFaVlIQfne4wO/wXC9A4+m8jYve7YBBvi59eq0SUpcshvG8h5Usw==", - "dev": true, - "requires": { - "language-subtag-registry": "^0.3.20" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "requires": { - "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - } - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", - "dev": true - }, - "magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.13" - } - }, - "material-colors": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", - "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" - }, - "mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true - }, - "mlly": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.1.0.tgz", - "integrity": "sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==", - "dev": true, - "requires": { - "acorn": "^8.8.1", - "pathe": "^1.0.0", - "pkg-types": "^1.0.1", - "ufo": "^1.0.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - } - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.entries": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", - "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "object.fromentries": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "object.hasown": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", - "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", - "dev": true, - "requires": { - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "requires": { - "entities": "^4.4.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pathe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", - "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==", - "dev": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" - }, - "pkg-types": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.1.tgz", - "integrity": "sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==", - "dev": true, - "requires": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.0.0", - "pathe": "^1.0.0" - } - }, - "postcss": { - "version": "8.4.20", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", - "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", - "dev": true, - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", - "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", - "dev": true, - "peer": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - } - } - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "protobufjs": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", - "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-color": { - "version": "2.19.3", - "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", - "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", - "requires": { - "@icons/material": "^0.2.4", - "lodash": "^4.17.15", - "lodash-es": "^4.17.15", - "material-colors": "^1.2.1", - "prop-types": "^15.5.10", - "reactcss": "^1.2.0", - "tinycolor2": "^1.4.1" - } - }, - "react-cookie": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz", - "integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==", - "requires": { - "@types/hoist-non-react-statics": "^3.0.1", - "hoist-non-react-statics": "^3.0.0", - "universal-cookie": "^4.0.0" - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, - "react-ga4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", - "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, - "react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", - "dev": true - }, - "react-router": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.5.0.tgz", - "integrity": "sha512-fqqUSU0NC0tSX0sZbyuxzuAzvGqbjiZItBQnyicWlOUmzhAU8YuLgRbaCL2hf3sJdtRy4LP/WBrWtARkMvdGPQ==", - "requires": { - "@remix-run/router": "1.1.0" - } - }, - "react-router-dom": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.5.0.tgz", - "integrity": "sha512-/XzRc5fq80gW1ctiIGilyKFZC/j4kfe75uivMsTChFbkvrK4ZrF3P3cGIc1f/SSkQ4JiJozPrf+AwUHHWVehVg==", - "requires": { - "@remix-run/router": "1.1.0", - "react-router": "6.5.0" - } - }, - "reactcss": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", - "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", - "requires": { - "lodash": "^4.0.1" - } - }, - "recoil": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.6.tgz", - "integrity": "sha512-hsBEw7jFdpBCY/tu2GweiyaqHKxVj6EqF2/SfrglbKvJHhpN57SANWvPW+gE90i3Awi+A5gssOd3u+vWlT+g7g==", - "requires": { - "hamt_plus": "1.0.2" - } - }, - "recoil-persist": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/recoil-persist/-/recoil-persist-4.2.0.tgz", - "integrity": "sha512-MHVfML9GxJP3RpkKR4F5rp7DtvzIvjWhowtMao/b7h2k4afMio/4sMAdUtltIrDaeVegH0Iga8Sx5XQ3oD7CzA==", - "requires": {} - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "dev": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dev": true, - "requires": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - } - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.7.5.tgz", - "integrity": "sha512-z0ZbqHBtS/et2EEUKMrAl2CoSdwN7ZPzL17UMiKN9RjjqHShTlv7F9J6ZJZJNREYjBh3TvBrdfjkFDIXFNeuiQ==", - "devOptional": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "rollup-plugin-visualizer": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz", - "integrity": "sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==", - "requires": { - "open": "^8.4.0", - "picomatch": "^2.3.1", - "source-map": "^0.7.4", - "yargs": "^17.5.1" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" - } - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "requires": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - } - } - }, - "snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "dev": true, - "requires": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true - }, - "std-env": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz", - "integrity": "sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==", - "dev": true - }, - "stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "requires": { - "internal-slot": "^1.0.4" - } - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - } - } - }, - "string.prototype.matchall": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", - "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strip-literal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz", - "integrity": "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==", - "dev": true, - "requires": { - "acorn": "^8.8.1" - } - }, - "styled-components": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", - "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "dev": true - }, - "svgo": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", - "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", - "dev": true, - "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.2.1", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "tinybench": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz", - "integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==", - "dev": true - }, - "tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" - }, - "tinypool": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", - "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", - "dev": true - }, - "tinyspy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz", - "integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true - }, - "ufo": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz", - "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true - }, - "universal-cookie": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", - "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", - "requires": { - "@types/cookie": "^0.3.3", - "cookie": "^0.4.0" - } - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "vite": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.1.tgz", - "integrity": "sha512-kZQPzbDau35iWOhy3CpkrRC7It+HIHtulAzBhMqzGHKRf/4+vmh8rPDDdv98SWQrFWo6//3ozwsRmwQIPZsK9g==", - "dev": true, - "requires": { - "esbuild": "^0.16.3", - "fsevents": "~2.3.2", - "postcss": "^8.4.20", - "resolve": "^1.22.1", - "rollup": "^3.7.0" - } - }, - "vite-node": { - "version": "0.28.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.3.tgz", - "integrity": "sha512-uJJAOkgVwdfCX8PUQhqLyDOpkBS5+j+FdbsXoPVPDlvVjRkb/W/mLYQPSL6J+t8R0UV8tJSe8c9VyxVQNsDSyg==", - "dev": true, - "requires": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "mlly": "^1.1.0", - "pathe": "^1.1.0", - "picocolors": "^1.0.0", - "source-map": "^0.6.1", - "source-map-support": "^0.5.21", - "vite": "^3.0.0 || ^4.0.0" - } - }, - "vite-plugin-svgr": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.1.0.tgz", - "integrity": "sha512-v7Qic+FWmCChgQNGSI4V8X63OEYsdUoLt66iqIcHozq9bfK/Dwmr0V+LBy1NE8CE98Y8HouEBJ+pto4AMfN5xw==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.4", - "@svgr/core": "^8.1.0", - "@svgr/plugin-jsx": "^8.1.0" - } - }, - "vitest": { - "version": "0.28.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.3.tgz", - "integrity": "sha512-N41VPNf3VGJlWQizGvl1P5MGyv3ZZA2Zvh+2V8L6tYBAAuqqDK4zExunT1Cdb6dGfZ4gr+IMrnG8d4Z6j9ctPw==", - "dev": true, - "requires": { - "@types/chai": "^4.3.4", - "@types/chai-subset": "^1.3.3", - "@types/node": "*", - "@vitest/expect": "0.28.3", - "@vitest/runner": "0.28.3", - "@vitest/spy": "0.28.3", - "@vitest/utils": "0.28.3", - "acorn": "^8.8.1", - "acorn-walk": "^8.2.0", - "cac": "^6.7.14", - "chai": "^4.3.7", - "debug": "^4.3.4", - "local-pkg": "^0.4.2", - "pathe": "^1.1.0", - "picocolors": "^1.0.0", - "source-map": "^0.6.1", - "std-env": "^3.3.1", - "strip-literal": "^1.0.0", - "tinybench": "^2.3.1", - "tinypool": "^0.3.1", - "tinyspy": "^1.0.2", - "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.28.3", - "why-is-node-running": "^2.2.2" - } - }, - "w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dev": true, - "requires": { - "xml-name-validator": "^4.0.0" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true - }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" - }, - "whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "requires": { - "iconv-lite": "0.6.3" - } - }, - "whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", - "dev": true, - "requires": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", - "dev": true, - "requires": {} - }, - "xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} + "d \ No newline at end of file diff --git a/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar.tsx b/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar.tsx index f04b0ab74..ba692619c 100644 --- a/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar.tsx +++ b/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar.tsx @@ -1,5 +1,4 @@ -import { useEffect, useRef, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useEffect, useState } from "react"; import styled from "styled-components"; import { SlackChannels } from "@/assets/data/SlackAlarm"; import { axiosSearchByIntraId } from "@/api/axios/axios.custom"; @@ -22,7 +21,6 @@ const AdminSlackNotiSearchBar = ({ searchInput: React.RefObject; renderReceiverInput: (title: string) => void; }) => { - const navigate = useNavigate(); const [searchListById, setSearchListById] = useState([]); const [searchListByChannel, setSearchListByChannel] = useState< ISlackChannel[] @@ -31,33 +29,6 @@ const AdminSlackNotiSearchBar = ({ const [onFocus, setOnFocus] = useState(true); const [targetIndex, setTargetIndex] = useState(-1); const [searchValue, setSearchValue] = useState(""); - const [floor, setFloor] = useState(0); - - const resetSearchState = () => { - setSearchListById([]); - setSearchListByChannel([]); - setTotalLength(0); - setTargetIndex(-1); - setFloor(0); - if (searchInput.current) { - searchInput.current.value = ""; - setSearchValue(""); - } - }; - - const clickSearchButton = () => { - if (searchInput.current) { - const searchValue = searchInput.current.value; - if (searchValue.length <= 0) { - resetSearchState(); - return alert("검색어를 입력해주세요."); - } else if (isNaN(Number(searchValue)) && searchValue.length <= 1) { - resetSearchState(); - return alert("두 글자 이상의 검색어를 입력해주세요."); - } - // TODO : search bar list item 선택됐을때 엔터눌렀을때 - } - }; const debounce = (func: Function, wait: number) => { let timeout: NodeJS.Timeout; @@ -134,7 +105,11 @@ const AdminSlackNotiSearchBar = ({ const handleInputKey = (e: React.KeyboardEvent) => { if (e.key === "Enter") { - clickSearchButton(); + if (targetIndex !== -1) { + searchInput.current!.value = valueChangeHandler(); + setSearchValue(searchInput.current!.value); + setTotalLength(0); + } } else if (e.key == "ArrowUp") { if (totalLength > 0) { setTargetIndex((prev) => From 72631e3a869a7978f0e796fdc891b8da3109ae6d Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 10 Apr 2024 14:30:44 +0900 Subject: [PATCH 0545/1029] =?UTF-8?q?[FE]=20FEAT:=20=EC=8A=AC=EB=9E=99=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20api=20=EC=A0=81=EC=9A=A9#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/admin/AdminSlackNotiPage.tsx | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index d2fc1ef9f..331dd1cef 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -4,6 +4,10 @@ import AdminSlackNotiSearchBar, { ISlackChannel, } from "@/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar"; import { SlackAlarmTemplates, SlackChannels } from "@/assets/data/SlackAlarm"; +import { + axiosSendSlackNotificationToChannel, + axiosSendSlackNotificationToUser, +} from "@/api/axios/axios.custom"; const AdminSlackNotiPage = () => { const searchInput = useRef(null); @@ -26,12 +30,42 @@ const AdminSlackNotiPage = () => { if (searchTextArea.current) searchTextArea.current.value = ""; }; + const submit = async () => { + if (!searchInput.current?.value) { + alert("받는이를 입력해주세요."); + } else if (!searchTextArea.current?.value) { + alert("메시지 내용을 입력해주세요."); + } else + try { + if (searchInput.current!.value[0] === "#") { + let channelId = SlackChannels.find((channel) => { + return searchInput.current!.value === channel.title; + })?.channelId; + await axiosSendSlackNotificationToChannel( + searchInput.current.value, + searchTextArea.current!.value, + channelId + ); + } else { + await axiosSendSlackNotificationToUser( + searchInput.current.value, + searchTextArea.current!.value + ); + } + // TODO : 성공적으로 보냈다는 모달? + } catch (error: any) { + // TODO : response modal? alert? + // setModalTitle(error.response.data.message); + // setModalContent(error.response.data.message); + // setHasErrorOnResponse(true); + } + }; + return (

알림

- 자주 쓰는 채널 @@ -47,7 +81,6 @@ const AdminSlackNotiPage = () => { })} - 자주 쓰는 템플릿 @@ -87,7 +120,7 @@ const AdminSlackNotiPage = () => { 초기화 - + 보내기
From 37d27ecf8496f408294cc61a52b9de6923c36e9f Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 10 Apr 2024 15:43:33 +0900 Subject: [PATCH 0546/1029] =?UTF-8?q?[FE]=20FEAT:=20search=20bar=20list=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=A1=B0=EC=A0=95#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/SlackAlarm.ts | 5 +- .../SlackNotiSearchBar.tsx} | 47 ++++---- .../SlackNotiSearchBarList.tsx} | 15 ++- .../SlackNotiSearchListItem.tsx} | 4 +- .../components/SlackAlarmJ/SlackAlarmJ.tsx | 100 ------------------ .../src/pages/admin/AdminSlackNotiPage.tsx | 44 ++++---- 6 files changed, 57 insertions(+), 158 deletions(-) rename frontend/src/components/Search/{AdminSlackNotiSearch/AdminSlackNotiSearchBar.tsx => SlackNotiSearch/SlackNotiSearchBar.tsx} (83%) rename frontend/src/components/Search/{AdminSlackNotiSearch/AdminSlackNotiSearchBarList.tsx => SlackNotiSearch/SlackNotiSearchBarList.tsx} (81%) rename frontend/src/components/Search/{AdminSlackNotiSearch/AdminSlackNotiSearchListItem.tsx => SlackNotiSearch/SlackNotiSearchListItem.tsx} (91%) delete mode 100644 frontend/src/components/SlackAlarmJ/SlackAlarmJ.tsx diff --git a/frontend/src/assets/data/SlackAlarm.ts b/frontend/src/assets/data/SlackAlarm.ts index 4ad74e956..f9426526e 100644 --- a/frontend/src/assets/data/SlackAlarm.ts +++ b/frontend/src/assets/data/SlackAlarm.ts @@ -1,4 +1,7 @@ -import { ISlackChannel } from "@/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar"; +export interface ISlackChannel { + title: string; + channelId: string; +} export const SlackChannels: ISlackChannel[] = [ { diff --git a/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar.tsx b/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx similarity index 83% rename from frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar.tsx rename to frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx index ba692619c..acdf6ca5c 100644 --- a/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar.tsx +++ b/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx @@ -1,20 +1,15 @@ import { useEffect, useState } from "react"; import styled from "styled-components"; -import { SlackChannels } from "@/assets/data/SlackAlarm"; +import SlackNotiSearchBarList from "@/components/Search/SlackNotiSearch/SlackNotiSearchBarList"; +import { ISlackChannel, SlackChannels } from "@/assets/data/SlackAlarm"; import { axiosSearchByIntraId } from "@/api/axios/axios.custom"; import useOutsideClick from "@/hooks/useOutsideClick"; -import SlackAlarmSearchBarList from "./AdminSlackNotiSearchBarList"; - -export interface ISlackChannel { - title: string; - channelId: string; -} -// TODO : 위치 옮기기 // TODO : 리팩토링 -// TODO : import +// TODO : disabled useEffect말고 더 좋은방법있나? +// TODO : 채널인지 유저인지에 따른 아이콘 -const AdminSlackNotiSearchBar = ({ +const SlackNotiSearchBar = ({ searchInput, renderReceiverInput, }: { @@ -128,9 +123,10 @@ const AdminSlackNotiSearchBar = ({ } } }; + return ( <> - + - - {onFocus && searchInput.current?.value && totalLength > 0 && ( - <> - - - )} + {onFocus && searchInput.current?.value && totalLength > 0 && ( + <> + + + )} + ); }; -const SearchBarStyled = styled.div` +const SearchBarWrapStyled = styled.div` position: relative; - width: 100%; `; const FormInputStyled = styled.input` @@ -178,4 +173,4 @@ const FormInputStyled = styled.input` } `; -export default AdminSlackNotiSearchBar; +export default SlackNotiSearchBar; diff --git a/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBarList.tsx b/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBarList.tsx similarity index 81% rename from frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBarList.tsx rename to frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBarList.tsx index 8df338e74..bb636dfac 100644 --- a/frontend/src/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBarList.tsx +++ b/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBarList.tsx @@ -1,13 +1,13 @@ import styled from "styled-components"; -import { ISlackChannel } from "./AdminSlackNotiSearchBar"; -import AdminSlackNotiSearchListItem from "./AdminSlackNotiSearchListItem"; +import SlackNotiSearchListItem from "@/components/Search/SlackNotiSearch/SlackNotiSearchListItem"; +import { ISlackChannel } from "@/assets/data/SlackAlarm"; interface ISearchListByIntraId { name: string; userId: number; } -const SlackAlarmSearchBarList = ({ +const SlackNotiSearchBarList = ({ searchListById, searchListByChannel, searchWord, @@ -24,7 +24,7 @@ const SlackAlarmSearchBarList = ({ {searchListById.map((item, index: number) => { return ( - { return ( - { - const searchTextArea = useRef(null); - const searchInput = useRef(null); - - const initializeInputandTextArea = () => { - if (searchInput.current) searchInput.current.value = ""; - if (searchTextArea.current) searchTextArea.current.value = ""; - }; - - const renderReceiverInput = (title: string) => { - if (searchInput.current) searchInput.current.value = title; - }; - - const renderTemplateTextArea = (title: string) => { - const template = SlackAlarmTemplates.find((template) => { - return template.title === title; - }); - if (searchTextArea.current) - searchTextArea.current.value = template!.content; - }; - - const submit = async () => { - if (!searchInput.current?.value) { - // TODO : 보내는이 입력하라고 알리기 - } else if (!searchInput.current.value) { - // TODO : 메세지 입력하라고 알리기 - } else - try { - if (searchInput.current!.value[0] === "#") { - let channelId = SlackChannels.find((channel) => { - return searchInput.current!.value === channel.title; - })?.channelId; - await axiosSendSlackNotificationToChannel( - searchInput.current.value, - searchTextArea.current!.value, - channelId - ); - } else { - await axiosSendSlackNotificationToUser( - searchInput.current.value, - searchTextArea.current!.value - ); - } - } catch (error: any) { - // TODO : response modal? - // setModalTitle(error.response.data.message); - // setModalContent(error.response.data.message); - // setHasErrorOnResponse(true); - } finally { - } - }; - - return ( - <> -

자주쓰는채널

- {SlackChannels.map((channel) => { - return ( - - ); - })} -

자주쓰는템플릿

- {SlackAlarmTemplates.map((template) => { - return ( - - ); - })} -

알림보내기

-
-
받는이(intraid/channel)
- {/* */} -
메시지내용
- {/* { - setOnFocus(true); - }} - ref={searchTextArea} - isOnFocus={onFocus} - /> */} - - -
- - ); -}; - -export default SlackAlarmJ; diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index 331dd1cef..f37cb7dc6 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -1,55 +1,57 @@ import { useRef } from "react"; import styled from "styled-components"; -import AdminSlackNotiSearchBar, { +import SlackNotiSearchBar from "@/components/Search/SlackNotiSearch/SlackNotiSearchBar"; +import { ISlackChannel, -} from "@/components/Search/AdminSlackNotiSearch/AdminSlackNotiSearchBar"; -import { SlackAlarmTemplates, SlackChannels } from "@/assets/data/SlackAlarm"; + SlackAlarmTemplates, + SlackChannels, +} from "@/assets/data/SlackAlarm"; import { axiosSendSlackNotificationToChannel, axiosSendSlackNotificationToUser, } from "@/api/axios/axios.custom"; const AdminSlackNotiPage = () => { - const searchInput = useRef(null); - const searchTextArea = useRef(null); + const receiverInputRef = useRef(null); + const msgTextAreaRef = useRef(null); const renderReceiverInput = (title: string) => { - if (searchInput.current) searchInput.current.value = title; + if (receiverInputRef.current) receiverInputRef.current.value = title; }; const renderTemplateTextArea = (title: string) => { const template = SlackAlarmTemplates.find((template) => { return template.title === title; }); - if (searchTextArea.current) - searchTextArea.current.value = template!.content; + if (msgTextAreaRef.current) + msgTextAreaRef.current.value = template!.content; }; const initializeInputandTextArea = () => { - if (searchInput.current) searchInput.current.value = ""; - if (searchTextArea.current) searchTextArea.current.value = ""; + if (receiverInputRef.current) receiverInputRef.current.value = ""; + if (msgTextAreaRef.current) msgTextAreaRef.current.value = ""; }; const submit = async () => { - if (!searchInput.current?.value) { + if (!receiverInputRef.current?.value) { alert("받는이를 입력해주세요."); - } else if (!searchTextArea.current?.value) { + } else if (!msgTextAreaRef.current?.value) { alert("메시지 내용을 입력해주세요."); } else try { - if (searchInput.current!.value[0] === "#") { + if (receiverInputRef.current!.value[0] === "#") { let channelId = SlackChannels.find((channel) => { - return searchInput.current!.value === channel.title; + return receiverInputRef.current!.value === channel.title; })?.channelId; await axiosSendSlackNotificationToChannel( - searchInput.current.value, - searchTextArea.current!.value, + receiverInputRef.current.value, + msgTextAreaRef.current!.value, channelId ); } else { await axiosSendSlackNotificationToUser( - searchInput.current.value, - searchTextArea.current!.value + receiverInputRef.current.value, + msgTextAreaRef.current!.value ); } // TODO : 성공적으로 보냈다는 모달? @@ -105,8 +107,8 @@ const AdminSlackNotiPage = () => { 받는이(Intra ID/ Channel)* - @@ -114,7 +116,7 @@ const AdminSlackNotiPage = () => { 메시지 내용* - + From 3089cfd0a6f52ae6fef200e9e5b37b0bae1d8064 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 11 Apr 2024 16:46:39 +0900 Subject: [PATCH 0547/1029] =?UTF-8?q?[FE]=20FEAT:=20=EB=B3=B4=EB=82=B8?= =?UTF-8?q?=EC=9D=B4,=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=82=B4=EC=9A=A9?= =?UTF-8?q?=20=EB=AA=A8=EB=91=90=20=EC=9E=85=EB=A0=A5=ED=96=88=EC=9D=84?= =?UTF-8?q?=EB=95=8C=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?css=20=EB=B0=94=EA=BF=88#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- frontend/package-lock.json | 2735 +---------------- .../SlackNotiSearch/SlackNotiSearchBar.tsx | 8 +- .../src/pages/admin/AdminSlackNotiPage.tsx | 34 +- 4 files changed, 36 insertions(+), 2743 deletions(-) diff --git a/config b/config index c250e0589..666f17037 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit c250e058923e07709a856c046d9452f974780125 +Subproject commit 666f170377b71d48c09d58f25d388e3911b4b972 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 82e7a5836..03b6b55fe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,7 +1,7 @@ { "name": "cabi_fontend", "version": "0.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -1957,54 +1957,6 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, - "node_modules/@esbuild/android-arm": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.9.tgz", - "integrity": "sha512-kW5ccqWHVOOTGUkkJbtfoImtqu3kA1PFkivM+9QPFSHphPfPBlBalX9eDRqPK+wHCqKhU48/78T791qPgC9e9A==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.9.tgz", - "integrity": "sha512-ndIAZJUeLx4O+4AJbFQCurQW4VRUXjDsUvt1L+nP8bVELOWdmdCEOtlIweCUE6P+hU0uxYbEK2AEP0n5IVQvhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.9.tgz", - "integrity": "sha512-UbMcJB4EHrAVOnknQklREPgclNU2CPet2h+sCBCXmF2mfoYWopBn/CfTfeyOkb/JglOcdEADqAljFndMKnFtOw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.16.9", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.9.tgz", @@ -2021,294 +1973,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.9.tgz", - "integrity": "sha512-LZc+Wlz06AkJYtwWsBM3x2rSqTG8lntDuftsUNQ3fCx9ZttYtvlDcVtgb+NQ6t9s6K5No5zutN3pcjZEC2a4iQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.9.tgz", - "integrity": "sha512-gIj0UQZlQo93CHYouHKkpzP7AuruSaMIm1etcWIxccFEVqCN1xDr6BWlN9bM+ol/f0W9w3hx3HDuEwcJVtGneQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.9.tgz", - "integrity": "sha512-GNors4vaMJ7lzGOuhzNc7jvgsQZqErGA8rsW+nck8N1nYu86CvsJW2seigVrQQWOV4QzEP8Zf3gm+QCjA2hnBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.9.tgz", - "integrity": "sha512-cNx1EF99c2t1Ztn0lk9N+MuwBijGF8mH6nx9GFsB3e0lpUpPkCE/yt5d+7NP9EwJf5uzqdjutgVYoH1SNqzudA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.9.tgz", - "integrity": "sha512-YPxQunReYp8RQ1FvexFrOEqqf+nLbS3bKVZF5FRT2uKM7Wio7BeATqAwO02AyrdSEntt3I5fhFsujUChIa8CZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.9.tgz", - "integrity": "sha512-zb12ixDIKNwFpIqR00J88FFitVwOEwO78EiUi8wi8FXlmSc3GtUuKV/BSO+730Kglt0B47+ZrJN1BhhOxZaVrw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.9.tgz", - "integrity": "sha512-X8te4NLxtHiNT6H+4Pfm5RklzItA1Qy4nfyttihGGX+Koc53Ar20ViC+myY70QJ8PDEOehinXZj/F7QK3A+MKQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.9.tgz", - "integrity": "sha512-ZqyMDLt02c5smoS3enlF54ndK5zK4IpClLTxF0hHfzHJlfm4y8IAkIF8LUW0W7zxcKy7oAwI7BRDqeVvC120SA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.9.tgz", - "integrity": "sha512-k+ca5W5LDBEF3lfDwMV6YNXwm4wEpw9krMnNvvlNz3MrKSD2Eb2c861O0MaKrZkG/buTQAP4vkavbLwgIe6xjg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.9.tgz", - "integrity": "sha512-GuInVdogjmg9DhgkEmNipHkC+3tzkanPJzgzTC2ihsvrruLyFoR1YrTGixblNSMPudQLpiqkcwGwwe0oqfrvfA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.9.tgz", - "integrity": "sha512-49wQ0aYkvwXonGsxc7LuuLNICMX8XtO92Iqmug5Qau0kpnV6SP34jk+jIeu4suHwAbSbRhVFtDv75yRmyfQcHw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.9.tgz", - "integrity": "sha512-Nx4oKEAJ6EcQlt4dK7qJyuZUoXZG7CAeY22R7rqZijFzwFfMOD+gLP56uV7RrV86jGf8PeRY8TBsRmOcZoG42w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.9.tgz", - "integrity": "sha512-d0WnpgJ+FTiMZXEQ1NOv9+0gvEhttbgKEvVqWWAtl1u9AvlspKXbodKHzQ5MLP6YV1y52Xp+p8FMYqj8ykTahg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.9.tgz", - "integrity": "sha512-jccK11278dvEscHFfMk5EIPjF4wv1qGD0vps7mBV1a6TspdR36O28fgPem/SA/0pcsCPHjww5ouCLwP+JNAFlw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.9.tgz", - "integrity": "sha512-OetwTSsv6mIDLqN7I7I2oX9MmHGwG+AP+wKIHvq+6sIHwcPPJqRx+DJB55jy9JG13CWcdcQno/7V5MTJ5a0xfQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.9.tgz", - "integrity": "sha512-tKSSSK6unhxbGbHg+Cc+JhRzemkcsX0tPBvG0m5qsWbkShDK9c+/LSb13L18LWVdOQZwuA55Vbakxmt6OjBDOQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.9.tgz", - "integrity": "sha512-ZTQ5vhNS5gli0KK8I6/s6+LwXmNEfq1ftjnSVyyNm33dBw8zDpstqhGXYUbZSWWLvkqiRRjgxgmoncmi6Yy7Ng==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.9.tgz", - "integrity": "sha512-C4ZX+YFIp6+lPrru3tpH6Gaapy8IBRHw/e7l63fzGDhn/EaiGpQgbIlT5paByyy+oMvRFQoxxyvC4LE0AjJMqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint/eslintrc": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", @@ -10850,2398 +10514,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@adobe/css-tools": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.1.0.tgz", - "integrity": "sha512-mMVJ/j/GbZ/De4ZHWbQAQO1J6iVnjtZLc9WEdkUQb8S/Bu2cAF2bETXUgMAdvMG3/ngtKmcNBe+Zms9bg6jnQQ==", - "dev": true - }, - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", - "requires": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" - } - }, - "@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", - "dev": true - }, - "@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - } - }, - "@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", - "requires": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", - "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "semver": "^6.3.1" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", - "dev": true, - "requires": { - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" - } - }, - "@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" - } - }, - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" - }, - "@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" - } - }, - "@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" - } - }, - "@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", - "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", - "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.15" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "requires": {} - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-async-generator-functions": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.2.tgz", - "integrity": "sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", - "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-static-block": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", - "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.11", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", - "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", - "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dynamic-import": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", - "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-export-namespace-from": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", - "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", - "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-json-strings": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", - "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", - "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", - "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", - "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", - "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", - "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-transform-numeric-separator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", - "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-transform-object-rest-spread": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", - "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.15" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" - } - }, - "@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", - "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-transform-optional-chaining": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", - "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", - "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-private-property-in-object": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", - "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.11", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-react-constant-elements": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz", - "integrity": "sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", - "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz", - "integrity": "sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/types": "^7.22.15" - } - }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", - "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", - "dev": true, - "requires": { - "@babel/plugin-transform-react-jsx": "^7.22.5" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz", - "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz", - "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", - "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", - "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz", - "integrity": "sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", - "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/preset-env": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.2.tgz", - "integrity": "sha512-BW3gsuDD+rvHL2VO2SjAUNTBe5YrjsTiDyqamPDWY723na3/yPQ65X5oQkFVJZ0o50/2d+svm1rkPoJeR1KxVQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.23.2", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.23.2", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.23.0", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.11", - "@babel/plugin-transform-classes": "^7.22.15", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.23.0", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.11", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.11", - "@babel/plugin-transform-for-of": "^7.22.15", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.11", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.23.0", - "@babel/plugin-transform-modules-commonjs": "^7.23.0", - "@babel/plugin-transform-modules-systemjs": "^7.23.0", - "@babel/plugin-transform-modules-umd": "^7.22.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", - "@babel/plugin-transform-numeric-separator": "^7.22.11", - "@babel/plugin-transform-object-rest-spread": "^7.22.15", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.11", - "@babel/plugin-transform-optional-chaining": "^7.23.0", - "@babel/plugin-transform-parameters": "^7.22.15", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.10", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.10", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "@babel/types": "^7.23.0", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" - } - }, - "@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-react": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.15.tgz", - "integrity": "sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-transform-react-display-name": "^7.22.5", - "@babel/plugin-transform-react-jsx": "^7.22.15", - "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@babel/plugin-transform-react-pure-annotations": "^7.22.5" - } - }, - "@babel/preset-typescript": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.2.tgz", - "integrity": "sha512-u4UJc1XsS1GhIGteM8rnGiIvf9rJpiVgMEeCnwlLA7WJPC+jcXWJAGxYmeqs5hOZD8BbAfnV5ezBOxQbb4OUxA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.23.0", - "@babel/plugin-transform-typescript": "^7.22.15" - } - }, - "@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, - "@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@babel/runtime-corejs3": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.6.tgz", - "integrity": "sha512-tqeujPiuEfcH067mx+7otTQWROVMKHXEaOQcAeNV5dDdbPWvPcFA8/W9LXw2NfjNmOetqLl03dfnG2WALPlsRQ==", - "dev": true, - "requires": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.11" - } - }, - "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - } - }, - "@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - } - }, - "@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", - "requires": { - "@emotion/memoize": "^0.8.0" - } - }, - "@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" - }, - "@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" - }, - "@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" - }, - "@esbuild/android-arm": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.9.tgz", - "integrity": "sha512-kW5ccqWHVOOTGUkkJbtfoImtqu3kA1PFkivM+9QPFSHphPfPBlBalX9eDRqPK+wHCqKhU48/78T791qPgC9e9A==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.9.tgz", - "integrity": "sha512-ndIAZJUeLx4O+4AJbFQCurQW4VRUXjDsUvt1L+nP8bVELOWdmdCEOtlIweCUE6P+hU0uxYbEK2AEP0n5IVQvhg==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.9.tgz", - "integrity": "sha512-UbMcJB4EHrAVOnknQklREPgclNU2CPet2h+sCBCXmF2mfoYWopBn/CfTfeyOkb/JglOcdEADqAljFndMKnFtOw==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.9.tgz", - "integrity": "sha512-d7D7/nrt4CxPul98lx4PXhyNZwTYtbdaHhOSdXlZuu5zZIznjqtMqLac8Bv+IuT6SVHiHUwrkL6ywD7mOgLW+A==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.9.tgz", - "integrity": "sha512-LZc+Wlz06AkJYtwWsBM3x2rSqTG8lntDuftsUNQ3fCx9ZttYtvlDcVtgb+NQ6t9s6K5No5zutN3pcjZEC2a4iQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.9.tgz", - "integrity": "sha512-gIj0UQZlQo93CHYouHKkpzP7AuruSaMIm1etcWIxccFEVqCN1xDr6BWlN9bM+ol/f0W9w3hx3HDuEwcJVtGneQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.9.tgz", - "integrity": "sha512-GNors4vaMJ7lzGOuhzNc7jvgsQZqErGA8rsW+nck8N1nYu86CvsJW2seigVrQQWOV4QzEP8Zf3gm+QCjA2hnBQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.9.tgz", - "integrity": "sha512-cNx1EF99c2t1Ztn0lk9N+MuwBijGF8mH6nx9GFsB3e0lpUpPkCE/yt5d+7NP9EwJf5uzqdjutgVYoH1SNqzudA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.9.tgz", - "integrity": "sha512-YPxQunReYp8RQ1FvexFrOEqqf+nLbS3bKVZF5FRT2uKM7Wio7BeATqAwO02AyrdSEntt3I5fhFsujUChIa8CZg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.9.tgz", - "integrity": "sha512-zb12ixDIKNwFpIqR00J88FFitVwOEwO78EiUi8wi8FXlmSc3GtUuKV/BSO+730Kglt0B47+ZrJN1BhhOxZaVrw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.9.tgz", - "integrity": "sha512-X8te4NLxtHiNT6H+4Pfm5RklzItA1Qy4nfyttihGGX+Koc53Ar20ViC+myY70QJ8PDEOehinXZj/F7QK3A+MKQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.9.tgz", - "integrity": "sha512-ZqyMDLt02c5smoS3enlF54ndK5zK4IpClLTxF0hHfzHJlfm4y8IAkIF8LUW0W7zxcKy7oAwI7BRDqeVvC120SA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.9.tgz", - "integrity": "sha512-k+ca5W5LDBEF3lfDwMV6YNXwm4wEpw9krMnNvvlNz3MrKSD2Eb2c861O0MaKrZkG/buTQAP4vkavbLwgIe6xjg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.9.tgz", - "integrity": "sha512-GuInVdogjmg9DhgkEmNipHkC+3tzkanPJzgzTC2ihsvrruLyFoR1YrTGixblNSMPudQLpiqkcwGwwe0oqfrvfA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.9.tgz", - "integrity": "sha512-49wQ0aYkvwXonGsxc7LuuLNICMX8XtO92Iqmug5Qau0kpnV6SP34jk+jIeu4suHwAbSbRhVFtDv75yRmyfQcHw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.9.tgz", - "integrity": "sha512-Nx4oKEAJ6EcQlt4dK7qJyuZUoXZG7CAeY22R7rqZijFzwFfMOD+gLP56uV7RrV86jGf8PeRY8TBsRmOcZoG42w==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.9.tgz", - "integrity": "sha512-d0WnpgJ+FTiMZXEQ1NOv9+0gvEhttbgKEvVqWWAtl1u9AvlspKXbodKHzQ5MLP6YV1y52Xp+p8FMYqj8ykTahg==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.9.tgz", - "integrity": "sha512-jccK11278dvEscHFfMk5EIPjF4wv1qGD0vps7mBV1a6TspdR36O28fgPem/SA/0pcsCPHjww5ouCLwP+JNAFlw==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.9.tgz", - "integrity": "sha512-OetwTSsv6mIDLqN7I7I2oX9MmHGwG+AP+wKIHvq+6sIHwcPPJqRx+DJB55jy9JG13CWcdcQno/7V5MTJ5a0xfQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.9.tgz", - "integrity": "sha512-tKSSSK6unhxbGbHg+Cc+JhRzemkcsX0tPBvG0m5qsWbkShDK9c+/LSb13L18LWVdOQZwuA55Vbakxmt6OjBDOQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.9.tgz", - "integrity": "sha512-ZTQ5vhNS5gli0KK8I6/s6+LwXmNEfq1ftjnSVyyNm33dBw8zDpstqhGXYUbZSWWLvkqiRRjgxgmoncmi6Yy7Ng==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.9.tgz", - "integrity": "sha512-C4ZX+YFIp6+lPrru3tpH6Gaapy8IBRHw/e7l63fzGDhn/EaiGpQgbIlT5paByyy+oMvRFQoxxyvC4LE0AjJMqQ==", - "dev": true, - "optional": true - }, - "@eslint/eslintrc": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", - "integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - } - } - }, - "@firebase/analytics": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.0.tgz", - "integrity": "sha512-Locv8gAqx0e+GX/0SI3dzmBY5e9kjVDtD+3zCFLJ0tH2hJwuCAiL+5WkHuxKj92rqQj/rvkBUCfA1ewlX2hehg==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/installations": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/analytics-compat": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.6.tgz", - "integrity": "sha512-4MqpVLFkGK7NJf/5wPEEP7ePBJatwYpyjgJ+wQHQGHfzaCDgntOnl9rL2vbVGGKCnRqWtZDIWhctB86UWXaX2Q==", - "requires": { - "@firebase/analytics": "0.10.0", - "@firebase/analytics-types": "0.8.0", - "@firebase/component": "0.6.4", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/analytics-types": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", - "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==" - }, - "@firebase/app": { - "version": "0.9.19", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.19.tgz", - "integrity": "sha512-t/SHyZ3xWkR77ZU9VMoobDNFLdDKQ5xqoCAn4o16gTsA1C8sJ6ZOMZ02neMOPxNHuQXVE4tA8ukilnDbnK7uJA==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/app-check": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.0.tgz", - "integrity": "sha512-dRDnhkcaC2FspMiRK/Vbp+PfsOAEP6ZElGm9iGFJ9fDqHoPs0HOPn7dwpJ51lCFi1+2/7n5pRPGhqF/F03I97g==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/app-check-compat": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.7.tgz", - "integrity": "sha512-cW682AxsyP1G+Z0/P7pO/WT2CzYlNxoNe5QejVarW2o5ZxeWSSPAiVEwpEpQR/bUlUmdeWThYTMvBWaopdBsqw==", - "requires": { - "@firebase/app-check": "0.8.0", - "@firebase/app-check-types": "0.5.0", - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/app-check-interop-types": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz", - "integrity": "sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg==" - }, - "@firebase/app-check-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", - "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==" - }, - "@firebase/app-compat": { - "version": "0.2.19", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.19.tgz", - "integrity": "sha512-QkJDqYqjhvs4fTMcRVXQkP9hbo5yfoJXDWkhU4VA5Vzs8Qsp76VPzYbqx5SD5OmBy+bz/Ot1UV8qySPGI4aKuw==", - "requires": { - "@firebase/app": "0.9.19", - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/app-types": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", - "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" - }, - "@firebase/auth": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.3.0.tgz", - "integrity": "sha512-vjK4CHbY9aWdiVOrKi6mpa8z6uxeaf7LB/MZTHuZOiGHMcUoTGB6TeMbRShyqk1uaMrxhhZ5Ar/dR0965E1qyA==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/auth-compat": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.4.6.tgz", - "integrity": "sha512-pKp1d4fSf+yoy1EBjTx9ISxlunqhW0vTICk0ByZ3e+Lp6ZIXThfUy4F1hAJlEafD/arM0oepRiAh7LXS1xn/BA==", - "requires": { - "@firebase/auth": "1.3.0", - "@firebase/auth-types": "0.12.0", - "@firebase/component": "0.6.4", - "@firebase/util": "1.9.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/auth-interop-types": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", - "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" - }, - "@firebase/auth-types": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", - "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", - "requires": {} - }, - "@firebase/component": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", - "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", - "requires": { - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/database": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.1.tgz", - "integrity": "sha512-VAhF7gYwunW4Lw/+RQZvW8dlsf2r0YYqV9W0Gi2Mz8+0TGg1mBJWoUtsHfOr8kPJXhcLsC4eP/z3x6L/Fvjk/A==", - "requires": { - "@firebase/auth-interop-types": "0.2.1", - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/database-compat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.1.tgz", - "integrity": "sha512-ky82yLIboLxtAIWyW/52a6HLMVTzD2kpZlEilVDok73pNPLjkJYowj8iaIWK5nTy7+6Gxt7d00zfjL6zckGdXQ==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/database": "1.0.1", - "@firebase/database-types": "1.0.0", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/database-types": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.0.tgz", - "integrity": "sha512-SjnXStoE0Q56HcFgNQ+9SsmJc0c8TqGARdI/T44KXy+Ets3r6x/ivhQozT66bMnCEjJRywYoxNurRTMlZF8VNg==", - "requires": { - "@firebase/app-types": "0.9.0", - "@firebase/util": "1.9.3" - } - }, - "@firebase/firestore": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.2.0.tgz", - "integrity": "sha512-iKZqIdOBJpJUcwY5airLX0W04TLrQSJuActOP1HG5WoIY5oyGTQE4Ml7hl5GW7mBqFieT4ojtUuDXj6MLrn1lA==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "@firebase/webchannel-wrapper": "0.10.3", - "@grpc/grpc-js": "~1.9.0", - "@grpc/proto-loader": "^0.7.8", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/firestore-compat": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.18.tgz", - "integrity": "sha512-hkqv4mb1oScKbEtzfcK8Go8c0VpDWmbAvbD6B6XnphLqi27pkXgo9Rp+aSKlD7cBL29VMEekP5bEm9lSVfZpNw==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/firestore": "4.2.0", - "@firebase/firestore-types": "3.0.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/firestore-types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.0.tgz", - "integrity": "sha512-Meg4cIezHo9zLamw0ymFYBD4SMjLb+ZXIbuN7T7ddXN6MGoICmOTq3/ltdCGoDCS2u+H1XJs2u/cYp75jsX9Qw==", - "requires": {} - }, - "@firebase/functions": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.10.0.tgz", - "integrity": "sha512-2U+fMNxTYhtwSpkkR6WbBcuNMOVaI7MaH3cZ6UAeNfj7AgEwHwMIFLPpC13YNZhno219F0lfxzTAA0N62ndWzA==", - "requires": { - "@firebase/app-check-interop-types": "0.3.0", - "@firebase/auth-interop-types": "0.2.1", - "@firebase/component": "0.6.4", - "@firebase/messaging-interop-types": "0.2.0", - "@firebase/util": "1.9.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/functions-compat": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.5.tgz", - "integrity": "sha512-uD4jwgwVqdWf6uc3NRKF8cSZ0JwGqSlyhPgackyUPe+GAtnERpS4+Vr66g0b3Gge0ezG4iyHo/EXW/Hjx7QhHw==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/functions": "0.10.0", - "@firebase/functions-types": "0.6.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/functions-types": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", - "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==" - }, - "@firebase/installations": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", - "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/util": "1.9.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - }, - "dependencies": { - "idb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", - "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/installations-compat": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.4.tgz", - "integrity": "sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/installations": "0.6.4", - "@firebase/installations-types": "0.5.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/installations-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", - "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", - "requires": {} - }, - "@firebase/logger": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", - "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", - "requires": { - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/messaging": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", - "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/installations": "0.6.4", - "@firebase/messaging-interop-types": "0.2.0", - "@firebase/util": "1.9.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - }, - "dependencies": { - "idb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", - "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/messaging-compat": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz", - "integrity": "sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/messaging": "0.12.4", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/messaging-interop-types": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", - "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==" - }, - "@firebase/performance": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", - "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/installations": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/performance-compat": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.4.tgz", - "integrity": "sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/performance": "0.6.4", - "@firebase/performance-types": "0.2.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/performance-types": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", - "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==" - }, - "@firebase/remote-config": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", - "integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/installations": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/remote-config-compat": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz", - "integrity": "sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/remote-config": "0.4.4", - "@firebase/remote-config-types": "0.3.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/remote-config-types": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", - "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==" - }, - "@firebase/storage": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", - "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/util": "1.9.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/storage-compat": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.2.tgz", - "integrity": "sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw==", - "requires": { - "@firebase/component": "0.6.4", - "@firebase/storage": "0.11.2", - "@firebase/storage-types": "0.8.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/storage-types": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", - "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", - "requires": {} - }, - "@firebase/util": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", - "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", - "requires": { - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, - "@firebase/webchannel-wrapper": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.3.tgz", - "integrity": "sha512-+ZplYUN3HOpgCfgInqgdDAbkGGVzES1cs32JJpeqoh87SkRobGXElJx+1GZSaDqzFL+bYiX18qEcBK76mYs8uA==" - }, - "@grpc/grpc-js": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.3.tgz", - "integrity": "sha512-b8iWtdrYIeT5fdZdS4Br/6h/kuk0PW5EVBUGk1amSbrpL8DlktJD43CdcCWwRdd6+jgwHhADSbL9CsNnm6EUPA==", - "requires": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" - } - }, - "@grpc/proto-loader": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", - "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==", - "requires": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.4", - "yargs": "^17.7.2" - } - }, - "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@icons/material": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", - "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", - "requires": {} - }, - "@jest/expect-utils": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.4.1.tgz", - "integrity": "sha512-w6YJMn5DlzmxjO00i9wu2YSozUYRBhIoJ6nQwpMYcBMtiqMGJm1QBzOf6DDgRao8dbtpDoaqLg6iiQTvv0UHhQ==", - "dev": true, - "requires": { - "jest-get-type": "^29.2.0" - } - }, - "@jest/schemas": { - "version": "29.4.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.0.tgz", - "integrity": "sha512-0E01f/gOZeNTG76i5eWWSupvSHaIINrTie7vCyjiYFKgzNdyEGd12BUv4oNBFHOqlHDbtoJi3HrQ38KCC90NsQ==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.25.16" - } - }, - "@jest/types": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.4.1.tgz", - "integrity": "sha512-zbrAXDUOnpJ+FMST2rV7QZOgec8rskg2zv8g2ajeqitp4tvZiyqTCYXANrKsM+ryj5o+LI+ZN2EgU9drrkiwSA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "@nivo/annotations": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.80.0.tgz", - "integrity": "sha512-bC9z0CLjU07LULTMWsqpjovRtHxP7n8oJjqBQBLmHOGB4IfiLbrryBfu9+aEZH3VN2jXHhdpWUz+HxeZzOzsLg==", - "requires": { - "@nivo/colors": "0.80.0", - "@react-spring/web": "9.4.5", - "lodash": "^4.17.21" - } - }, - "@nivo/arcs": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/arcs/-/arcs-0.80.0.tgz", - "integrity": "sha512-g5m/wM36Ey45J3hrVDBPMw1Z6GOgIRwgb5zTh7TFoPuhRBZEDQLmctk8XYOm0xOMVCzsm6WkU5wlSQUeBY6IHQ==", - "requires": { - "@nivo/colors": "0.80.0", - "@react-spring/web": "9.4.5", - "d3-shape": "^1.3.5" - } - }, - "@nivo/axes": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.80.0.tgz", - "integrity": "sha512-AsUyaSHGwQVSEK8QXpsn8X+poZxvakLMYW7crKY1xTGPNw+SU4SSBohPVumm2jMH3fTSLNxLhAjWo71GBJXfdA==", - "requires": { - "@nivo/scales": "0.80.0", - "@react-spring/web": "9.4.5", - "d3-format": "^1.4.4", - "d3-time-format": "^3.0.0" - } - }, - "@nivo/bar": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/bar/-/bar-0.80.0.tgz", - "integrity": "sha512-woE/S12Sp+RKQeOHtp302WXfy5usj73cV/gjP95PzJxMv+Rn01i1Uwys3BILzc9h4+OxYuWTFqLADAySAmi7qQ==", - "requires": { - "@nivo/annotations": "0.80.0", - "@nivo/axes": "0.80.0", - "@nivo/colors": "0.80.0", - "@nivo/legends": "0.80.0", - "@nivo/scales": "0.80.0", - "@nivo/tooltip": "0.80.0", - "@react-spring/web": "9.4.5", - "d3-scale": "^3.2.3", - "d3-shape": "^1.3.5", - "lodash": "^4.17.21" - } - }, - "@nivo/colors": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.80.0.tgz", - "integrity": "sha512-T695Zr411FU4RPo7WDINOAn8f79DPP10SFJmDdEqELE+cbzYVTpXqLGZ7JMv88ko7EOf9qxLQgcBqY69rp9tHQ==", - "requires": { - "d3-color": "^2.0.0", - "d3-scale": "^3.2.3", - "d3-scale-chromatic": "^2.0.0", - "lodash": "^4.17.21" - } - }, - "@nivo/core": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.80.0.tgz", - "integrity": "sha512-6caih0RavXdWWSfde+rC2pk17WrX9YQlqK26BrxIdXzv3Ydzlh5SkrC7dR2TEvMGBhunzVeLOfiC2DWT1S8CFg==", - "requires": { - "@nivo/recompose": "0.80.0", - "@react-spring/web": "9.4.5", - "d3-color": "^2.0.0", - "d3-format": "^1.4.4", - "d3-interpolate": "^2.0.1", - "d3-scale": "^3.2.3", - "d3-scale-chromatic": "^2.0.0", - "d3-shape": "^1.3.5", - "d3-time-format": "^3.0.0", - "lodash": "^4.17.21" - } - }, - "@nivo/legends": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.80.0.tgz", - "integrity": "sha512-h0IUIPGygpbKIZZZWIxkkxOw4SO0rqPrqDrykjaoQz4CvL4HtLIUS3YRA4akKOVNZfS5agmImjzvIe0s3RvqlQ==", - "requires": {} - }, - "@nivo/line": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.80.0.tgz", - "integrity": "sha512-6UAD/y74qq3DDRnVb+QUPvXYojxMtwXMipGSNvCGk8omv1QZNTaUrbV+eQacvn9yh//a0yZcWipnpq0tGJyJCA==", - "requires": { - "@nivo/annotations": "0.80.0", - "@nivo/axes": "0.80.0", - "@nivo/colors": "0.80.0", - "@nivo/legends": "0.80.0", - "@nivo/scales": "0.80.0", - "@nivo/tooltip": "0.80.0", - "@nivo/voronoi": "0.80.0", - "@react-spring/web": "9.4.5", - "d3-shape": "^1.3.5" - } - }, - "@nivo/pie": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/pie/-/pie-0.80.0.tgz", - "integrity": "sha512-Zj2PtozUg5wizxdI/2o13YzwnBwf8lLrgc8vH7ucsgOu5nj6oLLpGTuNd3CBmRJHFGIGNT39bP63lKnB3P6qOQ==", - "requires": { - "@nivo/arcs": "0.80.0", - "@nivo/colors": "0.80.0", - "@nivo/legends": "0.80.0", - "@nivo/tooltip": "0.80.0", - "d3-shape": "^1.3.5" - } - }, - "@nivo/recompose": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/recompose/-/recompose-0.80.0.tgz", - "integrity": "sha512-iL3g7j3nJGD9+mRDbwNwt/IXDXH6E29mhShY1I7SP91xrfusZV9pSFf4EzyYgruNJk/2iqMuaqn+e+TVFra44A==", - "requires": { - "react-lifecycles-compat": "^3.0.4" - } - }, - "@nivo/scales": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.80.0.tgz", - "integrity": "sha512-4y2pQdCg+f3n4TKXC2tYuq71veZM+xPRQbOTgGYJpuBvMc7pQsXF9T5z7ryeIG9hkpXkrlyjecU6XcAG7tLSNg==", - "requires": { - "d3-scale": "^3.2.3", - "d3-time": "^1.0.11", - "d \ No newline at end of file + } +} diff --git a/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx b/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx index acdf6ca5c..b36b465ba 100644 --- a/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx +++ b/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx @@ -6,15 +6,15 @@ import { axiosSearchByIntraId } from "@/api/axios/axios.custom"; import useOutsideClick from "@/hooks/useOutsideClick"; // TODO : 리팩토링 -// TODO : disabled useEffect말고 더 좋은방법있나? -// TODO : 채널인지 유저인지에 따른 아이콘 const SlackNotiSearchBar = ({ searchInput, renderReceiverInput, + checkInputs, }: { searchInput: React.RefObject; renderReceiverInput: (title: string) => void; + checkInputs: () => void; }) => { const [searchListById, setSearchListById] = useState([]); const [searchListByChannel, setSearchListByChannel] = useState< @@ -26,6 +26,8 @@ const SlackNotiSearchBar = ({ const [searchValue, setSearchValue] = useState(""); const debounce = (func: Function, wait: number) => { + checkInputs(); + let timeout: NodeJS.Timeout; return function executedFunction(...args: any[]) { @@ -51,7 +53,7 @@ const SlackNotiSearchBar = ({ } if (searchInput.current!.value[0] === "#") { // slack channel 검색 - if (searchValue.length <= 1) { + if (searchValue.length <= 0) { setSearchListByChannel([]); setTotalLength(0); setTargetIndex(-1); diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index f37cb7dc6..40e4324fa 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -1,4 +1,4 @@ -import { useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import styled from "styled-components"; import SlackNotiSearchBar from "@/components/Search/SlackNotiSearch/SlackNotiSearchBar"; import { @@ -14,6 +14,7 @@ import { const AdminSlackNotiPage = () => { const receiverInputRef = useRef(null); const msgTextAreaRef = useRef(null); + const [isDisabled, setIsDisabled] = useState(true); const renderReceiverInput = (title: string) => { if (receiverInputRef.current) receiverInputRef.current.value = title; @@ -63,6 +64,16 @@ const AdminSlackNotiPage = () => { } }; + const checkInputs = () => { + setIsDisabled( + !receiverInputRef.current?.value || !msgTextAreaRef.current?.value + ); + }; + + useEffect(() => { + checkInputs(); + }, []); + return ( @@ -96,8 +107,6 @@ const AdminSlackNotiPage = () => {
); })} - 사물함 대여 - {/* TODO : 사물함 대여일때 템플릿 */}
@@ -110,19 +119,24 @@ const AdminSlackNotiPage = () => { 메시지 내용* - + 초기화 - + 보내기 @@ -244,7 +258,9 @@ const FormButtonContainerStyled = styled.div` justify-content: space-between; `; -const FormButtonStyled = styled.div<{ primary?: boolean; disabled?: boolean }>` +const FormButtonStyled = styled.button<{ primary?: boolean }>` + width: auto; + height: auto; padding: 10px 16px; font-size: 0.875rem; background-color: ${(props) => @@ -253,10 +269,14 @@ const FormButtonStyled = styled.div<{ primary?: boolean; disabled?: boolean }>` font-weight: 700; border: 1px solid #eee; border-radius: 4px; - opacity: ${(props) => (props.disabled ? "0.3" : "1")}; cursor: pointer; :hover { opacity: 0.85; } + :disabled { + cursor: not-allowed; + opacity: 0.3; + } `; + export default AdminSlackNotiPage; From e2780ec3b9c9c8b59182808f38fce83a79457365 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 11 Apr 2024 18:52:58 +0900 Subject: [PATCH 0548/1029] =?UTF-8?q?[FE]=20FIX:=20=EB=B3=B4=EB=82=B4?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20css=20=EB=B0=94=EA=BE=B8?= =?UTF-8?q?=EB=8A=94=20=EB=8C=80=EC=8B=A0=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EB=88=8C=EB=A0=80=EC=9D=84=EB=95=8C=20?= =?UTF-8?q?=EB=B3=B4=EB=82=B8=EC=9D=B4,=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EB=91=98=20=EB=8B=A4=20=EC=9E=88=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1=EB=90=98?= =?UTF-8?q?=EA=B2=8C=20=EB=B0=94=EA=BF=88#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SlackNotiSearch/SlackNotiSearchBar.tsx | 16 ++++---- .../SlackNotiSearchBarList.tsx | 2 +- .../SlackNotiSearchListItem.tsx | 0 .../SlackNotiSearch/SlackNotiTextArea.tsx | 41 +++++++++++++++++++ .../src/pages/admin/AdminSlackNotiPage.tsx | 28 ++----------- 5 files changed, 54 insertions(+), 33 deletions(-) rename frontend/src/components/{Search => SlackNoti}/SlackNotiSearch/SlackNotiSearchBar.tsx (94%) rename frontend/src/components/{Search => SlackNoti}/SlackNotiSearch/SlackNotiSearchBarList.tsx (94%) rename frontend/src/components/{Search => SlackNoti}/SlackNotiSearch/SlackNotiSearchListItem.tsx (100%) create mode 100644 frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiTextArea.tsx diff --git a/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx similarity index 94% rename from frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx rename to frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx index b36b465ba..b4c4640a5 100644 --- a/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBar.tsx +++ b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import styled from "styled-components"; -import SlackNotiSearchBarList from "@/components/Search/SlackNotiSearch/SlackNotiSearchBarList"; +import SlackNotiSearchBarList from "@/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBarList"; import { ISlackChannel, SlackChannels } from "@/assets/data/SlackAlarm"; import { axiosSearchByIntraId } from "@/api/axios/axios.custom"; import useOutsideClick from "@/hooks/useOutsideClick"; @@ -10,11 +10,9 @@ import useOutsideClick from "@/hooks/useOutsideClick"; const SlackNotiSearchBar = ({ searchInput, renderReceiverInput, - checkInputs, }: { searchInput: React.RefObject; renderReceiverInput: (title: string) => void; - checkInputs: () => void; }) => { const [searchListById, setSearchListById] = useState([]); const [searchListByChannel, setSearchListByChannel] = useState< @@ -25,9 +23,11 @@ const SlackNotiSearchBar = ({ const [targetIndex, setTargetIndex] = useState(-1); const [searchValue, setSearchValue] = useState(""); - const debounce = (func: Function, wait: number) => { - checkInputs(); - + const debounce = ( + func: Function, + wait: number, + e: React.ChangeEvent + ) => { let timeout: NodeJS.Timeout; return function executedFunction(...args: any[]) { @@ -136,7 +136,7 @@ const SlackNotiSearchBar = ({ onFocus={() => { setOnFocus(true); }} - onChange={debounce(typeSearchInput, 300)} + onChange={(e) => debounce(typeSearchInput, 300, e)} onKeyDown={handleInputKey} /> {onFocus && searchInput.current?.value && totalLength > 0 && ( @@ -162,7 +162,7 @@ const SearchBarWrapStyled = styled.div` const FormInputStyled = styled.input` width: 100%; height: 40px; - background-color: #fff; + background-color: var(--white); border-radius: 8px; border: 1px solid #eee; :focus { diff --git a/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBarList.tsx b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBarList.tsx similarity index 94% rename from frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBarList.tsx rename to frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBarList.tsx index bb636dfac..2dc71d30a 100644 --- a/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchBarList.tsx +++ b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBarList.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import SlackNotiSearchListItem from "@/components/Search/SlackNotiSearch/SlackNotiSearchListItem"; +import SlackNotiSearchListItem from "@/components/SlackNoti/SlackNotiSearch/SlackNotiSearchListItem"; import { ISlackChannel } from "@/assets/data/SlackAlarm"; interface ISearchListByIntraId { diff --git a/frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchListItem.tsx b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchListItem.tsx similarity index 100% rename from frontend/src/components/Search/SlackNotiSearch/SlackNotiSearchListItem.tsx rename to frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchListItem.tsx diff --git a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiTextArea.tsx b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiTextArea.tsx new file mode 100644 index 000000000..19eabb0de --- /dev/null +++ b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiTextArea.tsx @@ -0,0 +1,41 @@ +import styled from "styled-components"; + +const SlackNotiTextArea = ({ + msgTextAreaRef, + checkInputs, + receiverInputRef, +}: { + msgTextAreaRef: React.RefObject; + checkInputs: (receiverInput?: string, msgTextArea?: string) => void; + receiverInputRef: React.RefObject; +}) => { + return ( + + checkInputs(receiverInputRef.current?.value, e.target.value) + } + /> + ); +}; + +const FormTextareaStyled = styled.textarea` + box-sizing: border-box; + width: 100%; + min-height: 200px; + background-color: #fff; + border-radius: 8px; + border: 1px solid #eee; + resize: none; + outline: none; + :focus { + border: 1px solid var(--main-color); + } + text-align: left; + padding: 10px; + ::placeholder { + color: var(--line-color); + } +`; + +export default SlackNotiTextArea; diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index 40e4324fa..1ebb70fcc 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -1,6 +1,6 @@ -import { useEffect, useRef, useState } from "react"; +import { useRef } from "react"; import styled from "styled-components"; -import SlackNotiSearchBar from "@/components/Search/SlackNotiSearch/SlackNotiSearchBar"; +import SlackNotiSearchBar from "@/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar"; import { ISlackChannel, SlackAlarmTemplates, @@ -14,7 +14,6 @@ import { const AdminSlackNotiPage = () => { const receiverInputRef = useRef(null); const msgTextAreaRef = useRef(null); - const [isDisabled, setIsDisabled] = useState(true); const renderReceiverInput = (title: string) => { if (receiverInputRef.current) receiverInputRef.current.value = title; @@ -64,16 +63,6 @@ const AdminSlackNotiPage = () => { } }; - const checkInputs = () => { - setIsDisabled( - !receiverInputRef.current?.value || !msgTextAreaRef.current?.value - ); - }; - - useEffect(() => { - checkInputs(); - }, []); - return ( @@ -119,24 +108,19 @@ const AdminSlackNotiPage = () => { 메시지 내용* - + 초기화 - + 보내기 @@ -273,10 +257,6 @@ const FormButtonStyled = styled.button<{ primary?: boolean }>` :hover { opacity: 0.85; } - :disabled { - cursor: not-allowed; - opacity: 0.3; - } `; export default AdminSlackNotiPage; From 54e07c6f1e0175eb4542e392e00a4755bf72179b Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 11 Apr 2024 19:19:23 +0900 Subject: [PATCH 0549/1029] =?UTF-8?q?[FE]=20FEAT:=20=EB=A1=9C=EC=BB=AC?= =?UTF-8?q?=EC=97=90=EC=84=9C=20slack=20notification=20=EC=A0=95=EC=83=81?= =?UTF-8?q?=20=EC=9E=91=EB=8F=99=EC=9C=84=ED=95=9C=20nginx=20config=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95=20#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev/configure.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev/configure.conf b/dev/configure.conf index 05f7ccfde..69327b801 100644 --- a/dev/configure.conf +++ b/dev/configure.conf @@ -82,4 +82,9 @@ server { proxy_pass http://host.docker.internal:2424; proxy_set_header Host $host; } + + location ^~ /slack { + proxy_pass http://host.docker.internal:2424; + proxy_set_header Host $host; + } } From 69b94d5328c57dc57ac9acff50b4cb3e947318c9 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 11 Apr 2024 19:30:39 +0900 Subject: [PATCH 0550/1029] =?UTF-8?q?[FE]=20FEAT:=20=EB=B3=B4=EB=82=B4?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EB=88=8C=EB=A0=80=EC=9D=84?= =?UTF-8?q?=EB=95=8C=20=EC=8B=A4=ED=8C=A8=20=EC=8B=9C=20alert=EB=A1=9C=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=9D=84=EC=9B=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SlackNotiSearch/SlackNotiSearchBar.tsx | 2 - .../SlackNotiSearch/SlackNotiTextArea.tsx | 41 ---------------- .../src/pages/admin/AdminSlackNotiPage.tsx | 47 +++++++++---------- 3 files changed, 22 insertions(+), 68 deletions(-) delete mode 100644 frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiTextArea.tsx diff --git a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx index b4c4640a5..aa15b98ec 100644 --- a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx +++ b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx @@ -5,8 +5,6 @@ import { ISlackChannel, SlackChannels } from "@/assets/data/SlackAlarm"; import { axiosSearchByIntraId } from "@/api/axios/axios.custom"; import useOutsideClick from "@/hooks/useOutsideClick"; -// TODO : 리팩토링 - const SlackNotiSearchBar = ({ searchInput, renderReceiverInput, diff --git a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiTextArea.tsx b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiTextArea.tsx deleted file mode 100644 index 19eabb0de..000000000 --- a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiTextArea.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import styled from "styled-components"; - -const SlackNotiTextArea = ({ - msgTextAreaRef, - checkInputs, - receiverInputRef, -}: { - msgTextAreaRef: React.RefObject; - checkInputs: (receiverInput?: string, msgTextArea?: string) => void; - receiverInputRef: React.RefObject; -}) => { - return ( - - checkInputs(receiverInputRef.current?.value, e.target.value) - } - /> - ); -}; - -const FormTextareaStyled = styled.textarea` - box-sizing: border-box; - width: 100%; - min-height: 200px; - background-color: #fff; - border-radius: 8px; - border: 1px solid #eee; - resize: none; - outline: none; - :focus { - border: 1px solid var(--main-color); - } - text-align: left; - padding: 10px; - ::placeholder { - color: var(--line-color); - } -`; - -export default SlackNotiTextArea; diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index 1ebb70fcc..6fe2f8108 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -34,33 +34,30 @@ const AdminSlackNotiPage = () => { const submit = async () => { if (!receiverInputRef.current?.value) { - alert("받는이를 입력해주세요."); + return alert("받는이를 입력해주세요."); } else if (!msgTextAreaRef.current?.value) { - alert("메시지 내용을 입력해주세요."); - } else - try { - if (receiverInputRef.current!.value[0] === "#") { - let channelId = SlackChannels.find((channel) => { - return receiverInputRef.current!.value === channel.title; - })?.channelId; - await axiosSendSlackNotificationToChannel( - receiverInputRef.current.value, - msgTextAreaRef.current!.value, - channelId - ); - } else { - await axiosSendSlackNotificationToUser( - receiverInputRef.current.value, - msgTextAreaRef.current!.value - ); - } - // TODO : 성공적으로 보냈다는 모달? - } catch (error: any) { - // TODO : response modal? alert? - // setModalTitle(error.response.data.message); - // setModalContent(error.response.data.message); - // setHasErrorOnResponse(true); + return alert("메시지 내용을 입력해주세요."); + } + + try { + if (receiverInputRef.current!.value[0] === "#") { + let channelId = SlackChannels.find((channel) => { + return receiverInputRef.current!.value === channel.title; + })?.channelId; + await axiosSendSlackNotificationToChannel( + receiverInputRef.current.value, + msgTextAreaRef.current!.value, + channelId + ); + } else { + await axiosSendSlackNotificationToUser( + receiverInputRef.current.value, + msgTextAreaRef.current!.value + ); } + } catch (error: any) { + alert(error.response.data.message); + } }; return ( From 79bb96c5013866b0b4c6321450734aba6c2546dc Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 11 Apr 2024 19:39:29 +0900 Subject: [PATCH 0551/1029] =?UTF-8?q?[FE]=20FIX:=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=82=98=EC=98=A4=EA=B2=8C=20?= =?UTF-8?q?=EA=B3=A0=EC=B9=A8=20#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx index aa15b98ec..d61a0ef1c 100644 --- a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx +++ b/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx @@ -21,11 +21,7 @@ const SlackNotiSearchBar = ({ const [targetIndex, setTargetIndex] = useState(-1); const [searchValue, setSearchValue] = useState(""); - const debounce = ( - func: Function, - wait: number, - e: React.ChangeEvent - ) => { + const debounce = (func: Function, wait: number) => { let timeout: NodeJS.Timeout; return function executedFunction(...args: any[]) { @@ -123,6 +119,7 @@ const SlackNotiSearchBar = ({ } } }; + console.log(onFocus, searchInput.current?.value, totalLength); return ( <> @@ -134,7 +131,7 @@ const SlackNotiSearchBar = ({ onFocus={() => { setOnFocus(true); }} - onChange={(e) => debounce(typeSearchInput, 300, e)} + onChange={debounce(typeSearchInput, 300)} onKeyDown={handleInputKey} /> {onFocus && searchInput.current?.value && totalLength > 0 && ( From d95ec233eb00337447d7e87a4919f74508a31b0a Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 11 Apr 2024 22:13:11 +0900 Subject: [PATCH 0552/1029] =?UTF-8?q?[FE]=20FIX:=20=EC=83=89=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=A0=81=EC=9A=A9=20=EC=9C=84=EC=A3=BC=EC=9D=98=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{SlackNotiSearch => }/SlackNotiSearchBar.tsx | 5 ++--- .../{SlackNotiSearch => }/SlackNotiSearchBarList.tsx | 2 +- .../SlackNotiSearchListItem.tsx | 0 frontend/src/pages/admin/AdminSlackNotiPage.tsx | 12 ++++++------ 4 files changed, 9 insertions(+), 10 deletions(-) rename frontend/src/components/SlackNoti/{SlackNotiSearch => }/SlackNotiSearchBar.tsx (97%) rename frontend/src/components/SlackNoti/{SlackNotiSearch => }/SlackNotiSearchBarList.tsx (97%) rename frontend/src/components/SlackNoti/{SlackNotiSearch => }/SlackNotiSearchListItem.tsx (100%) diff --git a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx b/frontend/src/components/SlackNoti/SlackNotiSearchBar.tsx similarity index 97% rename from frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx rename to frontend/src/components/SlackNoti/SlackNotiSearchBar.tsx index d61a0ef1c..7419293ca 100644 --- a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar.tsx +++ b/frontend/src/components/SlackNoti/SlackNotiSearchBar.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import styled from "styled-components"; -import SlackNotiSearchBarList from "@/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBarList"; +import SlackNotiSearchBarList from "@/components/SlackNoti/SlackNotiSearchBarList"; import { ISlackChannel, SlackChannels } from "@/assets/data/SlackAlarm"; import { axiosSearchByIntraId } from "@/api/axios/axios.custom"; import useOutsideClick from "@/hooks/useOutsideClick"; @@ -119,7 +119,6 @@ const SlackNotiSearchBar = ({ } } }; - console.log(onFocus, searchInput.current?.value, totalLength); return ( <> @@ -159,7 +158,7 @@ const FormInputStyled = styled.input` height: 40px; background-color: var(--white); border-radius: 8px; - border: 1px solid #eee; + border: 1px solid var(--full); :focus { border: 1px solid var(--main-color); } diff --git a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBarList.tsx b/frontend/src/components/SlackNoti/SlackNotiSearchBarList.tsx similarity index 97% rename from frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBarList.tsx rename to frontend/src/components/SlackNoti/SlackNotiSearchBarList.tsx index 2dc71d30a..f8700acda 100644 --- a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBarList.tsx +++ b/frontend/src/components/SlackNoti/SlackNotiSearchBarList.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import SlackNotiSearchListItem from "@/components/SlackNoti/SlackNotiSearch/SlackNotiSearchListItem"; +import SlackNotiSearchListItem from "@/components/SlackNoti/SlackNotiSearchListItem"; import { ISlackChannel } from "@/assets/data/SlackAlarm"; interface ISearchListByIntraId { diff --git a/frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchListItem.tsx b/frontend/src/components/SlackNoti/SlackNotiSearchListItem.tsx similarity index 100% rename from frontend/src/components/SlackNoti/SlackNotiSearch/SlackNotiSearchListItem.tsx rename to frontend/src/components/SlackNoti/SlackNotiSearchListItem.tsx diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index 6fe2f8108..e2bbdf1dd 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -1,6 +1,6 @@ import { useRef } from "react"; import styled from "styled-components"; -import SlackNotiSearchBar from "@/components/SlackNoti/SlackNotiSearch/SlackNotiSearchBar"; +import SlackNotiSearchBar from "@/components/SlackNoti/SlackNotiSearchBar"; import { ISlackChannel, SlackAlarmTemplates, @@ -177,8 +177,8 @@ const CapsuleButtonStyled = styled.span` justify-content: center; align-items: center; padding: 8px 20px; - background: #f5f5f5; - border: 1px solid #eeeeee; + background: var(--lightgray-color); + border: 1px solid var(--full); border-radius: 22px; cursor: pointer; @@ -218,9 +218,9 @@ const FormTextareaStyled = styled.textarea` box-sizing: border-box; width: 100%; min-height: 200px; - background-color: #fff; + background-color: var(--white); border-radius: 8px; - border: 1px solid #eee; + border: 1px solid var(--full); resize: none; outline: none; :focus { @@ -248,7 +248,7 @@ const FormButtonStyled = styled.button<{ primary?: boolean }>` props.primary ? "var(--main-color)" : "var(--white)"}; color: ${(props) => (props.primary ? "var(--white)" : "var(--black)")}; font-weight: 700; - border: 1px solid #eee; + border: 1px solid var(--full); border-radius: 4px; cursor: pointer; :hover { From 4e0024cdd1a403d1c5834b2553ae0cc5a039ab4b Mon Sep 17 00:00:00 2001 From: jusohn Date: Fri, 12 Apr 2024 15:15:39 +0900 Subject: [PATCH 0553/1029] =?UTF-8?q?[FE]=20FIX:=20SlackAlarmTemplates=20?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20ISlackAlarmTemplate=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/SlackAlarm.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/assets/data/SlackAlarm.ts b/frontend/src/assets/data/SlackAlarm.ts index f9426526e..b3e586e40 100644 --- a/frontend/src/assets/data/SlackAlarm.ts +++ b/frontend/src/assets/data/SlackAlarm.ts @@ -14,7 +14,12 @@ export const SlackChannels: ISlackChannel[] = [ }, ]; -export const SlackAlarmTemplates = [ +export interface ISlackAlarmTemplate { + title: string; + content: string; +} + +export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ { title: "점검 시작", content: `:alert::alert::alert::alert::alert::alert: From 9b31cdb15090ca040f060f474c84590fcc255e6f Mon Sep 17 00:00:00 2001 From: jusohn Date: Fri, 12 Apr 2024 15:17:12 +0900 Subject: [PATCH 0554/1029] =?UTF-8?q?[FE]=20FIX:=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20ISlackAlarmTemplate=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20#1581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/admin/AdminSlackNotiPage.tsx | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/frontend/src/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/pages/admin/AdminSlackNotiPage.tsx index e2bbdf1dd..3a02b41f6 100644 --- a/frontend/src/pages/admin/AdminSlackNotiPage.tsx +++ b/frontend/src/pages/admin/AdminSlackNotiPage.tsx @@ -2,6 +2,7 @@ import { useRef } from "react"; import styled from "styled-components"; import SlackNotiSearchBar from "@/components/SlackNoti/SlackNotiSearchBar"; import { + ISlackAlarmTemplate, ISlackChannel, SlackAlarmTemplates, SlackChannels, @@ -67,7 +68,7 @@ const AdminSlackNotiPage = () => { 자주 쓰는 채널 - + {SlackChannels.map((channel: ISlackChannel, idx: number) => { return ( { ); })} - + 자주 쓰는 템플릿 - - {SlackAlarmTemplates.map((template: any, idx: number) => { - return ( - renderTemplateTextArea(template.title)} - > - {template.title} - - ); - })} - + + {SlackAlarmTemplates.map( + (template: ISlackAlarmTemplate, idx: number) => { + return ( + renderTemplateTextArea(template.title)} + > + {template.title} + + ); + } + )} + 알림 보내기 @@ -164,7 +167,7 @@ const SubTitleStyled = styled.h2` margin-bottom: 20px; `; -const CapsuleWappingStyled = styled.div` +const CapsuleWrappingStyled = styled.div` width: 100%; display: flex; align-items: center; From f300a4e1e280be033dfa813a5c5f55d079b534e4 Mon Sep 17 00:00:00 2001 From: junyoung2015 Date: Fri, 12 Apr 2024 17:42:48 +0900 Subject: [PATCH 0555/1029] =?UTF-8?q?[COMMON]=20FEAT:=20=EC=88=98=EC=9A=94?= =?UTF-8?q?=EC=A7=80=EC=8B=9D=ED=9A=8C=20=EB=8F=84=EC=9E=85=20#1559=20(#15?= =?UTF-8?q?85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FE] FIX: 상세페이지, 메인페이지 api dto에 맞춰 변경#1559 * [FE] FEAT: axios dateTime type을 string에서 Date로 변경 #1559 * [FE] FEAT: dropdownChevron.svg 수정 #1559 * [FE] FEAT: font 크기 0.9rem에서 0.875rem으로 수정 및 css 수정 #1559 * [FE] FEAT: notification icon stroke를 main color에서 수지회 main 색상으로 임시 변경 #1559 * [FE] FEAT: 드롭다운 메뉴에 토글 아이콘 추가 #1559 * [FE] FEAT: RegisterPage css 속성값 조정 및 notificationIcon 추가 #1559 * [FE] FIX: period -> presentationTime 으로 변경 #1559 * [FE] FIX: period -> presentationTime 으로 변경 #1559 * [FE] FIX: 상세페이지 border radius 바로바로 반영되게 함#1559 * [FE] FEAT: NotificationIcon stroke 색상 main 색상으로 revert #1559 * [BE] FIX: 이미 예약된 날짜를 검증하는 로직 수정 Date -> LocalDateTime 형식 * [BE] REFACTOR: Date -> LocalDateTime 형식 * [BE] TEST : presentation update 검증 완료 * [FE] FIX: Admin 에서 발표 관리 모달을 예정된 발표에서만 띄우도록 변경 #1559 * [FE] FIX: fixed syntax error(PresentationStatusType)#1559 * [FE] FIX: table body 웹, 모바일 코드 구조 변경 #1559 * [FE] FIX: mock data가 아닌 실제 데이터로 상세페이지 띄우기#1559 * [FE] FEAT: 상세페이지 화살표 눌렀을때 해당 년월의 데이터 받아와서 띄우기#1559 * [FE] FEAT: 메인 화면 실제 데이터로 띄움#1559 * [BE] FIX: 상세 페이지 데이터 조회 시 날짜 순 정렬 * [FE] FEAT: 메인 화면 실제 데이터 정상적으로 띄움#1559 * [FE] FIX: 상세페이지 container 사용하는 구조로 변경#1559 * [FE] MERGE: 머지#1559 * [FE] FEAT: 수지회 발표 Admin API 추가 #1559 * [FE] FEAT: 선택한 발표를 구분하기 위한 recoil state 추가, Admin 모달 조건 변경 #1559 * [FE] FIX: 수지회, 새롬관을 LeftMainNavContainer 에서 구분 #1559 * [FE] FEAT: Admin 에서 선택된 발표의 상태를 관리하기 위한 recoil 및 type 추가 #1559 * [FE] FIX: date 관련 함수 정리 중 pagination 관련 함수 분리 #1559 * [FE] DOCS: 요일 / 날짜 함수 추가 전 dateUtils 문서화 #1559 * [FE] FIX: currentPresentationState 수정 #1559 * [FE] FIX: 필요없는 pagination 함수 제거 * [FE] FIX: typeScript 를 위한 Object 타입화 및 export #1559 * [BE] presentation update method수정 , admin으로 분리 * [FE] FEAT: 수지회 발표 날짜 계산을 위한 date-fns 라이브러리 추가 #1559 * [FE] FEAT: 수지회 발표 날짜 계산을 위한 함수 추가, 문서화 및 모듈화 #1559 * [FE] FIX: 날짜 계산 함수 설명 명료하게 수정 #1559 * [FE] FIX: 수지회 constants 들을 따로 분리 #1559 * [FE] FIX: Response Modal 중 SuccessResponseModal 을 ModalPortal 의 child 로 생성하도록 변경 #1559 * [FE] FIX: 시간을 제외한 일자만 비교해서 출력하도록 수정 #1559 * [FE] FEAT: 발표 가능 날짜를 정확하게 보여주도록 수정, axios 호출 이후 결과에 따라 Response Modal 출력 #1559 * [FE] FIX: 날짜 헬퍼 함수 filterInvalidDates 에 예외조건 추가 #1559 * [FE] FIX: DetailContentContainer~DetailTable까지 리팩토링#1559 * [BE] FIX: Presentation Admin API 의 PathVariable명 명시 #1559 * [FE] FEAT: 상세페이지 모바일 뷰 수정 #1559 * [FE] FIX: 상세페이지 테이블 행 발표 없을때 컴포넌트로 뺌#1559 * [FE] FEAT: main 기본 정리 #1559 * [FE] FEAT: main 카드가 3개 이하일때 로직 구현 #1559 * [FE] FIX: 발표 상태 Enum 을 백앤드와 맞게 수정 #1559 * [FE] FIX: 상세페이지 tableHeadArray 형태 변경#1559 * [FE] FIX: main 모바일뷰 카테고리 구현 #1559 * [FE] FIX : 모바일 뷰 테스트 #1559 * [FE] FEAT: 신청폼 detail 수정 #1559 * [FE] FEAT: 신청폼 dropdown 애니메이션 적용 #1559 * [FE] FEAT: 상세페이지 현재 달의 다음달~다다음달까지 발표 없을때 tr 뜨게함#1559 * [FE] FIX: 수지회 상세페이지 모바일 뷰 수정 #1559 * [FE] FIX: 상세페이지 현재 달~다다음달까지 발표 없을때 tr 뜨게함#1559 * [FE] FIX: 예외 처리 및 날짜 시간대 설정 #1559 * [FE] FIX: Modal 안내문구가 flex 로 인해 잘못된 문제를 수정 #1559 * [BE] FIX : admin update 날짜를 변경하지 않는 경우 valid 수행하지 않음 * [BE] FIX : admin update 날짜를 변경하지 않는 경우 valid 수행하지 않음 * [FE] FEAT: 신청폼 제목 및 한 줄 요약 textarea에서 input으로 변경 #1559 * [FE] FEAT: 드롭다운에 에니메이션 효과 적용 및 initial rendering시 의도하지 않은 애니메이션 효과 나타나지 않도록 구현 #1559 * [FE] FEAT: 상세페이지 현재 달에 발표 하나만 있을때 로직 처리 #1559 * [FE] FIX: Wednesday 폴더명 Presentation으로 변경 #1559 * [FE] FIX: 수지회 wed -> Presentation 으로 변경 #1559 * [FE] FIX: wednesday.dto > presentation.dto로 파일명 변경#1559 * [FE] FIX: Updated upstream~ 남아있어서 발생한 에러 해결#1559 * [FE] FEAT: 수지회 main wed폴더이름 변경 및 글자 애니메이션 #1559 * [FE] FIX: 수지회 main 변수명 바꿈 #1559 * [FE] FEAT: 상세페이지 코드 TableDetailTr 컴포넌트로 뺌#1559 * [FE] FIX: export 가능한 Object 분리 및 export default 문 최하단으로 이동 #1559 * [FE] FEAT: 수지회 웹 뷰 에니메이션 부드럽게 만들기 #1559 * [FE] FEAT: 시간 아이콘 cautionSign으로 변경 및 dropdown 말풍선 변경 #1559 * [FE] FEAT: multiToggleSwitch 모바일에서도 잘 작동하도록 구현 #1559 * [FE] FIX: tableHeadEntriesWithoutDate filter로 생성 & NOTE 추가#1559 * [FE] FIX: 각 cell 의 detail 을 랜더링하는 함수 추가 #1559 * [FE] FIX: item, itemStatus, itemDateInIDate를 자식으로 넘길때 itemInfo로 합침#1559 * [FE] FIX: 수지회 상세페이지 모바일 뷰 수정 #1559 * [FE] FEAT: 수지회 main 리팩토링 #1559 * [FE] FIX: 수지회 상세페이지 모바일 뷰 수정 #1559 * [FE] FIX: 수지회 상세페이지 모바일 뷰 호버 기능 수정 #1559 * [FE] FIX: main 이상한 파일 삭제 * [FE] FEAT: 신청폼 제출 전 모달 추가 #1559 * [FE] FEAT: RegisterModal 구현 #1559 * [FE] FEAT: 수지회 첫 발표날 이전 달 & 현재포함한 3개월 미래 이후의 달로 넘어가지 못하게 함 #1559 * [FE] FIX: 파일명 및 컴포넌트 상세페이지에 맞게 수정#1559 * [FE] FIX: import 경로 @ 넣어 변경#1559 * [FE] FEAT: 나의 발표기록을 조회할 수 있는 페이지 추가 #1559 * [FE] FEAT: 나의 발표기록을 조회할 수 있는 페이지 Route 추가 #1559 * [BE] FEAT: 유저 자신의 발표 현황 조회 기능 * [FE] FIX: 나의 발표기록 페이지가 Admin 에서 보이지 않도록 수정 #1559 * [FE] FEAT: Presentation 관련 Enum 을 한글로 Map 할 수 있도록 LabelMap 추가 #1559 * [FE] FEAT: Presentation 관련 컴포넌트들이 LabelMap 을 사용하도록 수정 #1559 * [FE] FEAT: Presentation 관련 컴포넌트들이 LabelMap 을 사용하도록 수정 #1559 * [FE] FEAT: 수지회 바뀐 main 첫 시안 #1559 * [FE] FEAT: 수지회 main 두번째 변경사항 #1559 * [FE] FIX: 수지회 main 아이콘 변경 #1559 * [FE] FEAT : main 크기 조정 #1559 * [FE] FIX: 상세페이지 클릭하기 전 웹, 모바일 코드 합침#1559 * [FE] FIX: 상세페이지 클릭한 후 웹, 모바일 코드 합침&상세 내용 잘 나옴#1559 * [FE] FEAT:수지회 main 모바일 카드 슬라이드 구현 #1559 * [FE] FIX: 수지회 main font size 변경 #1559 * [FE] FIX: 상세페이지 발표 있을 경우 정상적으로 됨#1559 * [FE] FIX: 상세페이지 웹 발표없을때 원상복구#1559 * [FE] FIX: 상세페이지 웹 & 모바일 뷰 코드 통합 완료#1559 * [FE] FEAT: DropdownOption에 invalid 한 날짜 선택되지 않도록 구현 #1559 * [FE] FEAT: dropdownDateMenu에 availableDate 전부 랜더링 되도록 useEffect 수정 및 invalidDate는 선택되지 않도록 수정 #1559 * [FE] FEAT: calculateAvailableDaysExceptPastDays 함수 추가 #1559 calculateAvailableDaysInWeeks 함수에서 현재 날짜 이전의 날짜를 예외처리 하도록 작성 * [FE] BUG: 신청하기 페이지에서 invalidDate가 없는 경우 날짜가 나타나지 않는 문제 해결 * [FE] FIX: 수지회 home 모바일 뷰 변경 #1559 * [FE] FIX: 상세페이지 unique key 관련 warning 해결 & DetailTableBodyRowMobile 컴포넌트 및 파일 삭제#1559 * [FE] FIX: 상세페이지 tableItem top, middle, bottom 중 top 1차 리팩토링 완료#1559 * [FE] FIX: Details 안에 DetailTable 폴더 만듦 & DetailTableBodyItemContainer > DetailTableBodyItem으로 바꾸고 기존 DetailTableBodyItem.tsx 삭제#1559 * [FE] FEAT: main페이지 불필요한 코드 정리 및 모바일 뷰 충돌 해결 #1559 * [FE] FIX: Layout 에서 필요없는 import 제거 #1559 * [FE] FIX: Dropdown 최적화 및 custom Hook 분리, RegisterPage 최적화 #1559 * [FE] FIX: 신청 페이지에서 시간enum 대신 String 을 보내는 문제 수정 #1559 * [FE] FIX: invalidDates 를 M/d 형태로 formatting #1559 * [FE] FIX: 수지회 상세페이지, 발표기록 css 수정 #1559 * [FE] FEAT: 수지회 main페이지 코드 리펙토링 #1559 * [FE] FIX: 내 발표기록을 날짜별로 정렬되게 변경 #1559 * [FE] FIX: RegisterPage 의 입력들을 InputField 로 컴포넌트 분리 #1559 * [FE] FIX: 발표 취소 CANCEL 오타 수정 #1559 * [FE] FIX: 필요없는 import 제거 #1559 * [FE] FIX: wed -> presentation 으로 route 변경 #1559 * [FE] FIX: wed -> presentation 으로 route 변경 #1559 * [FE] FIX: import 및 media query px 통일 #1559 * [FE] FIX: 제목 오류 수정 #1559 * [FE] FIX: PresentationPeriodTypeNumberLabelMap 에 NONE 타입 추가 #1559 * [FE] FIX: 시간 선택하지 않을 시 자동 30분으로 고정되는 문제 수정 #1559 * [BE] REFACTOR: 메인 데이터 폼 DTO 변수명 변경 과거, 미래 분기점 추가 * [FE] FEAT: multiToggleSwitch에 뜨는 항목 순서 조정 #1559 * [FE] FEAT: LeftMainNav에 있는 신청하기 버튼을 발표신청 버튼으로 수정 #1559 * [FE] FEAT: RegisterModal에 뜨는 문구 관리자에서 Cabi 슬랙 채널로 변경 #1559 * [FE] FEAT: DropdownMenu에서 hover background color 변경 #1559 * [FE] FEAT: NotificationDetail 중복되는 문구 제거 #1559 * [FE] FEAT: RegisterPage의 dateDropdown 목록에 표시되는 날짜들을 불러올때 현재 날짜 기준으로 받아오게 되는데 이때의 기준 시간을 default 에서 한국 시간으로 변경 #1559 * [FE] FEAT: 상세페이지 웹 뷰 테이블에 장소 추가 #1559 * [FE] FIX: DetailPartTr 대신 DetailTableBodyItemBottomTr 사용#1559 * [FE] FEAT: 웹 뷰 일때 발표 제목에 호버시 툴팁? 뜨게 함#1559 * [FE] FIX: table item 누르고 다른 달로 넘어가면 같은 위치의 아이템은 열려있지 않고 제대로 닫힘#1559 * [FE] FIX: 컴포넌트명 변경 & hasNoCurrentEvent를 itemInfo에 포함시킴 & tableHeadEntriesWithoutDate, tableHeadEntriesWithoutDateAndSubject 상수로 정의하고 사용#1559 * [FE] FIX: DetailTableBodyItemBottomTr 관련 리팩토링 완료#1559 * [FE] FIX: 상세페이지 DetailTableBodyItemMiddleTr 리팩토링 완료#1559 * [FE] FIX: 상세페이지 hasNoCurrentEvent -> hasNoUpcomingEvent 변수명 변경 & NoEventTableRow 리팩토링 완료#1559 * [FE] ETC: 상세페이지 DetailTableBodyItemTopTr 리팩토링 ing#1559 * [FE] ETC: 상세페이지 모바일뷰에서 DetailTableBodyItemMiddleTr border radius 문제 해결#1559 * [BE] REFACTOR: 불필요한 Setter 제거 * main 모바일 슬라이드시 밀림현상 수정 #1559 * [FE] FEAT: main api 변경사항 수정 #1559 * [FE] FIX: 수지회 상세페이지 날짜선택 안내 추가 및 메인 css 수정 #1559 * [FE] FEAT: dropdownOption 호버시 색상 변경 및 tooltipBox 위치 조정 * [FE] FIX: 신청하기 버튼 간격 줄이기 #1559 * [BE] FIX: 날짜 검증 방식 변경 범위 내의 날짜(3개월 이내)가 아니거나, 이미 예약된 날짜인지 검수 * [BE] REFACTOR: 검색 쿼리에 대한 중복 메서드 제거, CommandService 분리 * [BE] REFACTOR: 서비스 계층 분리 QueryService 분리 * [BE] REFACTOR: 과거, 이전 신청서 메서드 통합 Service에서 start, end 시간과 개수, sort 방식을 정하고 QueryService 에서 수행 * [FE] FEAT: admin 상세페이지 table에 발표 상태 column 추가#1559 * [FE] FEAT: admin 상세페이지에 이벤트 없을때 신청하기 버튼 안보이게#1559 * [FE] FEAT: RegisterErrorModal 추가 #1559 * [FE] FEAT: RegisterDetail 문구 수정 #1559 * [FE] FEAT: admin 상세일정 모달 바로 적용 설정 #1559 * [FE] FEAT: Dropdown css 속성 수정 #1559 * [FE] FEAT: 입력되지 않은 항목이 있을 경우 alert 대신 error modal 띄우도록 수정 #1559 * [BE] FIX: 상세 페이지 조회 시 취소된 신청서 제외 * [BE] FEAT: 상세 페이지 조회 시 취소된 신청서 제외 * [FE] FIX: 상세페이지 isAdmin 안넣어서 에러난거 해결#1559 * [FE] FIX: (재)상세페이지 isAdmin 안넣어서 에러난거 해결#1559 * [FE] FEAT: 상세페이지 admin용 api 적용#1559 * [FE] FEAT: 도메인 분리에 따른 폴더 분리 #1563 (#1564) * [FE] FEAT: 수지회 관련 파일들을 Presentation 디렉토리로 분리 #1563 * [FE] FIX: 수지회 관련 파일들을 Presentation 디렉토리로 분리 후 에러 수정 #1563 * [FE] FIX: 사라진 import 제거 #1563 * [FE] FIX: 없는 props 추가 #1563 * [FE] FIX: 없는 props 추가 #1563 * [FE] FEAT: 까비 관련 파일들을 Cabinet 폴더로 분리 #1563 * [FE] FIX: Cabinet 폴더로 옮기면서 생긴 import 오류 수정 #1563 * [FE] FIX: Cabinet 폴더로 옮기면서 생긴 import 오류 수정 #1563 * [FE] FIX: Presentation Layout 생성 #1563 * [FE] FIX: Presentation Layout 삭제 #1563 * [FE] FIX: import 수정 #1563 * [FE] FIX: RegisterModal 이전 #1563 * [FE] FIX: DetailContent.container 이전 #1563 * [FE] FIX: DetailTable.container 이전 #1563 * [FE] FIX: DetailTable 이전 #1563 * [FE] FIX: DetailTableBodyItemTopTr 이전 #1563 * [FE] FIX: DetailTableHead 복수 #1563 * [FE] FIX: NoEventTableRow 이전 #1563 * [FE] FIX: EditStatusModal 이전 #1563 * [FE] FIX: DropdownDateMenu 이전 #1563 * [FE] FIX: DropdownTimeMenu 이전 #1563 * [FE] FIX: RegisterPage 이전 #1563 * [FE] FIX: presentation.dto.ts 이전 #1563 * [FE] FIX: Presentation axios.custom.ts 이전 #1563 * [FE] FIX: missing import 추가 #1563 * [FE] FIX: 폴더 분리 후 conflix 고치기 #1559 * [FE] REMOVE: RegisterPage 경로 변경하면서 파일 삭제 * [FE] FEAT: 드롭다운 css 속성 변경 * [FE] FIX: presentation-main-color 추가 #1559 * [FE] FEAT: 수지회 전용 Layout 추가, 변경 시 main-color 가 바뀌도록 수정 #1559 * [FE] FIX: import 수정 #1563 * [FE] FIX: 수지회에서 지도 아이콘이 나오지 않도록 수지회 TopNav 생성 #1559 * [FE] FIX: 수지회에서 지도 아이콘이 나오지 않도록 수지회 TopNav 생성 #1559 * [FE] FIX: 새롬관에서 수지회 LeftNav 가 출력되는 문제 수정 #1559 * [FE] FIX: 수요지식회 navigation 경로를 절대경로로 수정 #1559 * [FE] FIX: 수요지식회 RegisterPage 신청하기 버튼 margin 수정 #1559 * [FE] FIX: dateUtils 의 calculateAvailableDaysInWeeks 와 calculateAvailableDaysExceptPastDays 함수 통합 #1559 * [FE] FIX: dateUtils 의 주석을 함수 파라메터가 증가함에 따라 수정 #1559 * [FE] FIX: presentation sub color 추가 #1559 * [FE] FIX: 수요지식회 전용 Layout, TopNav, LeftNav 생성 및 분리 #1559 * [FE] FIX: Cabinet Layout 에서 수지회 분리 #1559 * [FE] FIX: Presentation 용 AdminLayout 및 필요한 TopNav 설정 #1559 * [FE] FEAT: admin 수정시 변경사항 rerendering #1559 * [FE] FEAT: 도메인 이동을 위한 Top navigation bar 확장 #1559 * [FE] FIX: Domain 이동이 생김에 따라 Building 선택란에서 Domain 이동하는 기능 제거 #1559 * [FE] FIX: TopNav 길이 80px->120px 증가에 따라 모바일 Menu 위치 수정 #1559 * [FE] FIX: Layout 최적화 #1559 * [FE] FIX: TopNavDomainGroup 모듈화 #1559 * [FE] FIX: RegisterPage 최적화 및 필요없는 Modal 제거 #1559 * [FE] FIX: map() 에서 key 부여로 warning 제거 #1559 * [FE] FEAT: main 버튼 크기변경, 문구변경, padding 변경 #1559 * [FE] FIX: RegisterPage 최적화 #1559 * [FE] FIX: Admin 페이지에서 도메인 변경에 문제가 없도록 수정 #1559 * [FE] FIX: RegisterPage 다중 POST 요청 방지 및 validator 추가 #1559 * [FE] FIX: useInput 수정 #1559 * [FE] FEAT: CI/CD 오류 해결 #1559 * [FE] FEAT: (재)admin 상세페이지 table에 발표 상태 column 추가#1559 * [FE] FEAT: (재)admin 상세페이지에 이벤트 없을때 신청하기 버튼 안보이게#1559 * [FE] FEAT: (재)admin 상세일정 모달 바로 적용 설정 #1559 * [FE] ETC: console.log랑 주석 지움#1559 * [FE] FEAT: (재)(재)admin 상세페이지 table에 발표 상태 column 추가#1559 빠뜨린거 있었음 * [FE] FIX: AdminTopNav 에서 첫번째 건물을 자동으로 선택하도록 변경 #1559 * [FE] FIX: 발표 기록이 없을 때 RegisterPage 에서 undefined 읽는 문제 수정 #1559 * [FE] FIX: makeIDate 최적화 #1559 * [FE] FIX: main페이지 searchCategory 최적화 #1559 * [FE] FIX: isMobile함수 custom hook으로 뺴기 #1559 * [FE] FIX: useMemo로 변수 저장 #1559 * feat: ITopNavDomain 의 presentation path를 /presentation/home 으로 변경 * [BE] REFACTOR: 수지회 어드민 업데이트 * [FE] FIX: DetailTableBodyItemTopTr 리팩토링#1559 * [FE] FIX: index.html 의 logo.ico logo.png 경로 수정 #1559 * [FE] FIX: 과거 발표기록이 없을 때 미래 발표기록 3개가 보이도록 요청하는 upcoming 발표 수 수정 #1559 * [FE] FIX: 오타 수정 #1559 * [FE] FIX: toISOString 이 Timezone 을 보존하지 않아 toISOStringwithTimeZone 생성 후 해당 함수로 대체 #1559 * [FE] FIX: 사용되지 않는 react-ga 제거 #1559 * [FE] FIX: 발표 신청했을때 DetailTable 에 색상 채워지지 않는 문제 해결 #1559 --------- Co-authored-by: jnkeniaem Co-authored-by: gykoh42 Co-authored-by: Elineely Co-authored-by: saewoo1 Co-authored-by: jiminchoi Co-authored-by: Minkyu01 Co-authored-by: chyo1 --- .../AdminPresentationController.java | 44 ++ .../cabinet/dto/InvalidDateResponseDto.java | 15 + .../cabinet/dto/PresentationFormData.java | 24 + .../dto/PresentationFormRequestDto.java | 33 ++ .../dto/PresentationFormResponseDto.java | 10 + .../cabinet/dto/PresentationMainData.java | 11 + .../cabinet/dto/PresentationMyPageDto.java | 19 + .../dto/PresentationMyPagePaginationDto.java | 11 + .../cabinet/dto/PresentationUpdateDto.java | 15 + .../cabinet/exception/ExceptionStatus.java | 6 + .../cabinet/mapper/PresentationMapper.java | 23 + .../controller/PresentationController.java | 79 +++ .../cabinet/presentation/domain/Category.java | 6 + .../presentation/domain/Presentation.java | 87 +++ .../domain/PresentationLocation.java | 6 + .../domain/PresentationStatus.java | 5 + .../presentation/domain/PresentationTime.java | 5 + .../repository/PresentationRepository.java | 29 + .../service/PresentationPolicyService.java | 55 ++ .../service/PresentationQueryService.java | 52 ++ .../service/PresentationService.java | 229 ++++++++ config | 2 +- dev/configure.conf | 2 +- frontend/.prettierrc | 27 +- frontend/index.html | 10 +- frontend/package-lock.json | 17 + frontend/package.json | 1 + frontend/src/App.tsx | 60 ++- .../{ => Cabinet}/api/axios/axios.custom.ts | 190 +++---- .../{ => Cabinet}/api/axios/axios.instance.ts | 4 +- .../{ => Cabinet}/api/react_cookie/cookies.ts | 0 .../src/{ => Cabinet}/assets/css/homePage.css | 0 .../{ => Cabinet}/assets/css/loginPage.css | 0 .../src/{ => Cabinet}/assets/css/media.css | 12 +- .../src/{ => Cabinet}/assets/css/reset.css | 0 .../assets/data/ManualContent.ts | 12 +- .../assets/data/mapPositionData.ts | 2 +- .../src/{ => Cabinet}/assets/data/maps.ts | 10 +- .../assets/data/sectionColNumData.ts | 0 .../assets/images/LeftSectionButton.svg | 0 .../assets/images/PresentationAcademic.svg | 32 ++ .../assets/images/PresentationDevelop.svg | 45 ++ .../assets/images/PresentationEmpty.svg | 68 +++ .../Cabinet/assets/images/PresentationEtc.svg | 38 ++ .../assets/images/PresentationFortyTwo.svg | 130 +++++ .../assets/images/PresentationHobby.svg | 33 ++ .../Cabinet/assets/images/PresentationJob.svg | 44 ++ .../assets/images/adminLoginImg.svg | 0 .../src/{ => Cabinet}/assets/images/alarm.svg | 0 .../{ => Cabinet}/assets/images/cabinet.svg | 0 .../src/Cabinet/assets/images/calendar.svg | 12 + .../assets/images/cautionSign.svg | 0 .../{ => Cabinet}/assets/images/checkIcon.svg | 0 .../src/{ => Cabinet}/assets/images/clock.svg | 0 .../assets/images/close-circle.svg | 0 .../assets/images/close-square.svg | 0 .../{ => Cabinet}/assets/images/clubIcon.svg | 0 .../assets/images/clubIconGray.svg | 0 .../src/{ => Cabinet}/assets/images/crown.svg | 0 .../assets/images/desktopLogo.png | Bin .../Cabinet/assets/images/dropdownChevron.svg | 3 + .../src/{ => Cabinet}/assets/images/edit.svg | 0 .../{ => Cabinet}/assets/images/errorIcon.svg | 0 .../assets/images/exitButton.svg | 0 .../{ => Cabinet}/assets/images/extension.svg | 0 .../assets/images/extensionTicket.svg | 0 .../assets/images/happyCcabi.png | Bin .../src/Cabinet/assets/images/happyCcabi.svg | 53 ++ .../assets/images/happyCcabiWhite.png | Bin .../{ => Cabinet}/assets/images/leader.svg | 0 .../src/{ => Cabinet}/assets/images/link.svg | 0 .../src/{ => Cabinet}/assets/images/lock.svg | 0 .../src/{ => Cabinet}/assets/images/log.svg | 0 .../{ => Cabinet}/assets/images/loginImg.svg | 0 .../src/{ => Cabinet}/assets/images/logo.ico | Bin .../src/{ => Cabinet}/assets/images/logo.png | Bin .../src/{ => Cabinet}/assets/images/logo.svg | 0 .../{ => Cabinet}/assets/images/logoBlack.svg | 0 .../assets/images/manualPeople.svg | 0 .../src/{ => Cabinet}/assets/images/map.svg | 0 .../src/{ => Cabinet}/assets/images/more.svg | 0 .../assets/images/moveButton.svg | 0 .../assets/images/myCabinetIcon.svg | 0 .../assets/images/notificationSign.svg | 0 .../assets/images/notificationSign_grey.svg | 0 .../assets/images/privateIcon.svg | 0 .../assets/images/proceedMultiSelect.svg | 0 .../assets/images/profile-circle.svg | 0 .../{ => Cabinet}/assets/images/refresh.svg | 0 .../assets/images/rotateRight.svg | 0 .../{ => Cabinet}/assets/images/sadCcabi.png | Bin .../src/Cabinet/assets/images/sadCcabi.svg | 8 + .../assets/images/sadCcabiWhite.png | Bin .../{ => Cabinet}/assets/images/search.svg | 0 .../assets/images/searchWhite.svg | 0 .../{ => Cabinet}/assets/images/select.svg | 0 .../assets/images/selectFilterIconOff.svg | 0 .../assets/images/selectFilterIconOn.svg | 0 .../assets/images/selectMaincolor.svg | 0 .../assets/images/selectPurple.svg | 0 .../{ => Cabinet}/assets/images/shareIcon.svg | 0 .../src/{ => Cabinet}/assets/images/slack.svg | 0 .../{ => Cabinet}/assets/images/stairs.svg | 0 .../{ => Cabinet}/assets/images/subtract.svg | 0 frontend/src/Cabinet/assets/images/timer.svg | 3 + .../assets/images/warningTriangleIcon.svg | 0 .../components/AdminInfo/Chart/BarChart.tsx | 0 .../components/AdminInfo/Chart/LineChart.tsx | 2 +- .../components/AdminInfo/Chart/PieChart.tsx | 0 .../components/AdminInfo/Table/AdminTable.tsx | 4 +- .../components/AdminInfo/Table/Pagination.tsx | 0 .../components/Announce/AnnounceTemplate.tsx | 4 +- .../Available/AvailableCountdown.tsx | 4 +- .../components/Available/FloorContainer.tsx | 14 +- .../CabinetInfoArea/AdminCabinetInfoArea.tsx | 24 +- .../CabinetInfoArea.container.tsx | 20 +- .../CabinetInfoArea/CabinetInfoArea.tsx | 36 +- .../CabinetInfoArea/CountTime/CodeAndTime.tsx | 8 +- .../CountTime/CountTime.container.tsx | 13 +- .../CabinetInfoArea/CountTime/CountTime.tsx | 2 +- .../CabinetList/CabinetList.container.tsx | 21 +- .../components/CabinetList/CabinetList.tsx | 15 +- .../CabinetListItem/AdminCabinetListItem.tsx | 19 +- .../CabinetListItem/CabinetListItem.tsx | 16 +- .../CabinetList/EmptySection/EmptySection.tsx | 2 +- .../RealViewNotification.tsx | 2 +- .../{ => Cabinet}/components/Card/Card.tsx | 2 +- .../components/Card/CardStyles.ts | 2 +- .../ClubCabinetInfoCard.tsx | 12 +- .../Card/ClubNoticeCard/ClubNoticeCard.tsx | 8 +- .../ExtensionCard/ExtensionCard.container.tsx | 12 +- .../Card/ExtensionCard/ExtensionCard.tsx | 12 +- .../LentInfoCard/LentInfoCard.container.tsx | 16 +- .../Card/LentInfoCard/LentInfoCard.tsx | 14 +- .../NotificationCard.container.tsx | 12 +- .../NotificationCard/NotificationCard.tsx | 8 +- .../ProfileCard/ProfileCard.container.tsx | 6 +- .../Card/ProfileCard/ProfileCard.tsx | 4 +- .../Card/ThemeColorCard/ColorPicker.tsx | 0 .../ThemeColorCard.container.tsx | 4 +- .../Card/ThemeColorCard/ThemeColorCard.tsx | 10 +- .../Card/ThemeColorCard/colorInfo.ts | 2 +- .../Club/AdminClubLog.container.tsx | 8 +- .../components/Club/AdminClubLog.tsx | 39 +- .../components/Club/ClubInfo.tsx | 20 +- .../components/Club/ClubLogTable.tsx | 8 +- .../ClubMemberInfoArea.container.tsx | 6 +- .../ClubMemberInfoArea/ClubMemberInfoArea.tsx | 24 +- .../ClubMemberList.container.tsx | 11 +- .../Club/ClubMemberList/ClubMemberList.tsx | 18 +- .../ClubMemberListItem/ClubMemberListItem.tsx | 6 +- .../components/Common/Button.tsx | 1 - .../components/Common/ClubListDropdown.tsx | 2 +- .../components/Common/Dropdown.tsx | 15 +- .../components/Common/LoadingAnimation.tsx | 0 .../components/Common/MultiSelectButton.tsx | 0 .../Common/MultiSelectFilterButton.tsx | 4 +- .../components/Common/MultiToggleSwitch.tsx | 2 +- .../Common/MultiToggleSwitchSeparated.tsx | 65 ++- .../components/Common/PillButton.tsx | 0 .../components/Common/Selector.tsx | 2 +- .../components/Common/ToggleSwitch.tsx | 0 .../components/Common/WarningNotification.tsx | 8 +- .../components/Home/ManualContentBox.tsx | 12 +- .../components/Home/ServiceManual.tsx | 6 +- .../CabinetColorTable/CabinetColorTable.tsx | 0 .../LeftMainNav/LeftMainNav.container.tsx | 27 +- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 74 +-- .../components/LeftNav/LeftNav.tsx | 4 +- .../LeftSectionNav.container.tsx | 8 +- .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 6 +- .../LeftSectionNav/LeftSectionNavClubs.tsx | 8 +- .../LentLog/AdminCabinetLentLog.container.tsx | 13 +- .../LentLog/AdminCabinetLentLog.tsx | 14 +- .../components/LentLog/AdminLentLog.tsx | 11 +- .../LentLog/AdminUserLentLog.container.tsx | 14 +- .../components/LentLog/AdminUserLentLog.tsx | 14 +- .../components/LentLog/LentLog.container.tsx | 13 +- .../components/LentLog/LentLog.tsx | 8 +- .../LentLog/LogTable/AdminCabinetLogTable.tsx | 18 +- .../components/LentLog/LogTable/LogTable.tsx | 6 +- .../components/Login/AdminLoginTemplate.tsx | 12 +- .../components/Login/LoginTemplate.tsx | 7 +- .../Login/__tests__/LoginTemplate.test.tsx | 4 +- .../MapInfo/MapFloorSelect/MapFloorSelect.tsx | 6 +- .../MapFloorSelectOption.tsx | 0 .../components/MapInfo/MapGrid/MapGrid.tsx | 6 +- .../components/MapInfo/MapInfo.container.tsx | 11 +- .../components/MapInfo/MapInfo.tsx | 11 +- .../components/MapInfo/MapItem/MapItem.tsx | 12 +- .../components/Modals/BanModal/BanModal.tsx | 32 +- .../Modals/CancelModal/CancelModal.tsx | 34 +- .../AddClubMemberModal.container.tsx | 10 +- .../Modals/ClubModal/AddClubMemberModal.tsx | 2 +- .../ClubModal/ClubMemoModal.container.tsx | 10 +- .../Modals/ClubModal/ClubMemoModal.tsx | 6 +- .../components/Modals/ClubModal/ClubModal.tsx | 26 +- .../ClubModal/ClubPasswordModal.container.tsx | 18 +- .../Modals/ClubModal/ClubPasswordModal.tsx | 6 +- .../ClubModal/DeleteClubMemberModal.tsx | 18 +- .../ClubModal/MandateClubMemberModal.tsx | 12 +- .../Modals/ExtendModal/ExtendModal.tsx | 36 +- .../InvitationCodeModal.container.tsx | 36 +- .../Modals/LentModal/ClubLentModal.tsx | 30 +- .../components/Modals/LentModal/LentModal.tsx | 40 +- .../Modals/ManualModal/ManualModal.tsx | 6 +- .../Modals/MemoModal/MemoModal.container.tsx | 11 +- .../components/Modals/MemoModal/MemoModal.tsx | 6 +- .../{ => Cabinet}/components/Modals/Modal.tsx | 14 +- .../components/Modals/ModalPortal.tsx | 0 .../NotificationModal/NotificationModal.tsx | 10 +- .../OverduePenaltyModal.tsx | 14 +- .../PasswordCheckModal.container.tsx | 38 +- .../PasswordCheckModal/PasswordCheckModal.tsx | 8 +- .../PasswordCheckModal/PasswordContainer.tsx | 0 .../Modals/ResponseModal/ResponseModal.tsx | 12 +- .../Modals/ReturnModal/AdminReturnModal.tsx | 78 +-- .../Modals/ReturnModal/ReturnModal.tsx | 38 +- .../StatusModal/StatusModal.container.tsx | 16 +- .../Modals/StatusModal/StatusModal.tsx | 12 +- .../components/Modals/SwapModal/SwapModal.tsx | 22 +- .../UnavailableModal/UnavailableModal.tsx | 12 +- .../components/Search/NoSearch.tsx | 2 +- .../components/Search/SearchDefault.tsx | 2 +- .../components/Search/SearchItemByIntraId.tsx | 16 +- .../components/Search/SearchItemByNum.tsx | 16 +- .../SectionPagination.container.tsx | 10 +- .../SectionPagination/SectionPagination.tsx | 4 +- .../TopNav/AdminTopNav.container.tsx | 25 +- .../components/TopNav/SearchBar/SearchBar.tsx | 11 +- .../SearchBar/SearchBarList/SearchBarList.tsx | 4 +- .../SearchBar/SearchListItem/ChangeToHTML.tsx | 0 .../SearchListItem/SearchListItem.tsx | 6 +- .../components/TopNav/TopNav.container.tsx | 28 +- .../components/TopNav/TopNav.tsx | 103 ++-- .../TopNavButton/TopNavButton.tsx | 0 .../TopNavButtonGroup/TopNavButtonGroup.tsx | 27 +- .../TopNavDomainGroup/TopNavDomainGroup.tsx | 108 ++++ .../UserInfoArea/UserInfoArea.container.tsx | 8 +- .../components/UserInfoArea/UserInfoArea.tsx | 16 +- .../src/{ => Cabinet}/constants/StatusCode.ts | 0 .../firebase/firebase-messaging-sw.ts | 0 .../{ => Cabinet}/hooks/useAdminHomeApi.ts | 6 +- .../hooks/useCabinetListRefresh.ts | 6 +- .../src/{ => Cabinet}/hooks/useClubInfo.ts | 10 +- .../src/{ => Cabinet}/hooks/useDebounce.tsx | 0 .../src/{ => Cabinet}/hooks/useIsMount.ts | 0 frontend/src/{ => Cabinet}/hooks/useMenu.ts | 4 +- .../src/{ => Cabinet}/hooks/useMultiSelect.ts | 9 +- .../{ => Cabinet}/hooks/useOutsideClick.ts | 0 .../src/{ => Cabinet}/pages/AvailablePage.tsx | 23 +- frontend/src/{ => Cabinet}/pages/ClubPage.tsx | 10 +- frontend/src/{ => Cabinet}/pages/HomePage.tsx | 8 +- frontend/src/{ => Cabinet}/pages/Layout.tsx | 61 ++- frontend/src/{ => Cabinet}/pages/LogPage.tsx | 10 +- .../{ => Cabinet}/pages/LoginFailurePage.tsx | 2 +- .../src/{ => Cabinet}/pages/LoginPage.tsx | 6 +- frontend/src/{ => Cabinet}/pages/MainPage.tsx | 16 +- .../src/{ => Cabinet}/pages/NotFoundPage.tsx | 2 +- .../src/{ => Cabinet}/pages/PostLogin.tsx | 15 +- .../src/{ => Cabinet}/pages/ProfilePage.tsx | 23 +- .../pages/admin/AdminClubPage.tsx | 12 +- .../pages/admin/AdminHomePage.tsx | 20 +- .../{ => Cabinet}/pages/admin/AdminLayout.tsx | 23 +- .../pages/admin/AdminLoginFailurePage.tsx | 2 +- .../pages/admin/AdminLoginPage.tsx | 6 +- .../pages/admin/AdminMainPage.tsx | 23 +- .../pages/admin/AdminPagination.tsx | 0 .../{ => Cabinet}/pages/admin/SearchPage.tsx | 18 +- frontend/src/{ => Cabinet}/recoil/atoms.ts | 14 +- .../src/{ => Cabinet}/recoil/selectors.ts | 9 +- .../src/{ => Cabinet}/types/dto/admin.dto.ts | 0 .../src/{ => Cabinet}/types/dto/alarm.dto.ts | 0 .../{ => Cabinet}/types/dto/cabinet.dto.ts | 6 +- .../src/{ => Cabinet}/types/dto/club.dto.ts | 2 +- .../src/{ => Cabinet}/types/dto/lent.dto.ts | 2 +- .../src/{ => Cabinet}/types/dto/user.dto.ts | 6 +- .../types/enum/AnnounceType.enum.ts | 0 .../types/enum/cabinet.status.enum.ts | 0 .../types/enum/cabinet.type.enum.ts | 0 .../types/enum/color.type.enum.ts | 0 .../types/enum/content.status.enum.ts | 0 .../types/enum/icon.type.enum.ts | 0 .../{ => Cabinet}/types/enum/map.type.enum.ts | 0 .../src/{ => Cabinet}/types/enum/time.enum.ts | 0 frontend/src/{ => Cabinet}/utils/dateUtils.ts | 69 ++- frontend/src/Cabinet/utils/paginationUtils.ts | 3 + .../{ => Cabinet}/utils/recoilPersistUtils.ts | 0 .../src/{ => Cabinet}/utils/tableUtils.ts | 2 +- .../Presentation/api/axios/axios.custom.ts | 116 ++++ frontend/src/Presentation/assets/data/maps.ts | 61 +++ .../src/Presentation/assets/images/logo.svg | 10 + .../Details/DetailContent.container.tsx | 175 ++++++ .../components/Details/DetailContent.tsx | 115 ++++ .../DetailTable/DetailTable.container.tsx | 113 ++++ .../Details/DetailTable/DetailTable.tsx | 79 +++ .../DetailTable/DetailTableBodyItem.tsx | 113 ++++ .../DetailTableBodyItemBottomTr.tsx | 72 +++ .../DetailTableBodyItemMiddleTr.tsx | 100 ++++ .../DetailTable/DetailTableBodyItemTopTr.tsx | 218 ++++++++ .../Details/DetailTable/DetailTableHead.tsx | 76 +++ .../Details/DetailTable/NoEventTableRow.tsx | 96 ++++ .../Home/PresentationCard.container.tsx | 107 ++++ .../components/Home/PresentationCard.tsx | 156 ++++++ .../Home/PresentationCardMobile.tsx | 225 ++++++++ .../components/Home/RecentPresentation.tsx | 147 ++++++ .../LeftMainNav/LeftMainNav.container.tsx | 76 +++ .../LeftNav/LeftMainNav/LeftMainNav.tsx | 210 ++++++++ .../components/LeftNav/LeftNav.tsx | 20 + .../EditStatusModal/EditStatusModal.tsx | 297 +++++++++++ .../Modals/RegisterModal/RegisterModal.tsx | 108 ++++ .../components/PresentationLog/LogTable.tsx | 126 +++++ .../components/Register/DropdownDateMenu.tsx | 204 +++++++ .../components/Register/DropdownTimeMenu.tsx | 202 +++++++ .../components/Register/InputField.tsx | 112 ++++ .../TopNav/AdminTopNav.container.tsx | 14 + .../components/TopNav/TopNav.container.tsx | 37 ++ .../Presentation/components/TopNav/TopNav.tsx | 88 +++ .../Presentation/constants/dayOfTheWeek.ts | 7 + frontend/src/Presentation/constants/policy.ts | 13 + .../src/Presentation/hooks/useClickOutside.ts | 22 + frontend/src/Presentation/hooks/useInput.ts | 38 ++ .../src/Presentation/hooks/useInvalidDates.ts | 22 + .../src/Presentation/hooks/useIsMobile.ts | 18 + .../src/Presentation/pages/DetailPage.tsx | 7 + .../src/Presentation/pages/DropdownMenu.tsx | 113 ++++ frontend/src/Presentation/pages/HomePage.tsx | 13 + frontend/src/Presentation/pages/Layout.tsx | 95 ++++ frontend/src/Presentation/pages/LogPage.tsx | 70 +++ .../src/Presentation/pages/RegisterPage.tsx | 499 ++++++++++++++++++ .../Presentation/pages/admin/AdminLayout.tsx | 78 +++ frontend/src/Presentation/recoil/atoms.ts | 15 + .../types/dto/presentation.dto.ts | 56 ++ .../types/enum/presentation.type.enum.ts | 28 + frontend/src/Presentation/utils/dateUtils.ts | 150 ++++++ .../src/assets/images/dropdownChevron.svg | 3 - frontend/src/assets/images/sadCcabi.svg | 9 - frontend/src/components/Club/ClubList.tsx | 56 -- frontend/src/index.css | 7 + frontend/src/main.tsx | 15 +- 340 files changed, 7466 insertions(+), 1244 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/presentation/controller/AdminPresentationController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/InvalidDateResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormData.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormRequestDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationMainData.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPageDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPagePaginationDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationUpdateDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/mapper/PresentationMapper.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/Category.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationLocation.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationStatus.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationTime.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/repository/PresentationRepository.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationPolicyService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java rename frontend/src/{ => Cabinet}/api/axios/axios.custom.ts (83%) rename frontend/src/{ => Cabinet}/api/axios/axios.instance.ts (88%) rename frontend/src/{ => Cabinet}/api/react_cookie/cookies.ts (100%) rename frontend/src/{ => Cabinet}/assets/css/homePage.css (100%) rename frontend/src/{ => Cabinet}/assets/css/loginPage.css (100%) rename frontend/src/{ => Cabinet}/assets/css/media.css (92%) rename frontend/src/{ => Cabinet}/assets/css/reset.css (100%) rename frontend/src/{ => Cabinet}/assets/data/ManualContent.ts (94%) rename frontend/src/{ => Cabinet}/assets/data/mapPositionData.ts (98%) rename frontend/src/{ => Cabinet}/assets/data/maps.ts (94%) rename frontend/src/{ => Cabinet}/assets/data/sectionColNumData.ts (100%) rename frontend/src/{ => Cabinet}/assets/images/LeftSectionButton.svg (100%) create mode 100644 frontend/src/Cabinet/assets/images/PresentationAcademic.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationDevelop.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationEmpty.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationEtc.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationFortyTwo.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationHobby.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationJob.svg rename frontend/src/{ => Cabinet}/assets/images/adminLoginImg.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/alarm.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/cabinet.svg (100%) create mode 100644 frontend/src/Cabinet/assets/images/calendar.svg rename frontend/src/{ => Cabinet}/assets/images/cautionSign.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/checkIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/clock.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/close-circle.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/close-square.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/clubIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/clubIconGray.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/crown.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/desktopLogo.png (100%) create mode 100644 frontend/src/Cabinet/assets/images/dropdownChevron.svg rename frontend/src/{ => Cabinet}/assets/images/edit.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/errorIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/exitButton.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/extension.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/extensionTicket.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/happyCcabi.png (100%) create mode 100644 frontend/src/Cabinet/assets/images/happyCcabi.svg rename frontend/src/{ => Cabinet}/assets/images/happyCcabiWhite.png (100%) rename frontend/src/{ => Cabinet}/assets/images/leader.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/link.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/lock.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/log.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/loginImg.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/logo.ico (100%) rename frontend/src/{ => Cabinet}/assets/images/logo.png (100%) rename frontend/src/{ => Cabinet}/assets/images/logo.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/logoBlack.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/manualPeople.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/map.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/more.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/moveButton.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/myCabinetIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/notificationSign.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/notificationSign_grey.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/privateIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/proceedMultiSelect.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/profile-circle.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/refresh.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/rotateRight.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/sadCcabi.png (100%) create mode 100644 frontend/src/Cabinet/assets/images/sadCcabi.svg rename frontend/src/{ => Cabinet}/assets/images/sadCcabiWhite.png (100%) rename frontend/src/{ => Cabinet}/assets/images/search.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/searchWhite.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/select.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/selectFilterIconOff.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/selectFilterIconOn.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/selectMaincolor.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/selectPurple.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/shareIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/slack.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/stairs.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/subtract.svg (100%) create mode 100644 frontend/src/Cabinet/assets/images/timer.svg rename frontend/src/{ => Cabinet}/assets/images/warningTriangleIcon.svg (100%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Chart/BarChart.tsx (100%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Chart/LineChart.tsx (97%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Chart/PieChart.tsx (100%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Table/AdminTable.tsx (95%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Table/Pagination.tsx (100%) rename frontend/src/{ => Cabinet}/components/Announce/AnnounceTemplate.tsx (94%) rename frontend/src/{ => Cabinet}/components/Available/AvailableCountdown.tsx (94%) rename frontend/src/{ => Cabinet}/components/Available/FloorContainer.tsx (84%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/AdminCabinetInfoArea.tsx (92%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CabinetInfoArea.container.tsx (94%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CabinetInfoArea.tsx (89%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CountTime/CodeAndTime.tsx (93%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CountTime/CountTime.container.tsx (88%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CountTime/CountTime.tsx (94%) rename frontend/src/{ => Cabinet}/components/CabinetList/CabinetList.container.tsx (69%) rename frontend/src/{ => Cabinet}/components/CabinetList/CabinetList.tsx (74%) rename frontend/src/{ => Cabinet}/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx (93%) rename frontend/src/{ => Cabinet}/components/CabinetList/CabinetListItem/CabinetListItem.tsx (95%) rename frontend/src/{ => Cabinet}/components/CabinetList/EmptySection/EmptySection.tsx (91%) rename frontend/src/{ => Cabinet}/components/CabinetList/RealViewNotification/RealViewNotification.tsx (97%) rename frontend/src/{ => Cabinet}/components/Card/Card.tsx (97%) rename frontend/src/{ => Cabinet}/components/Card/CardStyles.ts (93%) rename frontend/src/{ => Cabinet}/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx (91%) rename frontend/src/{ => Cabinet}/components/Card/ClubNoticeCard/ClubNoticeCard.tsx (87%) rename frontend/src/{ => Cabinet}/components/Card/ExtensionCard/ExtensionCard.container.tsx (85%) rename frontend/src/{ => Cabinet}/components/Card/ExtensionCard/ExtensionCard.tsx (84%) rename frontend/src/{ => Cabinet}/components/Card/LentInfoCard/LentInfoCard.container.tsx (82%) rename frontend/src/{ => Cabinet}/components/Card/LentInfoCard/LentInfoCard.tsx (92%) rename frontend/src/{ => Cabinet}/components/Card/NotificationCard/NotificationCard.container.tsx (90%) rename frontend/src/{ => Cabinet}/components/Card/NotificationCard/NotificationCard.tsx (82%) rename frontend/src/{ => Cabinet}/components/Card/ProfileCard/ProfileCard.container.tsx (86%) rename frontend/src/{ => Cabinet}/components/Card/ProfileCard/ProfileCard.tsx (91%) rename frontend/src/{ => Cabinet}/components/Card/ThemeColorCard/ColorPicker.tsx (100%) rename frontend/src/{ => Cabinet}/components/Card/ThemeColorCard/ThemeColorCard.container.tsx (95%) rename frontend/src/{ => Cabinet}/components/Card/ThemeColorCard/ThemeColorCard.tsx (93%) rename frontend/src/{ => Cabinet}/components/Card/ThemeColorCard/colorInfo.ts (91%) rename frontend/src/{ => Cabinet}/components/Club/AdminClubLog.container.tsx (85%) rename frontend/src/{ => Cabinet}/components/Club/AdminClubLog.tsx (75%) rename frontend/src/{ => Cabinet}/components/Club/ClubInfo.tsx (78%) rename frontend/src/{ => Cabinet}/components/Club/ClubLogTable.tsx (89%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx (89%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx (88%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberList/ClubMemberList.container.tsx (90%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberList/ClubMemberList.tsx (88%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx (90%) rename frontend/src/{ => Cabinet}/components/Common/Button.tsx (98%) rename frontend/src/{ => Cabinet}/components/Common/ClubListDropdown.tsx (97%) rename frontend/src/{ => Cabinet}/components/Common/Dropdown.tsx (92%) rename frontend/src/{ => Cabinet}/components/Common/LoadingAnimation.tsx (100%) rename frontend/src/{ => Cabinet}/components/Common/MultiSelectButton.tsx (100%) rename frontend/src/{ => Cabinet}/components/Common/MultiSelectFilterButton.tsx (90%) rename frontend/src/{ => Cabinet}/components/Common/MultiToggleSwitch.tsx (98%) rename frontend/src/{ => Cabinet}/components/Common/MultiToggleSwitchSeparated.tsx (55%) rename frontend/src/{ => Cabinet}/components/Common/PillButton.tsx (100%) rename frontend/src/{ => Cabinet}/components/Common/Selector.tsx (93%) rename frontend/src/{ => Cabinet}/components/Common/ToggleSwitch.tsx (100%) rename frontend/src/{ => Cabinet}/components/Common/WarningNotification.tsx (91%) rename frontend/src/{ => Cabinet}/components/Home/ManualContentBox.tsx (90%) rename frontend/src/{ => Cabinet}/components/Home/ServiceManual.tsx (95%) rename frontend/src/{ => Cabinet}/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx (100%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx (88%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftMainNav/LeftMainNav.tsx (77%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftNav.tsx (66%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftSectionNav/LeftSectionNav.container.tsx (86%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx (93%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx (86%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminCabinetLentLog.container.tsx (79%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminCabinetLentLog.tsx (84%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminLentLog.tsx (87%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminUserLentLog.container.tsx (79%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminUserLentLog.tsx (85%) rename frontend/src/{ => Cabinet}/components/LentLog/LentLog.container.tsx (74%) rename frontend/src/{ => Cabinet}/components/LentLog/LentLog.tsx (93%) rename frontend/src/{ => Cabinet}/components/LentLog/LogTable/AdminCabinetLogTable.tsx (84%) rename frontend/src/{ => Cabinet}/components/LentLog/LogTable/LogTable.tsx (92%) rename frontend/src/{ => Cabinet}/components/Login/AdminLoginTemplate.tsx (93%) rename frontend/src/{ => Cabinet}/components/Login/LoginTemplate.tsx (93%) rename frontend/src/{ => Cabinet}/components/Login/__tests__/LoginTemplate.test.tsx (82%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx (84%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx (100%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapGrid/MapGrid.tsx (86%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapInfo.container.tsx (86%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapInfo.tsx (83%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapItem/MapItem.tsx (85%) rename frontend/src/{ => Cabinet}/components/Modals/BanModal/BanModal.tsx (85%) rename frontend/src/{ => Cabinet}/components/Modals/CancelModal/CancelModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/AddClubMemberModal.container.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/AddClubMemberModal.tsx (98%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubMemoModal.container.tsx (91%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubMemoModal.tsx (97%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubModal.tsx (95%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubPasswordModal.container.tsx (88%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubPasswordModal.tsx (93%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/DeleteClubMemberModal.tsx (80%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/MandateClubMemberModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/ExtendModal/ExtendModal.tsx (88%) rename frontend/src/{ => Cabinet}/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx (85%) rename frontend/src/{ => Cabinet}/components/Modals/LentModal/ClubLentModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/LentModal/LentModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/ManualModal/ManualModal.tsx (96%) rename frontend/src/{ => Cabinet}/components/Modals/MemoModal/MemoModal.container.tsx (89%) rename frontend/src/{ => Cabinet}/components/Modals/MemoModal/MemoModal.tsx (97%) rename frontend/src/{ => Cabinet}/components/Modals/Modal.tsx (91%) rename frontend/src/{ => Cabinet}/components/Modals/ModalPortal.tsx (100%) rename frontend/src/{ => Cabinet}/components/Modals/NotificationModal/NotificationModal.tsx (57%) rename frontend/src/{ => Cabinet}/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx (79%) rename frontend/src/{ => Cabinet}/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx (83%) rename frontend/src/{ => Cabinet}/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx (93%) rename frontend/src/{ => Cabinet}/components/Modals/PasswordCheckModal/PasswordContainer.tsx (100%) rename frontend/src/{ => Cabinet}/components/Modals/ResponseModal/ResponseModal.tsx (74%) rename frontend/src/{ => Cabinet}/components/Modals/ReturnModal/AdminReturnModal.tsx (81%) rename frontend/src/{ => Cabinet}/components/Modals/ReturnModal/ReturnModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/StatusModal/StatusModal.container.tsx (90%) rename frontend/src/{ => Cabinet}/components/Modals/StatusModal/StatusModal.tsx (95%) rename frontend/src/{ => Cabinet}/components/Modals/SwapModal/SwapModal.tsx (93%) rename frontend/src/{ => Cabinet}/components/Modals/UnavailableModal/UnavailableModal.tsx (56%) rename frontend/src/{ => Cabinet}/components/Search/NoSearch.tsx (88%) rename frontend/src/{ => Cabinet}/components/Search/SearchDefault.tsx (91%) rename frontend/src/{ => Cabinet}/components/Search/SearchItemByIntraId.tsx (92%) rename frontend/src/{ => Cabinet}/components/Search/SearchItemByNum.tsx (89%) rename frontend/src/{ => Cabinet}/components/SectionPagination/SectionPagination.container.tsx (91%) rename frontend/src/{ => Cabinet}/components/SectionPagination/SectionPagination.tsx (94%) rename frontend/src/{ => Cabinet}/components/TopNav/AdminTopNav.container.tsx (71%) rename frontend/src/{ => Cabinet}/components/TopNav/SearchBar/SearchBar.tsx (95%) rename frontend/src/{ => Cabinet}/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx (91%) rename frontend/src/{ => Cabinet}/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx (100%) rename frontend/src/{ => Cabinet}/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx (88%) rename frontend/src/{ => Cabinet}/components/TopNav/TopNav.container.tsx (74%) rename frontend/src/{ => Cabinet}/components/TopNav/TopNav.tsx (61%) rename frontend/src/{ => Cabinet}/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx (100%) rename frontend/src/{ => Cabinet}/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx (81%) create mode 100644 frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx rename frontend/src/{ => Cabinet}/components/UserInfoArea/UserInfoArea.container.tsx (75%) rename frontend/src/{ => Cabinet}/components/UserInfoArea/UserInfoArea.tsx (89%) rename frontend/src/{ => Cabinet}/constants/StatusCode.ts (100%) rename frontend/src/{ => Cabinet}/firebase/firebase-messaging-sw.ts (100%) rename frontend/src/{ => Cabinet}/hooks/useAdminHomeApi.ts (93%) rename frontend/src/{ => Cabinet}/hooks/useCabinetListRefresh.ts (95%) rename frontend/src/{ => Cabinet}/hooks/useClubInfo.ts (90%) rename frontend/src/{ => Cabinet}/hooks/useDebounce.tsx (100%) rename frontend/src/{ => Cabinet}/hooks/useIsMount.ts (100%) rename frontend/src/{ => Cabinet}/hooks/useMenu.ts (99%) rename frontend/src/{ => Cabinet}/hooks/useMultiSelect.ts (94%) rename frontend/src/{ => Cabinet}/hooks/useOutsideClick.ts (100%) rename frontend/src/{ => Cabinet}/pages/AvailablePage.tsx (88%) rename frontend/src/{ => Cabinet}/pages/ClubPage.tsx (79%) rename frontend/src/{ => Cabinet}/pages/HomePage.tsx (67%) rename frontend/src/{ => Cabinet}/pages/Layout.tsx (81%) rename frontend/src/{ => Cabinet}/pages/LogPage.tsx (80%) rename frontend/src/{ => Cabinet}/pages/LoginFailurePage.tsx (84%) rename frontend/src/{ => Cabinet}/pages/LoginPage.tsx (66%) rename frontend/src/{ => Cabinet}/pages/MainPage.tsx (88%) rename frontend/src/{ => Cabinet}/pages/NotFoundPage.tsx (86%) rename frontend/src/{ => Cabinet}/pages/PostLogin.tsx (79%) rename frontend/src/{ => Cabinet}/pages/ProfilePage.tsx (70%) rename frontend/src/{ => Cabinet}/pages/admin/AdminClubPage.tsx (86%) rename frontend/src/{ => Cabinet}/pages/admin/AdminHomePage.tsx (90%) rename frontend/src/{ => Cabinet}/pages/admin/AdminLayout.tsx (82%) rename frontend/src/{ => Cabinet}/pages/admin/AdminLoginFailurePage.tsx (85%) rename frontend/src/{ => Cabinet}/pages/admin/AdminLoginPage.tsx (58%) rename frontend/src/{ => Cabinet}/pages/admin/AdminMainPage.tsx (86%) rename frontend/src/{ => Cabinet}/pages/admin/AdminPagination.tsx (100%) rename frontend/src/{ => Cabinet}/pages/admin/SearchPage.tsx (90%) rename frontend/src/{ => Cabinet}/recoil/atoms.ts (91%) rename frontend/src/{ => Cabinet}/recoil/selectors.ts (96%) rename frontend/src/{ => Cabinet}/types/dto/admin.dto.ts (100%) rename frontend/src/{ => Cabinet}/types/dto/alarm.dto.ts (100%) rename frontend/src/{ => Cabinet}/types/dto/cabinet.dto.ts (87%) rename frontend/src/{ => Cabinet}/types/dto/club.dto.ts (92%) rename frontend/src/{ => Cabinet}/types/dto/lent.dto.ts (95%) rename frontend/src/{ => Cabinet}/types/dto/user.dto.ts (84%) rename frontend/src/{ => Cabinet}/types/enum/AnnounceType.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/cabinet.status.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/cabinet.type.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/color.type.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/content.status.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/icon.type.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/map.type.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/time.enum.ts (100%) rename frontend/src/{ => Cabinet}/utils/dateUtils.ts (51%) create mode 100644 frontend/src/Cabinet/utils/paginationUtils.ts rename frontend/src/{ => Cabinet}/utils/recoilPersistUtils.ts (100%) rename frontend/src/{ => Cabinet}/utils/tableUtils.ts (97%) create mode 100644 frontend/src/Presentation/api/axios/axios.custom.ts create mode 100644 frontend/src/Presentation/assets/data/maps.ts create mode 100644 frontend/src/Presentation/assets/images/logo.svg create mode 100644 frontend/src/Presentation/components/Details/DetailContent.container.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailContent.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTable.container.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTable.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItem.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableHead.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/NoEventTableRow.tsx create mode 100644 frontend/src/Presentation/components/Home/PresentationCard.container.tsx create mode 100644 frontend/src/Presentation/components/Home/PresentationCard.tsx create mode 100644 frontend/src/Presentation/components/Home/PresentationCardMobile.tsx create mode 100644 frontend/src/Presentation/components/Home/RecentPresentation.tsx create mode 100644 frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx create mode 100644 frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.tsx create mode 100644 frontend/src/Presentation/components/LeftNav/LeftNav.tsx create mode 100644 frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx create mode 100644 frontend/src/Presentation/components/Modals/RegisterModal/RegisterModal.tsx create mode 100644 frontend/src/Presentation/components/PresentationLog/LogTable.tsx create mode 100644 frontend/src/Presentation/components/Register/DropdownDateMenu.tsx create mode 100644 frontend/src/Presentation/components/Register/DropdownTimeMenu.tsx create mode 100644 frontend/src/Presentation/components/Register/InputField.tsx create mode 100644 frontend/src/Presentation/components/TopNav/AdminTopNav.container.tsx create mode 100644 frontend/src/Presentation/components/TopNav/TopNav.container.tsx create mode 100644 frontend/src/Presentation/components/TopNav/TopNav.tsx create mode 100644 frontend/src/Presentation/constants/dayOfTheWeek.ts create mode 100644 frontend/src/Presentation/constants/policy.ts create mode 100644 frontend/src/Presentation/hooks/useClickOutside.ts create mode 100644 frontend/src/Presentation/hooks/useInput.ts create mode 100644 frontend/src/Presentation/hooks/useInvalidDates.ts create mode 100644 frontend/src/Presentation/hooks/useIsMobile.ts create mode 100644 frontend/src/Presentation/pages/DetailPage.tsx create mode 100644 frontend/src/Presentation/pages/DropdownMenu.tsx create mode 100644 frontend/src/Presentation/pages/HomePage.tsx create mode 100644 frontend/src/Presentation/pages/Layout.tsx create mode 100644 frontend/src/Presentation/pages/LogPage.tsx create mode 100644 frontend/src/Presentation/pages/RegisterPage.tsx create mode 100644 frontend/src/Presentation/pages/admin/AdminLayout.tsx create mode 100644 frontend/src/Presentation/recoil/atoms.ts create mode 100644 frontend/src/Presentation/types/dto/presentation.dto.ts create mode 100644 frontend/src/Presentation/types/enum/presentation.type.enum.ts create mode 100644 frontend/src/Presentation/utils/dateUtils.ts delete mode 100644 frontend/src/assets/images/dropdownChevron.svg delete mode 100644 frontend/src/assets/images/sadCcabi.svg delete mode 100644 frontend/src/components/Club/ClubList.tsx diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/presentation/controller/AdminPresentationController.java b/backend/src/main/java/org/ftclub/cabinet/admin/presentation/controller/AdminPresentationController.java new file mode 100644 index 000000000..7b94a0e95 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/presentation/controller/AdminPresentationController.java @@ -0,0 +1,44 @@ +package org.ftclub.cabinet.admin.presentation.controller; + +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +import java.time.YearMonth; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.dto.PresentationFormResponseDto; +import org.ftclub.cabinet.dto.PresentationUpdateDto; +import org.ftclub.cabinet.presentation.service.PresentationService; +import org.springframework.format.annotation.DateTimeFormat; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("/v5/admin/presentation") +@RequiredArgsConstructor +public class AdminPresentationController { + + private final PresentationService presentationService; + + @PatchMapping("/{formId}/update") + @AuthGuard(level = ADMIN_ONLY) + public void updatePresentationByFormId( + @PathVariable("formId") Long formId, + @Valid @RequestBody PresentationUpdateDto dto) { + presentationService.updatePresentationByFormId(formId, dto); + } + + @GetMapping("/schedule") + @AuthGuard(level = ADMIN_ONLY) + public PresentationFormResponseDto adminSchedulePage(@RequestParam(value = "yearMonth") + @DateTimeFormat(pattern = "yyyy-MM") + YearMonth yearMonth) { + return presentationService.getAdminPresentationSchedule(yearMonth); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/InvalidDateResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/InvalidDateResponseDto.java new file mode 100644 index 000000000..f0621ef39 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/InvalidDateResponseDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + + +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class InvalidDateResponseDto { + + private List invalidDateList; + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormData.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormData.java new file mode 100644 index 000000000..f021780a5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormData.java @@ -0,0 +1,24 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import lombok.Data; +import org.ftclub.cabinet.presentation.domain.Category; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; +import org.ftclub.cabinet.presentation.domain.PresentationTime; + +@Data +public class PresentationFormData { + + private final Long id; + private final PresentationStatus presentationStatus; + private final PresentationTime presentationTime; + private final PresentationLocation presentationLocation; + private final String subject; + private final String summary; + private final String detail; + private final Category category; + private final LocalDateTime dateTime; + private final String userName; + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormRequestDto.java new file mode 100644 index 000000000..f8d1fd22a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormRequestDto.java @@ -0,0 +1,33 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import javax.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.ftclub.cabinet.presentation.domain.Category; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationTime; +import org.hibernate.validator.constraints.Length; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@ToString +public class PresentationFormRequestDto { + + private Category category; + private PresentationTime presentationTime; + private PresentationLocation presentationLocation; + private LocalDateTime dateTime; + @NotBlank + @Length(min = 1, max = 25) + private String subject; + @NotBlank + @Length(min = 1, max = 40) + private String summary; + @NotBlank + @Length(min = 1, max = 500) + private String detail; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormResponseDto.java new file mode 100644 index 000000000..dea3f5ed8 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormResponseDto.java @@ -0,0 +1,10 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class PresentationFormResponseDto { + + private final List forms; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMainData.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMainData.java new file mode 100644 index 000000000..3be344329 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMainData.java @@ -0,0 +1,11 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class PresentationMainData { + + private final List past; + private final List upcoming; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPageDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPageDto.java new file mode 100644 index 000000000..60d69f0e6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPageDto.java @@ -0,0 +1,19 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import lombok.Data; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; + +/** + * 날짜, 제목, 상태, 장소 + */ +@Data +public class PresentationMyPageDto { + + private final Integer id; + private final String subject; + private final LocalDateTime dateTime; + private final PresentationStatus presentationStatus; + private final PresentationLocation presentationLocation; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPagePaginationDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPagePaginationDto.java new file mode 100644 index 000000000..b6e391acd --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPagePaginationDto.java @@ -0,0 +1,11 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class PresentationMyPagePaginationDto { + + private final List result; + private final Long totalLength; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationUpdateDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationUpdateDto.java new file mode 100644 index 000000000..9dc671dfc --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationUpdateDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import javax.validation.constraints.NotEmpty; +import lombok.Data; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; + +@Data +public class PresentationUpdateDto { + + private final LocalDateTime dateTime; + private final PresentationStatus status; + private final PresentationLocation location; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index 6cf1251a9..26f2fa8ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -70,6 +70,12 @@ public enum ExceptionStatus { NOT_CLUB_MASTER(HttpStatus.BAD_REQUEST, "동아리 장이 아닙니다."), INVALID_CLUB_MASTER(HttpStatus.BAD_REQUEST, "동아리에 동아리 장이 없습니다."), NOT_FOUND_CLUB_LENT_HISTORY(HttpStatus.NOT_FOUND, "동아리가 대여한 사물함이 없습니다."), + INVALID_PRESENTATION_CATEGORY(HttpStatus.BAD_REQUEST, "발표회에 정의된 카테고리가 아닙니다."), + INVALID_DATE(HttpStatus.BAD_REQUEST, "잘못된 날짜입니다."), + PRESENTATION_ALREADY_EXISTED(HttpStatus.CONFLICT, "이미 예약된 발표 날짜입니다"), + NOT_FOUND_FORM(HttpStatus.NOT_FOUND, "신청서가 존재하지 않습니다."), + INVALID_FORM_ID(HttpStatus.BAD_REQUEST, "잘못된 신청번호입니다."), + INVALID_LOCATION(HttpStatus.BAD_REQUEST, "잘못된 장소입니다."), ; final private int statusCode; diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/PresentationMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/PresentationMapper.java new file mode 100644 index 000000000..7d36f41b5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/PresentationMapper.java @@ -0,0 +1,23 @@ +package org.ftclub.cabinet.mapper; + +import java.util.List; +import org.ftclub.cabinet.dto.PresentationFormData; +import org.ftclub.cabinet.dto.PresentationMainData; +import org.ftclub.cabinet.dto.PresentationMyPageDto; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface PresentationMapper { + + @Mapping(target = "userName", source = "user.name") + PresentationFormData toPresentationFormDataDto(Presentation presentation); + + + PresentationMyPageDto toPresentationMyPageDto(Presentation presentation); + + PresentationMainData toPresentationMainData(List past, + List upcoming); + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java b/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java new file mode 100644 index 000000000..971665968 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java @@ -0,0 +1,79 @@ +package org.ftclub.cabinet.presentation.controller; + + +import java.time.YearMonth; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.dto.InvalidDateResponseDto; +import org.ftclub.cabinet.dto.PresentationFormRequestDto; +import org.ftclub.cabinet.dto.PresentationFormResponseDto; +import org.ftclub.cabinet.dto.PresentationMainData; +import org.ftclub.cabinet.dto.PresentationMyPagePaginationDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.presentation.service.PresentationService; +import org.ftclub.cabinet.user.domain.UserSession; +import org.springframework.data.domain.Pageable; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("/v5/presentation") +@RequiredArgsConstructor +public class PresentationController { + + private final PresentationService presentationService; + + @PostMapping("/form") + @AuthGuard(level = AuthLevel.USER_ONLY) + public void createPresentationForm( + @UserSession UserSessionDto user, + @Valid @RequestBody PresentationFormRequestDto dto) { + presentationService.createPresentationFrom(user.getUserId(), dto); + } + + @GetMapping("/form/invalid-date") + public InvalidDateResponseDto getInvalidDate() { + return presentationService.getInvalidDate(); + } + + @GetMapping("") + @AuthGuard(level = AuthLevel.USER_ONLY) + public PresentationMainData getMainData( + @RequestParam(value = "pastFormCount") Integer pastFormCount, + @RequestParam(value = "upcomingFormCount") Integer upcomingFormCount) { + return presentationService.getPastAndUpcomingPresentations(pastFormCount, + upcomingFormCount); + } + + @GetMapping("/schedule") + public PresentationFormResponseDto getPresentationSchedule( + @RequestParam(value = "yearMonth") + @DateTimeFormat(pattern = "yyyy-MM") + YearMonth yearMonth) { + return presentationService.getUserPresentationSchedule(yearMonth); + } + + /** + * 자신의 수요지식회 발표 현황을 조회합니다. + * + * @param user + * @param pageable + * @return + */ + @GetMapping("/me/histories") + @AuthGuard(level = AuthLevel.USER_ONLY) + public PresentationMyPagePaginationDto getUserPresentation( + @UserSession UserSessionDto user, + Pageable pageable + ) { + return presentationService.getUserPresentations(user.getUserId(), pageable); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Category.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Category.java new file mode 100644 index 000000000..1370c758c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Category.java @@ -0,0 +1,6 @@ +package org.ftclub.cabinet.presentation.domain; + +public enum Category { + DEVELOP, HOBBY, JOB, ETC, TASK, STUDY + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java new file mode 100644 index 000000000..6913b08b5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java @@ -0,0 +1,87 @@ +package org.ftclub.cabinet.presentation.domain; + +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.ftclub.cabinet.user.domain.User; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Presentation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private Long id; + + @Enumerated(value = EnumType.STRING) + @Column(name = "PRESENTATION_STATUS") + private PresentationStatus presentationStatus; + + @Enumerated(value = EnumType.STRING) + @Column(name = "PRESENTATION_TIME") + private PresentationTime presentationTime; + + @Column(name = "SUBJECT", length = 25) + private String subject; + + @Column(name = "SUMMARY", length = 40) + private String summary; + + @Column(name = "DETAIL", length = 500) + private String detail; + + @Enumerated(value = EnumType.STRING) + @Column(name = "CATEGORY") + private Category category; + + @Column(name = "DATE_TIME") + private LocalDateTime dateTime; + + @Enumerated(value = EnumType.STRING) + private PresentationLocation presentationLocation; + + @Setter + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "USER_ID") + private User user; + + protected Presentation(Category category, LocalDateTime dateTime, + PresentationTime presentationTime, String subject, String summary, String detail) { + this.category = category; + this.dateTime = dateTime; + this.presentationTime = presentationTime; + this.subject = subject; + this.detail = detail; + this.summary = summary; + this.presentationStatus = PresentationStatus.EXPECTED; + this.presentationLocation = PresentationLocation.THIRD; + } + + public static Presentation of(Category category, LocalDateTime dateTime, + PresentationTime presentationTime, String subject, String summary, String detail) { + + return new Presentation(category, dateTime, presentationTime, subject, + summary, detail); + } + + public void adminUpdate(PresentationStatus newStatus, LocalDateTime newDateTime, + PresentationLocation newLocation) { + this.presentationStatus = newStatus; + this.dateTime = newDateTime; + this.presentationLocation = newLocation; + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationLocation.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationLocation.java new file mode 100644 index 000000000..7e6cdd32a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationLocation.java @@ -0,0 +1,6 @@ +package org.ftclub.cabinet.presentation.domain; + +public enum PresentationLocation { + BASEMENT, FIRST, THIRD + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationStatus.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationStatus.java new file mode 100644 index 000000000..be32e48f3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationStatus.java @@ -0,0 +1,5 @@ +package org.ftclub.cabinet.presentation.domain; + +public enum PresentationStatus { + CANCEL, DONE, EXPECTED +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationTime.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationTime.java new file mode 100644 index 000000000..66c6266e3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationTime.java @@ -0,0 +1,5 @@ +package org.ftclub.cabinet.presentation.domain; + +public enum PresentationTime { + HALF, HOUR, HOUR_HALF, TWO_HOUR +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/repository/PresentationRepository.java b/backend/src/main/java/org/ftclub/cabinet/presentation/repository/PresentationRepository.java new file mode 100644 index 000000000..13198aaab --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/repository/PresentationRepository.java @@ -0,0 +1,29 @@ +package org.ftclub.cabinet.presentation.repository; + +import java.time.LocalDateTime; +import java.util.List; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface PresentationRepository extends JpaRepository { + + @EntityGraph(attributePaths = "user") + List findAllByDateTimeBetweenOrderByDateTime(LocalDateTime start, + LocalDateTime end); + + List findAllByDateTimeBetween(LocalDateTime start, LocalDateTime end); + + @EntityGraph(attributePaths = "user") + List findByDateTimeBetween(@Param("start") LocalDateTime start, + @Param("end") LocalDateTime end, Pageable pageable); + + @Query("SELECT p " + + "FROM Presentation p " + + "WHERE p.user.id = :userId") + Page findPaginationById(@Param("userId") Long userId, Pageable pageable); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationPolicyService.java new file mode 100644 index 000000000..dbd06243e --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationPolicyService.java @@ -0,0 +1,55 @@ +package org.ftclub.cabinet.presentation.service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; +import org.ftclub.cabinet.presentation.repository.PresentationRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PresentationPolicyService { + + private static final Integer MAXIMUM_MONTH = 3; + private final PresentationRepository presentationRepository; + + /** + * 발표 날짜에 대해 예약 가능한지 검증한다. + *

+ * 범위 내의 날짜(등록일 기준 3개월 이내), 예약 날짜인지 검증 + * + * @param localDateTime 원하는 발표 날짜 + */ + public void verifyReservationDate(LocalDateTime localDateTime) { + LocalDate now = LocalDate.now(); + LocalDate reservationDate = localDateTime.toLocalDate(); + + LocalDateTime startOfDay = reservationDate.atStartOfDay(); + LocalDateTime endOfDay = startOfDay.plusDays(1); + + if (isOverRangeDate(reservationDate, now) + || isAlreadyRegisteredDate(startOfDay, + endOfDay)) { + throw ExceptionStatus.INVALID_DATE.asServiceException(); + } + } + + private boolean isAlreadyRegisteredDate(LocalDateTime startOfDay, LocalDateTime endOfDay) { + List presentations = + presentationRepository.findAllByDateTimeBetween(startOfDay, endOfDay); + + return presentations.stream() + .anyMatch(presentation -> + !presentation.getPresentationStatus().equals(PresentationStatus.CANCEL)); + } + + private boolean isOverRangeDate(LocalDate reservationDate, LocalDate now) { + return reservationDate.isBefore(now) || + reservationDate.isAfter(now.plusMonths(MAXIMUM_MONTH)); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationQueryService.java b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationQueryService.java new file mode 100644 index 000000000..636fe0179 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationQueryService.java @@ -0,0 +1,52 @@ +package org.ftclub.cabinet.presentation.service; + +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; +import org.ftclub.cabinet.presentation.repository.PresentationRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PresentationQueryService { + + private static final Integer START_DAY = 1; + + private final PresentationRepository presentationRepository; + + public List getRegisteredPresentations(LocalDateTime start, LocalDateTime end) { + List presentations = + presentationRepository.findAllByDateTimeBetween(start, end); + + return presentations.stream() + .filter(presentation -> + !presentation.getPresentationStatus().equals(PresentationStatus.CANCEL)) + .collect(Collectors.toList()); + } + + public List getPresentationsBetweenWithPageRequest(LocalDateTime start, + LocalDateTime end, + PageRequest pageRequest) { + return presentationRepository.findByDateTimeBetween(start, end, pageRequest); + } + + public List getPresentationsByYearMonth(YearMonth yearMonth) { + LocalDateTime startDate = yearMonth.atDay(START_DAY).atStartOfDay(); + LocalDateTime endDayDate = yearMonth.atEndOfMonth().atTime(23, 59, 59); + + return presentationRepository.findAllByDateTimeBetweenOrderByDateTime(startDate, + endDayDate); + } + + public Page getPresentationsById(Long id, Pageable pageable) { + return presentationRepository.findPaginationById(id, pageable); + } + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java new file mode 100644 index 000000000..4c08f928f --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java @@ -0,0 +1,229 @@ +package org.ftclub.cabinet.presentation.service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.dto.InvalidDateResponseDto; +import org.ftclub.cabinet.dto.PresentationFormData; +import org.ftclub.cabinet.dto.PresentationFormRequestDto; +import org.ftclub.cabinet.dto.PresentationFormResponseDto; +import org.ftclub.cabinet.dto.PresentationMainData; +import org.ftclub.cabinet.dto.PresentationMyPageDto; +import org.ftclub.cabinet.dto.PresentationMyPagePaginationDto; +import org.ftclub.cabinet.dto.PresentationUpdateDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.mapper.PresentationMapper; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; +import org.ftclub.cabinet.presentation.repository.PresentationRepository; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.service.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class PresentationService { + + private static final Integer START_DAY = 1; + private static final Integer DEFAULT_PAGE = 0; + // 쿼리로 받? + private static final Integer MAX_MONTH = 3; + private static final String DATE_TIME = "dateTime"; + private final PresentationRepository presentationRepository; + private final PresentationPolicyService presentationPolicyService; + private final PresentationMapper presentationMapper; + private final PresentationQueryService presentationQueryService; + private final UserQueryService userQueryService; + + /** + * 수요 지식회 폼 저장 + * + * @param userId 토큰 파싱을 통해 받아온 userId + * @param dto 신청서 작성에 필요한 정보들 + */ + @Transactional + public void createPresentationFrom(Long userId, PresentationFormRequestDto dto) { + presentationPolicyService.verifyReservationDate(dto.getDateTime()); + + Presentation presentation = + Presentation.of(dto.getCategory(), dto.getDateTime(), + dto.getPresentationTime(), dto.getSubject(), dto.getSummary(), + dto.getDetail()); + User user = userQueryService.getUser(userId); + + presentation.setUser(user); + + presentationRepository.save(presentation); + } + + /** + * 예약 불가능한 날짜들을 반환합니다. + * + * @return + */ + public InvalidDateResponseDto getInvalidDate() { + LocalDate now = LocalDate.now(); + LocalDateTime start = now.atStartOfDay(); + LocalDateTime end = start.plusMonths(MAX_MONTH); + + List invalidDates = + presentationQueryService.getRegisteredPresentations(start, end) + .stream() + .map(Presentation::getDateTime) + .collect(Collectors.toList()); + + return new InvalidDateResponseDto(invalidDates); + } + + /** + * 요청 날짜 기준 과거의 신청서 중 가장 최신 Date의 정보를 개수만큼 반환 + * + * @param count + * @return + */ + public List getLatestPastPresentations(int count) { + LocalDate now = LocalDate.now(); + LocalDateTime limit = now.atStartOfDay(); + LocalDateTime start = limit.minusYears(10); + PageRequest pageRequest = PageRequest.of(DEFAULT_PAGE, count, + Sort.by(DATE_TIME).descending()); + + List presentations = + presentationQueryService.getPresentationsBetweenWithPageRequest(start, limit, + pageRequest); + + return presentations.stream() + .filter(presentation -> + presentation.getPresentationStatus().equals(PresentationStatus.DONE)) + .collect(Collectors.toList()); + } + + /** + * 요청 날짜 기준 발표 예정의 신청서들을 개수만큼 반환 + * + * @param count + * @return + */ + public List getLatestUpcomingPresentations(int count) { + LocalDate now = LocalDate.now(); + LocalDateTime start = now.atStartOfDay(); + LocalDateTime end = start.plusMonths(MAX_MONTH); + PageRequest pageRequest = PageRequest.of(DEFAULT_PAGE, count, + Sort.by(DATE_TIME).ascending()); + + List presentations = presentationQueryService. + getPresentationsBetweenWithPageRequest(start, end, pageRequest); + + return presentations.stream() + .filter(presentation -> + presentation.getPresentationStatus().equals(PresentationStatus.EXPECTED)) + .collect(Collectors.toList()); + } + + /** + * 과거, 예정 신청서의 데이터들을 Dto 형식으로 반환 + * + * @param pastFormCount 가장 가까운 과거 신청서의 개수 + * @param upcomingFormCount 가장 가까운 미래 신청서의 개수 + * @return + */ + public PresentationMainData getPastAndUpcomingPresentations( + int pastFormCount, int upcomingFormCount) { + List pastPresentations = getLatestPastPresentations(pastFormCount); + List upcomingPresentations = getLatestUpcomingPresentations( + upcomingFormCount); + + List past = pastPresentations.stream() + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); + List upcoming = upcomingPresentations.stream() + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); + + return presentationMapper.toPresentationMainData(past, upcoming); + } + + /** + * 입력받은 기한 내의 신청서들을 반환합니다. + * + * @param yearMonth yyyy-MM 타입 + * @return + */ + public PresentationFormResponseDto getUserPresentationSchedule(YearMonth yearMonth) { + + List result = + presentationQueryService.getPresentationsByYearMonth(yearMonth) + .stream() + .filter(presentation -> + !presentation.getPresentationStatus().equals(PresentationStatus.CANCEL)) + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); + + return new PresentationFormResponseDto(result); + } + + public PresentationFormResponseDto getAdminPresentationSchedule(YearMonth yearMonth) { + + List result = + presentationQueryService.getPresentationsByYearMonth(yearMonth) + .stream() + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); + + return new PresentationFormResponseDto(result); + } + + + /** + * 해당 id 를 가진 발표의 상태를 변경합니다. + *

+ * **** 추가 고려사항 -> 발표 3일전엔 확정 되어 update 불가인 정책이 있는지..?? -> 확인후 추가 해야할듯 + *

+ * + * @Pathvariable Long formId; + * @RequestBody { LocalDateTime dateTime; // new Date().toISOString() String status; // [예정, 완료, + * 취소] String location; // [3층 회의실, 지하 1층 오픈스튜디오] } + */ + @Transactional + public void updatePresentationByFormId(Long formId, PresentationUpdateDto dto) { + Presentation presentationToUpdate = + presentationRepository.findById(formId) + .orElseThrow(ExceptionStatus.INVALID_FORM_ID::asServiceException); + //날짜 변경시에만 유효성 검증 + if (!presentationToUpdate.getDateTime().isEqual(dto.getDateTime())) { + presentationPolicyService.verifyReservationDate(dto.getDateTime()); + } + + presentationToUpdate.adminUpdate(dto.getStatus(), dto.getDateTime(), dto.getLocation()); + } + + /** + * Page 타입으로 유저의 Presentation 객체들을 조회 + * + * @param userId + * @param pageable + * @return + */ + public PresentationMyPagePaginationDto getUserPresentations(Long userId, Pageable pageable) { + Page presentations = presentationQueryService.getPresentationsById(userId, + pageable); + + List result = presentations.stream() + .map(presentationMapper::toPresentationMyPageDto) + .collect(Collectors.toList()); + + return new PresentationMyPagePaginationDto(result, presentations.getTotalElements()); + } +} diff --git a/config b/config index dcf6e4379..666f17037 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit dcf6e437959da4094013321fbc15f8856159c758 +Subproject commit 666f170377b71d48c09d58f25d388e3911b4b972 diff --git a/dev/configure.conf b/dev/configure.conf index 05f7ccfde..8c5368ec6 100644 --- a/dev/configure.conf +++ b/dev/configure.conf @@ -73,7 +73,7 @@ server { proxy_read_timeout 3600s; } - location ^~ /v4 { + location ~ ^/(v4|v5) { proxy_pass http://host.docker.internal:2424; proxy_set_header Host $host; } diff --git a/frontend/.prettierrc b/frontend/.prettierrc index a73181b6b..d55a2894b 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -8,15 +8,24 @@ "arrowParens": "always", "importOrder": [ "", - "^@/recoil/(.*)$", - "^@/pages/(.*)$", - "^@/components/(.*)$", - "^@/assets/(.*)$", - "^@/types/(.*)$", - "^@/api/(.*)$", - "^@/hooks/(.*)$", - "^@/utils/(.*)$", - "^@/constants/(.*)$", + "^@/Cabinet/recoil/(.*)$", + "^@/Cabinet/pages/(.*)$", + "^@/Cabinet/components/(.*)$", + "^@/Cabinet/assets/(.*)$", + "^@/Cabinet/types/(.*)$", + "^@/Cabinet/api/(.*)$", + "^@/Cabinet/hooks/(.*)$", + "^@/Cabinet/utils/(.*)$", + "^@/Cabinet/constants/(.*)$", + "^@/Presentation/recoil/(.*)$", + "^@/Presentation/pages/(.*)$", + "^@/Presentation/components/(.*)$", + "^@/Presentation/assets/(.*)$", + "^@/Presentation/types/(.*)$", + "^@/Presentation/api/(.*)$", + "^@/Presentation/hooks/(.*)$", + "^@/Presentation/utils/(.*)$", + "^@/Presentation/constants/(.*)$", "^[./]" ], "importOrderSeparation": false, diff --git a/frontend/index.html b/frontend/index.html index 5406b186c..4975eb9aa 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,13 @@ - - - + + + =12" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -14947,6 +14958,12 @@ "whatwg-url": "^11.0.0" } }, + "date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5bf787c66..5a8a4daae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,6 +44,7 @@ "@typescript-eslint/eslint-plugin": "^5.46.1", "@typescript-eslint/parser": "^5.46.1", "@vitejs/plugin-react": "^3.0.0", + "date-fns": "^3.6.0", "eslint": "^8.30.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^8.5.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6e725cfb2..b839e00aa 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,28 +1,36 @@ +import PageTracker from "@/api/analytics/PageTracker"; import React, { Suspense, lazy } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; -import AvailablePage from "@/pages/AvailablePage"; -import ClubPage from "@/pages/ClubPage"; -import HomePage from "@/pages/HomePage"; -import Layout from "@/pages/Layout"; -import LogPage from "@/pages/LogPage"; -import LoginPage from "@/pages/LoginPage"; -import MainPage from "@/pages/MainPage"; -import PostLogin from "@/pages/PostLogin"; -import ProfilePage from "@/pages/ProfilePage"; -import AdminMainPage from "@/pages/admin/AdminMainPage"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import PageTracker from "@/api/analytics/PageTracker"; +import AvailablePage from "@/Cabinet/pages/AvailablePage"; +import ClubPage from "@/Cabinet/pages/ClubPage"; +import HomePage from "@/Cabinet/pages/HomePage"; +import Layout from "@/Cabinet/pages/Layout"; +import LogPage from "@/Cabinet/pages/LogPage"; +import LoginPage from "@/Cabinet/pages/LoginPage"; +import MainPage from "@/Cabinet/pages/MainPage"; +import PostLogin from "@/Cabinet/pages/PostLogin"; +import ProfilePage from "@/Cabinet/pages/ProfilePage"; +import AdminMainPage from "@/Cabinet/pages/admin/AdminMainPage"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import DetailPage from "@/Presentation/pages/DetailPage"; +import PresentationHomePage from "@/Presentation/pages/HomePage"; +import PresentationLayout from "@/Presentation/pages/Layout"; +import PresentationLogPage from "@/Presentation/pages/LogPage"; +import RegisterPage from "@/Presentation/pages/RegisterPage"; +import AdminPresentationLayout from "@/Presentation/pages/admin/AdminLayout"; -const NotFoundPage = lazy(() => import("@/pages/NotFoundPage")); -const LoginFailurePage = lazy(() => import("@/pages/LoginFailurePage")); -const AdminLayout = lazy(() => import("@/pages/admin/AdminLayout")); -const AdminLoginPage = lazy(() => import("@/pages/admin/AdminLoginPage")); -const SearchPage = lazy(() => import("@/pages/admin/SearchPage")); -const AdminClubPage = lazy(() => import("@/pages/admin/AdminClubPage")); +const NotFoundPage = lazy(() => import("@/Cabinet/pages/NotFoundPage")); +const LoginFailurePage = lazy(() => import("@/Cabinet/pages/LoginFailurePage")); +const AdminLayout = lazy(() => import("@/Cabinet/pages/admin/AdminLayout")); +const AdminLoginPage = lazy( + () => import("@/Cabinet/pages/admin/AdminLoginPage") +); +const SearchPage = lazy(() => import("@/Cabinet/pages/admin/SearchPage")); +const AdminClubPage = lazy(() => import("@/Cabinet/pages/admin/AdminClubPage")); const AdminLoginFailurePage = lazy( - () => import("@/pages/admin/AdminLoginFailurePage") + () => import("@/Cabinet/pages/admin/AdminLoginFailurePage") ); -const AdminHomePage = lazy(() => import("@/pages/admin/AdminHomePage")); +const AdminHomePage = lazy(() => import("@/Cabinet/pages/admin/AdminHomePage")); function App(): React.ReactElement { return ( @@ -41,6 +49,12 @@ function App(): React.ReactElement { } /> } /> + }> + } /> + } /> + } /> + } /> + {/* admin용 라우터 */} }> } /> @@ -50,6 +64,12 @@ function App(): React.ReactElement { } /> } /> + } + > + } /> + } /> => { @@ -66,7 +66,7 @@ export const axiosUpdateAlarm = async (alarm: AlarmInfo): Promise => { const axiosUpdateDeviceTokenURL = "/v4/users/me/device-token"; export const axiosUpdateDeviceToken = async ( - deviceToken: string | null + deviceToken: string | null ): Promise => { try { const response = await instance.put(axiosUpdateDeviceTokenURL, { @@ -90,16 +90,16 @@ export const axiosMyClubList = async (): Promise => { const axiosGetClubInfoURL = "/v4/clubs/"; export const axiosGetClubInfo = async ( - clubId: number, - page: number, - size: number + clubId: number, + page: number, + size: number ): Promise => { try { const response = await instance.get( - axiosGetClubInfoURL + clubId.toString(), - { - params: {page: page, size: size}, - } + axiosGetClubInfoURL + clubId.toString(), + { + params: { page: page, size: size }, + } ); return response; } catch (error) { @@ -109,16 +109,16 @@ export const axiosGetClubInfo = async ( const axiosAddClubMemberURL = "/v4/clubs"; export const axiosAddClubMember = async ( - clubId: number, - name: String + clubId: number, + name: String ): Promise => { // TODO : 예외처리? try { const response = await instance.post( - `${axiosAddClubMemberURL}/${clubId}/users`, - { - name, - } + `${axiosAddClubMemberURL}/${clubId}/users`, + { + name, + } ); return response; } catch (error) { @@ -128,14 +128,14 @@ export const axiosAddClubMember = async ( const axiosMandateClubMemURL = "/v4/clubs"; export const axiosMandateClubMember = async ( - clubId: number, - clubMaster: string + clubId: number, + clubMaster: string ): Promise => { // TODO : 예외처리? try { const response = await instance.post( - `${axiosMandateClubMemURL}/${clubId}/mandate`, - {clubMaster} + `${axiosMandateClubMemURL}/${clubId}/mandate`, + { clubMaster } ); return response; } catch (error) { @@ -145,12 +145,12 @@ export const axiosMandateClubMember = async ( const axiosDeleteClubMemberURL = "/v4/clubs/"; export const axiosDeleteClubMember = async ( - clubId: number, - userId: number + clubId: number, + userId: number ): Promise => { try { const response = await instance.delete( - `${axiosDeleteClubMemberURL}${clubId}/users/${userId}` + `${axiosDeleteClubMemberURL}${clubId}/users/${userId}` ); return response; } catch (error) { @@ -160,15 +160,15 @@ export const axiosDeleteClubMember = async ( const axiosUpdateClubMemoURL = "/v4/clubs/"; export const axiosUpdateClubMemo = async ( - clubId: number, - clubMemo: string + clubId: number, + clubMemo: string ): Promise => { try { const response = await instance.post( - `${axiosUpdateClubMemoURL}${clubId}/memo`, - { - memo: clubMemo, - } + `${axiosUpdateClubMemoURL}${clubId}/memo`, + { + memo: clubMemo, + } ); return response; } catch (error) { @@ -178,15 +178,15 @@ export const axiosUpdateClubMemo = async ( const axiosUpdateClubNoticeURL = "/v4/clubs/"; export const axiosUpdateClubNotice = async ( - clubId: number, - notice: string + clubId: number, + notice: string ): Promise => { try { const response = await instance.post( - `${axiosUpdateClubNoticeURL}${clubId}/notice`, - { - notice, - } + `${axiosUpdateClubNoticeURL}${clubId}/notice`, + { + notice, + } ); return response; } catch (error) { @@ -207,12 +207,12 @@ export const axiosBuildingFloor = async (): Promise => { const axiosCabinetByBuildingFloorURL = "/v4/cabinets/buildings/"; export const axiosCabinetByBuildingFloor = async ( - Building: string, - floor: number + Building: string, + floor: number ): Promise => { try { const response = await instance.get( - `${axiosCabinetByBuildingFloorURL}${Building}/floors/${floor}` + `${axiosCabinetByBuildingFloorURL}${Building}/floors/${floor}` ); return response; } catch (error) { @@ -222,7 +222,7 @@ export const axiosCabinetByBuildingFloor = async ( const axiosCabinetByIdURL = "/v4/cabinets/"; export const axiosCabinetById = async ( - cabinetId: number | null + cabinetId: number | null ): Promise => { if (cabinetId === null) return; try { @@ -267,8 +267,8 @@ export const axiosSwapId = async (cabinetId: number | null): Promise => { const axiosUpdateMyCabinetInfoURL = "/v4/lent/me/cabinet"; export const axiosUpdateMyCabinetInfo = async ( - title: string | null, - memo: string | null + title: string | null, + memo: string | null ): Promise => { try { const response = await instance.patch(axiosUpdateMyCabinetInfoURL, { @@ -306,7 +306,7 @@ const axiosMyLentLogURL = "/v4/lent/me/histories"; export const axiosMyLentLog = async (page: number): Promise => { try { const response = await instance.get(axiosMyLentLogURL, { - params: {page: page, size: 10}, + params: { page: page, size: 10 }, }); return response; } catch (error) { @@ -327,8 +327,8 @@ export const axiosExtendLentPeriod = async (): Promise => { // Admin API const axiosAdminAuthLoginURL = "/v4/admin/auth/login"; export const axiosAdminAuthLogin = async ( - id: string, - password: string + id: string, + password: string ): Promise => { try { const response = await instance.post(axiosAdminAuthLoginURL, { @@ -343,12 +343,12 @@ export const axiosAdminAuthLogin = async ( const axiosAdminCabinetInfoByIdURL = "/v4/cabinets/"; export const axiosAdminCabinetInfoByCabinetId = async ( - cabinetId: number | null + cabinetId: number | null ): Promise => { if (cabinetId === null) return; try { const response = await instance.get( - axiosAdminCabinetInfoByIdURL + cabinetId + axiosAdminCabinetInfoByIdURL + cabinetId ); return response; } catch (error) { @@ -370,7 +370,7 @@ export const axiosReturnByUserId = async (userIds: number[]): Promise => { const axiosBundleReturnURL = "/v4/admin/return-cabinets"; export const axiosBundleReturn = async ( - cabinetIdList: number[] + cabinetIdList: number[] ): Promise => { try { const response = await instance.patch(axiosBundleReturnURL, { @@ -384,9 +384,9 @@ export const axiosBundleReturn = async ( const axiosUpdateCabinetsURL = "/v4/admin/cabinets/"; export const axiosUpdateCabinets = async ( - cabinetIds: number[], - lentType: CabinetType | null, - status: CabinetStatus | null + cabinetIds: number[], + lentType: CabinetType | null, + status: CabinetStatus | null ): Promise => { try { const response = await instance.patch(axiosUpdateCabinetsURL, { @@ -404,7 +404,7 @@ const axiosSearchByIntraIdURL = "/v4/admin/search/users-simple"; export const axiosSearchByIntraId = async (intraId: string) => { try { const response = await instance.get(axiosSearchByIntraIdURL, { - params: {name: intraId, page: 0, size: 10}, + params: { name: intraId, page: 0, size: 10 }, }); return response; } catch (error) { @@ -416,7 +416,7 @@ const axiosSearchByCabinetNumURL = "/v4/admin/search/cabinets"; export const axiosSearchByCabinetNum = async (number: number) => { try { const response = await instance.get(axiosSearchByCabinetNumURL, { - params: {visibleNum: number}, + params: { visibleNum: number }, }); return response; } catch (error) { @@ -428,7 +428,7 @@ const axiosSearchByCabinetNumSimpleURL = "/v4/admin/search/cabinets-simple"; export const axiosSearchByCabinetNumSimple = async (number: number) => { try { const response = await instance.get(axiosSearchByCabinetNumSimpleURL, { - params: {visibleNum: number}, + params: { visibleNum: number }, }); return response; } catch (error) { @@ -438,12 +438,12 @@ export const axiosSearchByCabinetNumSimple = async (number: number) => { const axiosSearchDetailByIntraIdURL = "/v4/admin/search/users"; export const axiosSearchDetailByIntraId = async ( - intraId: string, - page: number + intraId: string, + page: number ) => { try { const response = await instance.get(axiosSearchDetailByIntraIdURL, { - params: {name: intraId, page: page, size: 10}, + params: { name: intraId, page: page, size: 10 }, }); return response; } catch (error) { @@ -456,7 +456,7 @@ export const axiosDeleteCurrentBanLog = async (userId: number | null) => { if (userId === null) return; try { const response = await instance.delete( - axiosDeleteCurrentBanLogURL + userId?.toString() + "/ban-history" + axiosDeleteCurrentBanLogURL + userId?.toString() + "/ban-history" ); return response; } catch (error) { @@ -468,7 +468,7 @@ const axiosGetBrokenCabinetListURL = "/v4/admin/cabinets/status/BROKEN"; export const axiosGetBrokenCabinetList = async () => { try { const response = await instance.get(axiosGetBrokenCabinetListURL, { - params: {page: 0, size: 0}, + params: { page: 0, size: 0 }, }); return response.data.result; } catch (error) { @@ -480,7 +480,7 @@ const axiosGetBannedUserListURL = "v4/admin/statistics/users/banned"; export const axiosGetBannedUserList = async () => { try { const response = await instance.get(axiosGetBannedUserListURL, { - params: {page: 0, size: 0}, + params: { page: 0, size: 0 }, }); return response.data.result; } catch (error) { @@ -492,7 +492,7 @@ const axiosGetStatisticsURL = "/v4/admin/statistics/lent-histories"; export const axiosGetStatistics = async (start: Date, end: Date) => { try { const response = await instance.get(axiosGetStatisticsURL, { - params: {startDate: start, endDate: end}, + params: { startDate: start, endDate: end }, }); return response.data; } catch (error) { @@ -501,7 +501,7 @@ export const axiosGetStatistics = async (start: Date, end: Date) => { }; const axiosGetCabinetNumbersPerFloorURL = - "/v4/admin/statistics/buildings/floors/cabinets"; + "/v4/admin/statistics/buildings/floors/cabinets"; export const axiosGetCabinetNumbersPerFloor = async () => { try { const response = await instance.get(axiosGetCabinetNumbersPerFloorURL); @@ -515,7 +515,7 @@ const axiosGetOverdueUserListURL = "/v4/admin/statistics/users/overdue"; export const axiosGetOverdueUserList = async () => { try { const response = await instance.get(axiosGetOverdueUserListURL, { - params: {page: 0, size: 0}, + params: { page: 0, size: 0 }, }); return response.data.result; } catch (error) { @@ -525,16 +525,16 @@ export const axiosGetOverdueUserList = async () => { const axiosGetCabinetLentLogURL = "/v4/admin/cabinets/"; export const axiosGetCabinetLentLog = async ( - cabinetId: number | null, - page: number + cabinetId: number | null, + page: number ): Promise => { if (cabinetId == null) return; try { const response = await instance.get( - axiosGetCabinetLentLogURL + cabinetId.toString() + "/lent-histories", - { - params: {page: page, size: 8}, - } + axiosGetCabinetLentLogURL + cabinetId.toString() + "/lent-histories", + { + params: { page: page, size: 8 }, + } ); return response; } catch (error) { @@ -544,16 +544,16 @@ export const axiosGetCabinetLentLog = async ( const axiosGetUserLentLogURL = "/v4/admin/users/"; export const axiosGetUserLentLog = async ( - userId: number | null, - page: number + userId: number | null, + page: number ): Promise => { if (userId == null) return; try { const response = await instance.get( - axiosGetUserLentLogURL + userId.toString() + "/lent-histories", - { - params: {page: page, size: 8}, - } + axiosGetUserLentLogURL + userId.toString() + "/lent-histories", + { + params: { page: page, size: 8 }, + } ); return response; } catch (error) { @@ -563,12 +563,12 @@ export const axiosGetUserLentLog = async ( const axiosGetClubUserLogURL = "/v4/admin/clubs"; export const axiosGetClubUserLog = async ( - page: number, - size: number + page: number, + size: number ): Promise => { try { const response = await instance.get(axiosGetClubUserLogURL, { - params: {page: page, size: size}, + params: { page: page, size: size }, }); return response; } catch (error) { @@ -578,8 +578,8 @@ export const axiosGetClubUserLog = async ( const axiosCreateClubUserURL = "/v4/admin/clubs"; export const axiosCreateClubUser = async ( - clubName: string | null, - clubMaster: string | null + clubName: string | null, + clubMaster: string | null ): Promise => { if (clubName === null || clubMaster === null) return; try { @@ -595,15 +595,15 @@ export const axiosCreateClubUser = async ( const axiosEditClubUserURL = "/v4/admin/clubs/"; export const axiosEditClubUser = async ( - clubInfo: ClubUserDto + clubInfo: ClubUserDto ): Promise => { try { const response = await instance.patch( - axiosEditClubUserURL + clubInfo.clubId.toString(), - { - clubName: clubInfo.clubName, - clubMaster: clubInfo.clubMaster, - } + axiosEditClubUserURL + clubInfo.clubId.toString(), + { + clubName: clubInfo.clubName, + clubMaster: clubInfo.clubMaster, + } ); return response; } catch (error) { @@ -613,12 +613,12 @@ export const axiosEditClubUser = async ( const axiosDeleteClubUserURL = "/v4/admin/clubs/"; export const axiosDeleteClubUser = async ( - clubId: number | null + clubId: number | null ): Promise => { if (clubId === null) return; try { const response = await instance.delete( - axiosDeleteClubUserURL + clubId?.toString() + axiosDeleteClubUserURL + clubId?.toString() ); return response; } catch (error) { @@ -649,12 +649,12 @@ export const axiosDeleteClubUser = async ( const axiosLentClubCabinetURL = "/v4/admin/clubs/"; export const axiosLentClubCabinet = async ( - clubId: number, - cabinetId: number + clubId: number, + cabinetId: number ): Promise => { try { const response = await instance.post( - axiosLentClubCabinetURL + + axiosLentClubCabinetURL + clubId.toString() + "/cabinets/" + cabinetId.toString() @@ -667,8 +667,8 @@ export const axiosLentClubCabinet = async ( const axiosLentShareIdURL = "/v4/lent/cabinets/share/"; export const axiosLentShareId = async ( - cabinetId: number | null, - shareCode: string + cabinetId: number | null, + shareCode: string ): Promise => { if (cabinetId === null) return; try { diff --git a/frontend/src/api/axios/axios.instance.ts b/frontend/src/Cabinet/api/axios/axios.instance.ts similarity index 88% rename from frontend/src/api/axios/axios.instance.ts rename to frontend/src/Cabinet/api/axios/axios.instance.ts index a06986608..4017a2bbd 100644 --- a/frontend/src/api/axios/axios.instance.ts +++ b/frontend/src/Cabinet/api/axios/axios.instance.ts @@ -1,6 +1,6 @@ import axios from "axios"; -import { getCookie, removeCookie } from "@/api/react_cookie/cookies"; -import { STATUS_401_UNAUTHORIZED } from "@/constants/StatusCode"; +import { getCookie, removeCookie } from "@/Cabinet/api/react_cookie/cookies"; +import { STATUS_401_UNAUTHORIZED } from "@/Cabinet/constants/StatusCode"; axios.defaults.withCredentials = true; diff --git a/frontend/src/api/react_cookie/cookies.ts b/frontend/src/Cabinet/api/react_cookie/cookies.ts similarity index 100% rename from frontend/src/api/react_cookie/cookies.ts rename to frontend/src/Cabinet/api/react_cookie/cookies.ts diff --git a/frontend/src/assets/css/homePage.css b/frontend/src/Cabinet/assets/css/homePage.css similarity index 100% rename from frontend/src/assets/css/homePage.css rename to frontend/src/Cabinet/assets/css/homePage.css diff --git a/frontend/src/assets/css/loginPage.css b/frontend/src/Cabinet/assets/css/loginPage.css similarity index 100% rename from frontend/src/assets/css/loginPage.css rename to frontend/src/Cabinet/assets/css/loginPage.css diff --git a/frontend/src/assets/css/media.css b/frontend/src/Cabinet/assets/css/media.css similarity index 92% rename from frontend/src/assets/css/media.css rename to frontend/src/Cabinet/assets/css/media.css index 612f68ee4..b4195d39b 100644 --- a/frontend/src/assets/css/media.css +++ b/frontend/src/Cabinet/assets/css/media.css @@ -2,10 +2,10 @@ /* left navigation */ #leftNavWrap { position: fixed; - top: 80px; + top: 120px; left: 0; background-color: var(--white); - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(-100%); transition: transform 0.3s ease-in-out; @@ -33,9 +33,9 @@ /* cabinetDetailArea(rightSection) */ #cabinetDetailArea { position: fixed; - top: 80px; + top: 120px; right: 0; - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; @@ -54,9 +54,9 @@ /* ClubMemberInfoArea(rightSection) */ /* #clubMemberInfoArea { position: fixed; - top: 80px; + top: 120px; right: 0; - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; diff --git a/frontend/src/assets/css/reset.css b/frontend/src/Cabinet/assets/css/reset.css similarity index 100% rename from frontend/src/assets/css/reset.css rename to frontend/src/Cabinet/assets/css/reset.css diff --git a/frontend/src/assets/data/ManualContent.ts b/frontend/src/Cabinet/assets/data/ManualContent.ts similarity index 94% rename from frontend/src/assets/data/ManualContent.ts rename to frontend/src/Cabinet/assets/data/ManualContent.ts index 11d7623ec..070a182b3 100644 --- a/frontend/src/assets/data/ManualContent.ts +++ b/frontend/src/Cabinet/assets/data/ManualContent.ts @@ -1,4 +1,4 @@ -import ContentStatus from "@/types/enum/content.status.enum"; +import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface ContentStatusData { contentTitle: string; @@ -13,7 +13,7 @@ interface ContentStatusData { export const manualContentData: Record = { [ContentStatus.PRIVATE]: { contentTitle: "개인 사물함", - imagePath: "/src/assets/images/privateIcon.svg", + imagePath: "/src/Cabinet/assets/images/privateIcon.svg", background: "linear-gradient(to bottom, #A17BF3, #8337E5)", rentalPeriod: `${import.meta.env.VITE_PRIVATE_LENT_PERIOD}일`, capacity: "1인", @@ -34,7 +34,7 @@ export const manualContentData: Record = { }, [ContentStatus.SHARE]: { contentTitle: "공유 사물함", - imagePath: "/src/assets/images/shareIcon.svg", + imagePath: "/src/Cabinet/assets/images/shareIcon.svg", background: "linear-gradient(to bottom, #7EBFFB, #406EE4)", rentalPeriod: `${import.meta.env.VITE_SHARE_LENT_PERIOD}일 + n * ${ import.meta.env.VITE_SHARE_BONUS_PER_PERSON @@ -63,7 +63,7 @@ export const manualContentData: Record = { }, [ContentStatus.CLUB]: { contentTitle: "동아리 사물함", - imagePath: "/src/assets/images/clubIcon.svg", + imagePath: "/src/Cabinet/assets/images/clubIcon.svg", background: "linear-gradient(to bottom, #F473B1, #D72766)", rentalPeriod: "상세내용 참조", capacity: "동아리", @@ -106,7 +106,7 @@ export const manualContentData: Record = { }, [ContentStatus.IN_SESSION]: { contentTitle: "대기중", - imagePath: "/src/assets/images/clock.svg", + imagePath: "/src/Cabinet/assets/images/clock.svg", background: "#F5F5F7", contentText: `◦ 상세 내용

공유 사물함 대여 시 10분간의 대기 시간이 발생합니다.
@@ -126,7 +126,7 @@ export const manualContentData: Record = { }, [ContentStatus.EXTENSION]: { contentTitle: "연장권 이용방법 안내서", - imagePath: "/src/assets/images/extension.svg", + imagePath: "/src/Cabinet/assets/images/extension.svg", background: "#F5F5F7", contentText: `◦ 연장권 취득 조건
diff --git a/frontend/src/assets/data/mapPositionData.ts b/frontend/src/Cabinet/assets/data/mapPositionData.ts similarity index 98% rename from frontend/src/assets/data/mapPositionData.ts rename to frontend/src/Cabinet/assets/data/mapPositionData.ts index df708d49e..72287ecd8 100644 --- a/frontend/src/assets/data/mapPositionData.ts +++ b/frontend/src/Cabinet/assets/data/mapPositionData.ts @@ -1,4 +1,4 @@ -import SectionType from "@/types/enum/map.type.enum"; +import SectionType from "@/Cabinet/types/enum/map.type.enum"; /** * @interface diff --git a/frontend/src/assets/data/maps.ts b/frontend/src/Cabinet/assets/data/maps.ts similarity index 94% rename from frontend/src/assets/data/maps.ts rename to frontend/src/Cabinet/assets/data/maps.ts index 450756f56..5780c2528 100644 --- a/frontend/src/assets/data/maps.ts +++ b/frontend/src/Cabinet/assets/data/maps.ts @@ -1,5 +1,5 @@ -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; export enum additionalModalType { MODAL_RETURN = "MODAL_RETURN", @@ -18,9 +18,9 @@ export enum additionalModalType { } export const cabinetIconSrcMap = { - [CabinetType.PRIVATE]: "/src/assets/images/privateIcon.svg", - [CabinetType.SHARE]: "/src/assets/images/shareIcon.svg", - [CabinetType.CLUB]: "/src/assets/images/clubIcon.svg", + [CabinetType.PRIVATE]: "/src/Cabinet/assets/images/privateIcon.svg", + [CabinetType.SHARE]: "/src/Cabinet/assets/images/shareIcon.svg", + [CabinetType.CLUB]: "/src/Cabinet/assets/images/clubIcon.svg", }; export const cabinetLabelColorMap = { diff --git a/frontend/src/assets/data/sectionColNumData.ts b/frontend/src/Cabinet/assets/data/sectionColNumData.ts similarity index 100% rename from frontend/src/assets/data/sectionColNumData.ts rename to frontend/src/Cabinet/assets/data/sectionColNumData.ts diff --git a/frontend/src/assets/images/LeftSectionButton.svg b/frontend/src/Cabinet/assets/images/LeftSectionButton.svg similarity index 100% rename from frontend/src/assets/images/LeftSectionButton.svg rename to frontend/src/Cabinet/assets/images/LeftSectionButton.svg diff --git a/frontend/src/Cabinet/assets/images/PresentationAcademic.svg b/frontend/src/Cabinet/assets/images/PresentationAcademic.svg new file mode 100644 index 000000000..019970b4a --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationAcademic.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationDevelop.svg b/frontend/src/Cabinet/assets/images/PresentationDevelop.svg new file mode 100644 index 000000000..501dce15d --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationDevelop.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationEmpty.svg b/frontend/src/Cabinet/assets/images/PresentationEmpty.svg new file mode 100644 index 000000000..c89f6e1d1 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationEmpty.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationEtc.svg b/frontend/src/Cabinet/assets/images/PresentationEtc.svg new file mode 100644 index 000000000..46830a335 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationEtc.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationFortyTwo.svg b/frontend/src/Cabinet/assets/images/PresentationFortyTwo.svg new file mode 100644 index 000000000..f1d5205e7 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationFortyTwo.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationHobby.svg b/frontend/src/Cabinet/assets/images/PresentationHobby.svg new file mode 100644 index 000000000..723c4039f --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationHobby.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationJob.svg b/frontend/src/Cabinet/assets/images/PresentationJob.svg new file mode 100644 index 000000000..8105bb49e --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationJob.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/adminLoginImg.svg b/frontend/src/Cabinet/assets/images/adminLoginImg.svg similarity index 100% rename from frontend/src/assets/images/adminLoginImg.svg rename to frontend/src/Cabinet/assets/images/adminLoginImg.svg diff --git a/frontend/src/assets/images/alarm.svg b/frontend/src/Cabinet/assets/images/alarm.svg similarity index 100% rename from frontend/src/assets/images/alarm.svg rename to frontend/src/Cabinet/assets/images/alarm.svg diff --git a/frontend/src/assets/images/cabinet.svg b/frontend/src/Cabinet/assets/images/cabinet.svg similarity index 100% rename from frontend/src/assets/images/cabinet.svg rename to frontend/src/Cabinet/assets/images/cabinet.svg diff --git a/frontend/src/Cabinet/assets/images/calendar.svg b/frontend/src/Cabinet/assets/images/calendar.svg new file mode 100644 index 000000000..0e94e5a5b --- /dev/null +++ b/frontend/src/Cabinet/assets/images/calendar.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/src/assets/images/cautionSign.svg b/frontend/src/Cabinet/assets/images/cautionSign.svg similarity index 100% rename from frontend/src/assets/images/cautionSign.svg rename to frontend/src/Cabinet/assets/images/cautionSign.svg diff --git a/frontend/src/assets/images/checkIcon.svg b/frontend/src/Cabinet/assets/images/checkIcon.svg similarity index 100% rename from frontend/src/assets/images/checkIcon.svg rename to frontend/src/Cabinet/assets/images/checkIcon.svg diff --git a/frontend/src/assets/images/clock.svg b/frontend/src/Cabinet/assets/images/clock.svg similarity index 100% rename from frontend/src/assets/images/clock.svg rename to frontend/src/Cabinet/assets/images/clock.svg diff --git a/frontend/src/assets/images/close-circle.svg b/frontend/src/Cabinet/assets/images/close-circle.svg similarity index 100% rename from frontend/src/assets/images/close-circle.svg rename to frontend/src/Cabinet/assets/images/close-circle.svg diff --git a/frontend/src/assets/images/close-square.svg b/frontend/src/Cabinet/assets/images/close-square.svg similarity index 100% rename from frontend/src/assets/images/close-square.svg rename to frontend/src/Cabinet/assets/images/close-square.svg diff --git a/frontend/src/assets/images/clubIcon.svg b/frontend/src/Cabinet/assets/images/clubIcon.svg similarity index 100% rename from frontend/src/assets/images/clubIcon.svg rename to frontend/src/Cabinet/assets/images/clubIcon.svg diff --git a/frontend/src/assets/images/clubIconGray.svg b/frontend/src/Cabinet/assets/images/clubIconGray.svg similarity index 100% rename from frontend/src/assets/images/clubIconGray.svg rename to frontend/src/Cabinet/assets/images/clubIconGray.svg diff --git a/frontend/src/assets/images/crown.svg b/frontend/src/Cabinet/assets/images/crown.svg similarity index 100% rename from frontend/src/assets/images/crown.svg rename to frontend/src/Cabinet/assets/images/crown.svg diff --git a/frontend/src/assets/images/desktopLogo.png b/frontend/src/Cabinet/assets/images/desktopLogo.png similarity index 100% rename from frontend/src/assets/images/desktopLogo.png rename to frontend/src/Cabinet/assets/images/desktopLogo.png diff --git a/frontend/src/Cabinet/assets/images/dropdownChevron.svg b/frontend/src/Cabinet/assets/images/dropdownChevron.svg new file mode 100644 index 000000000..2917ac0ff --- /dev/null +++ b/frontend/src/Cabinet/assets/images/dropdownChevron.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/edit.svg b/frontend/src/Cabinet/assets/images/edit.svg similarity index 100% rename from frontend/src/assets/images/edit.svg rename to frontend/src/Cabinet/assets/images/edit.svg diff --git a/frontend/src/assets/images/errorIcon.svg b/frontend/src/Cabinet/assets/images/errorIcon.svg similarity index 100% rename from frontend/src/assets/images/errorIcon.svg rename to frontend/src/Cabinet/assets/images/errorIcon.svg diff --git a/frontend/src/assets/images/exitButton.svg b/frontend/src/Cabinet/assets/images/exitButton.svg similarity index 100% rename from frontend/src/assets/images/exitButton.svg rename to frontend/src/Cabinet/assets/images/exitButton.svg diff --git a/frontend/src/assets/images/extension.svg b/frontend/src/Cabinet/assets/images/extension.svg similarity index 100% rename from frontend/src/assets/images/extension.svg rename to frontend/src/Cabinet/assets/images/extension.svg diff --git a/frontend/src/assets/images/extensionTicket.svg b/frontend/src/Cabinet/assets/images/extensionTicket.svg similarity index 100% rename from frontend/src/assets/images/extensionTicket.svg rename to frontend/src/Cabinet/assets/images/extensionTicket.svg diff --git a/frontend/src/assets/images/happyCcabi.png b/frontend/src/Cabinet/assets/images/happyCcabi.png similarity index 100% rename from frontend/src/assets/images/happyCcabi.png rename to frontend/src/Cabinet/assets/images/happyCcabi.png diff --git a/frontend/src/Cabinet/assets/images/happyCcabi.svg b/frontend/src/Cabinet/assets/images/happyCcabi.svg new file mode 100644 index 000000000..60568953e --- /dev/null +++ b/frontend/src/Cabinet/assets/images/happyCcabi.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/happyCcabiWhite.png b/frontend/src/Cabinet/assets/images/happyCcabiWhite.png similarity index 100% rename from frontend/src/assets/images/happyCcabiWhite.png rename to frontend/src/Cabinet/assets/images/happyCcabiWhite.png diff --git a/frontend/src/assets/images/leader.svg b/frontend/src/Cabinet/assets/images/leader.svg similarity index 100% rename from frontend/src/assets/images/leader.svg rename to frontend/src/Cabinet/assets/images/leader.svg diff --git a/frontend/src/assets/images/link.svg b/frontend/src/Cabinet/assets/images/link.svg similarity index 100% rename from frontend/src/assets/images/link.svg rename to frontend/src/Cabinet/assets/images/link.svg diff --git a/frontend/src/assets/images/lock.svg b/frontend/src/Cabinet/assets/images/lock.svg similarity index 100% rename from frontend/src/assets/images/lock.svg rename to frontend/src/Cabinet/assets/images/lock.svg diff --git a/frontend/src/assets/images/log.svg b/frontend/src/Cabinet/assets/images/log.svg similarity index 100% rename from frontend/src/assets/images/log.svg rename to frontend/src/Cabinet/assets/images/log.svg diff --git a/frontend/src/assets/images/loginImg.svg b/frontend/src/Cabinet/assets/images/loginImg.svg similarity index 100% rename from frontend/src/assets/images/loginImg.svg rename to frontend/src/Cabinet/assets/images/loginImg.svg diff --git a/frontend/src/assets/images/logo.ico b/frontend/src/Cabinet/assets/images/logo.ico similarity index 100% rename from frontend/src/assets/images/logo.ico rename to frontend/src/Cabinet/assets/images/logo.ico diff --git a/frontend/src/assets/images/logo.png b/frontend/src/Cabinet/assets/images/logo.png similarity index 100% rename from frontend/src/assets/images/logo.png rename to frontend/src/Cabinet/assets/images/logo.png diff --git a/frontend/src/assets/images/logo.svg b/frontend/src/Cabinet/assets/images/logo.svg similarity index 100% rename from frontend/src/assets/images/logo.svg rename to frontend/src/Cabinet/assets/images/logo.svg diff --git a/frontend/src/assets/images/logoBlack.svg b/frontend/src/Cabinet/assets/images/logoBlack.svg similarity index 100% rename from frontend/src/assets/images/logoBlack.svg rename to frontend/src/Cabinet/assets/images/logoBlack.svg diff --git a/frontend/src/assets/images/manualPeople.svg b/frontend/src/Cabinet/assets/images/manualPeople.svg similarity index 100% rename from frontend/src/assets/images/manualPeople.svg rename to frontend/src/Cabinet/assets/images/manualPeople.svg diff --git a/frontend/src/assets/images/map.svg b/frontend/src/Cabinet/assets/images/map.svg similarity index 100% rename from frontend/src/assets/images/map.svg rename to frontend/src/Cabinet/assets/images/map.svg diff --git a/frontend/src/assets/images/more.svg b/frontend/src/Cabinet/assets/images/more.svg similarity index 100% rename from frontend/src/assets/images/more.svg rename to frontend/src/Cabinet/assets/images/more.svg diff --git a/frontend/src/assets/images/moveButton.svg b/frontend/src/Cabinet/assets/images/moveButton.svg similarity index 100% rename from frontend/src/assets/images/moveButton.svg rename to frontend/src/Cabinet/assets/images/moveButton.svg diff --git a/frontend/src/assets/images/myCabinetIcon.svg b/frontend/src/Cabinet/assets/images/myCabinetIcon.svg similarity index 100% rename from frontend/src/assets/images/myCabinetIcon.svg rename to frontend/src/Cabinet/assets/images/myCabinetIcon.svg diff --git a/frontend/src/assets/images/notificationSign.svg b/frontend/src/Cabinet/assets/images/notificationSign.svg similarity index 100% rename from frontend/src/assets/images/notificationSign.svg rename to frontend/src/Cabinet/assets/images/notificationSign.svg diff --git a/frontend/src/assets/images/notificationSign_grey.svg b/frontend/src/Cabinet/assets/images/notificationSign_grey.svg similarity index 100% rename from frontend/src/assets/images/notificationSign_grey.svg rename to frontend/src/Cabinet/assets/images/notificationSign_grey.svg diff --git a/frontend/src/assets/images/privateIcon.svg b/frontend/src/Cabinet/assets/images/privateIcon.svg similarity index 100% rename from frontend/src/assets/images/privateIcon.svg rename to frontend/src/Cabinet/assets/images/privateIcon.svg diff --git a/frontend/src/assets/images/proceedMultiSelect.svg b/frontend/src/Cabinet/assets/images/proceedMultiSelect.svg similarity index 100% rename from frontend/src/assets/images/proceedMultiSelect.svg rename to frontend/src/Cabinet/assets/images/proceedMultiSelect.svg diff --git a/frontend/src/assets/images/profile-circle.svg b/frontend/src/Cabinet/assets/images/profile-circle.svg similarity index 100% rename from frontend/src/assets/images/profile-circle.svg rename to frontend/src/Cabinet/assets/images/profile-circle.svg diff --git a/frontend/src/assets/images/refresh.svg b/frontend/src/Cabinet/assets/images/refresh.svg similarity index 100% rename from frontend/src/assets/images/refresh.svg rename to frontend/src/Cabinet/assets/images/refresh.svg diff --git a/frontend/src/assets/images/rotateRight.svg b/frontend/src/Cabinet/assets/images/rotateRight.svg similarity index 100% rename from frontend/src/assets/images/rotateRight.svg rename to frontend/src/Cabinet/assets/images/rotateRight.svg diff --git a/frontend/src/assets/images/sadCcabi.png b/frontend/src/Cabinet/assets/images/sadCcabi.png similarity index 100% rename from frontend/src/assets/images/sadCcabi.png rename to frontend/src/Cabinet/assets/images/sadCcabi.png diff --git a/frontend/src/Cabinet/assets/images/sadCcabi.svg b/frontend/src/Cabinet/assets/images/sadCcabi.svg new file mode 100644 index 000000000..b82e3eda9 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/sadCcabi.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/src/assets/images/sadCcabiWhite.png b/frontend/src/Cabinet/assets/images/sadCcabiWhite.png similarity index 100% rename from frontend/src/assets/images/sadCcabiWhite.png rename to frontend/src/Cabinet/assets/images/sadCcabiWhite.png diff --git a/frontend/src/assets/images/search.svg b/frontend/src/Cabinet/assets/images/search.svg similarity index 100% rename from frontend/src/assets/images/search.svg rename to frontend/src/Cabinet/assets/images/search.svg diff --git a/frontend/src/assets/images/searchWhite.svg b/frontend/src/Cabinet/assets/images/searchWhite.svg similarity index 100% rename from frontend/src/assets/images/searchWhite.svg rename to frontend/src/Cabinet/assets/images/searchWhite.svg diff --git a/frontend/src/assets/images/select.svg b/frontend/src/Cabinet/assets/images/select.svg similarity index 100% rename from frontend/src/assets/images/select.svg rename to frontend/src/Cabinet/assets/images/select.svg diff --git a/frontend/src/assets/images/selectFilterIconOff.svg b/frontend/src/Cabinet/assets/images/selectFilterIconOff.svg similarity index 100% rename from frontend/src/assets/images/selectFilterIconOff.svg rename to frontend/src/Cabinet/assets/images/selectFilterIconOff.svg diff --git a/frontend/src/assets/images/selectFilterIconOn.svg b/frontend/src/Cabinet/assets/images/selectFilterIconOn.svg similarity index 100% rename from frontend/src/assets/images/selectFilterIconOn.svg rename to frontend/src/Cabinet/assets/images/selectFilterIconOn.svg diff --git a/frontend/src/assets/images/selectMaincolor.svg b/frontend/src/Cabinet/assets/images/selectMaincolor.svg similarity index 100% rename from frontend/src/assets/images/selectMaincolor.svg rename to frontend/src/Cabinet/assets/images/selectMaincolor.svg diff --git a/frontend/src/assets/images/selectPurple.svg b/frontend/src/Cabinet/assets/images/selectPurple.svg similarity index 100% rename from frontend/src/assets/images/selectPurple.svg rename to frontend/src/Cabinet/assets/images/selectPurple.svg diff --git a/frontend/src/assets/images/shareIcon.svg b/frontend/src/Cabinet/assets/images/shareIcon.svg similarity index 100% rename from frontend/src/assets/images/shareIcon.svg rename to frontend/src/Cabinet/assets/images/shareIcon.svg diff --git a/frontend/src/assets/images/slack.svg b/frontend/src/Cabinet/assets/images/slack.svg similarity index 100% rename from frontend/src/assets/images/slack.svg rename to frontend/src/Cabinet/assets/images/slack.svg diff --git a/frontend/src/assets/images/stairs.svg b/frontend/src/Cabinet/assets/images/stairs.svg similarity index 100% rename from frontend/src/assets/images/stairs.svg rename to frontend/src/Cabinet/assets/images/stairs.svg diff --git a/frontend/src/assets/images/subtract.svg b/frontend/src/Cabinet/assets/images/subtract.svg similarity index 100% rename from frontend/src/assets/images/subtract.svg rename to frontend/src/Cabinet/assets/images/subtract.svg diff --git a/frontend/src/Cabinet/assets/images/timer.svg b/frontend/src/Cabinet/assets/images/timer.svg new file mode 100644 index 000000000..20b760c7e --- /dev/null +++ b/frontend/src/Cabinet/assets/images/timer.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/warningTriangleIcon.svg b/frontend/src/Cabinet/assets/images/warningTriangleIcon.svg similarity index 100% rename from frontend/src/assets/images/warningTriangleIcon.svg rename to frontend/src/Cabinet/assets/images/warningTriangleIcon.svg diff --git a/frontend/src/components/AdminInfo/Chart/BarChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/BarChart.tsx similarity index 100% rename from frontend/src/components/AdminInfo/Chart/BarChart.tsx rename to frontend/src/Cabinet/components/AdminInfo/Chart/BarChart.tsx diff --git a/frontend/src/components/AdminInfo/Chart/LineChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/LineChart.tsx similarity index 97% rename from frontend/src/components/AdminInfo/Chart/LineChart.tsx rename to frontend/src/Cabinet/components/AdminInfo/Chart/LineChart.tsx index f7b2fe829..b83f9e80e 100644 --- a/frontend/src/components/AdminInfo/Chart/LineChart.tsx +++ b/frontend/src/Cabinet/components/AdminInfo/Chart/LineChart.tsx @@ -1,6 +1,6 @@ +import { IMonthlyData } from "@/Cabinet/types/dto/admin.dto"; import { ResponsiveLine } from "@nivo/line"; import styled from "styled-components"; -import { IMonthlyData } from "@/types/dto/admin.dto"; interface IChartInfo { x: string; diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/PieChart.tsx similarity index 100% rename from frontend/src/components/AdminInfo/Chart/PieChart.tsx rename to frontend/src/Cabinet/components/AdminInfo/Chart/PieChart.tsx diff --git a/frontend/src/components/AdminInfo/Table/AdminTable.tsx b/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx similarity index 95% rename from frontend/src/components/AdminInfo/Table/AdminTable.tsx rename to frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx index 27def6ccb..2f7c2b570 100644 --- a/frontend/src/components/AdminInfo/Table/AdminTable.tsx +++ b/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx @@ -1,7 +1,7 @@ -import { ITableData } from "src/types/dto/admin.dto"; import { useState } from "react"; import styled from "styled-components"; -import Pagination from "./Pagination"; +import Pagination from "@/Cabinet/components/AdminInfo/Table/Pagination"; +import { ITableData } from "@/Cabinet/types/dto/admin.dto"; const AdminTable = ({ data, diff --git a/frontend/src/components/AdminInfo/Table/Pagination.tsx b/frontend/src/Cabinet/components/AdminInfo/Table/Pagination.tsx similarity index 100% rename from frontend/src/components/AdminInfo/Table/Pagination.tsx rename to frontend/src/Cabinet/components/AdminInfo/Table/Pagination.tsx diff --git a/frontend/src/components/Announce/AnnounceTemplate.tsx b/frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx similarity index 94% rename from frontend/src/components/Announce/AnnounceTemplate.tsx rename to frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx index f4c831efc..a83d05469 100644 --- a/frontend/src/components/Announce/AnnounceTemplate.tsx +++ b/frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx @@ -36,9 +36,9 @@ const AnnounceTemplate = (props: Itext) => { {title} {type === "ERROR" ? ( - + ) : ( - + )} {subTitle} diff --git a/frontend/src/components/Available/AvailableCountdown.tsx b/frontend/src/Cabinet/components/Available/AvailableCountdown.tsx similarity index 94% rename from frontend/src/components/Available/AvailableCountdown.tsx rename to frontend/src/Cabinet/components/Available/AvailableCountdown.tsx index d2c38f4fe..d8f37f3ea 100644 --- a/frontend/src/components/Available/AvailableCountdown.tsx +++ b/frontend/src/Cabinet/components/Available/AvailableCountdown.tsx @@ -1,8 +1,8 @@ +import { serverTimeState } from "@/Cabinet/recoil/atoms"; +import Time from "@/Cabinet/types/enum/time.enum"; import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { serverTimeState } from "@/recoil/atoms"; -import Time from "@/types/enum/time.enum"; const openTime = new Date(); openTime.setHours(13, 0, 0, 0); // 오픈 시간인 13:00:00(오후 1시)로 설정 diff --git a/frontend/src/components/Available/FloorContainer.tsx b/frontend/src/Cabinet/components/Available/FloorContainer.tsx similarity index 84% rename from frontend/src/components/Available/FloorContainer.tsx rename to frontend/src/Cabinet/components/Available/FloorContainer.tsx index 6be4547f2..c314c6419 100644 --- a/frontend/src/components/Available/FloorContainer.tsx +++ b/frontend/src/Cabinet/components/Available/FloorContainer.tsx @@ -1,9 +1,9 @@ import { useState } from "react"; import { useLocation } from "react-router-dom"; import styled from "styled-components"; -import AdminCabinetListItem from "@/components/CabinetList/CabinetListItem/AdminCabinetListItem"; -import CabinetListItem from "@/components/CabinetList/CabinetListItem/CabinetListItem"; -import { CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; +import AdminCabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem"; +import CabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem"; +import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; // 하나의 층에 대한 타이틀과 캐비넷 리스트를 담고 있는 컴포넌트 const FloorContainer = ({ @@ -40,7 +40,10 @@ const FloorContainer = ({ ) : (

해당 층에는 사용 가능한 사물함이 없습니다

- noAvailable + noAvailable
)} @@ -67,7 +70,8 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` z-index: 2; height: 30px; width: 20px; - background: url(/src/assets/images/select.svg) no-repeat center center; + background: url(/src/Cabinet/assets/images/select.svg) no-repeat center + center; transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; } diff --git a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx similarity index 92% rename from frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 120f66351..1c7f72070 100644 --- a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -4,27 +4,27 @@ import styled, { css } from "styled-components"; import { currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { IAdminCurrentModalStateInfo, IMultiSelectTargetInfo, ISelectedCabinetInfo, TAdminModalState, -} from "@/components/CabinetInfoArea/CabinetInfoArea.container"; -import ButtonContainer from "@/components/Common/Button"; -import ClubLentModal from "@/components/Modals/LentModal/ClubLentModal"; -import AdminReturnModal from "@/components/Modals/ReturnModal/AdminReturnModal"; -import StatusModalContainer from "@/components/Modals/StatusModal/StatusModal.container"; +} from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; +import ButtonContainer from "@/Cabinet/components/Common/Button"; +import ClubLentModal from "@/Cabinet/components/Modals/LentModal/ClubLentModal"; +import AdminReturnModal from "@/Cabinet/components/Modals/ReturnModal/AdminReturnModal"; +import StatusModalContainer from "@/Cabinet/components/Modals/StatusModal/StatusModal.container"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; -import { CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/assets/data/maps"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; const AdminCabinetInfoArea: React.FC<{ selectedCabinetInfo: ISelectedCabinetInfo | null; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container.tsx similarity index 94% rename from frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container.tsx index 5156711b9..52431b660 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container.tsx @@ -5,20 +5,20 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import AdminCabinetInfoArea from "@/components/CabinetInfoArea/AdminCabinetInfoArea"; -import CabinetInfoArea from "@/components/CabinetInfoArea/CabinetInfoArea"; -import AdminLentLog from "@/components/LentLog/AdminLentLog"; +} from "@/Cabinet/recoil/atoms"; +import AdminCabinetInfoArea from "@/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea"; +import CabinetInfoArea from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea"; +import AdminLentLog from "@/Cabinet/components/LentLog/AdminLentLog"; import { CabinetInfo, CabinetPreviewInfo, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; -import { UserDto } from "@/types/dto/user.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import useMenu from "@/hooks/useMenu"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/types/dto/cabinet.dto"; +import { UserDto } from "@/Cabinet/types/dto/user.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; export interface ISelectedCabinetInfo { floor: number; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx similarity index 89% rename from frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx index 2bd273d96..ad97002b5 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -4,29 +4,29 @@ import { ICurrentModalStateInfo, ISelectedCabinetInfo, TModalState, -} from "@/components/CabinetInfoArea/CabinetInfoArea.container"; -import CountTimeContainer from "@/components/CabinetInfoArea/CountTime/CountTime.container"; -import ButtonContainer from "@/components/Common/Button"; -import CancelModal from "@/components/Modals/CancelModal/CancelModal"; -import ExtendModal from "@/components/Modals/ExtendModal/ExtendModal"; -import InvitationCodeModalContainer from "@/components/Modals/InvitationCodeModal/InvitationCodeModal.container"; -import LentModal from "@/components/Modals/LentModal/LentModal"; -import MemoModalContainer from "@/components/Modals/MemoModal/MemoModal.container"; -import PasswordCheckModalContainer from "@/components/Modals/PasswordCheckModal/PasswordCheckModal.container"; -import ReturnModal from "@/components/Modals/ReturnModal/ReturnModal"; -import SwapModal from "@/components/Modals/SwapModal/SwapModal"; -import UnavailableModal from "@/components/Modals/UnavailableModal/UnavailableModal"; +} from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; +import CountTimeContainer from "@/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container"; +import ButtonContainer from "@/Cabinet/components/Common/Button"; +import CancelModal from "@/Cabinet/components/Modals/CancelModal/CancelModal"; +import ExtendModal from "@/Cabinet/components/Modals/ExtendModal/ExtendModal"; +import InvitationCodeModalContainer from "@/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container"; +import LentModal from "@/Cabinet/components/Modals/LentModal/LentModal"; +import MemoModalContainer from "@/Cabinet/components/Modals/MemoModal/MemoModal.container"; +import PasswordCheckModalContainer from "@/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.container"; +import ReturnModal from "@/Cabinet/components/Modals/ReturnModal/ReturnModal"; +import SwapModal from "@/Cabinet/components/Modals/SwapModal/SwapModal"; +import UnavailableModal from "@/Cabinet/components/Modals/UnavailableModal/UnavailableModal"; import { additionalModalType, cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import alertImg from "@/assets/images/cautionSign.svg"; -import { ReactComponent as ExtensionImg } from "@/assets/images/extensionTicket.svg"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/assets/data/maps"; +import alertImg from "@/Cabinet/assets/images/cautionSign.svg"; +import { ReactComponent as ExtensionImg } from "@/Cabinet/assets/images/extensionTicket.svg"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; const CabinetInfoArea: React.FC<{ selectedCabinetInfo: ISelectedCabinetInfo | null; diff --git a/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CodeAndTime.tsx similarity index 93% rename from frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CodeAndTime.tsx index 67b2c16cd..fec19ac5c 100644 --- a/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CodeAndTime.tsx @@ -1,10 +1,10 @@ import { useState } from "react"; import { useRecoilValue } from "recoil"; import styled from "styled-components"; -import { myCabinetInfoState } from "@/recoil/atoms"; -import alertImg from "@/assets/images/cautionSign.svg"; -import { ReactComponent as ClockImg } from "@/assets/images/clock.svg"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; +import { myCabinetInfoState } from "@/Cabinet/recoil/atoms"; +import alertImg from "@/Cabinet/assets/images/cautionSign.svg"; +import { ReactComponent as ClockImg } from "@/Cabinet/assets/images/clock.svg"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; interface CountTimeProps { minutes: string; diff --git a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.container.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx similarity index 88% rename from frontend/src/components/CabinetInfoArea/CountTime/CountTime.container.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx index 1931a8686..b69de2ce9 100644 --- a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.container.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx @@ -4,11 +4,14 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import CodeAndTime from "@/components/CabinetInfoArea/CountTime/CodeAndTime"; -import CountTime from "@/components/CabinetInfoArea/CountTime/CountTime"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import { axiosCabinetById, axiosMyLentInfo } from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import CodeAndTime from "@/Cabinet/components/CabinetInfoArea/CountTime/CodeAndTime"; +import CountTime from "@/Cabinet/components/CabinetInfoArea/CountTime/CountTime"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import { + axiosCabinetById, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; const returnCountTime = (countDown: number) => { const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)) diff --git a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.tsx similarity index 94% rename from frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.tsx index b9a989d9e..f409efa4f 100644 --- a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import { ReactComponent as ClockImg } from "@/assets/images/clock.svg"; +import { ReactComponent as ClockImg } from "@/Cabinet/assets/images/clock.svg"; interface CountTimeProps { minutes: string; diff --git a/frontend/src/components/CabinetList/CabinetList.container.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx similarity index 69% rename from frontend/src/components/CabinetList/CabinetList.container.tsx rename to frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx index e3ad681f6..64515e003 100644 --- a/frontend/src/components/CabinetList/CabinetList.container.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx @@ -1,17 +1,20 @@ import React from "react"; import { useRecoilValue } from "recoil"; -import { currentSectionNameState, isMultiSelectState } from "@/recoil/atoms"; +import { + currentSectionNameState, + isMultiSelectState, +} from "@/Cabinet/recoil/atoms"; import { currentSectionCabinetState, currentSectionColNumState, -} from "@/recoil/selectors"; -import CabinetList from "@/components/CabinetList/CabinetList"; -import EmptySection from "@/components/CabinetList/EmptySection/EmptySection"; -import RealViewNotification from "@/components/CabinetList/RealViewNotification/RealViewNotification"; -import MultiSelectFilterButton from "@/components/Common/MultiSelectFilterButton"; -import { CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import SectionType from "@/types/enum/map.type.enum"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/recoil/selectors"; +import CabinetList from "@/Cabinet/components/CabinetList/CabinetList"; +import EmptySection from "@/Cabinet/components/CabinetList/EmptySection/EmptySection"; +import RealViewNotification from "@/Cabinet/components/CabinetList/RealViewNotification/RealViewNotification"; +import MultiSelectFilterButton from "@/Cabinet/components/Common/MultiSelectFilterButton"; +import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import SectionType from "@/Cabinet/types/enum/map.type.enum"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; interface ICabinetListContainer { isAdmin: boolean; diff --git a/frontend/src/components/CabinetList/CabinetList.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetList.tsx similarity index 74% rename from frontend/src/components/CabinetList/CabinetList.tsx rename to frontend/src/Cabinet/components/CabinetList/CabinetList.tsx index ec26837fc..58802c633 100644 --- a/frontend/src/components/CabinetList/CabinetList.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetList.tsx @@ -1,9 +1,12 @@ import styled from "styled-components"; -import AdminCabinetListItem from "@/components/CabinetList/CabinetListItem/AdminCabinetListItem"; -import CabinetListItem from "@/components/CabinetList/CabinetListItem/CabinetListItem"; -import { CabinetInfo, CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import useMenu from "@/hooks/useMenu"; -import useMultiSelect from "@/hooks/useMultiSelect"; +import AdminCabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem"; +import CabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem"; +import { + CabinetInfo, + CabinetPreviewInfo, +} from "@/Cabinet/types/dto/cabinet.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; interface CabinetListInterface { colNum: number; @@ -43,7 +46,7 @@ const AdminToggleButtonStyled = styled.div` bottom: 40px; right: 40px; cursor: pointer; - background-image: url("/src/assets/images/proceedMultiSelect.svg"); + background-image: url("/src/Cabinet/assets/images/proceedMultiSelect.svg"); background-repeat: no-repeat; background-size: 100% 100%; `; diff --git a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx similarity index 93% rename from frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx rename to frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index ddcb1f3b6..7fb703ba2 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -4,19 +4,22 @@ import { currentCabinetIdState, selectedTypeOnSearchState, targetCabinetInfoState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { cabinetFilterMap, cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { CabinetInfo, CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { axiosCabinetById } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/assets/data/maps"; +import { + CabinetInfo, + CabinetPreviewInfo, +} from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { axiosCabinetById } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; const AdminCabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { const [currentCabinetId, setCurrentCabinetId] = useRecoilState( diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx similarity index 95% rename from frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx rename to frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx index fc0291034..ddc9d449e 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -9,27 +9,27 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import UnavailableModal from "@/components/Modals/UnavailableModal/UnavailableModal"; +} from "@/Cabinet/recoil/atoms"; +import UnavailableModal from "@/Cabinet/components/Modals/UnavailableModal/UnavailableModal"; import { cabinetFilterMap, cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; +} from "@/Cabinet/assets/data/maps"; import { CabinetInfo, CabinetPreviewInfo, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import { axiosCabinetByBuildingFloor, axiosCabinetById, axiosMyLentInfo, -} from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const CabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { const myCabinetInfo = diff --git a/frontend/src/components/CabinetList/EmptySection/EmptySection.tsx b/frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx similarity index 91% rename from frontend/src/components/CabinetList/EmptySection/EmptySection.tsx rename to frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx index 1e1df8b71..5f570a72a 100644 --- a/frontend/src/components/CabinetList/EmptySection/EmptySection.tsx +++ b/frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx @@ -4,7 +4,7 @@ const EmptySection = ({ message }: { message: string }): JSX.Element => { return ( {message} diff --git a/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx b/frontend/src/Cabinet/components/CabinetList/RealViewNotification/RealViewNotification.tsx similarity index 97% rename from frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx rename to frontend/src/Cabinet/components/CabinetList/RealViewNotification/RealViewNotification.tsx index 67d12969b..28ebe8e99 100644 --- a/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx +++ b/frontend/src/Cabinet/components/CabinetList/RealViewNotification/RealViewNotification.tsx @@ -33,7 +33,7 @@ const RealViewNotification: React.FC<{ colNum: number }> = (props) => { }; const ToolTipIcon = styled.div<{ hasEnoughWidth: boolean }>` - background-image: url("/src/assets/images/cautionSign.svg"); + background-image: url("/src/Cabinet/assets/images/cautionSign.svg"); width: 24px; height: 24px; margin: 0px auto; diff --git a/frontend/src/components/Card/Card.tsx b/frontend/src/Cabinet/components/Card/Card.tsx similarity index 97% rename from frontend/src/components/Card/Card.tsx rename to frontend/src/Cabinet/components/Card/Card.tsx index 7d55a85fb..c1a0272e3 100644 --- a/frontend/src/components/Card/Card.tsx +++ b/frontend/src/Cabinet/components/Card/Card.tsx @@ -97,7 +97,7 @@ export const CardTitleStyled = styled.div` `; const ToolTipIcon = styled.div` - background-image: url("/src/assets/images/notificationSign_grey.svg"); + background-image: url("/src/Cabinet/assets/images/notificationSign_grey.svg"); background-size: contain; width: 16px; height: 16px; diff --git a/frontend/src/components/Card/CardStyles.ts b/frontend/src/Cabinet/components/Card/CardStyles.ts similarity index 93% rename from frontend/src/components/Card/CardStyles.ts rename to frontend/src/Cabinet/components/Card/CardStyles.ts index de068c48e..80d830937 100644 --- a/frontend/src/components/Card/CardStyles.ts +++ b/frontend/src/Cabinet/components/Card/CardStyles.ts @@ -1,5 +1,5 @@ +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import styled from "styled-components"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; export const CardContentWrapper = styled.div` background-color: var(--white); diff --git a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx b/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx similarity index 91% rename from frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx rename to frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx index 4a3c08a74..10ed068a1 100644 --- a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx +++ b/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx @@ -1,12 +1,12 @@ import { useState } from "react"; import styled from "styled-components"; -import Card from "@/components/Card/Card"; +import Card from "@/Cabinet/components/Card/Card"; import { CardContentStyled, ContentDetailStyled, -} from "@/components/Card/CardStyles"; -import ClubPasswordModalContainer from "@/components/Modals/ClubModal/ClubPasswordModal.container"; -import { ClubInfoResponseDto } from "@/types/dto/club.dto"; +} from "@/Cabinet/components/Card/CardStyles"; +import ClubPasswordModalContainer from "@/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container"; +import { ClubInfoResponseDto } from "@/Cabinet/types/dto/club.dto"; const ClubCabinetInfoCard = ({ clubInfo, @@ -35,7 +35,7 @@ const ClubCabinetInfoCard = ({ ? [ { onClick: handleLockLogoClick, - icon: "/src/assets/images/lock.svg", + icon: "/src/Cabinet/assets/images/lock.svg", isClickable: true, }, ] @@ -150,7 +150,7 @@ const CabinetIconStyled = styled.div` width: 22px; height: 18px; margin-right: 0.5rem; - background-image: url("/src/assets/images/leader.svg"); + background-image: url("/src/Cabinet/assets/images/leader.svg"); background-size: contain; background-repeat: no-repeat; `; diff --git a/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx b/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx similarity index 87% rename from frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx rename to frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx index dcd426897..e70d7ed0a 100644 --- a/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx +++ b/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import styled from "styled-components"; -import Card from "@/components/Card/Card"; -import { CardContentWrapper } from "@/components/Card/CardStyles"; -import ClubMemoModalContainer from "@/components/Modals/ClubModal/ClubMemoModal.container"; +import Card from "@/Cabinet/components/Card/Card"; +import { CardContentWrapper } from "@/Cabinet/components/Card/CardStyles"; +import ClubMemoModalContainer from "@/Cabinet/components/Modals/ClubModal/ClubMemoModal.container"; const ClubNoticeCard = ({ notice, @@ -30,7 +30,7 @@ const ClubNoticeCard = ({ ? [ { onClick: openModal, - icon: "/src/assets/images/edit.svg", + icon: "/src/Cabinet/assets/images/edit.svg", isClickable: true, }, ] diff --git a/frontend/src/components/Card/ExtensionCard/ExtensionCard.container.tsx b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.container.tsx similarity index 85% rename from frontend/src/components/Card/ExtensionCard/ExtensionCard.container.tsx rename to frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.container.tsx index 6f2af76fd..18958f830 100644 --- a/frontend/src/components/Card/ExtensionCard/ExtensionCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.container.tsx @@ -4,12 +4,12 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import ExtensionCard from "@/components/Card/ExtensionCard/ExtensionCard"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentDto, LentExtensionDto } from "@/types/dto/lent.dto"; -import { axiosCabinetById } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import ExtensionCard from "@/Cabinet/components/Card/ExtensionCard/ExtensionCard"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentDto, LentExtensionDto } from "@/Cabinet/types/dto/lent.dto"; +import { axiosCabinetById } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const ExtensionCardContainer = ({ extensionInfo, diff --git a/frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx similarity index 84% rename from frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx rename to frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx index a5b865571..1aec4650e 100644 --- a/frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx +++ b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx @@ -1,14 +1,14 @@ -import { useState } from "react"; -import Card, { IButtonProps } from "@/components/Card/Card"; +import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, ContentDetailStyled, ContentInfoStyled, -} from "@/components/Card/CardStyles"; -import { NotificationModal } from "@/components/Modals/NotificationModal/NotificationModal"; -import { LentExtensionDto } from "@/types/dto/lent.dto"; -import { formatDate } from "@/utils/dateUtils"; +} from "@/Cabinet/components/Card/CardStyles"; +import { NotificationModal } from "@/Cabinet/components/Modals/NotificationModal/NotificationModal"; +import { LentExtensionDto } from "@/Cabinet/types/dto/lent.dto"; +import { formatDate } from "@/Cabinet/utils/dateUtils"; +import { useState } from "react"; interface ExtensionProps { extensionInfo: LentExtensionDto | null; diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx similarity index 82% rename from frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx rename to frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx index 57b6bc12f..c0f48e9d4 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx @@ -1,12 +1,12 @@ +import LentInfoCard from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard"; +import { getDefaultCabinetInfo } from "@/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; +import { myCabinetInfoState } from "@/Cabinet/recoil/atoms"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { getRemainingTime } from "@/Cabinet/utils/dateUtils"; import { useRecoilValue } from "recoil"; -import { myCabinetInfoState } from "@/recoil/atoms"; -import LentInfoCard from "@/components/Card/LentInfoCard/LentInfoCard"; -import { getDefaultCabinetInfo } from "@/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentDto } from "@/types/dto/lent.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { getRemainingTime } from "@/utils/dateUtils"; export interface MyCabinetInfo { name: string | null; diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx similarity index 92% rename from frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx rename to frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx index 559e9446d..2a441d12b 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx @@ -1,16 +1,16 @@ import styled from "styled-components"; -import Card from "@/components/Card/Card"; +import Card from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, ContentDetailStyled, ContentInfoStyled, -} from "@/components/Card/CardStyles"; -import { MyCabinetInfo } from "@/components/Card/LentInfoCard/LentInfoCard.container"; -import { cabinetIconSrcMap } from "@/assets/data/maps"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { formatDate } from "@/utils/dateUtils"; +} from "@/Cabinet/components/Card/CardStyles"; +import { MyCabinetInfo } from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard.container"; +import { cabinetIconSrcMap } from "@/Cabinet/assets/data/maps"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { formatDate } from "@/Cabinet/utils/dateUtils"; const calculateFontSize = (userCount: number): string => { const baseSize = 1; diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx similarity index 90% rename from frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx rename to frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx index 6539a4dcb..6a3d148f9 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx @@ -1,19 +1,19 @@ import { deleteFcmToken, requestFcmAndGetDeviceToken, -} from "@/firebase/firebase-messaging-sw"; +} from "@/Cabinet/firebase/firebase-messaging-sw"; import { useEffect, useMemo, useState } from "react"; -import NotificationCard from "@/components/Card/NotificationCard/NotificationCard"; -import ModalPortal from "@/components/Modals/ModalPortal"; +import NotificationCard from "@/Cabinet/components/Card/NotificationCard/NotificationCard"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { AlarmInfo } from "@/types/dto/alarm.dto"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { AlarmInfo } from "@/Cabinet/types/dto/alarm.dto"; import { axiosUpdateAlarm, axiosUpdateDeviceToken, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { const [showResponseModal, setShowResponseModal] = useState(false); diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.tsx b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.tsx similarity index 82% rename from frontend/src/components/Card/NotificationCard/NotificationCard.tsx rename to frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.tsx index 3b9a9d56c..c07812669 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.tsx +++ b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.tsx @@ -1,11 +1,11 @@ -import Card, { IButtonProps } from "@/components/Card/Card"; +import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, ContentInfoStyled, -} from "@/components/Card/CardStyles"; -import ToggleSwitch from "@/components/Common/ToggleSwitch"; -import { AlarmInfo } from "@/types/dto/alarm.dto"; +} from "@/Cabinet/components/Card/CardStyles"; +import ToggleSwitch from "@/Cabinet/components/Common/ToggleSwitch"; +import { AlarmInfo } from "@/Cabinet/types/dto/alarm.dto"; interface NotificationCardProps { alarm: AlarmInfo; diff --git a/frontend/src/components/Card/ProfileCard/ProfileCard.container.tsx b/frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.container.tsx similarity index 86% rename from frontend/src/components/Card/ProfileCard/ProfileCard.container.tsx rename to frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.container.tsx index f62303f92..ffebf6f60 100644 --- a/frontend/src/components/Card/ProfileCard/ProfileCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.container.tsx @@ -4,9 +4,9 @@ import { currentBuildingNameState, currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; -import ProfileCard from "@/components/Card/ProfileCard/ProfileCard"; -import { removeCookie } from "@/api/react_cookie/cookies"; +} from "@/Cabinet/recoil/atoms"; +import ProfileCard from "@/Cabinet/components/Card/ProfileCard/ProfileCard"; +import { removeCookie } from "@/Cabinet/api/react_cookie/cookies"; const ProfileCardContainer = ({ name }: { name: string | null }) => { const navigator = useNavigate(); diff --git a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx b/frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.tsx similarity index 91% rename from frontend/src/components/Card/ProfileCard/ProfileCard.tsx rename to frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.tsx index 92efc4b8b..18a96e3fe 100644 --- a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx +++ b/frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; -import Card, { IButtonProps } from "@/components/Card/Card"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; +import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; type ProfileProps = { name: string | null; diff --git a/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx b/frontend/src/Cabinet/components/Card/ThemeColorCard/ColorPicker.tsx similarity index 100% rename from frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx rename to frontend/src/Cabinet/components/Card/ThemeColorCard/ColorPicker.tsx diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx b/frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.container.tsx similarity index 95% rename from frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx rename to frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.container.tsx index a0662bd6f..6a12343e8 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.container.tsx @@ -1,6 +1,6 @@ +import ThemeColorCard from "@/Cabinet/components/Card/ThemeColorCard/ThemeColorCard"; +import ColorType from "@/Cabinet/types/enum/color.type.enum"; import { useEffect, useState } from "react"; -import ThemeColorCard from "@/components/Card/ThemeColorCard/ThemeColorCard"; -import ColorType from "@/types/enum/color.type.enum"; const ThemeColorCardContainer = () => { const savedMainColor = diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.tsx similarity index 93% rename from frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx rename to frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.tsx index 30358665b..8a74cdd87 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -1,15 +1,15 @@ -import styled, { css } from "styled-components"; -import Card from "@/components/Card/Card"; +import Card from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, ContentInfoStyled, -} from "@/components/Card/CardStyles"; -import ColorPicker from "@/components/Card/ThemeColorCard/ColorPicker"; +} from "@/Cabinet/components/Card/CardStyles"; +import ColorPicker from "@/Cabinet/components/Card/ThemeColorCard/ColorPicker"; import { customColors, themeColorData, -} from "@/components/Card/ThemeColorCard/colorInfo"; +} from "@/Cabinet/components/Card/ThemeColorCard/colorInfo"; +import styled, { css } from "styled-components"; interface ThemeColorProps { showColorPicker: boolean; diff --git a/frontend/src/components/Card/ThemeColorCard/colorInfo.ts b/frontend/src/Cabinet/components/Card/ThemeColorCard/colorInfo.ts similarity index 91% rename from frontend/src/components/Card/ThemeColorCard/colorInfo.ts rename to frontend/src/Cabinet/components/Card/ThemeColorCard/colorInfo.ts index 66b284607..fe643ca98 100644 --- a/frontend/src/components/Card/ThemeColorCard/colorInfo.ts +++ b/frontend/src/Cabinet/components/Card/ThemeColorCard/colorInfo.ts @@ -1,4 +1,4 @@ -import ColorType from "@/types/enum/color.type.enum"; +import ColorType from "@/Cabinet/types/enum/color.type.enum"; interface ColorData { title: string; diff --git a/frontend/src/components/Club/AdminClubLog.container.tsx b/frontend/src/Cabinet/components/Club/AdminClubLog.container.tsx similarity index 85% rename from frontend/src/components/Club/AdminClubLog.container.tsx rename to frontend/src/Cabinet/components/Club/AdminClubLog.container.tsx index ae487b13a..95b7235f9 100644 --- a/frontend/src/components/Club/AdminClubLog.container.tsx +++ b/frontend/src/Cabinet/components/Club/AdminClubLog.container.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -import AdminClubLog from "@/components/Club/AdminClubLog"; -import { ClubLogResponseType, ClubUserDto } from "@/types/dto/lent.dto"; -import { axiosGetClubUserLog } from "@/api/axios/axios.custom"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import AdminClubLog from "@/Cabinet/components/Club/AdminClubLog"; +import { ClubLogResponseType, ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import { axiosGetClubUserLog } from "@/Cabinet/api/axios/axios.custom"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const AdminClubLogContainer = (props: any) => { const [logs, setLogs] = useState(undefined); diff --git a/frontend/src/components/Club/AdminClubLog.tsx b/frontend/src/Cabinet/components/Club/AdminClubLog.tsx similarity index 75% rename from frontend/src/components/Club/AdminClubLog.tsx rename to frontend/src/Cabinet/components/Club/AdminClubLog.tsx index f9ed15e56..2f7f41493 100644 --- a/frontend/src/components/Club/AdminClubLog.tsx +++ b/frontend/src/Cabinet/components/Club/AdminClubLog.tsx @@ -1,7 +1,8 @@ import styled, { css } from "styled-components"; -import ClubLogTable from "@/components/Club/ClubLogTable"; -import LeftSectionButton from "@/assets/images/LeftSectionButton.svg"; -import { IClubLog } from "@/types/dto/lent.dto"; +import ClubLogTable from "@/Cabinet/components/Club/ClubLogTable"; +import { MoveSectionButtonStyled } from "@/Cabinet/components/SectionPagination/SectionPagination"; +import LeftSectionButton from "@/Cabinet/assets/images/LeftSectionButton.svg"; +import { IClubLog } from "@/Cabinet/types/dto/lent.dto"; const AdminClubLog = ({ logs, @@ -84,22 +85,22 @@ const SectionBarStyled = styled.div` align-items: center; `; -const MoveSectionButtonStyled = styled.img<{ arrowReversed?: boolean }>` - width: 24px; - height: 24px; - margin: 0px 15px; - opacity: 70%; - cursor: pointer; - transform: rotate(${(props) => (props.arrowReversed ? "180deg" : "0")}); - transition: all 0.2s; - @media (hover: hover) and (pointer: fine) { - &:hover { - opacity: 100%; - transform: rotate(${(props) => (props.arrowReversed ? "180deg" : "0")}) - scale(1.3); - } - } -`; +// const MoveSectionButtonStyled = styled.img<{ arrowReversed?: boolean }>` +// width: 24px; +// height: 24px; +// margin: 0px 15px; +// opacity: 70%; +// cursor: pointer; +// transform: rotate(${(props) => (props.arrowReversed ? "180deg" : "0")}); +// transition: all 0.2s; +// @media (hover: hover) and (pointer: fine) { +// &:hover { +// opacity: 100%; +// transform: rotate(${(props) => (props.arrowReversed ? "180deg" : "0")}) +// scale(1.3); +// } +// } +// `; const SectionIndexStyled = styled.div` width: 100%; diff --git a/frontend/src/components/Club/ClubInfo.tsx b/frontend/src/Cabinet/components/Club/ClubInfo.tsx similarity index 78% rename from frontend/src/components/Club/ClubInfo.tsx rename to frontend/src/Cabinet/components/Club/ClubInfo.tsx index cdb38d83a..33b933dff 100644 --- a/frontend/src/components/Club/ClubInfo.tsx +++ b/frontend/src/Cabinet/components/Club/ClubInfo.tsx @@ -1,15 +1,15 @@ import { useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; import styled from "styled-components"; -import { userState } from "@/recoil/atoms"; -import ClubCabinetInfoCard from "@/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard"; -import ClubNoticeCard from "@/components/Card/ClubNoticeCard/ClubNoticeCard"; -import ClubMemberListContainer from "@/components/Club/ClubMemberList/ClubMemberList.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { ClubInfoResponseDto } from "@/types/dto/club.dto"; -import useClubInfo from "@/hooks/useClubInfo"; -import useMenu from "@/hooks/useMenu"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { userState } from "@/Cabinet/recoil/atoms"; +import ClubCabinetInfoCard from "@/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard"; +import ClubNoticeCard from "@/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard"; +import ClubMemberListContainer from "@/Cabinet/components/Club/ClubMemberList/ClubMemberList.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ClubInfoResponseDto } from "@/Cabinet/types/dto/club.dto"; +import useClubInfo from "@/Cabinet/hooks/useClubInfo"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const ClubInfo = () => { const myInfo = useRecoilValue(userState); @@ -34,7 +34,7 @@ const ClubInfo = () => { 동아리 사물함이 없어요 - + ) : ( diff --git a/frontend/src/components/Club/ClubLogTable.tsx b/frontend/src/Cabinet/components/Club/ClubLogTable.tsx similarity index 89% rename from frontend/src/components/Club/ClubLogTable.tsx rename to frontend/src/Cabinet/components/Club/ClubLogTable.tsx index 8e661e434..66103f15a 100644 --- a/frontend/src/components/Club/ClubLogTable.tsx +++ b/frontend/src/Cabinet/components/Club/ClubLogTable.tsx @@ -1,9 +1,9 @@ import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { selectedClubInfoState } from "@/recoil/atoms"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { ClubLogResponseType, ClubUserDto } from "@/types/dto/lent.dto"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { selectedClubInfoState } from "@/Cabinet/recoil/atoms"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ClubLogResponseType, ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const ClubLogTable = ({ ClubList }: { ClubList: ClubLogResponseType }) => { const [selectedClubInfo, setSelectedClubInfo] = useRecoilState( diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx similarity index 89% rename from frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx index 134e28477..4c7ceef2f 100644 --- a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx @@ -5,9 +5,9 @@ import { targetClubInfoState, targetClubUserInfoState, userState, -} from "@/recoil/atoms"; -import ClubMemberInfoArea from "@/components/Club/ClubMemberInfoArea/ClubMemberInfoArea"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import ClubMemberInfoArea from "@/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea"; +import useMenu from "@/Cabinet/hooks/useMenu"; export type TClubModalState = "deleteModal" | "mandateModal"; diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx similarity index 88% rename from frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx index 43b9d58ca..2dff4552a 100644 --- a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -2,23 +2,23 @@ import styled from "styled-components"; import { ICurrentClubModalStateInfo, TClubModalState, -} from "@/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; -import Button from "@/components/Common/Button"; -import DeleteClubMemberModal from "@/components/Modals/ClubModal/DeleteClubMemberModal"; -import MandateClubMemberModal from "@/components/Modals/ClubModal/MandateClubMemberModal"; +} from "@/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; +import Button from "@/Cabinet/components/Common/Button"; +import DeleteClubMemberModal from "@/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal"; +import MandateClubMemberModal from "@/Cabinet/components/Modals/ClubModal/MandateClubMemberModal"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; +} from "@/Cabinet/assets/data/maps"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; import { ClubCabinetInfo, ClubResponseDto, ClubUserResponseDto, -} from "@/types/dto/club.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/types/dto/club.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; interface ClubMemberInfoAreaProps { selectedClubInfo: ClubResponseDto; @@ -132,11 +132,11 @@ const CabiLogoStyled = styled.div` `; const ClubMemberInfoAreaStyled = styled.div` position: fixed; - top: 80px; + top: 120px; right: 0; min-width: 330px; width: 330px; - height: calc(100% - 80px); + height: calc(100% - 120px); padding: 40px; z-index: 9; transform: translateX(120%); @@ -193,7 +193,7 @@ const ClubMasterIconStyled = styled.div` min-width: 24px; min-height: 24px; margin-bottom: 10px; - background-image: url("/src/assets/images/leader.svg"); + background-image: url("/src/Cabinet/assets/images/leader.svg"); background-size: contain; background-repeat: no-repeat; `; diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.container.tsx similarity index 90% rename from frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.container.tsx index b61bb24f1..7e7facac4 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.container.tsx @@ -1,9 +1,12 @@ import { useEffect, useState } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; -import { targetClubUserInfoState, userState } from "@/recoil/atoms"; -import ClubMemberList from "@/components/Club/ClubMemberList/ClubMemberList"; -import { ClubInfoResponseDto, ClubUserResponseDto } from "@/types/dto/club.dto"; -import useMenu from "@/hooks/useMenu"; +import { targetClubUserInfoState, userState } from "@/Cabinet/recoil/atoms"; +import ClubMemberList from "@/Cabinet/components/Club/ClubMemberList/ClubMemberList"; +import { + ClubInfoResponseDto, + ClubUserResponseDto, +} from "@/Cabinet/types/dto/club.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; export type TClubMemberModalState = "addModal"; diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx similarity index 88% rename from frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx index 19d368da7..f03696686 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx @@ -1,16 +1,16 @@ import { useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; import styled from "styled-components"; -import { targetClubUserInfoState, userState } from "@/recoil/atoms"; +import { targetClubUserInfoState, userState } from "@/Cabinet/recoil/atoms"; import { ICurrentClubMemberModalStateInfo, TClubMemberModalState, -} from "@/components/Club/ClubMemberList/ClubMemberList.container"; -import ClubMemberListItem from "@/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import AddClubMemberModalContainer from "@/components/Modals/ClubModal/AddClubMemberModal.container"; -import { ReactComponent as Select } from "@/assets/images/selectMaincolor.svg"; -import { ClubUserResponseDto } from "@/types/dto/club.dto"; +} from "@/Cabinet/components/Club/ClubMemberList/ClubMemberList.container"; +import ClubMemberListItem from "@/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import AddClubMemberModalContainer from "@/Cabinet/components/Modals/ClubModal/AddClubMemberModal.container"; +import { ReactComponent as Select } from "@/Cabinet/assets/images/selectMaincolor.svg"; +import { ClubUserResponseDto } from "@/Cabinet/types/dto/club.dto"; interface ClubMemberListProps { isLoading: boolean; @@ -46,7 +46,9 @@ const ClubMemberList = ({

동아리 멤버

- + {clubUserCount} diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx similarity index 90% rename from frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx index 66807ec52..0870e1627 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx @@ -1,8 +1,8 @@ import { memo } from "react"; import styled, { css } from "styled-components"; -import { ReactComponent as CrownImg } from "@/assets/images/crown.svg"; -import { ReactComponent as UserImg } from "@/assets/images/privateIcon.svg"; -import { ClubUserResponseDto } from "@/types/dto/club.dto"; +import { ReactComponent as CrownImg } from "@/Cabinet/assets/images/crown.svg"; +import { ReactComponent as UserImg } from "@/Cabinet/assets/images/privateIcon.svg"; +import { ClubUserResponseDto } from "@/Cabinet/types/dto/club.dto"; interface ClubMemberListItemProps { bgColor?: string; diff --git a/frontend/src/components/Common/Button.tsx b/frontend/src/Cabinet/components/Common/Button.tsx similarity index 98% rename from frontend/src/components/Common/Button.tsx rename to frontend/src/Cabinet/components/Common/Button.tsx index c5818725f..b799efdc3 100644 --- a/frontend/src/components/Common/Button.tsx +++ b/frontend/src/Cabinet/components/Common/Button.tsx @@ -1,4 +1,3 @@ -import React from "react"; import styled, { css } from "styled-components"; interface ButtonInterface { diff --git a/frontend/src/components/Common/ClubListDropdown.tsx b/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx similarity index 97% rename from frontend/src/components/Common/ClubListDropdown.tsx rename to frontend/src/Cabinet/components/Common/ClubListDropdown.tsx index 65569c513..51ad382c6 100644 --- a/frontend/src/components/Common/ClubListDropdown.tsx +++ b/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx @@ -30,7 +30,7 @@ const ClubListDropd = ({
)}

{currentName}

- + {options?.map((option) => { diff --git a/frontend/src/components/Common/Dropdown.tsx b/frontend/src/Cabinet/components/Common/Dropdown.tsx similarity index 92% rename from frontend/src/components/Common/Dropdown.tsx rename to frontend/src/Cabinet/components/Common/Dropdown.tsx index b1733efef..42826a742 100644 --- a/frontend/src/components/Common/Dropdown.tsx +++ b/frontend/src/Cabinet/components/Common/Dropdown.tsx @@ -1,8 +1,14 @@ import { useState } from "react"; import styled, { css } from "styled-components"; -interface IDropdown { - options: { name: string; value: any; imageSrc?: string }[]; +export interface IDropdownOptions { + name: string; + value: any; + imageSrc?: string; +} + +export interface IDropdown { + options: IDropdownOptions[]; defaultValue: string; defaultImageSrc?: string; onChangeValue?: (param: any) => any; @@ -12,6 +18,7 @@ const Dropdown = ({ options, defaultValue, onChangeValue }: IDropdown) => { const [currentName, setCurrentName] = useState(defaultValue); const [isOpen, setIsOpen] = useState(false); const selectedIdx = options.findIndex((op) => op.name === currentName) ?? 0; + return ( { }} isOpen={isOpen} > - {options[selectedIdx].imageSrc && ( + {options[selectedIdx]?.imageSrc?.length && (
)}

{currentName}

- +
{options?.map((option) => { diff --git a/frontend/src/components/Common/LoadingAnimation.tsx b/frontend/src/Cabinet/components/Common/LoadingAnimation.tsx similarity index 100% rename from frontend/src/components/Common/LoadingAnimation.tsx rename to frontend/src/Cabinet/components/Common/LoadingAnimation.tsx diff --git a/frontend/src/components/Common/MultiSelectButton.tsx b/frontend/src/Cabinet/components/Common/MultiSelectButton.tsx similarity index 100% rename from frontend/src/components/Common/MultiSelectButton.tsx rename to frontend/src/Cabinet/components/Common/MultiSelectButton.tsx diff --git a/frontend/src/components/Common/MultiSelectFilterButton.tsx b/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx similarity index 90% rename from frontend/src/components/Common/MultiSelectFilterButton.tsx rename to frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx index 223af324b..8f97cbd02 100644 --- a/frontend/src/components/Common/MultiSelectFilterButton.tsx +++ b/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx @@ -17,8 +17,8 @@ const MultiSelectFilterButton = ({ diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx similarity index 98% rename from frontend/src/components/Common/MultiToggleSwitch.tsx rename to frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx index 8fd8da98e..44a335380 100644 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx @@ -73,7 +73,7 @@ const WrapperStyled = styled.div` width: fit-content; min-width: 50px; border-radius: 10px; - font-size: 0.9rem; + font-size: 0.875rem; height: 30px; font-weight: 500; background-color: transparent; diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx similarity index 55% rename from frontend/src/components/Common/MultiToggleSwitchSeparated.tsx rename to frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx index 7770bd0b2..cc3c9db9b 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx @@ -3,19 +3,21 @@ import styled from "styled-components"; export interface toggleItem { name: string; - key: number; + key: string; } interface MultiToggleSwitchProps { initialState: T; setState: React.Dispatch>; toggleList: toggleItem[]; + fontSize?: string; } const MultiToggleSwitchSeparated = ({ initialState, setState, toggleList, + fontSize = "0.875rem", }: MultiToggleSwitchProps) => { const wrapperRef = useRef(null); @@ -23,36 +25,31 @@ const MultiToggleSwitchSeparated = ({ const buttons = wrapperRef.current?.querySelectorAll("button"); buttons?.forEach((button) => { - if (button.className === `${initialState}`) { - button.style.color = "white"; - button.style.backgroundColor = "var(--main-color)"; + if (button.classList.contains(`${initialState}`)) { + button.classList.add("selected"); + } else { + button.classList.add("categoryButton"); } }); }, [initialState]); function switchToggle(e: any) { const target = e.target as HTMLButtonElement; - if (target === e.currentTarget) return; - // setPage(0); + const selectedKey = target.className.split(" ")[0]; const buttons = wrapperRef.current?.querySelectorAll("button"); - buttons?.forEach((button) => { - button.style.color = "black"; - button.style.backgroundColor = "var(--lightgray-color)"; + button.classList.remove("selected"); }); - - target.style.color = "white"; - target.style.backgroundColor = "var(--main-color)"; - - setState(target.className as React.SetStateAction); + target.classList.add("selected"); + setState(selectedKey as React.SetStateAction); } return ( - + {toggleList.map((item) => ( - ))} @@ -60,26 +57,46 @@ const MultiToggleSwitchSeparated = ({ ); }; -const WrapperStyled = styled.div` +const WrapperStyled = styled.div<{ + fontSize: string; +}>` width: 100%; display: flex; + flex-wrap: wrap; + justify-content: space-between; align-items: center; - border-radius: 10px; + gap: 10px; button { display: flex; justify-content: center; align-items: center; - width: fit-content; - min-width: 50px; + width: calc(16.66% - 10px); + height: 36px; + min-width: 40px; border-radius: 10px; - font-size: 0.9rem; - height: 30px; + font-size: ${(props) => props.fontSize}; font-weight: 500; background-color: var(--lightgray-color); - color: black; + color: gray; padding: 4px 12px; - margin: 0px 4px; + box-sizing: border-box; + } + + @media (max-width: 560px) { + button { + width: calc(33.333% - 10px); + } + } + + button.categoryButton { + color: black; + background-color: var(--white); + } + + button.selected { + color: white; + background-color: #3f69fd; } `; diff --git a/frontend/src/components/Common/PillButton.tsx b/frontend/src/Cabinet/components/Common/PillButton.tsx similarity index 100% rename from frontend/src/components/Common/PillButton.tsx rename to frontend/src/Cabinet/components/Common/PillButton.tsx diff --git a/frontend/src/components/Common/Selector.tsx b/frontend/src/Cabinet/components/Common/Selector.tsx similarity index 93% rename from frontend/src/components/Common/Selector.tsx rename to frontend/src/Cabinet/components/Common/Selector.tsx index 400bbafac..170761990 100644 --- a/frontend/src/components/Common/Selector.tsx +++ b/frontend/src/Cabinet/components/Common/Selector.tsx @@ -1,5 +1,5 @@ +import PillButton from "@/Cabinet/components/Common/PillButton"; import styled from "styled-components"; -import PillButton from "@/components/Common/PillButton"; interface ISelectorProps { iconSrc?: string; diff --git a/frontend/src/components/Common/ToggleSwitch.tsx b/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx similarity index 100% rename from frontend/src/components/Common/ToggleSwitch.tsx rename to frontend/src/Cabinet/components/Common/ToggleSwitch.tsx diff --git a/frontend/src/components/Common/WarningNotification.tsx b/frontend/src/Cabinet/components/Common/WarningNotification.tsx similarity index 91% rename from frontend/src/components/Common/WarningNotification.tsx rename to frontend/src/Cabinet/components/Common/WarningNotification.tsx index 213da972e..cefd1ff05 100644 --- a/frontend/src/components/Common/WarningNotification.tsx +++ b/frontend/src/Cabinet/components/Common/WarningNotification.tsx @@ -20,7 +20,7 @@ const WarningNotification: React.FC = ({ const WarningIcon = styled.div<{ isVisible: boolean }>` display: ${({ isVisible }) => (isVisible ? "block" : "none")}; - background-image: url("/src/assets/images/warningTriangleIcon.svg"); + background-image: url("/src/Cabinet/assets/images/warningTriangleIcon.svg"); width: 24px; height: 24px; margin: 0px auto; @@ -47,7 +47,7 @@ const WarningBox = styled.div` white-space: pre-line; z-index: 100; transition: visibility 0.5s, color 0.5s, background-color 0.5s, width 0.5s, - padding 0.5s ease-in-out; + padding 0.5s ease-in-out; `; const WarningWrapper = styled.div<{ isVisible: boolean }>` @@ -62,9 +62,9 @@ const WarningWrapper = styled.div<{ isVisible: boolean }>` background-color: rgba(0, 0, 0, 0.8); &:before { border-color: transparent transparent rgba(0, 0, 0, 0.8) - rgba(0, 0, 0, 0.8); + rgba(0, 0, 0, 0.8); } } `; - + export default WarningNotification; diff --git a/frontend/src/components/Home/ManualContentBox.tsx b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx similarity index 90% rename from frontend/src/components/Home/ManualContentBox.tsx rename to frontend/src/Cabinet/components/Home/ManualContentBox.tsx index 62c405ff8..1d224aef2 100644 --- a/frontend/src/components/Home/ManualContentBox.tsx +++ b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx @@ -1,9 +1,9 @@ import styled, { css, keyframes } from "styled-components"; -import { manualContentData } from "@/assets/data/ManualContent"; -import { ReactComponent as ClockImg } from "@/assets/images/clock.svg"; -import { ReactComponent as ManualPeopleImg } from "@/assets/images/manualPeople.svg"; -import { ReactComponent as MoveBtnImg } from "@/assets/images/moveButton.svg"; -import ContentStatus from "@/types/enum/content.status.enum"; +import { manualContentData } from "@/Cabinet/assets/data/ManualContent"; +import { ReactComponent as ClockImg } from "@/Cabinet/assets/images/clock.svg"; +import { ReactComponent as ManualPeopleImg } from "@/Cabinet/assets/images/manualPeople.svg"; +import { ReactComponent as MoveBtnImg } from "@/Cabinet/assets/images/moveButton.svg"; +import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface MaunalContentBoxProps { contentStatus: ContentStatus; @@ -111,7 +111,7 @@ const MaunalContentBoxStyled = styled.div<{ `} p { - margin-top: 80px; + margin-top: 120px; ${({ contentStatus }) => (contentStatus === ContentStatus.PENDING || contentStatus === ContentStatus.IN_SESSION) && diff --git a/frontend/src/components/Home/ServiceManual.tsx b/frontend/src/Cabinet/components/Home/ServiceManual.tsx similarity index 95% rename from frontend/src/components/Home/ServiceManual.tsx rename to frontend/src/Cabinet/components/Home/ServiceManual.tsx index 4ff428e69..1d05ef481 100644 --- a/frontend/src/components/Home/ServiceManual.tsx +++ b/frontend/src/Cabinet/components/Home/ServiceManual.tsx @@ -1,8 +1,8 @@ +import MaunalContentBox from "@/Cabinet/components/Home/ManualContentBox"; +import ManualModal from "@/Cabinet/components/Modals/ManualModal/ManualModal"; +import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; import { useState } from "react"; import styled from "styled-components"; -import MaunalContentBox from "@/components/Home/ManualContentBox"; -import ManualModal from "@/components/Modals/ManualModal/ManualModal"; -import ContentStatus from "@/types/enum/content.status.enum"; const ServiceManual = ({ lentStartHandler, diff --git a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx b/frontend/src/Cabinet/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx similarity index 100% rename from frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx rename to frontend/src/Cabinet/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx diff --git a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx similarity index 88% rename from frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx rename to frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx index eab36d48e..acdd21610 100644 --- a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -14,19 +14,19 @@ import { currentSectionNameState, myCabinetInfoState, numberOfAdminWorkState, - userState, -} from "@/recoil/atoms"; -import { currentBuildingFloorState } from "@/recoil/selectors"; -import LeftMainNav from "@/components/LeftNav/LeftMainNav/LeftMainNav"; +} from "@/Cabinet/recoil/atoms"; +import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; +import LeftMainNav from "@/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav"; import { CabinetInfoByBuildingFloorDto, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; -import { axiosCabinetByBuildingFloor } from "@/api/axios/axios.custom"; -import { removeCookie } from "@/api/react_cookie/cookies"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/types/dto/cabinet.dto"; +import { axiosCabinetByBuildingFloor } from "@/Cabinet/api/axios/axios.custom"; +import { removeCookie } from "@/Cabinet/api/react_cookie/cookies"; +import useMenu from "@/Cabinet/hooks/useMenu"; const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { + const currentBuildingName = useRecoilValue(currentBuildingNameState); const floors = useRecoilValue>(currentBuildingFloorState); const [currentFloor, setCurrentFloor] = useRecoilState( currentFloorNumberState @@ -68,7 +68,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { currentSectionFromPersist && sections.includes(currentSectionFromPersist) ? setCurrentSection(currentSectionFromPersist) - : setCurrentSection(response.data[0].section); + : setCurrentSection(response.data[0]?.section); }) .catch((error) => { console.error(error); @@ -104,11 +104,6 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { closeAll(); }; - const onClickLentLogButton = () => { - navigator("log"); - closeAll(); - }; - const onClickSearchButton = () => { navigator("search"); closeAll(); @@ -150,16 +145,16 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { resetBuilding(); resetCurrentFloor(); resetCurrentSection(); - navigator("login"); + navigator("/login"); }; return ( - - Home - - {floors && - floors.map((floor, index) => ( + {currentBuildingName === "새롬관" && ( + <> + + Home + + {floors && + floors.map((floor, index) => ( + onClickFloorButton(floor)} + key={index} + > + {floor + "층"} + + ))} onClickFloorButton(floor)} - key={index} + onClick={onClickAvailableButton} > - {floor + "층"} + 사용가능 - ))} - - 사용가능 - + + )} @@ -121,7 +125,7 @@ const LeftMainNav = ({ )} - {!isAdmin && ( + {!isAdmin && currentBuildingName === "새롬관" && ( <> { const floorSection = useRecoilValue>(currentFloorSectionState); diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx similarity index 93% rename from frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx rename to frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index d291f4bfe..f5adb344b 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; -import CabinetColorTable from "@/components/LeftNav/CabinetColorTable/CabinetColorTable"; -import LeftSectionNavClubs from "@/components/LeftNav/LeftSectionNav/LeftSectionNavClubs"; -import { ReactComponent as LinkImg } from "@/assets/images/link.svg"; +import CabinetColorTable from "@/Cabinet/components/LeftNav/CabinetColorTable/CabinetColorTable"; +import LeftSectionNavClubs from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs"; +import { ReactComponent as LinkImg } from "@/Cabinet/assets/images/link.svg"; interface ILeftSectionNav { isVisible: boolean; diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx similarity index 86% rename from frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx rename to frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx index fe0ceb52e..6c425b127 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx @@ -1,12 +1,12 @@ import { useRecoilState, useRecoilValue } from "recoil"; import styled from "styled-components"; -import { myClubListState, targetClubInfoState } from "@/recoil/atoms"; -import { FloorSectionStyled } from "@/components/LeftNav/LeftSectionNav/LeftSectionNav"; +import { myClubListState, targetClubInfoState } from "@/Cabinet/recoil/atoms"; +import { FloorSectionStyled } from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav"; import { ClubPaginationResponseDto, ClubResponseDto, -} from "@/types/dto/club.dto"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/types/dto/club.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; const LeftSectionNavClubs = () => { const clubList = useRecoilValue(myClubListState); diff --git a/frontend/src/components/LentLog/AdminCabinetLentLog.container.tsx b/frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.container.tsx similarity index 79% rename from frontend/src/components/LentLog/AdminCabinetLentLog.container.tsx rename to frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.container.tsx index cbfde1e8b..d001b3cae 100644 --- a/frontend/src/components/LentLog/AdminCabinetLentLog.container.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.container.tsx @@ -1,12 +1,11 @@ import { useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; -import { currentCabinetIdState } from "@/recoil/atoms"; -import AdminCabinetLentLog from "@/components/LentLog/AdminCabinetLentLog"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { axiosGetCabinetLentLog } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import { getTotalPage } from "@/utils/dateUtils"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { currentCabinetIdState } from "@/Cabinet/recoil/atoms"; +import AdminCabinetLentLog from "@/Cabinet/components/LentLog/AdminCabinetLentLog"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { axiosGetCabinetLentLog } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const AdminCabinetLentLogContainer = () => { const { closeLent } = useMenu(); diff --git a/frontend/src/components/LentLog/AdminCabinetLentLog.tsx b/frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.tsx similarity index 84% rename from frontend/src/components/LentLog/AdminCabinetLentLog.tsx rename to frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.tsx index 465278fb9..62cfb8e14 100644 --- a/frontend/src/components/LentLog/AdminCabinetLentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.tsx @@ -1,6 +1,6 @@ import styled, { css } from "styled-components"; -import AdminCabinetLogTable from "@/components/LentLog/LogTable/AdminCabinetLogTable"; -import { ILentLog } from "@/types/dto/lent.dto"; +import AdminCabinetLogTable from "@/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable"; +import { ILentLog } from "@/Cabinet/types/dto/lent.dto"; const AdminCabinetLentLog = ({ closeLent, @@ -23,7 +23,10 @@ const AdminCabinetLentLog = ({ > - + @@ -36,7 +39,10 @@ const AdminCabinetLentLog = ({ > - + diff --git a/frontend/src/components/LentLog/AdminLentLog.tsx b/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx similarity index 87% rename from frontend/src/components/LentLog/AdminLentLog.tsx rename to frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx index 14b93ca70..d1eb6884c 100644 --- a/frontend/src/components/LentLog/AdminLentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; import styled from "styled-components"; -import AdminCabinetLentLogContainer from "@/components/LentLog/AdminCabinetLentLog.container"; -import AdminUserLentLogContainer from "@/components/LentLog/AdminUserLentLog.container"; -import useMenu from "@/hooks/useMenu"; +import AdminCabinetLentLogContainer from "@/Cabinet/components/LentLog/AdminCabinetLentLog.container"; +import AdminUserLentLogContainer from "@/Cabinet/components/LentLog/AdminUserLentLog.container"; +import useMenu from "@/Cabinet/hooks/useMenu"; const AdminLentLog = ({ lentType }: { lentType: string }) => { const { closeLent } = useMenu(); @@ -36,7 +36,10 @@ const AdminLentLog = ({ lentType }: { lentType: string }) => { {isSearchPage && ( - + )} {getLentTypeText(togglelentType)} 대여 기록 diff --git a/frontend/src/components/LentLog/AdminUserLentLog.container.tsx b/frontend/src/Cabinet/components/LentLog/AdminUserLentLog.container.tsx similarity index 79% rename from frontend/src/components/LentLog/AdminUserLentLog.container.tsx rename to frontend/src/Cabinet/components/LentLog/AdminUserLentLog.container.tsx index c3d81056a..637e1cc8a 100644 --- a/frontend/src/components/LentLog/AdminUserLentLog.container.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminUserLentLog.container.tsx @@ -1,13 +1,11 @@ import { useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; -import { targetUserInfoState } from "@/recoil/atoms"; -import AdminUserLentLog from "@/components/LentLog/AdminUserLentLog"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { axiosGetUserLentLog } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import { getTotalPage } from "@/utils/dateUtils"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; - +import { targetUserInfoState } from "@/Cabinet/recoil/atoms"; +import AdminUserLentLog from "@/Cabinet/components/LentLog/AdminUserLentLog"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { axiosGetUserLentLog } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const AdminUserLentLogContainer = () => { const { closeLent } = useMenu(); diff --git a/frontend/src/components/LentLog/AdminUserLentLog.tsx b/frontend/src/Cabinet/components/LentLog/AdminUserLentLog.tsx similarity index 85% rename from frontend/src/components/LentLog/AdminUserLentLog.tsx rename to frontend/src/Cabinet/components/LentLog/AdminUserLentLog.tsx index fb5f7b388..4891d092b 100644 --- a/frontend/src/components/LentLog/AdminUserLentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminUserLentLog.tsx @@ -1,6 +1,6 @@ import styled, { css } from "styled-components"; -import LogTable from "@/components/LentLog/LogTable/LogTable"; -import { ILentLog } from "@/types/dto/lent.dto"; +import LogTable from "@/Cabinet/components/LentLog/LogTable/LogTable"; +import { ILentLog } from "@/Cabinet/types/dto/lent.dto"; const AdminUserLentLog = ({ closeLent, @@ -23,7 +23,10 @@ const AdminUserLentLog = ({ > - + @@ -36,7 +39,10 @@ const AdminUserLentLog = ({ > - + diff --git a/frontend/src/components/LentLog/LentLog.container.tsx b/frontend/src/Cabinet/components/LentLog/LentLog.container.tsx similarity index 74% rename from frontend/src/components/LentLog/LentLog.container.tsx rename to frontend/src/Cabinet/components/LentLog/LentLog.container.tsx index aab412598..7ebed068b 100644 --- a/frontend/src/components/LentLog/LentLog.container.tsx +++ b/frontend/src/Cabinet/components/LentLog/LentLog.container.tsx @@ -1,11 +1,10 @@ import { useEffect, useState } from "react"; -import LentLog from "@/components/LentLog/LentLog"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { axiosMyLentLog } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import { getTotalPage } from "@/utils/dateUtils"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; - +import LentLog from "@/Cabinet/components/LentLog/LentLog"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { axiosMyLentLog } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { getTotalPage } from "@/Cabinet/utils/paginationUtils"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const LentLogContainer = () => { const { closeLent } = useMenu(); diff --git a/frontend/src/components/LentLog/LentLog.tsx b/frontend/src/Cabinet/components/LentLog/LentLog.tsx similarity index 93% rename from frontend/src/components/LentLog/LentLog.tsx rename to frontend/src/Cabinet/components/LentLog/LentLog.tsx index 506498117..1ae64d62f 100644 --- a/frontend/src/components/LentLog/LentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/LentLog.tsx @@ -1,6 +1,6 @@ import styled, { css } from "styled-components"; -import LogTable from "@/components/LentLog/LogTable/LogTable"; -import { ILentLog } from "@/types/dto/lent.dto"; +import LogTable from "@/Cabinet/components/LentLog/LogTable/LogTable"; +import { ILentLog } from "@/Cabinet/types/dto/lent.dto"; const LentLog = ({ closeLent, @@ -95,10 +95,10 @@ const TitleStyled = styled.h1` const LentLogStyled = styled.div` position: fixed; - top: 80px; + top: 120px; right: 0; min-width: 330px; - height: calc(100% - 80px); + height: calc(100% - 120px); padding: 40px 20px; z-index: 9; transform: translateX(120%); diff --git a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx b/frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx similarity index 84% rename from frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx rename to frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx index 8296cb404..f459bf18d 100644 --- a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx +++ b/frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const dateOptions: Intl.DateTimeFormatOptions = { year: "2-digit", @@ -35,10 +35,14 @@ const AdminCabinetLogTable = ({ {new Date(startedAt).toLocaleString("ko-KR", dateOptions)} - - {endedAt - ? new Date(endedAt).toLocaleString("ko-KR", dateOptions) - : '-'} + + {endedAt + ? new Date(endedAt).toLocaleString("ko-KR", dateOptions) + : "-"} ) diff --git a/frontend/src/components/LentLog/LogTable/LogTable.tsx b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx similarity index 92% rename from frontend/src/components/LentLog/LogTable/LogTable.tsx rename to frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx index 94e162a7f..502ba75e7 100644 --- a/frontend/src/components/LentLog/LogTable/LogTable.tsx +++ b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const dateOptions: Intl.DateTimeFormatOptions = { year: "2-digit", diff --git a/frontend/src/components/Login/AdminLoginTemplate.tsx b/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx similarity index 93% rename from frontend/src/components/Login/AdminLoginTemplate.tsx rename to frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx index 6d6cab21a..76b8ac459 100644 --- a/frontend/src/components/Login/AdminLoginTemplate.tsx +++ b/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx @@ -2,12 +2,12 @@ import axios from "axios"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import UnavailableModal from "@/components/Modals/UnavailableModal/UnavailableModal"; -import { additionalModalType } from "@/assets/data/maps"; -import { ReactComponent as AdminLoginImg } from "@/assets/images/adminLoginImg.svg"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; -import { axiosAdminAuthLogin } from "@/api/axios/axios.custom"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import UnavailableModal from "@/Cabinet/components/Modals/UnavailableModal/UnavailableModal"; +import { additionalModalType } from "@/Cabinet/assets/data/maps"; +import { ReactComponent as AdminLoginImg } from "@/Cabinet/assets/images/adminLoginImg.svg"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import { axiosAdminAuthLogin } from "@/Cabinet/api/axios/axios.custom"; const AdminLoginTemplate = (props: { url: string; diff --git a/frontend/src/components/Login/LoginTemplate.tsx b/frontend/src/Cabinet/components/Login/LoginTemplate.tsx similarity index 93% rename from frontend/src/components/Login/LoginTemplate.tsx rename to frontend/src/Cabinet/components/Login/LoginTemplate.tsx index ff55945b2..2ca9cb8d4 100644 --- a/frontend/src/components/Login/LoginTemplate.tsx +++ b/frontend/src/Cabinet/components/Login/LoginTemplate.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import styled from "styled-components"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { ReactComponent as LoginImg } from "@/assets/images/loginImg.svg"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ReactComponent as LoginImg } from "@/Cabinet/assets/images/loginImg.svg"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; const LoginTemplate = (props: { url: string; @@ -62,7 +62,6 @@ const LoginPageStyled = styled.div` display: flex; justify-content: space-between; align-items: center; - height: 80px; width: 100%; height: 100%; `; diff --git a/frontend/src/components/Login/__tests__/LoginTemplate.test.tsx b/frontend/src/Cabinet/components/Login/__tests__/LoginTemplate.test.tsx similarity index 82% rename from frontend/src/components/Login/__tests__/LoginTemplate.test.tsx rename to frontend/src/Cabinet/components/Login/__tests__/LoginTemplate.test.tsx index 7c929655f..e6da197ab 100644 --- a/frontend/src/components/Login/__tests__/LoginTemplate.test.tsx +++ b/frontend/src/Cabinet/components/Login/__tests__/LoginTemplate.test.tsx @@ -1,13 +1,13 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it, test } from "vitest"; -import LoginTemplate from "@/components/Login/LoginTemplate"; +import LoginTemplate from "@/Cabinet/components/Login/LoginTemplate"; describe("Login Template test", () => { it("should render properly", () => { const url = `${import.meta.env.VITE_BE_HOST}/v4/auth/login`; const pageTitle = "42cabi"; const pageSubTitle = "여러분의 일상을 가볍게"; - const imgSrc = "/src/assets/images/loginImg.svg"; + const imgSrc = "/src/Cabinet/assets/images/loginImg.svg"; render( { }; const MapFloorSelectStyled = styled.div` - background: url("/src/assets/images/select.svg") var(--main-color) no-repeat - 80% 55%; + background: url("/src/Cabinet/assets/images/select.svg") var(--main-color) + no-repeat 80% 55%; color: white; cursor: pointer; width: 65px; diff --git a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx b/frontend/src/Cabinet/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx similarity index 100% rename from frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx rename to frontend/src/Cabinet/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx diff --git a/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx b/frontend/src/Cabinet/components/MapInfo/MapGrid/MapGrid.tsx similarity index 86% rename from frontend/src/components/MapInfo/MapGrid/MapGrid.tsx rename to frontend/src/Cabinet/components/MapInfo/MapGrid/MapGrid.tsx index 5ec8c0e7e..7036f21e8 100644 --- a/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapGrid/MapGrid.tsx @@ -3,9 +3,9 @@ import styled from "styled-components"; import { currentSectionNameState, isCurrentSectionRenderState, -} from "@/recoil/atoms"; -import MapItem from "@/components/MapInfo/MapItem/MapItem"; -import { mapPostionData } from "@/assets/data/mapPositionData"; +} from "@/Cabinet/recoil/atoms"; +import MapItem from "@/Cabinet/components/MapInfo/MapItem/MapItem"; +import { mapPostionData } from "@/Cabinet/assets/data/mapPositionData"; const MapGrid = ({ floor }: { floor: number }) => { const setSection = useSetRecoilState(currentSectionNameState); diff --git a/frontend/src/components/MapInfo/MapInfo.container.tsx b/frontend/src/Cabinet/components/MapInfo/MapInfo.container.tsx similarity index 86% rename from frontend/src/components/MapInfo/MapInfo.container.tsx rename to frontend/src/Cabinet/components/MapInfo/MapInfo.container.tsx index bfee7ffb1..e64181718 100644 --- a/frontend/src/components/MapInfo/MapInfo.container.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapInfo.container.tsx @@ -1,9 +1,12 @@ import React, { useEffect, useRef, useState } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; -import { currentFloorNumberState, currentMapFloorState } from "@/recoil/atoms"; -import { currentBuildingFloorState } from "@/recoil/selectors"; -import MapInfo from "@/components/MapInfo/MapInfo"; -import useMenu from "@/hooks/useMenu"; +import { + currentFloorNumberState, + currentMapFloorState, +} from "@/Cabinet/recoil/atoms"; +import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; +import MapInfo from "@/Cabinet/components/MapInfo/MapInfo"; +import useMenu from "@/Cabinet/hooks/useMenu"; const MapInfoContainer = () => { const { closeMap } = useMenu(); diff --git a/frontend/src/components/MapInfo/MapInfo.tsx b/frontend/src/Cabinet/components/MapInfo/MapInfo.tsx similarity index 83% rename from frontend/src/components/MapInfo/MapInfo.tsx rename to frontend/src/Cabinet/components/MapInfo/MapInfo.tsx index 6d7e138f7..e03b7d385 100644 --- a/frontend/src/components/MapInfo/MapInfo.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapInfo.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; -import MapFloorSelect from "@/components/MapInfo/MapFloorSelect/MapFloorSelect"; -import MapGrid from "@/components/MapInfo/MapGrid/MapGrid"; +import MapFloorSelect from "@/Cabinet/components/MapInfo/MapFloorSelect/MapFloorSelect"; +import MapGrid from "@/Cabinet/components/MapInfo/MapGrid/MapGrid"; const MapInfo = ({ touchStart, @@ -28,7 +28,7 @@ const MapInfo = ({ @@ -54,11 +54,11 @@ const HeaderStyled = styled.div` const MapInfoContainerStyled = styled.div` position: fixed; - top: 80px; + top: 120px; right: 0; min-width: 330px; width: 330px; - height: calc(100% - 80px); + height: calc(100% - 120px); padding: 40px; z-index: 9; transform: translateX(120%); @@ -68,6 +68,7 @@ const MapInfoContainerStyled = styled.div` flex-direction: column; align-items: center; background: var(--white); + overflow-y: auto; `; export default MapInfo; diff --git a/frontend/src/components/MapInfo/MapItem/MapItem.tsx b/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx similarity index 85% rename from frontend/src/components/MapInfo/MapItem/MapItem.tsx rename to frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx index a20ddc0a6..fbf51d512 100644 --- a/frontend/src/components/MapInfo/MapItem/MapItem.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx @@ -2,11 +2,11 @@ import React from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { useRecoilValue, useSetRecoilState } from "recoil"; import styled from "styled-components"; -import { currentFloorNumberState } from "@/recoil/atoms"; -import { currentBuildingFloorState } from "@/recoil/selectors"; -import { ISectionInfo } from "@/assets/data/mapPositionData"; -import SectionType from "@/types/enum/map.type.enum"; -import useMenu from "@/hooks/useMenu"; +import { currentFloorNumberState } from "@/Cabinet/recoil/atoms"; +import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; +import { ISectionInfo } from "@/Cabinet/assets/data/mapPositionData"; +import SectionType from "@/Cabinet/types/enum/map.type.enum"; +import useMenu from "@/Cabinet/hooks/useMenu"; const MapItem: React.FC<{ floor: number; @@ -78,7 +78,7 @@ const ItemStyled = styled.div<{ const IconContainerStyled = styled.div` width: 100%; height: 48px; - background-image: url("/src/assets/images/stairs.svg"); + background-image: url("/src/Cabinet/assets/images/stairs.svg"); background-size: contain; background-repeat: no-repeat; `; diff --git a/frontend/src/components/Modals/BanModal/BanModal.tsx b/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx similarity index 85% rename from frontend/src/components/Modals/BanModal/BanModal.tsx rename to frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx index 820ba5c52..838f8fb59 100644 --- a/frontend/src/components/Modals/BanModal/BanModal.tsx +++ b/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx @@ -1,24 +1,24 @@ -import React, { useState } from "react"; -import { useSetRecoilState } from "recoil"; import { - bannedUserListState, - isCurrentSectionRenderState, - numberOfAdminWorkState, - targetUserInfoState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; + axiosDeleteCurrentBanLog, + axiosGetBannedUserList, +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import IconType from "@/types/enum/icon.type.enum"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { - axiosDeleteCurrentBanLog, - axiosGetBannedUserList, -} from "@/api/axios/axios.custom"; -import { handleBannedUserList } from "@/utils/tableUtils"; + bannedUserListState, + isCurrentSectionRenderState, + numberOfAdminWorkState, + targetUserInfoState, +} from "@/Cabinet/recoil/atoms"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { handleBannedUserList } from "@/Cabinet/utils/tableUtils"; +import React, { useState } from "react"; +import { useSetRecoilState } from "recoil"; const BanModal: React.FC<{ userId: number | null; diff --git a/frontend/src/components/Modals/CancelModal/CancelModal.tsx b/frontend/src/Cabinet/components/Modals/CancelModal/CancelModal.tsx similarity index 86% rename from frontend/src/components/Modals/CancelModal/CancelModal.tsx rename to frontend/src/Cabinet/components/Modals/CancelModal/CancelModal.tsx index 1620dab8a..97d7660c4 100644 --- a/frontend/src/components/Modals/CancelModal/CancelModal.tsx +++ b/frontend/src/Cabinet/components/Modals/CancelModal/CancelModal.tsx @@ -1,26 +1,26 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosCancel, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosCancel, - axiosMyLentInfo, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const CancelModal: React.FC<{ lentType: string; diff --git a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.container.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.container.tsx similarity index 86% rename from frontend/src/components/Modals/ClubModal/AddClubMemberModal.container.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.container.tsx index bc05995fc..d8a9e4452 100644 --- a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.container.tsx @@ -3,14 +3,14 @@ import { useRecoilValue, useSetRecoilState } from "recoil"; import { isCurrentSectionRenderState, targetClubInfoState, -} from "@/recoil/atoms"; -import AddClubMemberModal from "@/components/Modals/ClubModal/AddClubMemberModal"; -import ModalPortal from "@/components/Modals/ModalPortal"; +} from "@/Cabinet/recoil/atoms"; +import AddClubMemberModal from "@/Cabinet/components/Modals/ClubModal/AddClubMemberModal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { axiosAddClubMember } from "@/api/axios/axios.custom"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { axiosAddClubMember } from "@/Cabinet/api/axios/axios.custom"; const AddClubMemberModalContainer: React.FC<{ closeModal: React.MouseEventHandler; diff --git a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx similarity index 98% rename from frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx index 5962e3f87..bffce3be1 100644 --- a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx @@ -1,5 +1,5 @@ +import Button from "@/Cabinet/components/Common/Button"; import styled from "styled-components"; -import Button from "@/components/Common/Button"; const MAX_INPUT_LENGTH = 27; diff --git a/frontend/src/components/Modals/ClubModal/ClubMemoModal.container.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.container.tsx similarity index 91% rename from frontend/src/components/Modals/ClubModal/ClubMemoModal.container.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.container.tsx index bfb67014f..01696fadc 100644 --- a/frontend/src/components/Modals/ClubModal/ClubMemoModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.container.tsx @@ -3,14 +3,14 @@ import { useRecoilValue, useSetRecoilState } from "recoil"; import { isCurrentSectionRenderState, targetClubInfoState, -} from "@/recoil/atoms"; -import ModalPortal from "@/components//Modals/ModalPortal"; -import ClubMemoModal from "@/components/Modals/ClubModal/ClubMemoModal"; +} from "@/Cabinet/recoil/atoms"; +import ClubMemoModal from "@/Cabinet/components/Modals/ClubModal/ClubMemoModal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { axiosUpdateClubNotice } from "@/api/axios/axios.custom"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { axiosUpdateClubNotice } from "@/Cabinet/api/axios/axios.custom"; export const CLUB_MEMO_MAX_LENGTH = 100; interface ClubMemoModalContainerInterface { diff --git a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.tsx similarity index 97% rename from frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.tsx index 5307e1172..f2e73bef4 100644 --- a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.tsx @@ -1,9 +1,9 @@ -import styled from "styled-components"; -import Button from "@/components/Common/Button"; +import Button from "@/Cabinet/components/Common/Button"; import { CLUB_MEMO_MAX_LENGTH, ClubMemoModalInterface, -} from "@/components/Modals/ClubModal/ClubMemoModal.container"; +} from "@/Cabinet/components/Modals/ClubModal/ClubMemoModal.container"; +import styled from "styled-components"; const ClubMemoModal = ({ clubNotice, diff --git a/frontend/src/components/Modals/ClubModal/ClubModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx similarity index 95% rename from frontend/src/components/Modals/ClubModal/ClubModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx index 32badd0ec..e60ce09af 100644 --- a/frontend/src/components/Modals/ClubModal/ClubModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx @@ -1,20 +1,20 @@ -import React, { useEffect, useRef, useState } from "react"; -import { useRecoilState } from "recoil"; -import styled from "styled-components"; -import { selectedClubInfoState } from "@/recoil/atoms"; -import Button from "@/components/Common/Button"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { ClubUserDto } from "@/types/dto/lent.dto"; import { axiosCreateClubUser, axiosDeleteClubUser, axiosEditClubUser, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Button from "@/Cabinet/components/Common/Button"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { selectedClubInfoState } from "@/Cabinet/recoil/atoms"; +import { ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import React, { useEffect, useRef, useState } from "react"; +import { useRecoilState } from "recoil"; +import styled from "styled-components"; interface ClubModalContainerInterface { type: string; diff --git a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.container.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container.tsx similarity index 88% rename from frontend/src/components/Modals/ClubModal/ClubPasswordModal.container.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container.tsx index e2e456d97..f86267ec7 100644 --- a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container.tsx @@ -3,18 +3,18 @@ import { useRecoilValue, useSetRecoilState } from "recoil"; import { isCurrentSectionRenderState, targetClubInfoState, -} from "@/recoil/atoms"; -import { IModalContents } from "@/components//Modals/Modal"; -import ModalPortal from "@/components//Modals/ModalPortal"; +} from "@/Cabinet/recoil/atoms"; +import ClubPasswordModal from "@/Cabinet/components/Modals/ClubModal/ClubPasswordModal"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components//Modals/ResponseModal/ResponseModal"; -import ClubPasswordModal from "@/components/Modals/ClubModal/ClubPasswordModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import IconType from "@/types/enum/icon.type.enum"; -import { axiosUpdateClubMemo } from "@/api/axios/axios.custom"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { axiosUpdateClubMemo } from "@/Cabinet/api/axios/axios.custom"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; interface ClubPasswordModalContainerInterface { password: string; diff --git a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.tsx similarity index 93% rename from frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.tsx index da107fe15..436d795e4 100644 --- a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.tsx @@ -1,8 +1,8 @@ import React from "react"; import styled from "styled-components"; -import Button from "@/components/Common/Button"; -import { ClubPasswordModalInterface } from "@/components/Modals/ClubModal/ClubPasswordModal.container"; -import { ReactComponent as CheckIcon } from "@/assets/images/checkIcon.svg"; +import Button from "@/Cabinet/components/Common/Button"; +import { ClubPasswordModalInterface } from "@/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container"; +import { ReactComponent as CheckIcon } from "@/Cabinet/assets/images/checkIcon.svg"; const ClubPasswordModal: React.FC = ({ ClubPwModalContents, diff --git a/frontend/src/components/Modals/ClubModal/DeleteClubMemberModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal.tsx similarity index 80% rename from frontend/src/components/Modals/ClubModal/DeleteClubMemberModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal.tsx index c1ab35e03..62c1bb1d4 100644 --- a/frontend/src/components/Modals/ClubModal/DeleteClubMemberModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal.tsx @@ -1,17 +1,17 @@ import { useState } from "react"; import { useSetRecoilState } from "recoil"; -import { isCurrentSectionRenderState } from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; +import { isCurrentSectionRenderState } from "@/Cabinet/recoil/atoms"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import { ClubUserResponseDto } from "@/types/dto/club.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { axiosDeleteClubMember } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { ClubUserResponseDto } from "@/Cabinet/types/dto/club.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { axiosDeleteClubMember } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const DeleteClubMemberModal: React.FC<{ closeModal: React.MouseEventHandler; diff --git a/frontend/src/components/Modals/ClubModal/MandateClubMemberModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/MandateClubMemberModal.tsx similarity index 86% rename from frontend/src/components/Modals/ClubModal/MandateClubMemberModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/MandateClubMemberModal.tsx index 56beb78fc..b9797e3a4 100644 --- a/frontend/src/components/Modals/ClubModal/MandateClubMemberModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/MandateClubMemberModal.tsx @@ -1,11 +1,11 @@ import { useState } from "react"; import { useSetRecoilState } from "recoil"; -import { isCurrentSectionRenderState } from "@/recoil/atoms"; -import { modalPropsMap } from "@/assets/data/maps"; -import { ClubUserResponseDto } from "@/types/dto/club.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { axiosMandateClubMember } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +import { isCurrentSectionRenderState } from "@/Cabinet/recoil/atoms"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { ClubUserResponseDto } from "@/Cabinet/types/dto/club.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { axiosMandateClubMember } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; import Modal, { IModalContents } from "../Modal"; import ModalPortal from "../ModalPortal"; import { diff --git a/frontend/src/components/Modals/ExtendModal/ExtendModal.tsx b/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx similarity index 88% rename from frontend/src/components/Modals/ExtendModal/ExtendModal.tsx rename to frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx index f200c983c..60ebafbe3 100644 --- a/frontend/src/components/Modals/ExtendModal/ExtendModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx @@ -1,27 +1,27 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosMyLentInfo, + axiosUseExtension, // axiosExtend, // TODO: 연장권 api 생성 후 연결해야 함 +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosMyLentInfo, - axiosUseExtension, // axiosExtend, // TODO: 연장권 api 생성 후 연결해야 함 -} from "@/api/axios/axios.custom"; -import { getExtendedDateString } from "@/utils/dateUtils"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { getExtendedDateString } from "@/Cabinet/utils/dateUtils"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const ExtendModal: React.FC<{ onClose: () => void; diff --git a/frontend/src/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx b/frontend/src/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx similarity index 85% rename from frontend/src/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx rename to frontend/src/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx index d39faded9..5638faf55 100644 --- a/frontend/src/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx @@ -1,27 +1,27 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosLentShareId, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import PasswordContainer from "@/Cabinet/components/Modals/PasswordCheckModal/PasswordContainer"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import PasswordContainer from "@/components/Modals/PasswordCheckModal/PasswordContainer"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosLentShareId, - axiosMyLentInfo, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import PasswordCheckModal from "../PasswordCheckModal/PasswordCheckModal"; const InvitationCodeModalContainer: React.FC<{ diff --git a/frontend/src/components/Modals/LentModal/ClubLentModal.tsx b/frontend/src/Cabinet/components/Modals/LentModal/ClubLentModal.tsx similarity index 86% rename from frontend/src/components/Modals/LentModal/ClubLentModal.tsx rename to frontend/src/Cabinet/components/Modals/LentModal/ClubLentModal.tsx index 9493920d5..c11fbd791 100644 --- a/frontend/src/components/Modals/LentModal/ClubLentModal.tsx +++ b/frontend/src/Cabinet/components/Modals/LentModal/ClubLentModal.tsx @@ -1,24 +1,24 @@ -import React, { useState } from "react"; -import { useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosLentClubCabinet, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, numberOfAdminWorkState, selectedClubInfoState, targetCabinetInfoState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { - axiosCabinetById, - axiosLentClubCabinet, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import React, { useState } from "react"; +import { useRecoilValue, useSetRecoilState } from "recoil"; const ClubLentModal: React.FC<{ lentType: string; diff --git a/frontend/src/components/Modals/LentModal/LentModal.tsx b/frontend/src/Cabinet/components/Modals/LentModal/LentModal.tsx similarity index 86% rename from frontend/src/components/Modals/LentModal/LentModal.tsx rename to frontend/src/Cabinet/components/Modals/LentModal/LentModal.tsx index 8b978ce9c..614cfaafa 100644 --- a/frontend/src/components/Modals/LentModal/LentModal.tsx +++ b/frontend/src/Cabinet/components/Modals/LentModal/LentModal.tsx @@ -1,29 +1,29 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosLentId, + axiosLentShareId, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosLentId, - axiosLentShareId, - axiosMyLentInfo, -} from "@/api/axios/axios.custom"; -import { getExpireDateString } from "@/utils/dateUtils"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { getExpireDateString } from "@/Cabinet/utils/dateUtils"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const LentModal: React.FC<{ lentType: string; diff --git a/frontend/src/components/Modals/ManualModal/ManualModal.tsx b/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx similarity index 96% rename from frontend/src/components/Modals/ManualModal/ManualModal.tsx rename to frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx index b3755a336..02d415aee 100644 --- a/frontend/src/components/Modals/ManualModal/ManualModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx @@ -1,9 +1,9 @@ import React from "react"; import { useState } from "react"; import styled, { css, keyframes } from "styled-components"; -import { manualContentData } from "@/assets/data/ManualContent"; -import { ReactComponent as MoveBtnImg } from "@/assets/images/moveButton.svg"; -import ContentStatus from "@/types/enum/content.status.enum"; +import { manualContentData } from "@/Cabinet/assets/data/ManualContent"; +import { ReactComponent as MoveBtnImg } from "@/Cabinet/assets/images/moveButton.svg"; +import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface ModalProps { contentStatus: ContentStatus; diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.container.tsx b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.container.tsx similarity index 89% rename from frontend/src/components/Modals/MemoModal/MemoModal.container.tsx rename to frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.container.tsx index e0b32e12d..4f795b5c0 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.container.tsx @@ -1,15 +1,18 @@ import React from "react"; import { useRecoilState } from "recoil"; -import { currentFloorCabinetState, myCabinetInfoState } from "@/recoil/atoms"; -import MemoModal from "@/components/Modals/MemoModal/MemoModal"; +import { + currentFloorCabinetState, + myCabinetInfoState, +} from "@/Cabinet/recoil/atoms"; +import MemoModal from "@/Cabinet/components/Modals/MemoModal/MemoModal"; import { CabinetInfoByBuildingFloorDto, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/types/dto/cabinet.dto"; import { axiosUpdateMyCabinetInfo, // axiosUpdateCabinetMemo, // axiosUpdateCabinetTitle, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; const MemoModalContainer = (props: { onClose: React.MouseEventHandler; diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx similarity index 97% rename from frontend/src/components/Modals/MemoModal/MemoModal.tsx rename to frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx index a41c76fcb..e065aee58 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx @@ -1,8 +1,8 @@ +import Button from "@/Cabinet/components/Common/Button"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import React, { useRef, useState } from "react"; import styled from "styled-components"; -import Button from "@/components/Common/Button"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import CabinetType from "@/types/enum/cabinet.type.enum"; export interface MemoModalInterface { cabinetType: CabinetType; diff --git a/frontend/src/components/Modals/Modal.tsx b/frontend/src/Cabinet/components/Modals/Modal.tsx similarity index 91% rename from frontend/src/components/Modals/Modal.tsx rename to frontend/src/Cabinet/components/Modals/Modal.tsx index 4422d0790..0f8d25869 100644 --- a/frontend/src/components/Modals/Modal.tsx +++ b/frontend/src/Cabinet/components/Modals/Modal.tsx @@ -1,12 +1,12 @@ import React, { ReactElement } from "react"; import styled, { css } from "styled-components"; -import AdminClubLogContainer from "@/components/Club/AdminClubLog.container"; -import Button from "@/components/Common/Button"; -import { ReactComponent as CheckIcon } from "@/assets/images/checkIcon.svg"; -import { ReactComponent as ErrorIcon } from "@/assets/images/errorIcon.svg"; -import { ReactComponent as NotificationIcon } from "@/assets/images/notificationSign.svg"; -import IconType from "@/types/enum/icon.type.enum"; -import useMultiSelect from "@/hooks/useMultiSelect"; +import AdminClubLogContainer from "@/Cabinet/components/Club/AdminClubLog.container"; +import Button from "@/Cabinet/components/Common/Button"; +import { ReactComponent as CheckIcon } from "@/Cabinet/assets/images/checkIcon.svg"; +import { ReactComponent as ErrorIcon } from "@/Cabinet/assets/images/errorIcon.svg"; +import { ReactComponent as NotificationIcon } from "@/Cabinet/assets/images/notificationSign.svg"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; /** * @interface diff --git a/frontend/src/components/Modals/ModalPortal.tsx b/frontend/src/Cabinet/components/Modals/ModalPortal.tsx similarity index 100% rename from frontend/src/components/Modals/ModalPortal.tsx rename to frontend/src/Cabinet/components/Modals/ModalPortal.tsx diff --git a/frontend/src/components/Modals/NotificationModal/NotificationModal.tsx b/frontend/src/Cabinet/components/Modals/NotificationModal/NotificationModal.tsx similarity index 57% rename from frontend/src/components/Modals/NotificationModal/NotificationModal.tsx rename to frontend/src/Cabinet/components/Modals/NotificationModal/NotificationModal.tsx index a997a1095..297cad861 100644 --- a/frontend/src/components/Modals/NotificationModal/NotificationModal.tsx +++ b/frontend/src/Cabinet/components/Modals/NotificationModal/NotificationModal.tsx @@ -1,22 +1,24 @@ -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import IconType from "@/types/enum/icon.type.enum"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; export const NotificationModal = ({ title, detail, closeModal, + iconType = IconType.NOTIFICATIONICON, }: { title: string; detail: string; closeModal: React.MouseEventHandler; + iconType?: IconType; }) => { const modalContents: IModalContents = { type: "noBtn", title: title, detail: detail, closeModal: closeModal, - iconType: IconType.NOTIFICATIONICON, + iconType: iconType, }; return ( diff --git a/frontend/src/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx b/frontend/src/Cabinet/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx similarity index 79% rename from frontend/src/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx rename to frontend/src/Cabinet/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx index 3611f66be..0878099b2 100644 --- a/frontend/src/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx +++ b/frontend/src/Cabinet/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useState } from "react"; -import Modal from "@/components/Modals/Modal"; -import { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import IconType from "@/types/enum/icon.type.enum"; -import { formatDate } from "@/utils/dateUtils"; +import Modal from "@/Cabinet/components/Modals/Modal"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { formatDate } from "@/Cabinet/utils/dateUtils"; const OverduePenaltyModal: React.FC<{ status: CabinetStatus | additionalModalType; diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx similarity index 83% rename from frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx rename to frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx index 0307005f8..181041663 100644 --- a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx @@ -1,28 +1,28 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosMyLentInfo, + axiosSendCabinetPassword, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import PasswordCheckModal from "@/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal"; +import PasswordContainer from "@/Cabinet/components/Modals/PasswordCheckModal/PasswordContainer"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import PasswordCheckModal from "@/components/Modals/PasswordCheckModal/PasswordCheckModal"; -import PasswordContainer from "@/components/Modals/PasswordCheckModal/PasswordContainer"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosMyLentInfo, - axiosSendCabinetPassword, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const PasswordCheckModalContainer: React.FC<{ onClose: () => void; diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx similarity index 93% rename from frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx rename to frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx index 4618d460d..992bd59e2 100644 --- a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx +++ b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx @@ -1,9 +1,9 @@ import React from "react"; import styled, { css } from "styled-components"; -import Button from "@/components/Common/Button"; -import { IModalContents } from "@/components/Modals/Modal"; -import { ReactComponent as CheckIcon } from "@/assets/images/checkIcon.svg"; -import useMultiSelect from "@/hooks/useMultiSelect"; +import Button from "@/Cabinet/components/Common/Button"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import { ReactComponent as CheckIcon } from "@/Cabinet/assets/images/checkIcon.svg"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; const PasswordCheckModal: React.FC<{ modalContents: IModalContents; diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordContainer.tsx b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordContainer.tsx similarity index 100% rename from frontend/src/components/Modals/PasswordCheckModal/PasswordContainer.tsx rename to frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordContainer.tsx diff --git a/frontend/src/components/Modals/ResponseModal/ResponseModal.tsx b/frontend/src/Cabinet/components/Modals/ResponseModal/ResponseModal.tsx similarity index 74% rename from frontend/src/components/Modals/ResponseModal/ResponseModal.tsx rename to frontend/src/Cabinet/components/Modals/ResponseModal/ResponseModal.tsx index a3eb2b329..e65691b22 100644 --- a/frontend/src/components/Modals/ResponseModal/ResponseModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ResponseModal/ResponseModal.tsx @@ -1,7 +1,7 @@ +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; import React from "react"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import IconType from "@/types/enum/icon.type.enum"; export const SuccessResponseModal: React.FC<{ modalTitle?: string; @@ -15,7 +15,11 @@ export const SuccessResponseModal: React.FC<{ iconType: IconType.CHECKICON, }; - return ; + return ( + + + + ); }; export const FailResponseModal: React.FC<{ diff --git a/frontend/src/components/Modals/ReturnModal/AdminReturnModal.tsx b/frontend/src/Cabinet/components/Modals/ReturnModal/AdminReturnModal.tsx similarity index 81% rename from frontend/src/components/Modals/ReturnModal/AdminReturnModal.tsx rename to frontend/src/Cabinet/components/Modals/ReturnModal/AdminReturnModal.tsx index 60926544a..4d9998cba 100644 --- a/frontend/src/components/Modals/ReturnModal/AdminReturnModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ReturnModal/AdminReturnModal.tsx @@ -9,27 +9,27 @@ import { overdueCabinetListState, targetCabinetInfoState, targetUserInfoState, -} from "@/recoil/atoms"; -import Selector from "@/components/Common/Selector"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; +} from "@/Cabinet/recoil/atoms"; +import Selector from "@/Cabinet/components/Common/Selector"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { UserInfo } from "@/types/dto/user.dto"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import IconType from "@/types/enum/icon.type.enum"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { UserInfo } from "@/Cabinet/types/dto/user.dto"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; import { axiosBundleReturn, axiosCabinetById, axiosGetOverdueUserList, axiosReturnByUserId, -} from "@/api/axios/axios.custom"; -import useMultiSelect from "@/hooks/useMultiSelect"; -import { handleOverdueUserList } from "@/utils/tableUtils"; +} from "@/Cabinet/api/axios/axios.custom"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; +import { handleOverdueUserList } from "@/Cabinet/utils/tableUtils"; const AdminReturnModal: React.FC<{ lentType?: CabinetType; @@ -90,7 +90,7 @@ const AdminReturnModal: React.FC<{ const renderSelector = () => ( { return { key: info.userId, value: info.name }; })} @@ -151,31 +151,31 @@ const AdminReturnModal: React.FC<{ return; } await axiosReturnByUserId(selectedUserIds) - .then(async (res) => { - setIsCurrentSectionRender(true); - setNumberOfAdminWork((prev) => prev + 1); - setModalTitle("반납되었습니다"); - const overdueUserData = await axiosGetOverdueUserList(); - setOverdueUserList(handleOverdueUserList(overdueUserData)); - setSelectedUserIds([]); - try { - const { data } = await axiosCabinetById(currentCabinetId); - setTargetCabinetInfo(data); - } catch (error) { - throw error; - } - }) - .catch((error) => { - setHasErrorOnResponse(true); - setModalTitle(error.response.data.message); - }) - .finally(() => { - setShowResponseModal(true); - }); - } catch (error) { - console.error(error); - } -}; + .then(async (res) => { + setIsCurrentSectionRender(true); + setNumberOfAdminWork((prev) => prev + 1); + setModalTitle("반납되었습니다"); + const overdueUserData = await axiosGetOverdueUserList(); + setOverdueUserList(handleOverdueUserList(overdueUserData)); + setSelectedUserIds([]); + try { + const { data } = await axiosCabinetById(currentCabinetId); + setTargetCabinetInfo(data); + } catch (error) { + throw error; + } + }) + .catch((error) => { + setHasErrorOnResponse(true); + setModalTitle(error.response.data.message); + }) + .finally(() => { + setShowResponseModal(true); + }); + } catch (error) { + console.error(error); + } + }; const tryBundleReturnRequest = async (e: React.MouseEvent) => { const returnableCabinetIdList = targetCabinetInfoList diff --git a/frontend/src/components/Modals/ReturnModal/ReturnModal.tsx b/frontend/src/Cabinet/components/Modals/ReturnModal/ReturnModal.tsx similarity index 86% rename from frontend/src/components/Modals/ReturnModal/ReturnModal.tsx rename to frontend/src/Cabinet/components/Modals/ReturnModal/ReturnModal.tsx index 4c3c4744a..ba6bd38c2 100644 --- a/frontend/src/components/Modals/ReturnModal/ReturnModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ReturnModal/ReturnModal.tsx @@ -1,31 +1,31 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosMyLentInfo, + axiosReturn, +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { getDefaultCabinetInfo } from "@/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { getDefaultCabinetInfo } from "@/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosMyLentInfo, - axiosReturn, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; import { getExpireDateString, getShortenedExpireDateString, -} from "@/utils/dateUtils"; +} from "@/Cabinet/utils/dateUtils"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const ReturnModal: React.FC<{ lentType: string; diff --git a/frontend/src/components/Modals/StatusModal/StatusModal.container.tsx b/frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.container.tsx similarity index 90% rename from frontend/src/components/Modals/StatusModal/StatusModal.container.tsx rename to frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.container.tsx index 2c07ad33e..3d4ba5bb0 100644 --- a/frontend/src/components/Modals/StatusModal/StatusModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.container.tsx @@ -6,19 +6,19 @@ import { isCurrentSectionRenderState, numberOfAdminWorkState, targetCabinetInfoState, -} from "@/recoil/atoms"; -import StatusModal from "@/components/Modals/StatusModal/StatusModal"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/recoil/atoms"; +import StatusModal from "@/Cabinet/components/Modals/StatusModal/StatusModal"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import { axiosCabinetById, axiosGetBrokenCabinetList, // axiosBundleUpdateCabinetStatus, // axiosBundleUpdateCabinetType, axiosUpdateCabinets, -} from "@/api/axios/axios.custom"; -import useMultiSelect from "@/hooks/useMultiSelect"; -import { handleBrokenCabinetList } from "@/utils/tableUtils"; +} from "@/Cabinet/api/axios/axios.custom"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; +import { handleBrokenCabinetList } from "@/Cabinet/utils/tableUtils"; const StatusModalContainer = (props: { onClose: React.MouseEventHandler; diff --git a/frontend/src/components/Modals/StatusModal/StatusModal.tsx b/frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.tsx similarity index 95% rename from frontend/src/components/Modals/StatusModal/StatusModal.tsx rename to frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.tsx index 8c31e0b45..34244abd0 100644 --- a/frontend/src/components/Modals/StatusModal/StatusModal.tsx +++ b/frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.tsx @@ -1,17 +1,17 @@ import React, { useState } from "react"; import styled from "styled-components"; -import Button from "@/components/Common/Button"; -import Dropdown from "@/components/Common/Dropdown"; +import Button from "@/Cabinet/components/Common/Button"; +import Dropdown from "@/Cabinet/components/Common/Dropdown"; import WarningNotification, { WarningNotificationProps, -} from "@/components/Common/WarningNotification"; +} from "@/Cabinet/components/Common/WarningNotification"; import { cabinetIconSrcMap, cabinetStatusLabelMap, cabinetTypeLabelMap, -} from "@/assets/data/maps"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/assets/data/maps"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import ModalPortal from "../ModalPortal"; export interface StatusModalInterface { diff --git a/frontend/src/components/Modals/SwapModal/SwapModal.tsx b/frontend/src/Cabinet/components/Modals/SwapModal/SwapModal.tsx similarity index 93% rename from frontend/src/components/Modals/SwapModal/SwapModal.tsx rename to frontend/src/Cabinet/components/Modals/SwapModal/SwapModal.tsx index 98aece572..43daf74f4 100644 --- a/frontend/src/components/Modals/SwapModal/SwapModal.tsx +++ b/frontend/src/Cabinet/components/Modals/SwapModal/SwapModal.tsx @@ -1,20 +1,20 @@ -import { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosMyLentInfo, + axiosSwapId, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import { modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosMyLentInfo, - axiosSwapId, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import Modal, { IModalContents } from "../Modal"; import ModalPortal from "../ModalPortal"; import { diff --git a/frontend/src/components/Modals/UnavailableModal/UnavailableModal.tsx b/frontend/src/Cabinet/components/Modals/UnavailableModal/UnavailableModal.tsx similarity index 56% rename from frontend/src/components/Modals/UnavailableModal/UnavailableModal.tsx rename to frontend/src/Cabinet/components/Modals/UnavailableModal/UnavailableModal.tsx index b613e69dd..1fac1ff39 100644 --- a/frontend/src/components/Modals/UnavailableModal/UnavailableModal.tsx +++ b/frontend/src/Cabinet/components/Modals/UnavailableModal/UnavailableModal.tsx @@ -1,10 +1,10 @@ import React from "react"; -import Modal from "@/components/Modals/Modal"; -import { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import IconType from "@/types/enum/icon.type.enum"; +import Modal from "@/Cabinet/components/Modals/Modal"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; const UnavailableModal: React.FC<{ status: CabinetStatus | additionalModalType; diff --git a/frontend/src/components/Search/NoSearch.tsx b/frontend/src/Cabinet/components/Search/NoSearch.tsx similarity index 88% rename from frontend/src/components/Search/NoSearch.tsx rename to frontend/src/Cabinet/components/Search/NoSearch.tsx index 8b7f36302..1a9376ca1 100644 --- a/frontend/src/components/Search/NoSearch.tsx +++ b/frontend/src/Cabinet/components/Search/NoSearch.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; const NoSearch = () => ( - 로고 블랙 + 로고 블랙

검색 결과가 없습니다

diff --git a/frontend/src/components/Search/SearchDefault.tsx b/frontend/src/Cabinet/components/Search/SearchDefault.tsx similarity index 91% rename from frontend/src/components/Search/SearchDefault.tsx rename to frontend/src/Cabinet/components/Search/SearchDefault.tsx index b0fd9fd59..abe268566 100644 --- a/frontend/src/components/Search/SearchDefault.tsx +++ b/frontend/src/Cabinet/components/Search/SearchDefault.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; const SearchDefault = () => ( diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/Cabinet/components/Search/SearchItemByIntraId.tsx similarity index 92% rename from frontend/src/components/Search/SearchItemByIntraId.tsx rename to frontend/src/Cabinet/components/Search/SearchItemByIntraId.tsx index a061463d0..9da747cff 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/Cabinet/components/Search/SearchItemByIntraId.tsx @@ -6,18 +6,18 @@ import { selectedTypeOnSearchState, targetCabinetInfoState, targetUserInfoState, -} from "@/recoil/atoms"; -import ChangeToHTML from "@/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; +} from "@/Cabinet/recoil/atoms"; +import ChangeToHTML from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { axiosAdminCabinetInfoByCabinetId } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/assets/data/maps"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { axiosAdminCabinetInfoByCabinetId } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; interface ISearchDetail { name: string; diff --git a/frontend/src/components/Search/SearchItemByNum.tsx b/frontend/src/Cabinet/components/Search/SearchItemByNum.tsx similarity index 89% rename from frontend/src/components/Search/SearchItemByNum.tsx rename to frontend/src/Cabinet/components/Search/SearchItemByNum.tsx index db67bfab6..c92023965 100644 --- a/frontend/src/components/Search/SearchItemByNum.tsx +++ b/frontend/src/Cabinet/components/Search/SearchItemByNum.tsx @@ -4,18 +4,18 @@ import { currentCabinetIdState, selectedTypeOnSearchState, targetCabinetInfoState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentDto } from "@/types/dto/lent.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { axiosAdminCabinetInfoByCabinetId } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/assets/data/maps"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { axiosAdminCabinetInfoByCabinetId } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const reformIntraId = (lents: LentDto[]) => { if (!lents || lents.length === 0) { diff --git a/frontend/src/components/SectionPagination/SectionPagination.container.tsx b/frontend/src/Cabinet/components/SectionPagination/SectionPagination.container.tsx similarity index 91% rename from frontend/src/components/SectionPagination/SectionPagination.container.tsx rename to frontend/src/Cabinet/components/SectionPagination/SectionPagination.container.tsx index 26fb258eb..b1e6a1718 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.container.tsx +++ b/frontend/src/Cabinet/components/SectionPagination/SectionPagination.container.tsx @@ -1,11 +1,11 @@ -import React from "react"; -import { useRecoilState, useRecoilValue } from "recoil"; +import SectionPagination from "@/Cabinet/components/SectionPagination/SectionPagination"; import { currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; -import { currentFloorSectionState } from "@/recoil/selectors"; -import SectionPagination from "@/components/SectionPagination/SectionPagination"; +} from "@/Cabinet/recoil/atoms"; +import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; +import React from "react"; +import { useRecoilState, useRecoilValue } from "recoil"; const SectionPaginationContainer = (): JSX.Element => { const floor = useRecoilValue(currentFloorNumberState); diff --git a/frontend/src/components/SectionPagination/SectionPagination.tsx b/frontend/src/Cabinet/components/SectionPagination/SectionPagination.tsx similarity index 94% rename from frontend/src/components/SectionPagination/SectionPagination.tsx rename to frontend/src/Cabinet/components/SectionPagination/SectionPagination.tsx index 2e42cedd5..98def7f03 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.tsx +++ b/frontend/src/Cabinet/components/SectionPagination/SectionPagination.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import LeftSectionButton from "@/assets/images/LeftSectionButton.svg"; +import LeftSectionButton from "@/Cabinet/assets/images/LeftSectionButton.svg"; const SectionPagination: React.FC<{ currentSectionName: string; @@ -71,7 +71,7 @@ const SectionBarStyled = styled.div` align-items: center; `; -const MoveSectionButtonStyled = styled.img<{ arrowReversed?: boolean }>` +export const MoveSectionButtonStyled = styled.img<{ arrowReversed?: boolean }>` width: 24px; height: 24px; margin: 0px 15px; diff --git a/frontend/src/components/TopNav/AdminTopNav.container.tsx b/frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx similarity index 71% rename from frontend/src/components/TopNav/AdminTopNav.container.tsx rename to frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx index 6afb724f4..157ac8157 100644 --- a/frontend/src/components/TopNav/AdminTopNav.container.tsx +++ b/frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx @@ -1,10 +1,14 @@ import React, { SetStateAction, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; -import { buildingsFloorState, currentBuildingNameState } from "@/recoil/atoms"; -import { buildingsState } from "@/recoil/selectors"; -import TopNav from "@/components/TopNav/TopNav"; -import { axiosBuildingFloor } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +import { + buildingsFloorState, + currentBuildingNameState, +} from "@/Cabinet/recoil/atoms"; +import { buildingsState } from "@/Cabinet/recoil/selectors"; +import TopNav from "@/Cabinet/components/TopNav/TopNav"; +import { axiosBuildingFloor } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const AdminTopNavContainer: React.FC<{ setIsLoading: React.Dispatch>; @@ -17,6 +21,7 @@ const AdminTopNavContainer: React.FC<{ const buildingsList = useRecoilValue>(buildingsState); const { setIsLoading } = props; const { toggleLeftNav } = useMenu(); + const navigator = useNavigate(); const onClickLogo = () => { toggleLeftNav(); @@ -32,7 +37,7 @@ const AdminTopNavContainer: React.FC<{ try { await setTimeoutPromise(500); const buildingsFloorData = await axiosBuildingFloor(); - setBuildingsFloor(buildingsFloorData.data); + setBuildingsFloor([...buildingsFloorData.data]); } catch (error) { console.log(error); } @@ -43,10 +48,16 @@ const AdminTopNavContainer: React.FC<{ useEffect(() => { if (buildingsList.length === 0) return; - setCurrentBuildingName(buildingsList[0]); }, [buildingsList]); + useEffect(() => { + if (currentBuildingName === undefined) return; + else if (currentBuildingName === "새롬관") { + navigator("/admin/main"); + } + }, [currentBuildingName]); + return ( { const navigate = useNavigate(); @@ -218,7 +218,8 @@ const SearchBarInputStyled = styled.input` `; const SearchButtonStyled = styled.button` - background: url("/src/assets/images/searchWhite.svg") no-repeat 50% 50%; + background: url("/src/Cabinet/assets/images/searchWhite.svg") no-repeat 50% + 50%; width: 32px; height: 32px; position: absolute; diff --git a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx similarity index 91% rename from frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx rename to frontend/src/Cabinet/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx index cc56979f0..e3204e8d4 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx +++ b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx @@ -1,6 +1,6 @@ +import SearchListItem from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem"; +import { CabinetSimple } from "@/Cabinet/types/dto/cabinet.dto"; import styled from "styled-components"; -import SearchListItem from "@/components/TopNav/SearchBar/SearchListItem/SearchListItem"; -import { CabinetSimple } from "@/types/dto/cabinet.dto"; interface ISearchListByIntraId { name: string; diff --git a/frontend/src/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx similarity index 100% rename from frontend/src/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx rename to frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx diff --git a/frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx similarity index 88% rename from frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx rename to frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx index 549ccfa0a..225e52838 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx +++ b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import ChangeToHTML from "@/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; +import ChangeToHTML from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; const SearchListItem = (props: { floor?: number; @@ -21,8 +21,8 @@ const SearchListItem = (props: { const navigate = useNavigate(); const chooseImage = (isCabinet: boolean | undefined) => { - if (isCabinet) return "/src/assets/images/cabinet.svg"; - return "/src/assets/images/privateIcon.svg"; + if (isCabinet) return "/src/Cabinet/assets/images/cabinet.svg"; + return "/src/Cabinet/assets/images/privateIcon.svg"; }; return ( diff --git a/frontend/src/components/TopNav/TopNav.container.tsx b/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx similarity index 74% rename from frontend/src/components/TopNav/TopNav.container.tsx rename to frontend/src/Cabinet/components/TopNav/TopNav.container.tsx index d12cfebfc..23ee2077e 100644 --- a/frontend/src/components/TopNav/TopNav.container.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx @@ -1,15 +1,19 @@ import React, { SetStateAction, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import { buildingsFloorState, currentBuildingNameState, myCabinetInfoState, -} from "@/recoil/atoms"; -import { buildingsState } from "@/recoil/selectors"; -import TopNav from "@/components/TopNav/TopNav"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import { axiosBuildingFloor, axiosMyLentInfo } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import { buildingsState } from "@/Cabinet/recoil/selectors"; +import TopNav from "@/Cabinet/components/TopNav/TopNav"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import { + axiosBuildingFloor, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const TopNavContainer: React.FC<{ setIsLoading: React.Dispatch>; @@ -24,6 +28,7 @@ const TopNavContainer: React.FC<{ const buildingsList = useRecoilValue>(buildingsState); const { setIsLoading } = props; const { toggleLeftNav } = useMenu(); + const navigator = useNavigate(); const onClickLogo = () => { toggleLeftNav(); @@ -37,8 +42,7 @@ const TopNavContainer: React.FC<{ try { await setTimeoutPromise(500); const buildingsFloorData = await axiosBuildingFloor(); - - setBuildingsFloor(buildingsFloorData.data); + setBuildingsFloor([...buildingsFloorData.data]); } catch (error) { console.log(error); } @@ -60,10 +64,16 @@ const TopNavContainer: React.FC<{ useEffect(() => { if (buildingsList.length === 0) return; - setCurrentBuildingName(buildingsList[0]); }, [buildingsList]); + useEffect(() => { + if (currentBuildingName === undefined) return; + else if (currentBuildingName === "새롬관") { + navigator("/home"); + } + }, [currentBuildingName]); + return ( ; } +interface ITopNavProps { + currentBuildingName: string; + buildingsList: Array; + buildingClicked: boolean; + setBuildingClicked: React.Dispatch; + onClickLogo: React.MouseEventHandler; + setCurrentBuildingName: SetterOrUpdater; + isAdmin?: boolean; +} + const BuildingListItem: React.FC = ({ building, onUpdate, @@ -30,16 +41,6 @@ const BuildingListItem: React.FC = ({ ); }; -interface ITopNav { - currentBuildingName: string; - buildingsList: Array; - buildingClicked: boolean; - setBuildingClicked: React.Dispatch; - onClickLogo: React.MouseEventHandler; - setCurrentBuildingName: SetterOrUpdater; - isAdmin?: boolean; -} - const TopNav = ({ currentBuildingName, buildingsList, @@ -48,55 +49,67 @@ const TopNav = ({ onClickLogo, setCurrentBuildingName, isAdmin = false, -}: ITopNav) => { +}: ITopNavProps): JSX.Element => { const buildingDom = React.useRef(null); useOutsideClick(buildingDom, () => { if (buildingClicked) setBuildingClicked(false); }); return ( - - - {isAdmin && } - + + + + + {isAdmin && } + + ); }; const TopNavContainerStyled = styled.nav` + width: 100%; + display: flex; + flex-direction: column; + background-color: white; + z-index: 10; +`; + +const TopNavWrapperStyled = styled.div` width: 100%; height: 80px; min-height: 80px; display: flex; justify-content: space-between; align-items: center; - background-color: white; - border-bottom: 1px solid #bcbcbc; + border-bottom: 1px solid var(--line-color); padding: 0 28px; color: var(--gray-color); - z-index: 10; `; const LogoStyled = styled.div` @@ -140,7 +153,7 @@ const BuildingSelectBoxStyled = styled.span` transform: translateY(-50%); width: 20px; height: 20px; - background: url(/src/assets/images/select.svg) no-repeat 100%; + background: url(/src/Cabinet/assets/images/select.svg) no-repeat 100%; } `; diff --git a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx similarity index 100% rename from frontend/src/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx rename to frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx diff --git a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx similarity index 81% rename from frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx rename to frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx index ef9b90630..b55572cae 100644 --- a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx @@ -6,17 +6,17 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import TopNavButton from "@/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentDto } from "@/types/dto/lent.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/recoil/atoms"; +import TopNavButton from "@/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import { axiosCabinetById, axiosDeleteCurrentBanLog, -} from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; export const getDefaultCabinetInfo = () => ({ building: "", @@ -93,7 +93,7 @@ const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => { {import.meta.env.VITE_UNBAN === "true" && ( axiosDeleteCurrentBanLog(myInfo.userId)} - imgSrc="/src/assets/images/happyCcabiWhite.png" + imgSrc="/src/Cabinet/assets/images/happyCcabiWhite.png" width="32px" height="32px" /> @@ -102,7 +102,7 @@ const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => { { )} - + ); }; diff --git a/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx b/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx new file mode 100644 index 000000000..1cf23e060 --- /dev/null +++ b/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx @@ -0,0 +1,108 @@ +import { useLocation, useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { ReactComponent as CabiLogo } from "@/Cabinet/assets/images/logo.svg"; +import { ReactComponent as PresentationLogo } from "@/Presentation/assets/images/logo.svg"; + +interface ITopNavDomain { + path: string; + adminPath: string; + logo: React.FunctionComponent>; + title: string; + active: (pathname: string) => boolean; +} + +const domains: ITopNavDomain[] = [ + { + path: "/home", + adminPath: "/admin/home", + logo: CabiLogo, + title: "Cabi", + active: (pathname) => !pathname.includes("presentation"), + }, + { + path: "/presentation/home", + adminPath: "/admin/presentation/detail", + logo: PresentationLogo, + title: "수요지식회", + active: (pathname) => pathname.includes("presentation"), + }, +]; + +const TopNavDomainGroup = ({ isAdmin = false }: { isAdmin?: boolean }) => { + const navigator = useNavigate(); + const { pathname } = useLocation(); + return ( + + {domains.map((domain, index) => ( + + navigator(isAdmin ? domain.adminPath : domain.path)} + > + + + + + {domain.title} + + + {index < domains.length - 1 && } + + ))} + + ); +}; + +const DomainGroupContainerStyled = styled.div` + display: flex; + align-items: center; + width: 100%; + height: 40px; + border-bottom: 1px solid var(--line-color); + padding: 0 28px; + color: var(--gray-color); + font-size: 0.875rem; +`; + +const DomainWrapperStyled = styled.div` + display: flex; + align-items: center; +`; + +const DomainContainerStyled = styled.div` + display: flex; + align-items: center; +`; + +const LogoContainerStyled = styled.div` + display: flex; + align-items: center; + width: 14px; + height: 14px; + cursor: pointer; + svg { + .logo_svg__currentPath { + fill: var(--default-main-color); + } + } +`; + +const DomainTitleStyled = styled.div<{ fontWeight: string }>` + display: flex; + align-items: center; + font-size: 0.875rem; + font-weight: ${(props) => props.fontWeight}; + margin-left: 4px; + cursor: pointer; +`; + +const DomainSeparatorStyled = styled.div` + width: 1px; + height: 20px; + margin: 0 8px; + background-color: #d9d9d9; +`; + +export default TopNavDomainGroup; diff --git a/frontend/src/components/UserInfoArea/UserInfoArea.container.tsx b/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.container.tsx similarity index 75% rename from frontend/src/components/UserInfoArea/UserInfoArea.container.tsx rename to frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.container.tsx index 600d098ad..9fd2b7c08 100644 --- a/frontend/src/components/UserInfoArea/UserInfoArea.container.tsx +++ b/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.container.tsx @@ -1,10 +1,10 @@ import { useRecoilValue } from "recoil"; -import { targetUserInfoState } from "@/recoil/atoms"; -import AdminLentLog from "@/components/LentLog/AdminLentLog"; +import { targetUserInfoState } from "@/Cabinet/recoil/atoms"; +import AdminLentLog from "@/Cabinet/components/LentLog/AdminLentLog"; import UserInfoArea, { ISelectedUserInfo, -} from "@/components/UserInfoArea/UserInfoArea"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/components/UserInfoArea/UserInfoArea"; +import useMenu from "@/Cabinet/hooks/useMenu"; const UserInfoAreaContainer = (): JSX.Element => { const targetUserInfo = useRecoilValue(targetUserInfoState); diff --git a/frontend/src/components/UserInfoArea/UserInfoArea.tsx b/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.tsx similarity index 89% rename from frontend/src/components/UserInfoArea/UserInfoArea.tsx rename to frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.tsx index 2ef8ef0aa..8ed3518a5 100644 --- a/frontend/src/components/UserInfoArea/UserInfoArea.tsx +++ b/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.tsx @@ -1,17 +1,17 @@ import React, { useState } from "react"; import styled from "styled-components"; -import ButtonContainer from "@/components/Common/Button"; -import BanModal from "@/components/Modals/BanModal/BanModal"; -import AdminReturnModal from "@/components/Modals/ReturnModal/AdminReturnModal"; -import ChangeToHTML from "@/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; +import ButtonContainer from "@/Cabinet/components/Common/Button"; +import BanModal from "@/Cabinet/components/Modals/BanModal/BanModal"; +import AdminReturnModal from "@/Cabinet/components/Modals/ReturnModal/AdminReturnModal"; +import ChangeToHTML from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import cabiLogo from "@/assets/images/logo.svg"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/assets/data/maps"; +import cabiLogo from "@/Cabinet/assets/images/logo.svg"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; export interface ISelectedUserInfo { name: string; diff --git a/frontend/src/constants/StatusCode.ts b/frontend/src/Cabinet/constants/StatusCode.ts similarity index 100% rename from frontend/src/constants/StatusCode.ts rename to frontend/src/Cabinet/constants/StatusCode.ts diff --git a/frontend/src/firebase/firebase-messaging-sw.ts b/frontend/src/Cabinet/firebase/firebase-messaging-sw.ts similarity index 100% rename from frontend/src/firebase/firebase-messaging-sw.ts rename to frontend/src/Cabinet/firebase/firebase-messaging-sw.ts diff --git a/frontend/src/hooks/useAdminHomeApi.ts b/frontend/src/Cabinet/hooks/useAdminHomeApi.ts similarity index 93% rename from frontend/src/hooks/useAdminHomeApi.ts rename to frontend/src/Cabinet/hooks/useAdminHomeApi.ts index 34f91767e..6a75e83c5 100644 --- a/frontend/src/hooks/useAdminHomeApi.ts +++ b/frontend/src/Cabinet/hooks/useAdminHomeApi.ts @@ -3,19 +3,19 @@ import { ICabinetNumbersPerFloor, IMonthlyData, ITableData, -} from "@/types/dto/admin.dto"; +} from "@/Cabinet/types/dto/admin.dto"; import { axiosGetBannedUserList, axiosGetBrokenCabinetList, axiosGetCabinetNumbersPerFloor, axiosGetOverdueUserList, axiosGetStatistics, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; import { handleBannedUserList, handleBrokenCabinetList, handleOverdueUserList, -} from "@/utils/tableUtils"; +} from "@/Cabinet/utils/tableUtils"; export async function useAdminHomeApi( setMonthlyData: React.Dispatch>, diff --git a/frontend/src/hooks/useCabinetListRefresh.ts b/frontend/src/Cabinet/hooks/useCabinetListRefresh.ts similarity index 95% rename from frontend/src/hooks/useCabinetListRefresh.ts rename to frontend/src/Cabinet/hooks/useCabinetListRefresh.ts index 4c03f7982..f3850cc9c 100644 --- a/frontend/src/hooks/useCabinetListRefresh.ts +++ b/frontend/src/Cabinet/hooks/useCabinetListRefresh.ts @@ -5,17 +5,17 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { CabinetInfo, CabinetPreview, CabinetPreviewInfo, -} from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/types/dto/cabinet.dto"; import { axiosCabinetByBuildingFloor, axiosCabinetById, axiosMyLentInfo, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; const shouldUpdateTargetCabinetInfo = ( cabinet: CabinetPreviewInfo, diff --git a/frontend/src/hooks/useClubInfo.ts b/frontend/src/Cabinet/hooks/useClubInfo.ts similarity index 90% rename from frontend/src/hooks/useClubInfo.ts rename to frontend/src/Cabinet/hooks/useClubInfo.ts index 32081ba21..152676f21 100644 --- a/frontend/src/hooks/useClubInfo.ts +++ b/frontend/src/Cabinet/hooks/useClubInfo.ts @@ -4,14 +4,14 @@ import { isCurrentSectionRenderState, targetClubCabinetInfoState, targetClubInfoState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { ClubInfoResponseDto, ClubInfoResponseType, -} from "@/types/dto/club.dto"; -import { axiosGetClubInfo } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +} from "@/Cabinet/types/dto/club.dto"; +import { axiosGetClubInfo } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const useClubInfo = () => { const [clubState, setClubState] = useState({ clubId: 0, page: 0 }); diff --git a/frontend/src/hooks/useDebounce.tsx b/frontend/src/Cabinet/hooks/useDebounce.tsx similarity index 100% rename from frontend/src/hooks/useDebounce.tsx rename to frontend/src/Cabinet/hooks/useDebounce.tsx diff --git a/frontend/src/hooks/useIsMount.ts b/frontend/src/Cabinet/hooks/useIsMount.ts similarity index 100% rename from frontend/src/hooks/useIsMount.ts rename to frontend/src/Cabinet/hooks/useIsMount.ts diff --git a/frontend/src/hooks/useMenu.ts b/frontend/src/Cabinet/hooks/useMenu.ts similarity index 99% rename from frontend/src/hooks/useMenu.ts rename to frontend/src/Cabinet/hooks/useMenu.ts index 228b228e8..c3fa83f95 100644 --- a/frontend/src/hooks/useMenu.ts +++ b/frontend/src/Cabinet/hooks/useMenu.ts @@ -1,11 +1,11 @@ -import { useResetRecoilState } from "recoil"; import { currentCabinetIdState, currentIntraIdState, targetCabinetInfoState, targetClubUserInfoState, targetUserInfoState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; +import { useResetRecoilState } from "recoil"; const useMenu = () => { const resetTargetCabinetInfo = useResetRecoilState(targetCabinetInfoState); diff --git a/frontend/src/hooks/useMultiSelect.ts b/frontend/src/Cabinet/hooks/useMultiSelect.ts similarity index 94% rename from frontend/src/hooks/useMultiSelect.ts rename to frontend/src/Cabinet/hooks/useMultiSelect.ts index 03010a5d1..1b5a46a21 100644 --- a/frontend/src/hooks/useMultiSelect.ts +++ b/frontend/src/Cabinet/hooks/useMultiSelect.ts @@ -3,9 +3,12 @@ import { isMultiSelectState, selectedTypeOnSearchState, targetCabinetInfoListState, -} from "@/recoil/atoms"; -import { CabinetInfo, CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import { + CabinetInfo, + CabinetPreviewInfo, +} from "@/Cabinet/types/dto/cabinet.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; const useMultiSelect = () => { const [targetCabinetInfoList, setTargetCabinetInfoList] = useRecoilState< diff --git a/frontend/src/hooks/useOutsideClick.ts b/frontend/src/Cabinet/hooks/useOutsideClick.ts similarity index 100% rename from frontend/src/hooks/useOutsideClick.ts rename to frontend/src/Cabinet/hooks/useOutsideClick.ts diff --git a/frontend/src/pages/AvailablePage.tsx b/frontend/src/Cabinet/pages/AvailablePage.tsx similarity index 88% rename from frontend/src/pages/AvailablePage.tsx rename to frontend/src/Cabinet/pages/AvailablePage.tsx index f5a853c6f..0326464e1 100644 --- a/frontend/src/pages/AvailablePage.tsx +++ b/frontend/src/Cabinet/pages/AvailablePage.tsx @@ -1,20 +1,20 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { isCurrentSectionRenderState } from "@/recoil/atoms"; -import AvailableCountdown from "@/components/Available/AvailableCountdown"; -import FloorContainer from "@/components/Available/FloorContainer"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; +import { isCurrentSectionRenderState } from "@/Cabinet/recoil/atoms"; +import AvailableCountdown from "@/Cabinet/components/Available/AvailableCountdown"; +import FloorContainer from "@/Cabinet/components/Available/FloorContainer"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; import MultiToggleSwitch, { toggleItem, -} from "@/components/Common/MultiToggleSwitch"; +} from "@/Cabinet/components/Common/MultiToggleSwitch"; import { AvailableCabinetsInfo, CabinetPreviewInfo, -} from "@/types/dto/cabinet.dto"; -import { axiosGetAvailableCabinets } from "@/api/axios/axios.custom"; -import useDebounce from "@/hooks/useDebounce"; -import { deleteRecoilPersistFloorSection } from "@/utils/recoilPersistUtils"; +} from "@/Cabinet/types/dto/cabinet.dto"; +import { axiosGetAvailableCabinets } from "@/Cabinet/api/axios/axios.custom"; +import useDebounce from "@/Cabinet/hooks/useDebounce"; +import { deleteRecoilPersistFloorSection } from "@/Cabinet/utils/recoilPersistUtils"; enum AvailableCabinetsType { ALL = "ALL", @@ -137,7 +137,10 @@ const AvailablePage = () => { ) : ( <> - 새로고침 + 새로고침 setIsOpenTime(true)} /> )} diff --git a/frontend/src/pages/ClubPage.tsx b/frontend/src/Cabinet/pages/ClubPage.tsx similarity index 79% rename from frontend/src/pages/ClubPage.tsx rename to frontend/src/Cabinet/pages/ClubPage.tsx index 07462db5c..306a3f13a 100644 --- a/frontend/src/pages/ClubPage.tsx +++ b/frontend/src/Cabinet/pages/ClubPage.tsx @@ -1,11 +1,11 @@ import { useEffect } from "react"; import { useRecoilValue } from "recoil"; import styled from "styled-components"; -import { myClubListState } from "@/recoil/atoms"; -import ClubInfo from "@/components/Club/ClubInfo"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; -import { ClubPaginationResponseDto } from "@/types/dto/club.dto"; -import { deleteRecoilPersistFloorSection } from "@/utils/recoilPersistUtils"; +import { myClubListState } from "@/Cabinet/recoil/atoms"; +import ClubInfo from "@/Cabinet/components/Club/ClubInfo"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import { ClubPaginationResponseDto } from "@/Cabinet/types/dto/club.dto"; +import { deleteRecoilPersistFloorSection } from "@/Cabinet/utils/recoilPersistUtils"; const ClubPage = () => { const clubList = useRecoilValue(myClubListState); diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/Cabinet/pages/HomePage.tsx similarity index 67% rename from frontend/src/pages/HomePage.tsx rename to frontend/src/Cabinet/pages/HomePage.tsx index 579f58ac5..114b16dae 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/Cabinet/pages/HomePage.tsx @@ -1,9 +1,9 @@ import { useNavigate } from "react-router-dom"; import { useRecoilValue, useSetRecoilState } from "recoil"; -import { currentFloorNumberState } from "@/recoil/atoms"; -import { currentBuildingFloorState } from "@/recoil/selectors"; -import ServiceManual from "@/components/Home/ServiceManual"; -import "@/assets/css/homePage.css"; +import { currentFloorNumberState } from "@/Cabinet/recoil/atoms"; +import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; +import ServiceManual from "@/Cabinet/components/Home/ServiceManual"; +import "@/Cabinet/assets/css/homePage.css"; const HomePage = () => { const floors = useRecoilValue>(currentBuildingFloorState); diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/Cabinet/pages/Layout.tsx similarity index 81% rename from frontend/src/pages/Layout.tsx rename to frontend/src/Cabinet/pages/Layout.tsx index 5b6e6c440..569bfc95e 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/Cabinet/pages/Layout.tsx @@ -8,24 +8,26 @@ import { serverTimeState, targetClubInfoState, userState, -} from "@/recoil/atoms"; -import CabinetInfoAreaContainer from "@/components/CabinetInfoArea/CabinetInfoArea.container"; -import ClubMemberInfoAreaContainer from "@/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import LeftNav from "@/components/LeftNav/LeftNav"; -import MapInfoContainer from "@/components/MapInfo/MapInfo.container"; -import OverduePenaltyModal from "@/components/Modals/OverduePenaltyModal/OverduePenaltyModal"; -import TopNavContainer from "@/components/TopNav/TopNav.container"; -import { additionalModalType } from "@/assets/data/maps"; +} from "@/Cabinet/recoil/atoms"; +import CabinetInfoAreaContainer from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; +import ClubMemberInfoAreaContainer from "@/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import LeftNav from "@/Cabinet/components/LeftNav/LeftNav"; +import MapInfoContainer from "@/Cabinet/components/MapInfo/MapInfo.container"; +import OverduePenaltyModal from "@/Cabinet/components/Modals/OverduePenaltyModal/OverduePenaltyModal"; +import TopNavContainer from "@/Cabinet/components/TopNav/TopNav.container"; +import { additionalModalType } from "@/Cabinet/assets/data/maps"; import { ClubPaginationResponseDto, ClubResponseDto, -} from "@/types/dto/club.dto"; -import { UserDto, UserInfo } from "@/types/dto/user.dto"; -import ColorType from "@/types/enum/color.type.enum"; -import { axiosMyClubList, axiosMyInfo } from "@/api/axios/axios.custom"; -import { getCookie } from "@/api/react_cookie/cookies"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/types/dto/club.dto"; +import { UserDto, UserInfo } from "@/Cabinet/types/dto/user.dto"; +import { axiosMyClubList, axiosMyInfo } from "@/Cabinet/api/axios/axios.custom"; +import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; +import useMenu from "@/Cabinet/hooks/useMenu"; + +const root: HTMLElement = document.documentElement; +const token = getCookie("access_token"); const Layout = (): JSX.Element => { const [isLoading, setIsLoading] = useState(true); @@ -40,12 +42,16 @@ const Layout = (): JSX.Element => { useSetRecoilState(targetClubInfoState); const navigate = useNavigate(); const location = useLocation(); - const token = getCookie("access_token"); + const { closeAll } = useMenu(); const isRootPath: boolean = location.pathname === "/"; const isLoginPage: boolean = location.pathname === "/login"; const isMainPage: boolean = location.pathname === "/main"; + const savedMainColor = localStorage.getItem("main-color"); + const savedSubColor = localStorage.getItem("sub-color"); + const savedMineColor = localStorage.getItem("mine-color"); + const openModal = () => { setIsModalOpen(true); }; @@ -54,6 +60,10 @@ const Layout = (): JSX.Element => { setIsModalOpen(false); }; + const handleClickBg = () => { + closeAll(); + }; + const getMyInfo = async () => { try { const { @@ -91,11 +101,6 @@ const Layout = (): JSX.Element => { } }; - const savedMainColor = localStorage.getItem("main-color"); - const savedSubColor = localStorage.getItem("sub-color"); - const savedMineColor = localStorage.getItem("mine-color"); - const root: HTMLElement = document.documentElement; - useEffect(() => { if (!token && !isLoginPage) navigate("/login"); else if (token) { @@ -116,12 +121,6 @@ const Layout = (): JSX.Element => { root.style.setProperty("--mine", savedMineColor); }, [savedMainColor, savedSubColor, savedMineColor]); - const { closeAll } = useMenu(); - - const handleClickBg = () => { - closeAll(); - }; - return isLoginPage ? ( ) : ( @@ -157,8 +156,6 @@ const Layout = (): JSX.Element => { ); }; -export default Layout; - const WrapperStyled = styled.div` width: 100%; height: 100%; @@ -184,9 +181,9 @@ const DetailInfoContainerStyled = styled.div<{ isHomePage: boolean }>` props.isHomePage && css` position: fixed; - top: 80px; + top: 120px; right: 0; - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; @@ -200,3 +197,5 @@ const DetailInfoContainerStyled = styled.div<{ isHomePage: boolean }>` const MenuBgStyled = styled.div` position: none; `; + +export default Layout; diff --git a/frontend/src/pages/LogPage.tsx b/frontend/src/Cabinet/pages/LogPage.tsx similarity index 80% rename from frontend/src/pages/LogPage.tsx rename to frontend/src/Cabinet/pages/LogPage.tsx index 56ec7f156..8d84d5d79 100644 --- a/frontend/src/pages/LogPage.tsx +++ b/frontend/src/Cabinet/pages/LogPage.tsx @@ -1,10 +1,10 @@ import { useEffect, useState } from "react"; import styled from "styled-components"; -import LogTable from "@/components/LentLog/LogTable/LogTable"; -import { LentHistoryDto } from "@/types/dto/lent.dto"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { axiosMyLentLog } from "@/api/axios/axios.custom"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import LogTable from "@/Cabinet/components/LentLog/LogTable/LogTable"; +import { LentHistoryDto } from "@/Cabinet/types/dto/lent.dto"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { axiosMyLentLog } from "@/Cabinet/api/axios/axios.custom"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const LogPage = () => { const [lentLog, setLentLog] = useState(undefined); diff --git a/frontend/src/pages/LoginFailurePage.tsx b/frontend/src/Cabinet/pages/LoginFailurePage.tsx similarity index 84% rename from frontend/src/pages/LoginFailurePage.tsx rename to frontend/src/Cabinet/pages/LoginFailurePage.tsx index 53872545e..527b0cf06 100644 --- a/frontend/src/pages/LoginFailurePage.tsx +++ b/frontend/src/Cabinet/pages/LoginFailurePage.tsx @@ -1,5 +1,5 @@ +import AnnounceTemplate from "@/Cabinet/components/Announce/AnnounceTemplate"; import { useNavigate } from "react-router-dom"; -import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/Cabinet/pages/LoginPage.tsx similarity index 66% rename from frontend/src/pages/LoginPage.tsx rename to frontend/src/Cabinet/pages/LoginPage.tsx index 38479f786..880d5fab1 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/Cabinet/pages/LoginPage.tsx @@ -1,5 +1,5 @@ -import LoginTemplate from "@/components/Login/LoginTemplate"; -import "@/assets/css/loginPage.css"; +import LoginTemplate from "@/Cabinet/components/Login/LoginTemplate"; +import "@/Cabinet/assets/css/loginPage.css"; const LoginPage = () => { const ORIGIN_URL = window.location.origin; @@ -10,7 +10,7 @@ const LoginPage = () => { url={`${url}?redirect=${ORIGIN_URL}/post-login`} pageTitle="Cabi" pageSubTitle="여러분의 일상을 가볍게" - imgSrc="/src/assets/images/loginImg.svg" + imgSrc="/src/Cabinet/assets/images/loginImg.svg" /> ); }; diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/Cabinet/pages/MainPage.tsx similarity index 88% rename from frontend/src/pages/MainPage.tsx rename to frontend/src/Cabinet/pages/MainPage.tsx index 05848c76b..6948d9879 100644 --- a/frontend/src/pages/MainPage.tsx +++ b/frontend/src/Cabinet/pages/MainPage.tsx @@ -8,14 +8,14 @@ import { currentFloorNumberState, currentSectionNameState, targetCabinetInfoState, -} from "@/recoil/atoms"; -import { currentFloorSectionState } from "@/recoil/selectors"; -import CabinetListContainer from "@/components/CabinetList/CabinetList.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import SectionPaginationContainer from "@/components/SectionPagination/SectionPagination.container"; -import SectionType from "@/types/enum/map.type.enum"; -import useCabinetListRefresh from "@/hooks/useCabinetListRefresh"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; +import CabinetListContainer from "@/Cabinet/components/CabinetList/CabinetList.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import SectionPaginationContainer from "@/Cabinet/components/SectionPagination/SectionPagination.container"; +import SectionType from "@/Cabinet/types/enum/map.type.enum"; +import useCabinetListRefresh from "@/Cabinet/hooks/useCabinetListRefresh"; +import useMenu from "@/Cabinet/hooks/useMenu"; const MainPage = () => { const touchStartPosX = useRef(0); diff --git a/frontend/src/pages/NotFoundPage.tsx b/frontend/src/Cabinet/pages/NotFoundPage.tsx similarity index 86% rename from frontend/src/pages/NotFoundPage.tsx rename to frontend/src/Cabinet/pages/NotFoundPage.tsx index 0222f2fad..a481b69ce 100644 --- a/frontend/src/pages/NotFoundPage.tsx +++ b/frontend/src/Cabinet/pages/NotFoundPage.tsx @@ -1,5 +1,5 @@ +import AnnounceTemplate from "@/Cabinet/components/Announce/AnnounceTemplate"; import { useNavigate } from "react-router-dom"; -import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); diff --git a/frontend/src/pages/PostLogin.tsx b/frontend/src/Cabinet/pages/PostLogin.tsx similarity index 79% rename from frontend/src/pages/PostLogin.tsx rename to frontend/src/Cabinet/pages/PostLogin.tsx index f7a723b17..c840badad 100644 --- a/frontend/src/pages/PostLogin.tsx +++ b/frontend/src/Cabinet/pages/PostLogin.tsx @@ -1,15 +1,18 @@ import { deleteFcmToken, requestFcmAndGetDeviceToken, -} from "@/firebase/firebase-messaging-sw"; +} from "@/Cabinet/firebase/firebase-messaging-sw"; import { useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { useRecoilState, useSetRecoilState } from "recoil"; -import { userState } from "@/recoil/atoms"; -import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; -import { UserDto } from "@/types/dto/user.dto"; -import { axiosMyInfo, axiosUpdateDeviceToken } from "@/api/axios/axios.custom"; -import { getCookie } from "@/api/react_cookie/cookies"; +import { userState } from "@/Cabinet/recoil/atoms"; +import AnnounceTemplate from "@/Cabinet/components/Announce/AnnounceTemplate"; +import { UserDto } from "@/Cabinet/types/dto/user.dto"; +import { + axiosMyInfo, + axiosUpdateDeviceToken, +} from "@/Cabinet/api/axios/axios.custom"; +import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; const PostLogin = (): JSX.Element => { const [isLoading, setIsLoading] = useState(true); diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/Cabinet/pages/ProfilePage.tsx similarity index 70% rename from frontend/src/pages/ProfilePage.tsx rename to frontend/src/Cabinet/pages/ProfilePage.tsx index fb554cc5c..d8ab061e2 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/Cabinet/pages/ProfilePage.tsx @@ -1,19 +1,22 @@ import { deleteFcmToken, requestFcmAndGetDeviceToken, -} from "@/firebase/firebase-messaging-sw"; +} from "@/Cabinet/firebase/firebase-messaging-sw"; import { useEffect, useState } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; import styled from "styled-components"; -import { userState } from "@/recoil/atoms"; -import ExtensionCardContainer from "@/components/Card/ExtensionCard/ExtensionCard.container"; -import LentInfoCardContainer from "@/components/Card/LentInfoCard/LentInfoCard.container"; -import NotificationCardContainer from "@/components/Card/NotificationCard/NotificationCard.container"; -import ProfileCardContainer from "@/components/Card/ProfileCard/ProfileCard.container"; -import ThemeColorCardContainer from "@/components/Card/ThemeColorCard/ThemeColorCard.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { axiosMyInfo, axiosUpdateDeviceToken } from "@/api/axios/axios.custom"; -import { deleteRecoilPersistFloorSection } from "@/utils/recoilPersistUtils"; +import { userState } from "@/Cabinet/recoil/atoms"; +import ExtensionCardContainer from "@/Cabinet/components/Card/ExtensionCard/ExtensionCard.container"; +import LentInfoCardContainer from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard.container"; +import NotificationCardContainer from "@/Cabinet/components/Card/NotificationCard/NotificationCard.container"; +import ProfileCardContainer from "@/Cabinet/components/Card/ProfileCard/ProfileCard.container"; +import ThemeColorCardContainer from "@/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { + axiosMyInfo, + axiosUpdateDeviceToken, +} from "@/Cabinet/api/axios/axios.custom"; +import { deleteRecoilPersistFloorSection } from "@/Cabinet/utils/recoilPersistUtils"; const ProfilePage = () => { const [isLoading, setIsLoading] = useState(true); diff --git a/frontend/src/pages/admin/AdminClubPage.tsx b/frontend/src/Cabinet/pages/admin/AdminClubPage.tsx similarity index 86% rename from frontend/src/pages/admin/AdminClubPage.tsx rename to frontend/src/Cabinet/pages/admin/AdminClubPage.tsx index c831f5474..c5dc710f6 100644 --- a/frontend/src/pages/admin/AdminClubPage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminClubPage.tsx @@ -1,12 +1,12 @@ +import AdminClubLogContainer from "@/Cabinet/components/Club/AdminClubLog.container"; +import Button from "@/Cabinet/components/Common/Button"; +import ClubModal from "@/Cabinet/components/Modals/ClubModal/ClubModal"; +import { selectedClubInfoState } from "@/Cabinet/recoil/atoms"; +import { ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import { deleteRecoilPersistFloorSection } from "@/Cabinet/utils/recoilPersistUtils"; import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { selectedClubInfoState } from "@/recoil/atoms"; -import AdminClubLogContainer from "@/components/Club/AdminClubLog.container"; -import Button from "@/components/Common/Button"; -import ClubModal from "@/components/Modals/ClubModal/ClubModal"; -import { ClubUserDto } from "@/types/dto/lent.dto"; -import { deleteRecoilPersistFloorSection } from "@/utils/recoilPersistUtils"; const AdminClubPage = () => { const [shouldFetchData, setShouldFetchData] = useState(false); diff --git a/frontend/src/pages/admin/AdminHomePage.tsx b/frontend/src/Cabinet/pages/admin/AdminHomePage.tsx similarity index 90% rename from frontend/src/pages/admin/AdminHomePage.tsx rename to frontend/src/Cabinet/pages/admin/AdminHomePage.tsx index 188c2a1f2..cc8d0a296 100644 --- a/frontend/src/pages/admin/AdminHomePage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminHomePage.tsx @@ -9,20 +9,20 @@ import { selectedTypeOnSearchState, targetCabinetInfoState, targetUserInfoState, -} from "@/recoil/atoms"; -import BarChart from "@/components/AdminInfo/Chart/BarChart"; -import LineChart from "@/components/AdminInfo/Chart/LineChart"; -import PieChart from "@/components/AdminInfo/Chart/PieChart"; -import AdminTable from "@/components/AdminInfo/Table/AdminTable"; +} from "@/Cabinet/recoil/atoms"; +import BarChart from "@/Cabinet/components/AdminInfo/Chart/BarChart"; +import LineChart from "@/Cabinet/components/AdminInfo/Chart/LineChart"; +import PieChart from "@/Cabinet/components/AdminInfo/Chart/PieChart"; +import AdminTable from "@/Cabinet/components/AdminInfo/Table/AdminTable"; import { ICabinetNumbersPerFloor, IMonthlyData, ITableData, -} from "@/types/dto/admin.dto"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { axiosCabinetById } from "@/api/axios/axios.custom"; -import { useAdminHomeApi } from "@/hooks/useAdminHomeApi"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/types/dto/admin.dto"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { axiosCabinetById } from "@/Cabinet/api/axios/axios.custom"; +import { useAdminHomeApi } from "@/Cabinet/hooks/useAdminHomeApi"; +import useMenu from "@/Cabinet/hooks/useMenu"; const AdminHomePage = () => { const [overdueUserList, setOverdueUserList] = useRecoilState( diff --git a/frontend/src/pages/admin/AdminLayout.tsx b/frontend/src/Cabinet/pages/admin/AdminLayout.tsx similarity index 82% rename from frontend/src/pages/admin/AdminLayout.tsx rename to frontend/src/Cabinet/pages/admin/AdminLayout.tsx index e0ec0e3b2..fdc2d84eb 100644 --- a/frontend/src/pages/admin/AdminLayout.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminLayout.tsx @@ -3,16 +3,15 @@ import { Outlet } from "react-router"; import { useLocation, useNavigate } from "react-router-dom"; import { useRecoilValue } from "recoil"; import styled, { css } from "styled-components"; -import { selectedTypeOnSearchState } from "@/recoil/atoms"; -import CabinetInfoAreaContainer from "@/components/CabinetInfoArea/CabinetInfoArea.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import LeftNav from "@/components/LeftNav/LeftNav"; -import MapInfoContainer from "@/components/MapInfo/MapInfo.container"; -import AdminTopNavContainer from "@/components/TopNav/AdminTopNav.container"; -import UserInfoAreaContainer from "@/components/UserInfoArea/UserInfoArea.container"; -import ColorType from "@/types/enum/color.type.enum"; -import { getCookie } from "@/api/react_cookie/cookies"; -import useMenu from "@/hooks/useMenu"; +import { selectedTypeOnSearchState } from "@/Cabinet/recoil/atoms"; +import CabinetInfoAreaContainer from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import LeftNav from "@/Cabinet/components/LeftNav/LeftNav"; +import MapInfoContainer from "@/Cabinet/components/MapInfo/MapInfo.container"; +import AdminTopNavContainer from "@/Cabinet/components/TopNav/AdminTopNav.container"; +import UserInfoAreaContainer from "@/Cabinet/components/UserInfoArea/UserInfoArea.container"; +import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; +import useMenu from "@/Cabinet/hooks/useMenu"; const Layout = (): JSX.Element => { const [isLoading, setIsLoading] = useState(true); @@ -115,9 +114,9 @@ const DetailInfoContainerStyled = styled.div<{ isFloat: boolean }>` props.isFloat && css` position: fixed; - top: 80px; + top: 120px; right: 0; - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; diff --git a/frontend/src/pages/admin/AdminLoginFailurePage.tsx b/frontend/src/Cabinet/pages/admin/AdminLoginFailurePage.tsx similarity index 85% rename from frontend/src/pages/admin/AdminLoginFailurePage.tsx rename to frontend/src/Cabinet/pages/admin/AdminLoginFailurePage.tsx index 2e20c6cb8..3daaaa8a4 100644 --- a/frontend/src/pages/admin/AdminLoginFailurePage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminLoginFailurePage.tsx @@ -1,5 +1,5 @@ +import AnnounceTemplate from "@/Cabinet/components/Announce/AnnounceTemplate"; import { useNavigate } from "react-router-dom"; -import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); diff --git a/frontend/src/pages/admin/AdminLoginPage.tsx b/frontend/src/Cabinet/pages/admin/AdminLoginPage.tsx similarity index 58% rename from frontend/src/pages/admin/AdminLoginPage.tsx rename to frontend/src/Cabinet/pages/admin/AdminLoginPage.tsx index 0d63e3734..e40391ae4 100644 --- a/frontend/src/pages/admin/AdminLoginPage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminLoginPage.tsx @@ -1,5 +1,5 @@ -import AdminLoginTemplate from "@/components/Login/AdminLoginTemplate"; -import "@/assets/css/loginPage.css"; +import AdminLoginTemplate from "@/Cabinet/components/Login/AdminLoginTemplate"; +import "@/Cabinet/assets/css/loginPage.css"; const LoginPage = () => { const url = `${import.meta.env.VITE_BE_HOST}/v4/admin/auth/login`; @@ -9,7 +9,7 @@ const LoginPage = () => { url={url} pageTitle="Cabi Admin" pageSubTitle="관리자 페이지" - imgSrc="/src/assets/images/adminLoginImg.svg" + imgSrc="/src/Cabinet/assets/images/adminLoginImg.svg" /> ); }; diff --git a/frontend/src/pages/admin/AdminMainPage.tsx b/frontend/src/Cabinet/pages/admin/AdminMainPage.tsx similarity index 86% rename from frontend/src/pages/admin/AdminMainPage.tsx rename to frontend/src/Cabinet/pages/admin/AdminMainPage.tsx index f2d52fe86..4c0cbb376 100644 --- a/frontend/src/pages/admin/AdminMainPage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminMainPage.tsx @@ -5,16 +5,19 @@ import { currentBuildingNameState, currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; -import { currentCabinetIdState, targetCabinetInfoState } from "@/recoil/atoms"; -import { currentFloorSectionState } from "@/recoil/selectors"; -import CabinetListContainer from "@/components/CabinetList/CabinetList.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import MultiSelectButton from "@/components/Common/MultiSelectButton"; -import SectionPaginationContainer from "@/components/SectionPagination/SectionPagination.container"; -import useCabinetListRefresh from "@/hooks/useCabinetListRefresh"; -import useMenu from "@/hooks/useMenu"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/recoil/atoms"; +import { + currentCabinetIdState, + targetCabinetInfoState, +} from "@/Cabinet/recoil/atoms"; +import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; +import CabinetListContainer from "@/Cabinet/components/CabinetList/CabinetList.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import MultiSelectButton from "@/Cabinet/components/Common/MultiSelectButton"; +import SectionPaginationContainer from "@/Cabinet/components/SectionPagination/SectionPagination.container"; +import useCabinetListRefresh from "@/Cabinet/hooks/useCabinetListRefresh"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; const AdminMainPage = () => { const touchStartPosX = useRef(0); diff --git a/frontend/src/pages/admin/AdminPagination.tsx b/frontend/src/Cabinet/pages/admin/AdminPagination.tsx similarity index 100% rename from frontend/src/pages/admin/AdminPagination.tsx rename to frontend/src/Cabinet/pages/admin/AdminPagination.tsx diff --git a/frontend/src/pages/admin/SearchPage.tsx b/frontend/src/Cabinet/pages/admin/SearchPage.tsx similarity index 90% rename from frontend/src/pages/admin/SearchPage.tsx rename to frontend/src/Cabinet/pages/admin/SearchPage.tsx index 7e5e2bdc2..e659b7cfd 100644 --- a/frontend/src/pages/admin/SearchPage.tsx +++ b/frontend/src/Cabinet/pages/admin/SearchPage.tsx @@ -6,17 +6,17 @@ import { currentCabinetIdState, currentIntraIdState, numberOfAdminWorkState, -} from "@/recoil/atoms"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import NoSearch from "@/components/Search/NoSearch"; -import SearchDefault from "@/components/Search/SearchDefault"; -import SearchItemByIntraId from "@/components/Search/SearchItemByIntraId"; -import SearchItemByNum from "@/components/Search/SearchItemByNum"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/recoil/atoms"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import NoSearch from "@/Cabinet/components/Search/NoSearch"; +import SearchDefault from "@/Cabinet/components/Search/SearchDefault"; +import SearchItemByIntraId from "@/Cabinet/components/Search/SearchItemByIntraId"; +import SearchItemByNum from "@/Cabinet/components/Search/SearchItemByNum"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; import { axiosSearchByCabinetNum, axiosSearchDetailByIntraId, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; interface ISearchDetail { name: string; @@ -189,7 +189,7 @@ const MoreButtonStyled = styled.button` transform: translateY(-40%); width: 20px; height: 20px; - background: url(/src/assets/images/selectPurple.svg) no-repeat 100%; + background: url(/src/Cabinet/assets/images/selectPurple.svg) no-repeat 100%; } `; diff --git a/frontend/src/recoil/atoms.ts b/frontend/src/Cabinet/recoil/atoms.ts similarity index 91% rename from frontend/src/recoil/atoms.ts rename to frontend/src/Cabinet/recoil/atoms.ts index 82f32a5c0..888bdc2fc 100644 --- a/frontend/src/recoil/atoms.ts +++ b/frontend/src/Cabinet/recoil/atoms.ts @@ -1,23 +1,23 @@ import { atom } from "recoil"; import { recoilPersist } from "recoil-persist"; -import { staticColNumData } from "@/assets/data/sectionColNumData"; -import { IBuildingColNum } from "@/assets/data/sectionColNumData"; -import { ITableData } from "@/types/dto/admin.dto"; +import { staticColNumData } from "@/Cabinet/assets/data/sectionColNumData"; +import { IBuildingColNum } from "@/Cabinet/assets/data/sectionColNumData"; +import { ITableData } from "@/Cabinet/types/dto/admin.dto"; import { CabinetBuildingFloorDto, CabinetInfo, CabinetInfoByBuildingFloorDto, CabinetPreviewInfo, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/types/dto/cabinet.dto"; import { ClubCabinetInfo, ClubPaginationResponseDto, ClubResponseDto, ClubUserResponseDto, -} from "@/types/dto/club.dto"; -import { ClubUserDto } from "@/types/dto/lent.dto"; -import { UserDto, UserInfo } from "@/types/dto/user.dto"; +} from "@/Cabinet/types/dto/club.dto"; +import { ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import { UserDto, UserInfo } from "@/Cabinet/types/dto/user.dto"; const { persistAtom } = recoilPersist(); diff --git a/frontend/src/recoil/selectors.ts b/frontend/src/Cabinet/recoil/selectors.ts similarity index 96% rename from frontend/src/recoil/selectors.ts rename to frontend/src/Cabinet/recoil/selectors.ts index cfd01dcd8..c15744474 100644 --- a/frontend/src/recoil/selectors.ts +++ b/frontend/src/Cabinet/recoil/selectors.ts @@ -6,12 +6,15 @@ import { currentFloorCabinetState, currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { IFloorSectionColNum, ISectionColNum, -} from "@/assets/data/sectionColNumData"; -import { CabinetInfo, CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/assets/data/sectionColNumData"; +import { + CabinetInfo, + CabinetPreviewInfo, +} from "@/Cabinet/types/dto/cabinet.dto"; export const buildingsState = selector>({ key: "Buildings", diff --git a/frontend/src/types/dto/admin.dto.ts b/frontend/src/Cabinet/types/dto/admin.dto.ts similarity index 100% rename from frontend/src/types/dto/admin.dto.ts rename to frontend/src/Cabinet/types/dto/admin.dto.ts diff --git a/frontend/src/types/dto/alarm.dto.ts b/frontend/src/Cabinet/types/dto/alarm.dto.ts similarity index 100% rename from frontend/src/types/dto/alarm.dto.ts rename to frontend/src/Cabinet/types/dto/alarm.dto.ts diff --git a/frontend/src/types/dto/cabinet.dto.ts b/frontend/src/Cabinet/types/dto/cabinet.dto.ts similarity index 87% rename from frontend/src/types/dto/cabinet.dto.ts rename to frontend/src/Cabinet/types/dto/cabinet.dto.ts index 88a5c3ad4..6a65cd4c6 100644 --- a/frontend/src/types/dto/cabinet.dto.ts +++ b/frontend/src/Cabinet/types/dto/cabinet.dto.ts @@ -1,6 +1,6 @@ -import { LentDto } from "@/types/dto/lent.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; // TODO :hybae // lentType을 LentType으로 변경 예정 diff --git a/frontend/src/types/dto/club.dto.ts b/frontend/src/Cabinet/types/dto/club.dto.ts similarity index 92% rename from frontend/src/types/dto/club.dto.ts rename to frontend/src/Cabinet/types/dto/club.dto.ts index ae416037f..379462796 100644 --- a/frontend/src/types/dto/club.dto.ts +++ b/frontend/src/Cabinet/types/dto/club.dto.ts @@ -1,4 +1,4 @@ -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; export type ClubListReponseType = | ClubPaginationResponseDto diff --git a/frontend/src/types/dto/lent.dto.ts b/frontend/src/Cabinet/types/dto/lent.dto.ts similarity index 95% rename from frontend/src/types/dto/lent.dto.ts rename to frontend/src/Cabinet/types/dto/lent.dto.ts index 88b71fba5..05f483c44 100644 --- a/frontend/src/types/dto/lent.dto.ts +++ b/frontend/src/Cabinet/types/dto/lent.dto.ts @@ -1,4 +1,4 @@ -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; /** * @interface diff --git a/frontend/src/types/dto/user.dto.ts b/frontend/src/Cabinet/types/dto/user.dto.ts similarity index 84% rename from frontend/src/types/dto/user.dto.ts rename to frontend/src/Cabinet/types/dto/user.dto.ts index 64ecffd4e..71336948b 100644 --- a/frontend/src/types/dto/user.dto.ts +++ b/frontend/src/Cabinet/types/dto/user.dto.ts @@ -1,6 +1,6 @@ -import { AlarmInfo } from "@/types/dto/alarm.dto"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentExtensionDto } from "@/types/dto/lent.dto"; +import { AlarmInfo } from "@/Cabinet/types/dto/alarm.dto"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentExtensionDto } from "@/Cabinet/types/dto/lent.dto"; /** * @description 유저 정보 diff --git a/frontend/src/types/enum/AnnounceType.enum.ts b/frontend/src/Cabinet/types/enum/AnnounceType.enum.ts similarity index 100% rename from frontend/src/types/enum/AnnounceType.enum.ts rename to frontend/src/Cabinet/types/enum/AnnounceType.enum.ts diff --git a/frontend/src/types/enum/cabinet.status.enum.ts b/frontend/src/Cabinet/types/enum/cabinet.status.enum.ts similarity index 100% rename from frontend/src/types/enum/cabinet.status.enum.ts rename to frontend/src/Cabinet/types/enum/cabinet.status.enum.ts diff --git a/frontend/src/types/enum/cabinet.type.enum.ts b/frontend/src/Cabinet/types/enum/cabinet.type.enum.ts similarity index 100% rename from frontend/src/types/enum/cabinet.type.enum.ts rename to frontend/src/Cabinet/types/enum/cabinet.type.enum.ts diff --git a/frontend/src/types/enum/color.type.enum.ts b/frontend/src/Cabinet/types/enum/color.type.enum.ts similarity index 100% rename from frontend/src/types/enum/color.type.enum.ts rename to frontend/src/Cabinet/types/enum/color.type.enum.ts diff --git a/frontend/src/types/enum/content.status.enum.ts b/frontend/src/Cabinet/types/enum/content.status.enum.ts similarity index 100% rename from frontend/src/types/enum/content.status.enum.ts rename to frontend/src/Cabinet/types/enum/content.status.enum.ts diff --git a/frontend/src/types/enum/icon.type.enum.ts b/frontend/src/Cabinet/types/enum/icon.type.enum.ts similarity index 100% rename from frontend/src/types/enum/icon.type.enum.ts rename to frontend/src/Cabinet/types/enum/icon.type.enum.ts diff --git a/frontend/src/types/enum/map.type.enum.ts b/frontend/src/Cabinet/types/enum/map.type.enum.ts similarity index 100% rename from frontend/src/types/enum/map.type.enum.ts rename to frontend/src/Cabinet/types/enum/map.type.enum.ts diff --git a/frontend/src/types/enum/time.enum.ts b/frontend/src/Cabinet/types/enum/time.enum.ts similarity index 100% rename from frontend/src/types/enum/time.enum.ts rename to frontend/src/Cabinet/types/enum/time.enum.ts diff --git a/frontend/src/utils/dateUtils.ts b/frontend/src/Cabinet/utils/dateUtils.ts similarity index 51% rename from frontend/src/utils/dateUtils.ts rename to frontend/src/Cabinet/utils/dateUtils.ts index bc5736daa..0e6de4d3c 100644 --- a/frontend/src/utils/dateUtils.ts +++ b/frontend/src/Cabinet/utils/dateUtils.ts @@ -1,7 +1,26 @@ +/** + * @description 해당 월, 일의 앞자리를 0으로 채워 두 자리로 만듦 + * + * @param num 날짜 + * + * @returns 두 자리로 만들어진 날짜 + */ export const padTo2Digits = (num: number) => { return num.toString().padStart(2, "0"); }; +/** + * @description 해당 날짜의 년, 월, 일을 divider 로 구분하여 반환 + * + * @param date 날짜 + * @param divider 구분자 + * + * @returns 구분자로 구분된 년, 월, 일 + * + * @example + * const result = formatDate(new Date(), "-") + * //=> "2023-04-14" + */ export const formatDate = (date: Date | null, divider: string) => { if (date === null) return ""; return [ @@ -11,6 +30,14 @@ export const formatDate = (date: Date | null, divider: string) => { ].join(divider); }; +/** + * @description 주어진 lentType에 따라 대여 만료일을 구해 "YYYY/MM/DD" 형식으로 반환. 예정된 대여 만료일이 있다면 그 일자를 반환 + * + * @param lentType 대여 타입 + * @param existExpireDate 예정된 만료일 + * + * @returns "YYYY/MM/DD" 형식의 대여 만료일 + */ export const getExpireDateString = ( lentType: string, existExpireDate?: Date @@ -26,7 +53,15 @@ export const getExpireDateString = ( return formatDate(expireDate, "/"); }; -// 공유 사물함 반납 시 남은 대여일 수 차감 (원래 남은 대여일 수 * (남은 인원 / 원래 있던 인원)) +/** + * @description 공유 사물함 반납 시 남은 대여일 수를 공식 (원래 남은 대여일 수 * (남은 인원 / 원래 있던 인원)) 에 따라 차감해 새로운 만료일을 "YYYY/MM/DD" 형식으로 반환 + * + * @param lentType 대여 타입 + * @param currentNumUsers 현재 대여 중인 인원 + * @param existExpireDate 예정된 만료일 + * + * @returns "YYYY/MM/DD" 형식의 새로운 대여 만료일 + */ export const getShortenedExpireDateString = ( lentType: string, currentNumUsers: number, @@ -43,6 +78,14 @@ export const getShortenedExpireDateString = ( return formatDate(new Date(newExpireDate), "/"); }; +/** + * @description 주어진 날짜를 기준으로 주어진 일자만큼 만료일을 연장하여 "YYYY/MM/DD" 형식으로 반환 + * + * @param existExpireDate 예정된 만료일 + * @param dateToExtend 연장할 일자 + * + * @returns "YYYY/MM/DD" 형식의 연장된 대여 만료일 + */ export const getExtendedDateString = ( existExpireDate: Date | undefined | null, dateToExtend: number | undefined @@ -53,23 +96,27 @@ export const getExtendedDateString = ( return formatDate(expireDate, "/"); }; -export const getTotalPage = (totalLength: number, size: number) => { - return Math.ceil(totalLength / size); -}; - +/** + * @description 주어진 대여 만료일을 기준으로 남은 대여일 수를 계산하여 반환. 만료일이 지났다면 음수로 반환 + * + * @param expireTime 대여 만료일 + * + * @returns 남은 대여일 수 + */ export const calExpiredTime = (expireTime: Date) => Math.floor( (expireTime.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24) ); +/** + * @description 주어진 대여 만료일을 기준으로 남은 대여일 수를 계산하여 반환. 만료일이 지났다면 음수로 반환. expireTime 을 Date 로 변환 후 사용하는 Wrapper 함수 + * + * @param expireTime 대여 만료일 + * + * @returns 남은 대여일 수 + */ export const getRemainingTime = (expireTime: Date | undefined) => { if (!expireTime) return 0; const remainTime = calExpiredTime(new Date(expireTime)); return remainTime < 0 ? -remainTime : remainTime; }; - -export const getExpireDate = (date: Date | undefined) => { - if (!date) return null; - if (date.toString().slice(0, 4) === "9999") return null; - return date.toString().slice(0, 10); -}; diff --git a/frontend/src/Cabinet/utils/paginationUtils.ts b/frontend/src/Cabinet/utils/paginationUtils.ts new file mode 100644 index 000000000..a5e55fc20 --- /dev/null +++ b/frontend/src/Cabinet/utils/paginationUtils.ts @@ -0,0 +1,3 @@ +export const getTotalPage = (totalLength: number, size: number) => { + return Math.ceil(totalLength / size); +}; diff --git a/frontend/src/utils/recoilPersistUtils.ts b/frontend/src/Cabinet/utils/recoilPersistUtils.ts similarity index 100% rename from frontend/src/utils/recoilPersistUtils.ts rename to frontend/src/Cabinet/utils/recoilPersistUtils.ts diff --git a/frontend/src/utils/tableUtils.ts b/frontend/src/Cabinet/utils/tableUtils.ts similarity index 97% rename from frontend/src/utils/tableUtils.ts rename to frontend/src/Cabinet/utils/tableUtils.ts index 55e35641f..383a61293 100644 --- a/frontend/src/utils/tableUtils.ts +++ b/frontend/src/Cabinet/utils/tableUtils.ts @@ -3,7 +3,7 @@ import { BrokenCabinetDto, ITableData, OverdueUserDto, -} from "@/types/dto/admin.dto"; +} from "@/Cabinet/types/dto/admin.dto"; const calcLeftDays = (end: Date) => Math.ceil((end.getTime() - new Date().getTime()) / 1000 / 3600 / 24); diff --git a/frontend/src/Presentation/api/axios/axios.custom.ts b/frontend/src/Presentation/api/axios/axios.custom.ts new file mode 100644 index 000000000..3e90ae9d9 --- /dev/null +++ b/frontend/src/Presentation/api/axios/axios.custom.ts @@ -0,0 +1,116 @@ +import instance from "@/Cabinet/api/axios/axios.instance"; + +/** + * 수요지식회 (구 까비지식회) API + */ +const axiosGetPresentationURL = "/v5/presentation/"; +export const axiosGetPresentation = async () => { + try { + const response = await instance.get(axiosGetPresentationURL, { + params: { pastFormCount: 1, upcomingFormCount: 3 }, + }); + return response; + } catch (error) { + throw error; + } +}; + +const axiosGetPresentationScheduleURL = "/v5/presentation/schedule/"; +export const axiosGetPresentationSchedule = async ( + yearMonth: string +): Promise => { + try { + const response = await instance.get(axiosGetPresentationScheduleURL, { + params: { yearMonth }, + }); + return response; + } catch (error) { + throw error; + } +}; + +const axiosPostPresentationFormURL = "/v5/presentation/form"; +export const axiosPostPresentationForm = async ( + subject: string, + summary: string, + detail: string, + dateTime: Date, + category: string, + presentationTime: string +): Promise => { + try { + const response = await instance.post(axiosPostPresentationFormURL, { + subject, + summary, + detail, + dateTime: dateTime.toISOString(), + category, + presentationTime, + }); + return response; + } catch (error) { + throw error; + } +}; + +const axiosGetInvalidDatesURL = "/v5/presentation/form/invalid-date"; +export const axiosGetInvalidDates = async (): Promise => { + try { + const response = await instance.get(axiosGetInvalidDatesURL); + return response; + } catch (error) { + throw error; + } +}; + +const axiosMyPresentationLogURL = "/v5/presentation/me/histories"; +export const axiosMyPresentationLog = async (page: number): Promise => { + try { + const response = await instance.get( + `${axiosMyPresentationLogURL}?page=${page}&size=10&sort=dateTime,desc` + ); + return response; + } catch (error) { + throw error; + } +}; + +/** + * 수요지식회 (구 까비지식회) Admin API + */ +const axiosUpdatePresentationStatusURL = + "/v5/admin/presentation/{formId}/update"; +export const axiosUpdatePresentationStatus = async ( + formId: number, + dateTime: string, + status: string, + location: string +): Promise => { + try { + const response = await instance.patch( + axiosUpdatePresentationStatusURL.replace("{formId}", formId.toString()), + { + dateTime, + status, + location, + } + ); + return response; + } catch (error) { + throw error; + } +}; + +const axiosGetAdminPresentationScheduleURL = "/v5/admin/presentation/schedule/"; +export const getAdminPresentationSchedule = async ( + yearMonth: string +): Promise => { + try { + const response = await instance.get(axiosGetAdminPresentationScheduleURL, { + params: { yearMonth }, + }); + return response; + } catch (error) { + throw error; + } +}; diff --git a/frontend/src/Presentation/assets/data/maps.ts b/frontend/src/Presentation/assets/data/maps.ts new file mode 100644 index 000000000..de667a4b6 --- /dev/null +++ b/frontend/src/Presentation/assets/data/maps.ts @@ -0,0 +1,61 @@ +import { PresentationTimeKey } from "@/Presentation/pages/RegisterPage"; +import { + PresentationCategoryType, + PresentationLocation, + PresentationPeriodType, + PresentationStatusType, +} from "@/Presentation/types/enum/presentation.type.enum"; + +export const PresentationStatusTypeLabelMap = { + [PresentationStatusType.EXPECTED]: "발표예정", + [PresentationStatusType.DONE]: "발표완료", + [PresentationStatusType.CANCEL]: "발표취소", +}; + +export const PresentationPeriodTypeNumberLabelMap = { + [PresentationPeriodType.NONE]: 0, + [PresentationPeriodType.HALF]: 30, + [PresentationPeriodType.HOUR]: 60, + [PresentationPeriodType.HOUR_HALF]: 90, + [PresentationPeriodType.TWO_HOUR]: 120, +}; + +export const PresentationTimeMap: { + [key in PresentationTimeKey]: PresentationPeriodType; +} = { + "": PresentationPeriodType.NONE, + "30분": PresentationPeriodType.HALF, + "1시간": PresentationPeriodType.HOUR, + "1시간 30분": PresentationPeriodType.HOUR_HALF, + "2시간": PresentationPeriodType.TWO_HOUR, +}; + +export const PresentationLocationLabelMap = { + [PresentationLocation.BASEMENT]: "지하 1층 오픈스튜디오", + [PresentationLocation.FIRST]: "1층 오픈스튜디오", + [PresentationLocation.THIRD]: "3층 세미나실", +}; + +export const PresentationCategoryTypeLabelMap = { + [PresentationCategoryType.DEVELOP]: "개발", + [PresentationCategoryType.STUDY]: "학술", + [PresentationCategoryType.HOBBY]: "취미", + [PresentationCategoryType.JOB]: "취업", + [PresentationCategoryType.TASK]: "42", + [PresentationCategoryType.ETC]: "기타", +}; + +export const presentationCategoryIconMap = { + [PresentationCategoryType.DEVELOP]: + "/src/Cabinet/assets/images/PresentationDevelop.svg", + [PresentationCategoryType.STUDY]: + "/src/Cabinet/assets/images/PresentationAcademic.svg", + [PresentationCategoryType.HOBBY]: + "/src/Cabinet/assets/images/PresentationHobby.svg", + [PresentationCategoryType.JOB]: + "/src/Cabinet/assets/images/PresentationJob.svg", + [PresentationCategoryType.TASK]: + "/src/Cabinet/assets/images/PresentationFortyTwo.svg", + [PresentationCategoryType.ETC]: + "/src/Cabinet/assets/images/PresentationEtc.svg", +}; diff --git a/frontend/src/Presentation/assets/images/logo.svg b/frontend/src/Presentation/assets/images/logo.svg new file mode 100644 index 000000000..1f0bdf423 --- /dev/null +++ b/frontend/src/Presentation/assets/images/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/src/Presentation/components/Details/DetailContent.container.tsx b/frontend/src/Presentation/components/Details/DetailContent.container.tsx new file mode 100644 index 000000000..a1cb85818 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailContent.container.tsx @@ -0,0 +1,175 @@ +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import { useRecoilState } from "recoil"; +import { isCurrentModalState } from "@/Presentation/recoil/atoms"; +import DetailContent from "@/Presentation/components/Details/DetailContent"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { + axiosGetPresentationSchedule, + getAdminPresentationSchedule, +} from "@/Presentation/api/axios/axios.custom"; +import { + calculateAvailableDaysInWeeks, + makeIDateObj, + toISOStringwithTimeZone, +} from "@/Presentation/utils/dateUtils"; +import { + AVAILABLE_WEEKS, + FUTURE_MONTHS_TO_DISPLAY, +} from "@/Presentation/constants/policy"; + +export interface IDate { + year: string; + month: string; + day: string; +} + +const DetailContentContainer = () => { + const [currentDate, setCurrentDate] = useState(null); + const [todayDate, setTodayDate] = useState(null); + const [presentationDetailInfo, setPresentationDetailInfo] = useState< + IPresentationScheduleDetailInfo[] | null + >(null); + const firstPresentationDate: IDate = { year: "2024", month: "4", day: "24" }; + const { pathname } = useLocation(); + const isAdmin = pathname.includes("admin/presentation"); + const [isCurrentRender, setIsCurrentRender] = + useRecoilState(isCurrentModalState); + + useEffect(() => { + const tmpTodayDate = makeIDateObj(new Date()); + + if ( + !( + todayDate?.year === tmpTodayDate.year || + todayDate?.month === tmpTodayDate.month || + todayDate?.day === tmpTodayDate.day + ) + ) + setTodayDate(tmpTodayDate); + + setCurrentDate(tmpTodayDate); + }, []); + + useEffect(() => { + setIsCurrentRender(false); + if (currentDate) getPresentationSchedule(currentDate); + }, [currentDate, isCurrentRender]); + + const getPresentationSchedule = async (requestDate: IDate) => { + try { + const response = !isAdmin + ? await axiosGetPresentationSchedule( + requestDate.year + "-" + requestDate.month + ) + : await getAdminPresentationSchedule( + requestDate.year + "-" + requestDate.month + ); + let objAry: IPresentationScheduleDetailInfo[] = response.data.forms; + const availableDays = calculateAvailableDaysInWeeks( + new Date( + parseInt(todayDate!.year), + parseInt(todayDate!.month) - 1, + parseInt(todayDate!.day) + ), + AVAILABLE_WEEKS, + 3, + FUTURE_MONTHS_TO_DISPLAY + ); + if (objAry.length < 2) { + // availableDays 중 requestDate랑 달이 같은 것들 추출 + const sameMonth = availableDays.filter((date) => { + return date.getMonth() + 1 === parseInt(requestDate.month); + }); + if (objAry.length === 0) { + // 해당 월의 날짜 2개의 IPresentationScheduleDetailInfo 배열을 만든다 + objAry = sameMonth.map((day) => { + return { + id: null, + subject: null, + summary: null, + detail: null, + dateTime: toISOStringwithTimeZone(day), + category: null, + userName: null, + presentationTime: null, + presentationStatus: null, + presentationLocation: null, + }; + }); + } + if (objAry.length === 1) { + const date = new Date(objAry[0].dateTime); + objAry = sameMonth.map((day) => { + if (day.getDate() === date.getDate()) return objAry[0]; + else + return { + id: null, + subject: null, + summary: null, + detail: null, + dateTime: toISOStringwithTimeZone(day), + category: null, + userName: null, + presentationTime: null, + presentationStatus: null, + presentationLocation: null, + }; + }); + } + } + + setPresentationDetailInfo(objAry); + } catch (error: any) { + // TODO + } finally { + // TODO + } + }; + + const moveMonth = (direction: string) => { + let requestDate: IDate = { ...currentDate! }; + let currentDateMonth = parseInt(currentDate!.month); + let currentDateYear = parseInt(currentDate!.year); + + if (direction === "left") { + // 현재 페이지 날짜의 월-1 axios 요청 + if (currentDateMonth === 1) { + requestDate.year = (currentDateYear - 1).toString(); + requestDate.month = (12).toString(); + } else { + requestDate.month = (currentDateMonth - 1).toString().padStart(2, "0"); + } + } else { + if (currentDateMonth === 12) { + requestDate.year = (currentDateYear + 1).toString(); + requestDate.month = (1).toString(); + } else { + requestDate.month = (currentDateMonth + 1).toString().padStart(2, "0"); + } + } + + setCurrentDate(requestDate); + }; + + return ( + parseInt(firstPresentationDate.month) + : false + } + canMoveRight={ + currentDate && todayDate + ? parseInt(currentDate.month) < + parseInt(todayDate.month) + FUTURE_MONTHS_TO_DISPLAY - 1 + : false + } + /> + ); +}; + +export default DetailContentContainer; diff --git a/frontend/src/Presentation/components/Details/DetailContent.tsx b/frontend/src/Presentation/components/Details/DetailContent.tsx new file mode 100644 index 000000000..02f864325 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailContent.tsx @@ -0,0 +1,115 @@ +import styled from "styled-components"; +import { MoveSectionButtonStyled } from "@/Cabinet/components/SectionPagination/SectionPagination"; +import LeftSectionButton from "@/Cabinet/assets/images/LeftSectionButton.svg"; +import { IDate } from "@/Presentation/components/Details/DetailContent.container"; +import DetailTableContainer from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +const DetailContent = ({ + moveMonth, + currentDate, + presentationDetailInfo, + + canMoveLeft, + canMoveRight, +}: { + moveMonth: (direction: string) => void; + currentDate: IDate | null; + presentationDetailInfo: IPresentationScheduleDetailInfo[] | null; + + canMoveLeft: boolean; + canMoveRight: boolean; +}) => { + return ( + + + {canMoveLeft ? ( + moveMonth("left")} + className="cabiButton" + /> + ) : ( +
+ )} +
+ {currentDate?.year}년 {currentDate?.month}월 +
+ {canMoveRight ? ( + moveMonth("right")} + arrowReversed={true} + className="cabiButton" + /> + ) : ( +
+ )} +
+ + + +
+ ); +}; + +export default DetailContent; + +const ContainerStyled = styled.div` + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + overflow-y: scroll; +`; + +const HeaderStyled = styled.div` + margin-top: 70px; + text-align: center; + width: 340px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + + & > #headerDate { + width: 200px; + height: 50px; + font-size: 2rem; + line-height: 3rem; + font-weight: 600; + } + + & > img { + width: 2.5rem; + height: 2.5rem; + } + + & > #replaceOfMoveButton { + width: 2.5rem; + height: 2.5rem; + margin: 0px 15px; + } + + @media (max-width: 1150px) { + margin-top: 30px; + } +`; + +const BodyStyled = styled.div` + margin-top: 50px; + margin-bottom: 50px; + width: 80%; + padding: 24px 20px 10px 20px; + background-color: var(--lightgray-color); + border-radius: 10px; + display: flex; + min-width: 375px; + + @media (max-width: 1150px) { + margin-top: 16px; + width: 96%; + padding: 0 16px; + } +`; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTable.container.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTable.container.tsx new file mode 100644 index 000000000..b90bab33d --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTable.container.tsx @@ -0,0 +1,113 @@ +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import DetailTable from "@/Presentation/components/Details/DetailTable/DetailTable"; +import EditStatusModal from "@/Presentation/components/Modals/EditStatusModal/EditStatusModal"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { toISOStringwithTimeZone } from "@/Presentation/utils/dateUtils"; + +export interface IAdminCurrentModalStateInfo { + statusModal: boolean; +} + +export type TAdminModalState = "statusModal"; + +export enum itemType { + EVENT_AVAILABLE = "", + NO_EVENT_CURRENT = "noEventCurrent", + NO_EVENT_PAST = "noEventPast", +} + +const tableHeadArray = { + date: "날짜", + subject: "제목", + userName: "ID", + category: "카테고리", + presentationTime: "시간", + presentationLocation: "장소", +}; + +const adminTableHeadArray = { + date: "날짜", + subject: "제목", + userName: "ID", + category: "카테고리", + presentationTime: "시간", + presentationLocation: "장소", + presentationStatus: "상태", +}; + +const DetailTableContainer = ({ + presentationDetailInfo, +}: { + presentationDetailInfo: IPresentationScheduleDetailInfo[] | null; +}) => { + const [adminModal, setAdminModal] = useState({ + statusModal: false, + }); + const { pathname } = useLocation(); + const isAdmin = pathname.includes("admin/presentation"); + const [list, setList] = useState( + null + ); + const [isMobile, setIsMobile] = useState(false); + const tableHeadEntries = !isAdmin + ? Object.entries(tableHeadArray) + : Object.entries(adminTableHeadArray); + + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < 1150); + }; + window.addEventListener("resize", handleResize); + handleResize(); + + return () => window.removeEventListener("resize", handleResize); + }, []); + + useEffect(() => { + if (presentationDetailInfo) setList(presentationDetailInfo); + }, [presentationDetailInfo]); + + const openAdminModal = (modal: TAdminModalState) => { + setAdminModal({ ...adminModal, [modal]: true }); + }; + + const closeAdminModal = (modal: TAdminModalState) => { + setAdminModal({ ...adminModal, [modal]: false }); + }; + + const groupEvent = (item: IPresentationScheduleDetailInfo) => { + let itemStatus = itemType.EVENT_AVAILABLE; + + // 발표가 없다면 + if (!item.id) { + const date = new Date(); + let dateISO = toISOStringwithTimeZone(date); + const dateObj = new Date(dateISO); + + const itemDateObj = new Date(item.dateTime); + if (dateObj > itemDateObj) itemStatus = itemType.NO_EVENT_PAST; + else itemStatus = itemType.NO_EVENT_CURRENT; + } + + return itemStatus; + }; + + return ( + <> + + {adminModal.statusModal && ( + closeAdminModal("statusModal")} /> + )} + + ); +}; + +export default DetailTableContainer; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTable.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTable.tsx new file mode 100644 index 000000000..18fb9ebf1 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTable.tsx @@ -0,0 +1,79 @@ +import styled from "styled-components"; +import { IDate } from "@/Presentation/components/Details/DetailContent.container"; +import { + TAdminModalState, + itemType, +} from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import DetailTableBodyItem from "@/Presentation/components/Details/DetailTable/DetailTableBodyItem"; +import DetailTableHead from "@/Presentation/components/Details/DetailTable/DetailTableHead"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { makeIDateObj } from "@/Presentation/utils/dateUtils"; + +const DetailTable = ({ + isMobile, + list, + isAdmin, + openAdminModal, + groupEvent, + tableHeadEntries, +}: { + isMobile: boolean; + list: IPresentationScheduleDetailInfo[] | null; + isAdmin: boolean; + openAdminModal: (modal: TAdminModalState) => void; + groupEvent: (item: IPresentationScheduleDetailInfo) => itemType; + tableHeadEntries: [string, string][]; +}) => { + return ( + + + + + {list?.map((item, idx) => { + let itemStatus = groupEvent(item); + const itemInfo = { + item: item, + itemStatus: itemStatus, + itemDateInIDate: makeIDateObj(new Date(item.dateTime)), + hasNoUpcomingEvent: itemStatus === itemType.NO_EVENT_CURRENT, + }; + return ( + + ); + })} + + + ); +}; + +const TableStyled = styled.table` + width: 100%; + table-layout: fixed; + word-break: break-all; +`; + +const TableBodyStyled = styled.tbody` + width: 100%; + + & #selected { + background-color: #91b5fa; + } +`; + +export const WhiteSpaceTrStyled = styled.tr` + height: 24px; + width: 100%; +`; + +export default DetailTable; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItem.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItem.tsx new file mode 100644 index 000000000..288a214b9 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItem.tsx @@ -0,0 +1,113 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useSetRecoilState } from "recoil"; +import { currentPresentationState } from "@/Presentation/recoil/atoms"; +import { IDate } from "@/Presentation/components/Details/DetailContent.container"; +import { WhiteSpaceTrStyled } from "@/Presentation/components/Details/DetailTable/DetailTable"; +import { + TAdminModalState, + itemType, +} from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import DetailTableBodyItemBottomTr from "@/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr"; +import DetailTableBodyItemMiddleTr from "@/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr"; +import DetailTableBodyItemTopTr from "@/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +export interface IItem { + item: IPresentationScheduleDetailInfo; + itemStatus: itemType; + itemDateInIDate: IDate; + hasNoUpcomingEvent: boolean; +} + +const DetailTableBodyItem = ({ + isAdmin, + openAdminModal, + tableHeadEntries, + itemInfo, + isMobile, +}: { + isAdmin: boolean; + openAdminModal: (modal: TAdminModalState) => void; + tableHeadEntries: [string, string][]; + itemInfo: IItem; + isMobile: boolean; +}) => { + const [clickedItem, setClickedItem] = + useState(null); + const navigator = useNavigate(); + const setCurrentPresentation = useSetRecoilState(currentPresentationState); + const [isItemOpen, setIsItemOpen] = useState(false); + const tableHeadEntriesWithoutDate = tableHeadEntries.filter( + (head) => head[0] !== "date" + ); + const tableHeadEntriesWithoutDateAndSubject = tableHeadEntries.filter( + (head) => head[0] !== "date" && head[0] !== "subject" + ); + + useEffect(() => { + if (isItemOpen) { + setIsItemOpen(false); + } + }, [itemInfo]); + + useEffect(() => { + setIsItemOpen(clickedItem?.dateTime === itemInfo.item.dateTime); + }, [clickedItem]); + + const handleItemClick = (item: IPresentationScheduleDetailInfo) => { + if (isAdmin && !itemInfo.itemStatus) { + setCurrentPresentation({ + id: item.id, + dateTime: item.dateTime, + presentationTime: item.presentationTime, + presentationStatus: item.presentationStatus, + presentationLocation: item.presentationLocation, + detail: item.detail, + }); + openAdminModal("statusModal"); + } else { + if (clickedItem?.dateTime === item.dateTime) setClickedItem(null); + else setClickedItem(item); + } + }; + + return ( + <> + + + + + + ); +}; + +export default DetailTableBodyItem; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr.tsx new file mode 100644 index 000000000..c8f2d7b4e --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr.tsx @@ -0,0 +1,72 @@ +import styled from "styled-components"; +import { itemType } from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import { IItem } from "@/Presentation/components/Details/DetailTable/DetailTableBodyItem"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +const DetailTableBodyItemBottomTr = ({ + itemInfo, + isItemOpen, + handleItemClick, + isMobile, + tableHeadEntriesWithoutDate, + tableHeadEntriesWithoutDateAndSubject, +}: { + itemInfo: IItem; + isItemOpen: boolean; + handleItemClick: (item: IPresentationScheduleDetailInfo) => void; + isMobile: boolean; + tableHeadEntriesWithoutDate: [string, string][]; + tableHeadEntriesWithoutDateAndSubject: [string, string][]; +}) => { + return ( + <> + {isItemOpen ? ( + { + !itemInfo.itemStatus && handleItemClick(itemInfo.item); + }} + itemStatus={itemInfo.itemStatus} + > + +
{itemInfo.item.detail}
+ +
+ ) : null} + + ); +}; + +export default DetailTableBodyItemBottomTr; + +const BottomTrStyled = styled.tr<{ + itemStatus: itemType; +}>` + background-color: #91b5fa; + width: 100%; + + & > td { + border-radius: 0 0 10px 10px; + padding: 0; + } + + & > td > div { + background-color: var(--white); + border-radius: 10px; + margin: 24px; + margin-top: 0; + line-height: 24px; + padding: 30px 50px; + font-size: 18px; + } + + &:hover { + cursor: ${(props) => (props.itemStatus ? "" : "pointer")}; + background-color: ${(props) => (props.itemStatus ? "" : "#91B5FA")}; + } +`; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr.tsx new file mode 100644 index 000000000..a869c3808 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr.tsx @@ -0,0 +1,100 @@ +import { NavigateFunction } from "react-router-dom"; +import styled from "styled-components"; +import { itemType } from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import { IItem } from "@/Presentation/components/Details/DetailTable/DetailTableBodyItem"; +import NoEventTableRow from "@/Presentation/components/Details/DetailTable/NoEventTableRow"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +const noEventPhraseMobile = { + noEventPast: "발표가 없었습니다", + noEventCurrent: "지금 바로 발표를 신청해보세요", +}; + +const DetailTableBodyItemMiddleTr = ({ + isAdmin, + itemInfo, + isItemOpen, + handleItemClick, + mobileColSpanSize, + navigator, +}: { + isAdmin: boolean; + itemInfo: IItem; + isItemOpen: boolean; + handleItemClick: (item: IPresentationScheduleDetailInfo) => void; + mobileColSpanSize: number; + navigator: NavigateFunction; +}) => { + return ( + { + !itemInfo.itemStatus && handleItemClick(itemInfo.item); + }} + isItemOpen={isItemOpen} + > + {itemInfo.itemStatus ? ( + + ) : ( + +
{itemInfo.item.subject}
+ + )} +
+ ); +}; + +const MobileMiddleTrStysled = styled.tr<{ + itemStatus: itemType; + isItemOpen: boolean; +}>` + background-color: ${(props) => + !props.itemStatus + ? "#dce7fd" + : props.itemStatus === itemType.NO_EVENT_CURRENT + ? "var(--white)" + : "var(--full)"}; + width: 100%; + height: 50px; + font-size: 14px; + line-height: 50px; + text-align: center; + padding: 0 50px; + + &:hover { + cursor: ${(props) => (props.itemStatus ? "" : "pointer")}; + } + + & button { + width: 100px; + height: 36px; + background-color: #3f69fd; + font-weight: bold; + font-size: 1rem; + } + + & > td { + border-radius: ${(props) => (props.isItemOpen ? "" : "0 0 10px 10px")}; + } + + & > td > #mobileSubjectDiv { + padding: 0 10px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + @media (min-width: 1150px) { + display: none; + } +`; + +export default DetailTableBodyItemMiddleTr; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr.tsx new file mode 100644 index 000000000..c0abc21f6 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr.tsx @@ -0,0 +1,218 @@ +import { NavigateFunction } from "react-router-dom"; +import styled from "styled-components"; +import { itemType } from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import { IItem } from "@/Presentation/components/Details/DetailTable/DetailTableBodyItem"; +import NoEventTableRow from "@/Presentation/components/Details/DetailTable/NoEventTableRow"; +import { + PresentationCategoryTypeLabelMap, + PresentationLocationLabelMap, + PresentationPeriodTypeNumberLabelMap, + PresentationStatusTypeLabelMap, +} from "@/Presentation/assets/data/maps"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +const noEventPhraseDesktop = { + noEventPast: "수요지식회가 열리지 않았습니다", + noEventCurrent: + "다양한 관심사를 함께 나누고 싶으신 분은 지금 바로 발표를 신청해보세요", +}; + +const renderTableData = ( + className: string, + key: number, + title: string, + head: string, + item: IPresentationScheduleDetailInfo +) => { + return ( + +
{renderCellDetail(head, item)}
+ + ); +}; + +const renderCellDetail = ( + head: string, + item: IPresentationScheduleDetailInfo +) => { + switch (head) { + case "subject": + return item.subject; + case "userName": + return item.userName; + case "category": + return PresentationCategoryTypeLabelMap[item.category!]; + case "presentationTime": + return ( + PresentationPeriodTypeNumberLabelMap[item.presentationTime!] + "분" + ); + case "presentationLocation": + return PresentationLocationLabelMap[item.presentationLocation!]; + case "presentationStatus": + return PresentationStatusTypeLabelMap[item.presentationStatus!]; + default: + return null; + } +}; + +const DetailTableBodyItemTopTr = ({ + isAdmin, + itemInfo, + isItemOpen, + handleItemClick, + isMobile, + mobileColSpanSize, + navigator, + tableHeadEntriesWithoutDate, + tableHeadEntriesWithoutDateAndSubject, +}: { + isAdmin: boolean; + itemInfo: IItem; + isItemOpen: boolean; + handleItemClick: (item: IPresentationScheduleDetailInfo) => void; + isMobile: boolean; + mobileColSpanSize: number; + navigator: NavigateFunction; + tableHeadEntriesWithoutDate: [string, string][]; + tableHeadEntriesWithoutDateAndSubject: [string, string][]; +}) => { + return ( + <> + { + !itemInfo.itemStatus && handleItemClick(itemInfo.item); + }} + open={isItemOpen} + > + {/* date cell */} + +
+ {itemInfo.itemDateInIDate?.month}월 {itemInfo.itemDateInIDate?.day} + 일 +
+ + {!isMobile ? ( + // 웹 뷰 + <> + {itemInfo.itemStatus && ( + // 발표 없을때 + <> + + + )} + {!itemInfo.itemStatus && ( + // 발표 있을때 + <> + {tableHeadEntriesWithoutDate.map((head, idx) => { + return renderTableData( + (!isAdmin && head[0] === "presentationLocation") || + (isAdmin && head[0] === "presentationStatus") + ? "rightEnd" + : "", + idx, + head[0] === "subject" ? itemInfo.item.subject ?? "" : "", + head[0], + itemInfo.item + ); + })} + + )} + + ) : ( + // 모바일 뷰 + <> + {!isItemOpen && <>} + {isItemOpen && ( + <> + {tableHeadEntriesWithoutDateAndSubject.map((head, idx) => { + return renderTableData( + head[0] === "presentationLocation" ? "rightEnd" : "", + idx, + "", + head[0], + itemInfo.item + ); + })} + + )} + + )} +
+ + ); +}; +export default DetailTableBodyItemTopTr; + +const TopTrStyled = styled.tr<{ + itemStatus: itemType; + open?: boolean; +}>` + width: 100%; + text-align: center; + & .leftEnd { + border-radius: ${(props) => (props.open ? "10px 0 0 0" : "10px 0 0 10px")}; + } + & .rightEnd { + border-radius: ${(props) => (props.open ? "0 10px 0 0" : "0 10px 10px 0")}; + } + @media (min-width: 1150px) { + font-size: 18px; + background-color: ${(props) => + !props.itemStatus + ? "#dce7fd" + : props.itemStatus === itemType.NO_EVENT_CURRENT + ? "var(--white)" + : "var(--full)"}; + height: 70px; + line-height: 70px; + & > td { + padding: 0 10px; + } + & > td > div { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + & button { + width: 120px; + height: 36px; + background-color: #3f69fd; + font-weight: bold; + font-size: 1rem; + } + &:hover { + cursor: ${(props) => (props.itemStatus ? "" : "pointer")}; + background-color: ${(props) => (props.itemStatus ? "" : "#91B5FB")}; + } + } + @media (max-width: 1150px) { + font-size: 16px; + height: 40px; + line-height: 40px; + width: 100%; + background-color: ${(props) => (props.open ? "#91B5FB" : "#3f69fd")}; + & > td { + border-radius: ${(props) => (props.open ? "" : "10px 10px 0 0")}; + } + & > td > #mobileTopDate { + color: ${(props) => (props.open ? "var(--black)" : "var(--white)")}; + text-align: ${(props) => (props.open ? "center" : "start")}; + padding-left: 10px; + } + &:hover { + cursor: ${(props) => (props.itemStatus ? "" : "pointer")}; + } + } +`; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableHead.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableHead.tsx new file mode 100644 index 000000000..7bae95584 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableHead.tsx @@ -0,0 +1,76 @@ +import styled from "styled-components"; + +const DetailTableHead = ({ + isMobile, + headEntries, + isAdmin, +}: { + isMobile: boolean; + headEntries: [string, string][]; + isAdmin: boolean; +}) => { + return ( + <> + {!isMobile ? ( + + + {headEntries.map((head, idx) => { + return ( + + {head[1]} + + ); + })} + + + ) : null} + + ); +}; + +const TableHeadStyled = styled.thead<{ isAdmin: boolean }>` + margin-bottom: 10px; + height: 40px; + line-height: 40px; + background-color: #3f69fd; + color: var(--white); + width: 100%; + + & > td { + font-size: 1rem; + text-align: center; + } + + & #date { + width: 13%; + border-radius: 10px 0 0 10px; + } + + & #subject { + width: ${(props) => (!props.isAdmin ? "42%" : "31%")}; + } + + & #userName { + width: 14%; + } + + & #category { + width: 9%; + } + + & #presentationTime { + width: 8%; + } + + & #presentationLocation { + width: 14%; + border-radius: ${(props) => (!props.isAdmin ? "0 10px 10px 0" : "")}; + } + + & #presentationStatus { + width: 11%; + border-radius: 0 10px 10px 0; + } +`; + +export default DetailTableHead; diff --git a/frontend/src/Presentation/components/Details/DetailTable/NoEventTableRow.tsx b/frontend/src/Presentation/components/Details/DetailTable/NoEventTableRow.tsx new file mode 100644 index 000000000..61e1b3989 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/NoEventTableRow.tsx @@ -0,0 +1,96 @@ +import { NavigateFunction } from "react-router-dom"; +import styled from "styled-components"; +import { ReactComponent as HappyCcabiImg } from "@/Cabinet/assets/images/happyCcabi.svg"; +import { ReactComponent as SadCcabiImg } from "@/Cabinet/assets/images/sadCcabi.svg"; +import { itemType } from "@/Presentation/components/Details/DetailTable/DetailTable.container"; + +const NoEventTableRow = ({ + isAdmin, + itemStatus, + hasNoUpcomingEvent, + navigator, + colSpanSize, + phrase, +}: { + isAdmin: boolean; + itemStatus: itemType.NO_EVENT_CURRENT | itemType.NO_EVENT_PAST; + hasNoUpcomingEvent: boolean; + navigator: NavigateFunction; + colSpanSize: number; + phrase: { + noEventPast: string; + noEventCurrent: string; + }; +}) => { + return ( + <> + {/* TODO : event 관련 current -> upcoming */} + + + +
{phrase[itemStatus]}
+ + {hasNoUpcomingEvent ? : } + +
+ {hasNoUpcomingEvent && !isAdmin ? ( + + ) : null} +
+ + + ); +}; + +const NoEventDivStyled = styled.div<{ hasNoUpcomingEvent: boolean }>` + display: flex; + justify-content: ${(props) => + props.hasNoUpcomingEvent ? "space-evenly" : "center"}; + align-items: center; + + @media (max-width: 1150px) { + justify-content: center; + align-items: center; + padding: 0 10px; + } +`; + +const NoEventPhraseStyled = styled.div<{ hasNoUpcomingEvent: boolean }>` + display: flex; + align-items: center; + padding: 0 10px; + + & > div { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } +`; + +const CcabiStyled = styled.div<{ hasNoUpcomingEvent: boolean }>` + width: 30px; + height: 30px; + display: flex; + margin-left: 10px; + + & > svg { + width: 30px; + height: 30px; + } + + & svg > path { + fill: var(--black); + } + + @media (max-width: 1220px) { + display: ${(props) => (props.hasNoUpcomingEvent ? "none" : "")}; + } +`; + +export default NoEventTableRow; diff --git a/frontend/src/Presentation/components/Home/PresentationCard.container.tsx b/frontend/src/Presentation/components/Home/PresentationCard.container.tsx new file mode 100644 index 000000000..3553d6e14 --- /dev/null +++ b/frontend/src/Presentation/components/Home/PresentationCard.container.tsx @@ -0,0 +1,107 @@ +import { useEffect, useMemo, useState } from "react"; +import styled from "styled-components"; +import PresentationCard from "@/Presentation/components/Home/PresentationCard"; +import PresentationCardMobile from "@/Presentation/components/Home/PresentationCardMobile"; +import { presentationCategoryIconMap } from "@/Presentation/assets/data/maps"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { PresentationCategoryType } from "@/Presentation/types/enum/presentation.type.enum"; +import useIsMobile from "@/Presentation/hooks/useIsMobile"; + +const PresentationCardContainer = ({ + currentPresentations, +}: { + currentPresentations: IPresentationScheduleDetailInfo[] | null; +}) => { + const isMobile = useIsMobile(1150); + const [selectIndex, setSelectIndex] = useState(1); + const [slide, setSlide] = useState(0); + + const searchCategory = ( + categoryName?: keyof typeof presentationCategoryIconMap + ): string => { + return categoryName != undefined + ? presentationCategoryIconMap[categoryName] + : "/src/Cabinet/assets/images/PresentationEmpty.svg"; + }; + + const onCardClick = (index: number) => { + if (selectIndex !== index) { + setSelectIndex(index); + setSlide(slide + (selectIndex - index) * 345); + } + }; + + const swipeSection = ( + touchEndPosX: number, + touchEndPosY: number, + touchStartPosX: number, + touchStartPosY: number + ) => { + const touchOffsetX = Math.round(touchEndPosX - touchStartPosX); + const touchOffsetY = Math.round(touchEndPosY - touchStartPosY); + + if ( + Math.abs(touchOffsetX) < 50 || + Math.abs(touchOffsetX) < Math.abs(touchOffsetY) + ) { + return; + } + + if (touchOffsetX > 0) { + slideSectionTo("left"); + } else { + slideSectionTo("right"); + } + }; + + const slideSectionTo = (direction: string) => { + if (direction === "left" && selectIndex !== 0) { + setSelectIndex(selectIndex - 1); + setSlide(slide + 345); + } else if (direction === "right" && selectIndex !== 2) { + setSelectIndex(selectIndex + 1); + setSlide(slide - 345); + } + }; + + const refinePresentations = useMemo(() => { + return currentPresentations?.concat( + new Array(Math.max(3 - (currentPresentations?.length || 0), 0)).fill({ + id: -1, + subject: "예정된 일정이 없습니다. 당신의 이야기를 들려주세요", + category: "", + }) + ); + }, [currentPresentations]); + + return ( + + {isMobile ? ( + + ) : ( + + )} + + ); +}; + +export default PresentationCardContainer; + +const ConTainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + max-width: 1000px; + width: 80%; + height: 550px; +`; diff --git a/frontend/src/Presentation/components/Home/PresentationCard.tsx b/frontend/src/Presentation/components/Home/PresentationCard.tsx new file mode 100644 index 000000000..9786afbd3 --- /dev/null +++ b/frontend/src/Presentation/components/Home/PresentationCard.tsx @@ -0,0 +1,156 @@ +import styled from "styled-components"; +import { presentationCategoryIconMap } from "@/Presentation/assets/data/maps"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { makeIDateObj } from "@/Presentation/utils/dateUtils"; + +const PresentationCard = ({ + searchCategory, + refinePresentations, +}: { + searchCategory: ( + categoryName?: keyof typeof presentationCategoryIconMap + ) => string | undefined; + refinePresentations: IPresentationScheduleDetailInfo[] | undefined; +}) => { + return ( + + {refinePresentations?.map((p, index) => { + const tmpDate = p.id !== -1 ? makeIDateObj(new Date(p.dateTime)) : null; + + return ( + + {p.id !== -1 ? ( + <> + + + + {p.category && } + + + {p.subject} + {p.summary} + + {p.userName} + + + + + + {tmpDate?.month}/{tmpDate?.day} + + + + + + ) : ( + <> + + + + + + 예정된 일정이 없습니다. + 당신의 이야기를 들려주세요 + + )} + + ); + })} + + ); +}; + +export default PresentationCard; + +const Container = styled.div` + width: 100%; + display: flex; + align-items: flex-start; +`; + +const PresentationCardStyled = styled.div` + width: 300px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + margin-right: 50px; +`; + +const DetailStyled = styled.div` + width: 300px; +`; + +const CategoryStyled = styled.div` + width: 300px; + height: 300px; + margin-bottom: 16px; + border-radius: 30px; + background-color: #3f69fd; + display: flex; + justify-content: center; + align-items: center; +`; + +const CategoryIconStyled = styled.div` + width: 300px; + height: 220px; +`; + +const TitleStyled = styled.div` + width: 300px; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1.2rem; + font-weight: 700; + word-break: break-all; + line-height: 1.5; + margin-bottom: 12px; +`; + +const SubTitleStyled = styled.div` + width: 300px; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1.1rem; + word-break: break-all; + line-height: 1.5; + margin-bottom: 12px; + color: #797979; +`; + +const DetailFooterStyled = styled.div` + display: flex; + align-items: center; +`; + +const NameStyled = styled.div` + white-space: nowrap; + margin-right: 5px; + color: #9d9d9d; + font-weight: 500; + + ::after { + content: " ㅣ"; + } +`; + +const CalendarStyled = styled.div` + display: flex; + align-items: flex-end; + justify-content: flex-end; + font-size: 1rem; + & > span { + color: #797979; + } +`; + +const IconStyled = styled.div` + height: 15px; + margin-right: 8px; +`; diff --git a/frontend/src/Presentation/components/Home/PresentationCardMobile.tsx b/frontend/src/Presentation/components/Home/PresentationCardMobile.tsx new file mode 100644 index 000000000..30a727ff6 --- /dev/null +++ b/frontend/src/Presentation/components/Home/PresentationCardMobile.tsx @@ -0,0 +1,225 @@ +import { useRef } from "react"; +import styled from "styled-components"; +import { IDate } from "@/Presentation/components/Details/DetailContent.container"; +import { presentationCategoryIconMap } from "@/Presentation/assets/data/maps"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { makeIDateObj } from "@/Presentation/utils/dateUtils"; + +const PresentationCardMobile = ({ + refinePresentations, + searchCategory, + selectIndex, + slide, + onCardClick, + swipeSection, +}: { + refinePresentations: IPresentationScheduleDetailInfo[] | undefined; + searchCategory: ( + categoryName?: keyof typeof presentationCategoryIconMap + ) => string; + selectIndex: number; + slide: number; + onCardClick: (index: number) => void; + swipeSection: ( + touchEndPosX: number, + touchEndPosY: number, + touchStartPosX: number, + touchStartPosY: number + ) => void; +}) => { + const touchStartPosX = useRef(0); + const touchStartPosY = useRef(0); + const components = []; + + for (let i = 0; i < 3; i++) { + components.push( + onCardClick(i)} + current={i == selectIndex} + > + ); + } + + return ( + <> + + {refinePresentations?.map((p, index) => { + const tmpDate = + p.id !== -1 ? makeIDateObj(new Date(p.dateTime)) : null; + + return ( + onCardClick(index)} + className={index == slide ? "check" : "not-check"} + onTouchStart={(e: React.TouchEvent) => { + touchStartPosX.current = e.changedTouches[0].screenX; + touchStartPosY.current = e.changedTouches[0].screenY; + }} + onTouchEnd={(e: React.TouchEvent) => { + swipeSection( + e.changedTouches[0].screenX, + e.changedTouches[0].screenY, + touchStartPosX.current, + touchStartPosY.current + ); + }} + > + {p.id !== -1 ? ( + <> + + + {p.category && } + + + + {p.subject} + {p.summary} + + {p.userName} + + + + + + {tmpDate?.month}/{tmpDate?.day} + + + + + + ) : ( + <> + + + + + + 예정된 일정이 없습니다. + 당신의 이야기를 들려주세요 + + )} + + ); + })} + + {components} + + ); +}; + +export default PresentationCardMobile; + +const Container = styled.div<{ select: number }>` + overflow-x: hidden; + display: flex; + align-items: flex-start; + width: 100%; + min-width: 1000px; + min-height: 500px; + margin-bottom: 20px; + transform: translateX(${(props) => props.select}px); + transition: all 500ms ease-in-out; +`; + +const PresentationCardStyled = styled.div` + width: 300px; + margin-right: 50px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; +`; + +const CategoryStyled = styled.div` + width: 300px; + height: 300px; + margin-bottom: 16px; + border-radius: 30px; + background-color: #3f69fd; + display: flex; + justify-content: center; + align-items: center; +`; + +const CategoryIconStyled = styled.div` + width: 300px; + height: 220px; +`; + +const DetailStyled = styled.div` + width: 300px; +`; + +const TitleStyled = styled.div` + width: 300px; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1.2rem; + font-weight: 700; + word-break: break-all; + line-height: 1.5; + margin-bottom: 12px; +`; + +const SubTitleStyled = styled.div` + width: 300px; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1.1rem; + word-break: break-all; + line-height: 1.5; + margin-bottom: 12px; + color: #797979; +`; + +const DetailFooterStyled = styled.div` + display: flex; + align-items: center; +`; + +const NameStyled = styled.div` + white-space: nowrap; + margin-right: 5px; + color: #9d9d9d; + font-weight: 500; + + ::after { + content: "ㅣ"; + } +`; + +const CalendarStyled = styled.div` + display: flex; + align-items: flex-end; + justify-content: flex-end; + font-size: 1rem; + + & > span { + color: #797979; + } +`; + +const IconStyled = styled.div` + height: 15px; + margin-right: 8px; +`; + +const PaginationStyled = styled.div` + display: flex; + padding-bottom: 30px; +`; + +const Paginations = styled.div<{ current: boolean }>` + width: 30px; + height: 10px; + background-color: ${(props) => (props.current ? "#5378fd" : "gray")}; + border-radius: 12px; + margin: 0 5px; +`; diff --git a/frontend/src/Presentation/components/Home/RecentPresentation.tsx b/frontend/src/Presentation/components/Home/RecentPresentation.tsx new file mode 100644 index 000000000..23a02b238 --- /dev/null +++ b/frontend/src/Presentation/components/Home/RecentPresentation.tsx @@ -0,0 +1,147 @@ +import { useEffect, useState } from "react"; +import styled from "styled-components"; +import PresentationCardContainer from "@/Presentation/components/Home/PresentationCard.container"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { axiosGetPresentation } from "@/Presentation/api/axios/axios.custom"; + +const RecentPresentation = ({ + presentButtonHandler, +}: { + presentButtonHandler: () => void; +}) => { + const [currentPresentations, setCurrentPresentations] = useState< + IPresentationScheduleDetailInfo[] | null + >(null); + + const getCurrentPresentation = async () => { + try { + const response = await axiosGetPresentation(); + setCurrentPresentations([ + ...response.data.past, + ...response.data.upcoming, + ]); + } catch (error: any) { + // TODO + } finally { + // TODO + } + }; + + useEffect(() => { + getCurrentPresentation(); + }, []); + + return ( + + + +

수요지식회

+ { + presentButtonHandler(); + }} + > + 발표신청 + +
+ 지식이 일상이 되다. +
+ + + { + presentButtonHandler(); + }} + > + 발표신청 + +
+ ); +}; + +export default RecentPresentation; + +const ContainerStyled = styled.div` + padding-top: 60px; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + overflow-y: scroll; + overflow-x: hidden; +`; + +const HeaderStyled = styled.div` + display: flex; + max-width: 1000px; + width: 80%; + justify-content: space-between; + flex-wrap: wrap; + align-items: flex-end; + + & > span { + margin-top: 10px; + margin-bottom: 40px; + display: block; + font-size: 2rem; + font-weight: 600; + word-break: keep-all; + } + + @media screen and (max-width: 430px) { + & > span { + font-size: 1.25rem; + } + & > div { + margin-bottom: 0px; + padding-bottom: 0px; + align-items: center; + } + } +`; + +const TitleContainerStyled = styled.div` + padding-bottom: 10px; + margin-bottom: 10px; + height: 60px; + display: flex; + justify-content: space-between; + align-items: flex-start; + font-weight: 700; + border-bottom: 2px solid #d9d9d9; + width: 100%; + font-size: 2.5rem; + + @media screen and (max-width: 430px) { + & > p { + font-size: 1.5rem; + padding-bottom: 10px; + } + height: 35px; + } + + & > h1 { + font-size: 2.5rem; + font-weight: 700; + } +`; + +const RegisterButtonStyled = styled.button` + background-color: #3f69fd; + width: 150px; + height: 50px; + @media screen and (max-width: 425px) { + display: none; + } +`; + +const MobileRegisertButtonStyled = styled.button` + background-color: #3f69fd; + width: 80%; + height: 50px; + margin-bottom: 30px; + margin-top: 30px; + @media screen and (min-width: 425px) { + display: none; + } +`; diff --git a/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx new file mode 100644 index 000000000..367f83995 --- /dev/null +++ b/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -0,0 +1,76 @@ +import { useLocation, useNavigate } from "react-router-dom"; +import { useResetRecoilState } from "recoil"; +import { + currentBuildingNameState, + currentFloorNumberState, + currentSectionNameState, +} from "@/Cabinet/recoil/atoms"; +import { removeCookie } from "@/Cabinet/api/react_cookie/cookies"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import LeftMainNav from "@/Presentation/components/LeftNav/LeftMainNav/LeftMainNav"; + +const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { + const resetCurrentFloor = useResetRecoilState(currentFloorNumberState); + const resetCurrentSection = useResetRecoilState(currentSectionNameState); + const resetBuilding = useResetRecoilState(currentBuildingNameState); + const navigator = useNavigate(); + const { pathname } = useLocation(); + + const { closeAll } = useMenu(); + + const onClickPresentationHomeButton = () => { + navigator("/presentation/home"); + closeAll(); + }; + + const onClickPresentationRegisterButton = () => { + navigator("/presentation/register"); + closeAll(); + }; + + const onClickPresentationDetailButton = () => { + if (isAdmin) { + navigator("/presentation/detail"); + } else { + navigator("detail"); + } + closeAll(); + }; + + const onClickPresentationLogButton = () => { + navigator("/presentation/log"); + closeAll(); + }; + + const onClickLogoutButton = (): void => { + const adminToken = isAdmin ? "admin_" : ""; + if (import.meta.env.VITE_IS_LOCAL === "true") { + removeCookie(adminToken + "access_token", { + path: "/", + domain: "localhost", + }); + } else { + removeCookie(adminToken + "access_token", { + path: "/", + domain: "cabi.42seoul.io", + }); + } + resetBuilding(); + resetCurrentFloor(); + resetCurrentSection(); + navigator("/login"); + }; + return ( + + ); +}; + +export default LeftMainNavContainer; diff --git a/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.tsx new file mode 100644 index 000000000..2bee6f4fd --- /dev/null +++ b/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -0,0 +1,210 @@ +import styled from "styled-components"; +import { ReactComponent as LogoutImg } from "@/Cabinet/assets/images/close-square.svg"; + +interface ILeftMainNav { + pathname: string; + onClickLogoutButton: React.MouseEventHandler; + onClickPresentationHomeButton: React.MouseEventHandler; + onClickPresentationRegisterButton: React.MouseEventHandler; + onClickPresentationDetailButton: React.MouseEventHandler; + onClickPresentationLogButton: React.MouseEventHandler; + isAdmin?: boolean; +} + +const LeftMainNav = ({ + pathname, + onClickLogoutButton, + onClickPresentationHomeButton, + onClickPresentationRegisterButton, + onClickPresentationDetailButton, + onClickPresentationLogButton, + isAdmin, +}: ILeftMainNav) => { + return ( + + + + {isAdmin ? ( + + {"일정관리"} + + ) : ( + <> + + Home + + + 발표신청 + + + 발표기록 + + + {isAdmin ? "일정관리" : "상세정보"} + + + )} + + + + + {!isAdmin && ( + + + Logout + + )} + + + + ); +}; + +const LeftNavStyled = styled.nav` + width: 90px; + min-width: 90px; + position: relative; + height: 100%; + border-right: 1px solid var(--line-color); + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; +`; + +const TopSectionStyled = styled.section` + position: relative; + overflow-x: hidden; +`; + +const TopBtnsStyled = styled.ul` + text-align: center; + padding: 30px 10px; +`; + +const TopBtnStyled = styled.li` + width: 100%; + height: 48px; + line-height: 48px; + font-weight: 300; + margin-bottom: 2.5vh; + border-radius: 10px; + color: var(--gray-color); + cursor: pointer; + &:last-child { + margin-bottom: 0; + } + @media (hover: hover) and (pointer: fine) { + &:hover { + color: var(--white); + background-color: var(--main-color); + } + } +`; + +const BottomSectionStyled = styled.section` + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-end; + &::after { + content: ""; + position: absolute; + top: 0; + left: 17px; + margin: 0 auto; + width: 56px; + height: 1px; + background-color: var(--line-color); + } +`; +const BottomBtnsStyled = styled.ul` + padding: 30px 10px; + text-align: center; +`; + +const BottomBtnStyled = styled.li` + width: 100%; + min-height: 48px; + line-height: 1.125rem; + font-weight: 300; + margin-top: 2.5vh; + border-radius: 10px; + color: var(--gray-color); + cursor: pointer; + display: flex; + flex-direction: column; + justify-content: center; + &:first-child { + margin-top: 0; + } + & a { + color: var(--gray-color); + } + & div { + width: 24px; + height: 24px; + margin: 0 auto; + margin-bottom: 4px; + } + &.active { + color: var(--main-color); + svg { + stroke: var(--main-color); + } + } + svg { + margin: 0 auto; + } + @media (hover: hover) and (pointer: fine) { + &:hover { + color: var(--main-color); + svg { + stroke: var(--main-color); + } + a { + color: var(--main-color); + } + } + } +`; + +export default LeftMainNav; diff --git a/frontend/src/Presentation/components/LeftNav/LeftNav.tsx b/frontend/src/Presentation/components/LeftNav/LeftNav.tsx new file mode 100644 index 000000000..5d34e1664 --- /dev/null +++ b/frontend/src/Presentation/components/LeftNav/LeftNav.tsx @@ -0,0 +1,20 @@ +import styled from "styled-components"; +import LeftMainNavContainer from "@/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container"; + +const LeftNav: React.FC<{ + isVisible: boolean; + isAdmin?: boolean; +}> = ({ isAdmin, isVisible }) => { + return ( + + + {/* */} + + ); +}; + +const LeftNavWrapStyled = styled.div` + display: flex; +`; + +export default LeftNav; diff --git a/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx b/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx new file mode 100644 index 000000000..3fad633e7 --- /dev/null +++ b/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx @@ -0,0 +1,297 @@ +import { format } from "date-fns"; +import { useEffect, useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import styled from "styled-components"; +import Button from "@/Cabinet/components/Common/Button"; +import Dropdown, { + IDropdown, + IDropdownOptions, +} from "@/Cabinet/components/Common/Dropdown"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { + currentPresentationState, + isCurrentModalState, +} from "@/Presentation/recoil/atoms"; +import { + PresentationLocation, + PresentationStatusType, +} from "@/Presentation/types/enum/presentation.type.enum"; +import { + axiosGetInvalidDates, + axiosUpdatePresentationStatus, +} from "@/Presentation/api/axios/axios.custom"; +import { + calculateAvailableDaysInWeeks, + filterInvalidDates, +} from "@/Presentation/utils/dateUtils"; +import { WEDNESDAY } from "@/Presentation/constants/dayOfTheWeek"; +import { + AVAILABLE_WEEKS, + FUTURE_MONTHS_TO_DISPLAY, +} from "@/Presentation/constants/policy"; + +interface EditStatusModalProps { + closeModal: React.MouseEventHandler; +} + +const statusOptions: IDropdownOptions[] = [ + { name: "발표 예정", value: PresentationStatusType.EXPECTED }, + { name: "발표 완료", value: PresentationStatusType.DONE }, + { name: "발표 취소", value: PresentationStatusType.CANCEL }, +]; + +const floorOptions: IDropdownOptions[] = [ + { name: "지하 1층", value: PresentationLocation.BASEMENT }, + { name: "1층", value: PresentationLocation.FIRST }, + { name: "3층", value: PresentationLocation.THIRD }, +]; + +const EditStatusModal = ({ closeModal }: EditStatusModalProps) => { + const [currentPresentation, setCurrentPresentation] = useRecoilState( + currentPresentationState + ); + const setIsCurrentModalRender = useSetRecoilState(isCurrentModalState); + const [showResponseModal, setShowResponseModal] = useState(false); + const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false); + const [modalTitle, setModalTitle] = useState(""); + const [presentationDate, setPresentationDate] = useState( + currentPresentation?.dateTime + ? new Date(currentPresentation?.dateTime).toISOString() + : "" + ); + const [presentationStatus, setPresentationStatus] = + useState(PresentationStatusType.EXPECTED); + const [location, setLocation] = useState( + PresentationLocation.THIRD + ); + const [invalidDates, setInvalidDates] = useState([]); + const [statusDropdownProps, setStatusDropdownProps] = useState({ + options: statusOptions, + defaultValue: + statusOptions.find( + (option) => option.value === currentPresentation?.presentationStatus + )?.name ?? "발표 예정", + defaultImageSrc: "", + onChangeValue: (val: PresentationStatusType) => { + setPresentationStatus(val); + }, + }); + const [datesDropdownProps, setDatesDropdownProps] = useState({ + options: [], + defaultValue: currentPresentation?.dateTime + ? format(currentPresentation?.dateTime.split("T")[0], "M월 d일") + : "", + defaultImageSrc: "", + onChangeValue: (val: string) => { + setPresentationDate(val); + }, + }); + const [locationDropdownProps, setLocationDropdownProps] = useState( + { + options: floorOptions, + defaultValue: + floorOptions.find( + (option) => option.value === currentPresentation?.presentationLocation + )?.name ?? "3층", + defaultImageSrc: "", + onChangeValue: (val: PresentationLocation) => { + setLocation(val); + }, + } + ); + + const tryEditPresentationStatus = async (e: React.MouseEvent) => { + if (!currentPresentation || !currentPresentation.id) return; + const data = new Date(presentationDate); + // NOTE: Date 객체의 시간은 UTC 기준이므로 한국 시간 (GMT + 9) 으로 변환, 이후 발표 시작 시간인 14시를 더해줌 + data.setHours(9 + 14); + + try { + await axiosUpdatePresentationStatus( + currentPresentation.id, + data.toISOString(), + presentationStatus, + location + ); + setModalTitle("수정이 완료되었습니다"); + setIsCurrentModalRender(true); + } catch (error: any) { + setModalTitle(error.response.data.message); + setHasErrorOnResponse(true); + } finally { + setShowResponseModal(true); + } + }; + + const getInvalidDates = async () => { + try { + const response = await axiosGetInvalidDates(); + setInvalidDates(response.data.invalidDateList); + } catch (error: any) { + setModalTitle(error.response.data.message); + setHasErrorOnResponse(true); + setShowResponseModal(true); + } + }; + + useEffect(() => { + getInvalidDates(); + }, []); + + useEffect(() => { + if (!currentPresentation) return; + // NOTE: 발표 가능한 날짜들을 계산 + const availableDates: Date[] = calculateAvailableDaysInWeeks( + new Date(), + AVAILABLE_WEEKS, + WEDNESDAY, + FUTURE_MONTHS_TO_DISPLAY + ); + // NOTE: 발표 가능한 날짜 중 유효하지 않은 날짜를 필터링 + const availableDatesFiltered: Date[] = filterInvalidDates( + availableDates, + invalidDates + ); + // NOTE: 발표 가능 날짜들을 Dropdown options으로 변환 + const dropdownOptions: IDropdownOptions[] = availableDatesFiltered.map( + (date) => ({ + name: format(date, "M월 d일"), + value: date, + }) + ); + setDatesDropdownProps({ + options: dropdownOptions, + defaultValue: dropdownOptions[0].name, + onChangeValue: (val: string) => { + setPresentationDate(val); + }, + }); + }, [invalidDates]); + + return ( + <> + + {!showResponseModal && ( + <> + + + 일정 관리 + + + + 발표 상태 + + + + 날짜 + + + + 장소 + + + + + +
`, - pointColor: "var(--color-text-with-bg)", + pointColor: "var(--text-with-bg-color)", }, [ContentStatus.SHARE]: { contentTitle: "공유 사물함", @@ -60,7 +60,7 @@ export const manualContentData: Record = { 연체 페널티는 누적됩니다. `, - pointColor: "var(--color-text-with-bg)", + pointColor: "var(--text-with-bg-color)", }, [ContentStatus.CLUB]: { contentTitle: "동아리 사물함", @@ -87,7 +87,7 @@ export const manualContentData: Record = { 상세 페이지가 제공되지 않습니다.
비밀번호는 동아리 내에서 공유하여 이용하세요.`, - pointColor: "var(--color-text-with-bg)", + pointColor: "var(--text-with-bg-color)", }, [ContentStatus.PENDING]: { contentTitle: "오픈예정", @@ -103,12 +103,12 @@ export const manualContentData: Record = { title="슬랙 캐비닛 채널 새창으로 열기" > 슬랙 캐비닛 채널에서 확인하세요.`, - pointColor: "var(--color-text-with-bg)", + pointColor: "var(--text-with-bg-color)", }, [ContentStatus.IN_SESSION]: { contentTitle: "대기중", imagePath: "/src/assets/images/clock.svg", - background: "var(--gray-tmp-1)", + background: "var(--shared-gray-color-100)", contentText: `◦ 상세 내용
공유 사물함 대여 시 10분간의 대기 시간이 발생합니다.
대여 과정에서 생성된 초대 코드를 통해 공유 사물함에 입장할 수 있습니다.
@@ -128,7 +128,7 @@ export const manualContentData: Record = { [ContentStatus.EXTENSION]: { contentTitle: "연장권 이용방법 안내서", imagePath: "/src/assets/images/extension.svg", - background: "var(--gray-tmp-1)", + background: "var(--shared-gray-color-100)", contentText: `◦ 연장권 취득 조건
월 출석 시간이 기준 시간 이상일 시 연장권이 부여됩니다.
@@ -144,6 +144,6 @@ export const manualContentData: Record = { 연장권은 해당 월의 마지막 날까지 사용 가능합니다.
공유 사물함에 한 명만 남거나 연체된 상태라면 연장권 사용이 불가합니다.
`, - pointColor: "var(--color-text-normal)", + pointColor: "var(--normal-text-color)", }, }; diff --git a/frontend/src/assets/data/maps.ts b/frontend/src/assets/data/maps.ts index b5805dd26..bc185d829 100644 --- a/frontend/src/assets/data/maps.ts +++ b/frontend/src/assets/data/maps.ts @@ -37,27 +37,27 @@ export const cabinetIconSrcMap = { }; export const cabinetLabelColorMap = { - [CabinetStatus.AVAILABLE]: "var(--color-text-with-bg)", + [CabinetStatus.AVAILABLE]: "var(--text-with-bg-color)", [CabinetStatus.FULL]: "var(--black)", // black - [CabinetStatus.OVERDUE]: "var(--color-text-with-bg)", - [CabinetStatus.BROKEN]: "var(--color-text-with-bg)", - [CabinetStatus.BANNED]: "var(--color-text-with-bg)", + [CabinetStatus.OVERDUE]: "var(--text-with-bg-color)", + [CabinetStatus.BROKEN]: "var(--text-with-bg-color)", + [CabinetStatus.BANNED]: "var(--text-with-bg-color)", [CabinetStatus.IN_SESSION]: "var(--main-color)", - [CabinetStatus.PENDING]: "var(--color-text-with-bg)", + [CabinetStatus.PENDING]: "var(--text-with-bg-color)", MINE: "var(--black)", // black }; export const cabinetStatusColorMap = { - [CabinetStatus.AVAILABLE]: "var(--available)", - [CabinetStatus.FULL]: "var(--full)", - [CabinetStatus.OVERDUE]: "var(--expired)", - [CabinetStatus.BROKEN]: "var(--broken)", - [CabinetStatus.BANNED]: "var(--banned)", - [CabinetStatus.IN_SESSION]: "var(--session)", - [CabinetStatus.PENDING]: "var(--pending)", - MINE: "var(--mine)", + [CabinetStatus.AVAILABLE]: "var(--available-color)", + [CabinetStatus.FULL]: "var(--full-color)", + [CabinetStatus.OVERDUE]: "var(--expired-color)", + [CabinetStatus.BROKEN]: "var(--broken-color)", + [CabinetStatus.BANNED]: "var(--banned-color)", + [CabinetStatus.IN_SESSION]: "var(--session-color)", + [CabinetStatus.PENDING]: "var(--pending-color)", + MINE: "var(--mine-color)", }; export const modalPropsMap = { @@ -199,14 +199,14 @@ export const modalPropsMap = { }; export const cabinetFilterMap = { - [CabinetStatus.AVAILABLE]: "var(--color-text-with-bg)", + [CabinetStatus.AVAILABLE]: "var(--text-with-bg-color)", [CabinetStatus.FULL]: "var(--black)", // black - [CabinetStatus.OVERDUE]: "var(--color-text-with-bg)", - [CabinetStatus.BROKEN]: "var(--color-text-with-bg)", - [CabinetStatus.BANNED]: "var(--color-text-with-bg)", - [CabinetStatus.IN_SESSION]: "var(--color-text-normal)", - [CabinetStatus.PENDING]: "var(--color-text-with-bg)", + [CabinetStatus.OVERDUE]: "var(--text-with-bg-color)", + [CabinetStatus.BROKEN]: "var(--text-with-bg-color)", + [CabinetStatus.BANNED]: "var(--text-with-bg-color)", + [CabinetStatus.IN_SESSION]: "var(--normal-text-color)", + [CabinetStatus.PENDING]: "var(--text-with-bg-color)", }; export const cabinetStatusLabelMap = { diff --git a/frontend/src/components/AdminInfo/Chart/BarChart.tsx b/frontend/src/components/AdminInfo/Chart/BarChart.tsx index 2ef2d2f36..d452aed25 100644 --- a/frontend/src/components/AdminInfo/Chart/BarChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/BarChart.tsx @@ -46,12 +46,12 @@ const BarChart = ({ data }: { data: IRentInfo[] }) => ( legends: { text: { fontSize: "14px" } }, axis: { ticks: { text: { fontSize: "14px" } } }, labels: { text: { fontSize: "14px" } }, - textColor: "var(--color-text-normal)", + textColor: "var(--normal-text-color)", tooltip: { container: { - backgroundColor: "var(--color-background)", - boxShadow: "var(--border-shadow-200) 0 1px 2px", - color: "var(--color-text-normal)", + backgroundColor: "var(--bg-color)", + boxShadow: "var(--border-shadow-color-200) 0 1px 2px", + color: "var(--normal-text-color)", }, }, }} @@ -63,9 +63,9 @@ const BarChart = ({ data }: { data: IRentInfo[] }) => ( valueScale={{ type: "linear" }} indexScale={{ type: "band", round: true }} colors={[ - "var(--banned)", - "var(--expired)", - "var(--full)", + "var(--banned-color)", + "var(--expired-color)", + "var(--full-color)", "var(--main-color)", ]} // 사용불가와 사용 중은 헷갈릴 수 있으니 색 그대로 diff --git a/frontend/src/components/AdminInfo/Chart/LineChart.tsx b/frontend/src/components/AdminInfo/Chart/LineChart.tsx index c98ef4c1d..6a6760390 100644 --- a/frontend/src/components/AdminInfo/Chart/LineChart.tsx +++ b/frontend/src/components/AdminInfo/Chart/LineChart.tsx @@ -48,12 +48,12 @@ const LineChart = ({ data }: { data: IMonthlyData[] }) => ( { labels: { text: { fontSize: "15px" } }, tooltip: { container: { - backgroundColor: "var(--color-background)", - boxShadow: "var(--border-shadow-200) 0 1px 2px", - color: "var(--color-text-normal)", + backgroundColor: "var(--bg-color)", + boxShadow: "var(--border-shadow-color-200) 0 1px 2px", + color: "var(--normal-text-color)", }, }, }} margin={{ top: 40, right: 80, bottom: 80, left: 80 }} colors={[ - "var(--full)", - "var(--expired)", + "var(--full-color)", + "var(--expired-color)", "var(--main-color)", - "var(--banned)", + "var(--banned-color)", ]} // 사용불가와 사용 중은 헷갈릴 수 있으니 색 그대로 innerRadius={0.5} @@ -85,7 +85,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { modifiers: [["darker", 0.2]], }} arcLinkLabelsSkipAngle={10} - arcLinkLabelsTextColor="var(--gray-tmp-6)" + arcLinkLabelsTextColor="var(--shared-gray-color-600)" arcLinkLabelsThickness={2} arcLinkLabelsColor={{ from: "color" }} arcLabelsSkipAngle={10} @@ -103,7 +103,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { itemsSpacing: 5, itemWidth: 70, itemHeight: 18, - itemTextColor: "var(--gray-tmp-5)", + itemTextColor: "var(--shared-gray-color-500)", itemDirection: "top-to-bottom", itemOpacity: 1, symbolSize: 12, @@ -112,7 +112,7 @@ const PieChart = ({ data }: { data: IRentInfo[] }) => { { on: "hover", style: { - itemTextColor: "var(--color-text-normal)", + itemTextColor: "var(--normal-text-color)", }, }, ], diff --git a/frontend/src/components/AdminInfo/Table/AdminTable.tsx b/frontend/src/components/AdminInfo/Table/AdminTable.tsx index b88502a26..613a9e008 100644 --- a/frontend/src/components/AdminInfo/Table/AdminTable.tsx +++ b/frontend/src/components/AdminInfo/Table/AdminTable.tsx @@ -80,18 +80,18 @@ const TableWrapperStyled = styled.div` width: 80%; height: 100%; margin: 0 auto; - background: var(--color-background); + background: var(--bg-color); `; const TableBorderStyled = styled.div` border-radius: 10px; overflow: hidden; - box-shadow: 0 10px 10px 0px var(--border-shadow-100); + box-shadow: 0 10px 10px 0px var(--border-shadow-color-100); `; const TableStyled = styled.table` width: 100%; - background: var(--color-background); + background: var(--bg-color); overflow: scroll; @media screen and (max-width: 1300px) { @@ -104,7 +104,7 @@ const TheadStyled = styled.thead` height: 45px; line-height: 45px; background-color: var(--main-color); - color: var(--color-background); + color: var(--bg-color); `; const TbodyStyled = styled.tbody` @@ -117,7 +117,7 @@ const TbodyStyled = styled.tbody` line-height: 45px; } & > tr:nth-child(2n) { - background: var(--purple-tmp-1); + background: var(--shared-purple-color-100); } cursor: pointer; diff --git a/frontend/src/components/AdminInfo/Table/Pagination.tsx b/frontend/src/components/AdminInfo/Table/Pagination.tsx index 5f09aa0e0..f5cd31b09 100644 --- a/frontend/src/components/AdminInfo/Table/Pagination.tsx +++ b/frontend/src/components/AdminInfo/Table/Pagination.tsx @@ -33,15 +33,15 @@ const Pagination = ({ const PageButtonStyled = styled.div` width: 10px; height: 10px; - border: 2px solid var(--color-line); + border: 2px solid var(--line-color); border-radius: 100%; margin: 0 5px; cursor: pointer; `; const ActivaPageButtonStyled = styled(PageButtonStyled)` - background: var(--gray-tmp-5); - border: 2px solid var(--gray-tmp-5); + background: var(--shared-gray-color-500); + border: 2px solid var(--shared-gray-color-500); `; const ButtonContainerStyled = styled.div` diff --git a/frontend/src/components/Announce/AnnounceTemplate.tsx b/frontend/src/components/Announce/AnnounceTemplate.tsx index 17c032bfb..b9ba6dc3f 100644 --- a/frontend/src/components/Announce/AnnounceTemplate.tsx +++ b/frontend/src/components/Announce/AnnounceTemplate.tsx @@ -61,14 +61,14 @@ const AnnounceTemplateStyled = styled.div<{ backgroundColor: string }>` align-items: center; flex-direction: column; background-color: ${(props) => props.backgroundColor}; - color: var(--color-background); + color: var(--bg-color); `; const TitleStyled = styled.h1` font-size: 5rem; - color: var(--mine); + color: var(--mine-color); font-family: "Do Hyeon", sans-serif; - filter: drop-shadow(0 0 0.75rem var(--mine)); + filter: drop-shadow(0 0 0.75rem var(--mine-color)); text-align: center; @media screen and (max-width: 768px) { font-size: 3.25rem; @@ -122,7 +122,7 @@ const CabiImgStyled = styled.div` `; const ButtonStyled = styled.button` - box-shadow: 10px 10px 40px 0px var(--border-shadow-200); + box-shadow: 10px 10px 40px 0px var(--border-shadow-color-200); `; export default AnnounceTemplate; diff --git a/frontend/src/components/Available/FloorContainer.tsx b/frontend/src/components/Available/FloorContainer.tsx index d55713790..8c4decb82 100644 --- a/frontend/src/components/Available/FloorContainer.tsx +++ b/frontend/src/components/Available/FloorContainer.tsx @@ -56,10 +56,10 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` display: flex; justify-content: space-between; font-size: 1.1rem; - color: var(--color-text-normal); + color: var(--normal-text-color); padding-left: 5px; padding-right: 5px; - border-bottom: 1.5px solid var(--gray-tmp-3); + border-bottom: 1.5px solid var(--shared-gray-color-300); cursor: pointer; button { all: initial; @@ -91,7 +91,7 @@ const NoAvailableCabinetMessageStyled = styled.div<{ isToggled: boolean }>` margin-top: 20px; margin-left: 5px; p { - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); line-height: 1.5; word-break: keep-all; } diff --git a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 9f71508dd..0650a743d 100644 --- a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -70,7 +70,10 @@ const AdminCabinetInfoArea: React.FC<{ - + 사물함/유저를
선택해주세요
@@ -80,7 +83,7 @@ const AdminCabinetInfoArea: React.FC<{ if (multiSelectTargetInfo) { return ( - + {currentFloor + "F - " + currentSection} @@ -132,7 +135,7 @@ const AdminCabinetInfoArea: React.FC<{ return ( 대여기록 - + {selectedCabinetInfo!.floor + "F - " + selectedCabinetInfo!.section} {CabinetIcon && } - + {selectedCabinetInfo!.userNameList} @@ -174,7 +177,7 @@ const AdminCabinetInfoArea: React.FC<{ > {selectedCabinetInfo!.detailMessage} - + {expireDate} {adminModal.returnModal && ( @@ -231,7 +234,7 @@ const CabinetTypeIconStyled = styled.div` margin-bottom: 10px; & path { - stroke: var(--color-text-normal); + stroke: var(--normal-text-color); } `; @@ -272,7 +275,7 @@ const CabinetRectangleStyled = styled.div<{ ${(props) => props.isMine && css` - background-color: var(--mine); + background-color: var(--mine-color); `}; font-size: 2rem; color: ${(props) => @@ -289,7 +292,7 @@ const CabinetRectangleStyled = styled.div<{ `} ${({ cabinetStatus }) => css` box-shadow: ${cabinetStatus === "PENDING" && - "inset 0px 0px 0px 2px var(--color-background)"}; + "inset 0px 0px 0px 2px var(--bg-color)"}; `} `; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx index f647e0612..01d7c2d95 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx @@ -143,17 +143,17 @@ export const getDetailMessageColor = ( const { status, lentType, lents } = selectedCabinetInfo; // 밴, 고장 사물함 if (status === CabinetStatus.BANNED || status === CabinetStatus.BROKEN) - return "var(--expired)"; + return "var(--expired-color)"; // 사용 중 사물함 else if ( (status === CabinetStatus.FULL && lentType !== "CLUB") || status === CabinetStatus.OVERDUE ) return calExpiredTime(new Date(lents[0].expiredAt)) < 0 - ? "var(--expired)" - : "var(--color-text-normal)"; + ? "var(--expired-color)" + : "var(--normal-text-color)"; // 빈 사물함 - else return "var(--color-text-normal)"; + else return "var(--normal-text-color)"; }; const CabinetInfoAreaContainer = (): JSX.Element => { diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx index 58ecdc123..087024aa3 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -68,14 +68,14 @@ const CabinetInfoArea: React.FC<{ - + 사물함을
선택해주세요
) : ( - + {selectedCabinetInfo!.floor !== 0 ? selectedCabinetInfo!.floor + "F - " + selectedCabinetInfo!.section : "-"} @@ -91,7 +91,7 @@ const CabinetInfoArea: React.FC<{ {CabinetIcon && } - + {selectedCabinetInfo!.userNameList} @@ -177,7 +177,7 @@ const CabinetInfoArea: React.FC<{ > {selectedCabinetInfo!.detailMessage}
- + {selectedCabinetInfo!.cabinetId === 0 ? "" : expireDate} @@ -308,7 +308,7 @@ const CabinetTypeIconStyled = styled.div` margin-bottom: 10px; & path { - stroke: var(--color-text-normal); + stroke: var(--normal-text-color); } `; @@ -332,7 +332,7 @@ const CabinetRectangleStyled = styled.div<{ margin-top: 15px; margin-bottom: 3vh; background-color: ${({ cabinetStatus, isMine }) => - isMine ? "var(--mine)" : cabinetStatusColorMap[cabinetStatus]}; + isMine ? "var(--mine-color)" : cabinetStatusColorMap[cabinetStatus]}; font-size: 2rem; color: ${(props) => @@ -350,7 +350,7 @@ const CabinetRectangleStyled = styled.div<{ cabinetStatus === "PENDING" && css` border: 2px double var(--main-color); - box-shadow: inset 0px 0px 0px 2px var(--color-background); + box-shadow: inset 0px 0px 0px 2px var(--bg-color); `} `; @@ -375,7 +375,7 @@ const HoverBox = styled.div<{ padding: 10px; background-color: rgba(73, 73, 73, 0.99); border-radius: 10px; - box-shadow: 4px 4px 20px 0px var(--bg-shadow-300); + box-shadow: 4px 4px 20px 0px var(--bg-shadow-color-300); /* TODO : HoverBox 안쓰면 지우고, 쓰면 바꾸기 */ font-size: 0.875rem; text-align: center; @@ -432,7 +432,7 @@ const AvailableMessageStyled = styled.p` text-align: center; font-weight: 700; line-height: 26px; - color: var(--color-text-normal); + color: var(--normal-text-color); `; const ButtonContainerStyled = styled.button` @@ -453,7 +453,7 @@ const ButtonContainerStyled = styled.button` ${(props) => props.theme === "line" && css` - background: var(--color-background); + background: var(--bg-color); color: var(--main-color); border: 1px solid var(--main-color); `} diff --git a/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx b/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx index 1456f757e..6c23bb8e2 100644 --- a/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx +++ b/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx @@ -60,10 +60,10 @@ const HoverBox = styled.div` padding: 10px; background-color: rgba(73, 73, 73, 0.99); border-radius: 10px; - box-shadow: 4px 4px 20px 0px var(--bg-shadow-300); + box-shadow: 4px 4px 20px 0px var(--bg-shadow-color-300); /* TODO : HoverBox 안쓰면 지우고, 쓰면 바꾸기 */ font-size: 0.75rem; - color: var(--color-background); + color: var(--bg-color); display: flex; flex-direction: column; justify-content: space-around; @@ -83,7 +83,7 @@ const CodeAndTimeStyled = styled.div` flex-direction: column; border-radius: 10px; margin-bottom: 15px; - background: var(--color-background); + background: var(--bg-color); color: var(--main-color); border: 1px solid var(--main-color); position: relative; @@ -97,7 +97,7 @@ const CodeStyled = styled.div<{ copySuccess: boolean }>` height: 48px; background-color: var(--main-color); mask-image: url("data:image/svg+xml,%3Csvg width='184' height='48' viewBox='0 0 184 48' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0H184V14.4C184 14.4 184 14.4 184 14.4C178.766 14.4 174.523 18.6981 174.523 24C174.523 29.3019 178.766 33.6 184 33.6C184 33.6 184 33.6 184 33.6V48H0V33.6C5.2335 33.5998 9.47603 29.3018 9.47603 24C9.47603 18.6982 5.2335 14.4002 0 14.4V0Z' fill='%239747FF'/%3E%3C/svg%3E%0A"); - color: var(--color-background); + color: var(--bg-color); display: flex; justify-content: center; align-items: center; diff --git a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx b/frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx index 760decfa8..8a1612525 100644 --- a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx +++ b/frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx @@ -33,7 +33,7 @@ const CountTimeStyled = styled.div` align-items: center; border-radius: 10px; margin-bottom: 15px; - background: var(--color-background); + background: var(--bg-color); color: var(--main-color); border: 1px solid var(--main-color); `; diff --git a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index 3cf0fd82c..1131da4dd 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -155,23 +155,23 @@ const CabinetListItemStyled = styled.div<{ css` opacity: 0.9; transform: scale(1.05); - box-shadow: inset 5px 5px 5px var(--border-shadow-200), - 0px 4px 4px var(--border-shadow-200); + box-shadow: inset 5px 5px 5px var(--border-shadow-color-200), + 0px 4px 4px var(--border-shadow-color-200); `} ${({ isMultiSelected }) => isMultiSelected && css` opacity: 0.9; transform: scale(1.05); - box-shadow: inset 5px 5px 5px var(--border-shadow-200), - 0px 4px 4px var(--border-shadow-200); + box-shadow: inset 5px 5px 5px var(--border-shadow-color-200), + 0px 4px 4px var(--border-shadow-color-200); `} ${({ status }) => status === "PENDING" && css` border: 2px double var(--main-color); - box-shadow: inset 0px 0px 0px 2px var(--color-background); + box-shadow: inset 0px 0px 0px 2px var(--bg-color); // 테두리 `} @@ -236,7 +236,7 @@ const CabinetNumberStyled = styled.p<{ ${({ status }) => status === "IN_SESSION" && css` - color: var(--color-text-normal); + color: var(--normal-text-color); `} `; diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx index a537c9fb2..f6abf10d5 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -182,7 +182,7 @@ const CabinetListItemStyled = styled.div<{ }>` position: relative; background-color: ${({ status, isMine }) => - isMine ? "var(--mine)" : cabinetStatusColorMap[status]}; + isMine ? "var(--mine-color)" : cabinetStatusColorMap[status]}; width: 80px; height: 80px; @@ -200,15 +200,15 @@ const CabinetListItemStyled = styled.div<{ css` opacity: 0.9; transform: scale(1.05); - box-shadow: inset 5px 5px 5px var(--border-shadow-200), - 0px 4px 4px var(--border-shadow-200); + box-shadow: inset 5px 5px 5px var(--border-shadow-color-200), + 0px 4px 4px var(--border-shadow-color-200); `} ${({ status }) => status === "PENDING" && css` border: 2px double var(--main-color); - box-shadow: inset 0px 0px 0px 2px var(--color-background); + box-shadow: inset 0px 0px 0px 2px var(--bg-color); `} ${({ status }) => @@ -233,9 +233,7 @@ const CabinetListItemStyled = styled.div<{ status === "IN_SESSION" && css` animation: ${Rotation} 1s linear infinite; - background-color: ${isMine - ? "var(--color-background)" - : "var(--main-color)"}; + background-color: ${isMine ? "var(--bg-color)" : "var(--main-color)"}; `} } @@ -278,7 +276,7 @@ const CabinetNumberStyled = styled.p<{ ${({ status }) => status === "IN_SESSION" && css` - color: var(--color-text-normal); + color: var(--normal-text-color); `} `; diff --git a/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx b/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx index 8278d5fe5..6b17b347f 100644 --- a/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx +++ b/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx @@ -71,11 +71,11 @@ const TooltipCard = styled.div<{ hasEnoughWidth: boolean }>` justify-content: center; & ${ToolTipIcon}:hover + ${TooltipBox} { visibility: visible; - color: var(--color-text-normal); - background-color: var(--bg-shadow-400); + color: var(--normal-text-color); + background-color: var(--bg-shadow-color-400); &:before { - border-color: transparent transparent var(--bg-shadow-400) - var(--bg-shadow-400); + border-color: transparent transparent var(--bg-shadow-color-400) + var(--bg-shadow-color-400); } } `; diff --git a/frontend/src/components/Card/Card.tsx b/frontend/src/components/Card/Card.tsx index b2cb25f43..60601aec3 100644 --- a/frontend/src/components/Card/Card.tsx +++ b/frontend/src/components/Card/Card.tsx @@ -69,7 +69,7 @@ export const CardStyled = styled.div<{ }>` width: ${(props) => props.width}; border-radius: 10px; - background-color: var(--gray-tmp-1); + background-color: var(--shared-gray-color-100); display: flex; flex-direction: column; align-items: center; @@ -131,13 +131,13 @@ export const CardButtonStyled = styled.div<{ background-color: ${props.backgroundColor ? props.backgroundColor : props.isClickable - ? "var(--color-card-content-bg)" - : "var(--color-background)"}; + ? "var(--card-content-bg-color)" + : "var(--bg-color)"}; color: ${props.fontColor ? props.fontColor : props.isExtensible ? "var(--main-color)" - : "var(--gray-tmp-5)"}; + : "var(--shared-gray-color-500)"}; padding: 5px 15px; border: none; border-radius: 5px; @@ -157,7 +157,7 @@ export const CardButtonStyled = styled.div<{ & > svg > path { transform: ${(props) => props.icon?.name === "SvgLock" ? "" : "scale(1.1)"}; - stroke: var(--color-text-normal); + stroke: var(--normal-text-color); } `; diff --git a/frontend/src/components/Card/CardStyles.ts b/frontend/src/components/Card/CardStyles.ts index 2bdb16917..9ef62f29f 100644 --- a/frontend/src/components/Card/CardStyles.ts +++ b/frontend/src/components/Card/CardStyles.ts @@ -2,7 +2,7 @@ import styled from "styled-components"; import CabinetStatus from "@/types/enum/cabinet.status.enum"; export const CardContentWrapper = styled.div` - background-color: var(--color-card-content-bg); + background-color: var(--card-content-bg-color); border-radius: 10px; padding: 10px 0; margin: 5px 5px 5px 5px; @@ -30,7 +30,7 @@ export const ContentInfoStyled = styled.div<{ props.isSelected && ` background-color: ${props.selectedColor}; - color: var(--color-text-with-bg); + color: var(--text-with-bg-color); border-radius: 8px; `} `; @@ -39,7 +39,7 @@ export const ContentDetailStyled = styled.div<{ status?: CabinetStatus; }>` color: ${(props) => - props.status === CabinetStatus.OVERDUE ? "var(--expired)" : ""}; + props.status === CabinetStatus.OVERDUE ? "var(--expired-color)" : ""}; display: flex; margin: 5px 10px 5px 10px; font-weight: bold; diff --git a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx b/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx index b9ff56fc1..b41de2dd9 100644 --- a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx +++ b/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx @@ -44,7 +44,7 @@ const ClubCabinetInfoCard = ({ : [ // NOTE: 이 부분은 레이아웃을 유지하기 위한 placeholder 버튼입니다. { - backgroundColor: "var(--gray-tmp-1)", + backgroundColor: "var(--shared-gray-color-100)", onClick: () => {}, icon: null, isClickable: false, @@ -61,7 +61,7 @@ const ClubCabinetInfoCard = ({ {clubInfo.clubName} {clubInfo.floor + "층 - " + clubInfo.section} @@ -71,7 +71,7 @@ const ClubCabinetInfoCard = ({ {clubInfo.clubMaster.userName} @@ -115,7 +115,7 @@ const CabinetRectangleStyled = styled.div` line-height: 90px; border-radius: 10px; margin-right: 20px; - background-color: var(--full); + background-color: var(--full-color); color: var(--black); /* black */ font-size: 2rem; @@ -165,7 +165,7 @@ const CabinetIconStyled = styled.div` } & > svg > path { - stroke: var(--color-text-normal); + stroke: var(--normal-text-color); transform: scale(1.1); } `; @@ -181,13 +181,13 @@ const ContentInfoStyled = styled.div<{ props.isSelected && ` background-color: ${props.selectedColor}; - color: var(--color-background); + color: var(--bg-color); border-radius: 8px; `} `; const CardContentWrapper = styled.div` - background-color: var(--color-card-content-bg); + background-color: var(--card-content-bg-color); border-radius: 10px; padding: 10px 0; margin: 5px 5px 5px 5px; diff --git a/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx b/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx index 487977fbc..4eb4ddb95 100644 --- a/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx +++ b/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx @@ -38,7 +38,7 @@ const ClubNoticeCard = ({ : [ // NOTE: 이 부분은 레이아웃을 유지하기 위한 placeholder 버튼입니다. { - backgroundColor: "var(--gray-tmp-1)", + backgroundColor: "var(--shared-gray-color-100)", onClick: () => {}, icon: null, isClickable: false, @@ -81,7 +81,7 @@ const ClubNoticeTextStyled = styled.div` } ::-webkit-scrollbar-thumb { - background: var(--gray-tmp-4); + background: var(--shared-gray-color-400); border-radius: 50px; border: 6px solid transparent; background-clip: padding-box; diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx index 2e2530fb2..da7daa86b 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx @@ -54,7 +54,7 @@ const LentInfoCard = ({ {cabinetInfo.floor !== 0 ? cabinetInfo.floor + "층 - " + cabinetInfo.section @@ -67,7 +67,7 @@ const LentInfoCard = ({ {cabinetInfo.userNameList} @@ -138,13 +138,13 @@ const CabinetRectangleStyled = styled.div<{ margin-right: 20px; background-color: ${(props) => props.banned - ? "var(--expired)" + ? "var(--expired-color)" : props.isLented - ? "var(--mine)" - : "var(--full)"}; + ? "var(--mine-color)" + : "var(--full-color)"}; color: ${(props) => props.banned - ? "var(--color-background)" + ? "var(--bg-color)" : props.status === "IN_SESSION" ? "var(--main-color)" : "var(--black)"}; @@ -188,7 +188,7 @@ const CabinetIconStyled = styled.div` } & > svg > path { - stroke: var(--color-text-normal); + stroke: var(--normal-text-color); transform: scale(0.8); } `; diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx index 456c3cae8..c2e783371 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx @@ -84,7 +84,7 @@ const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { { label: "저장", onClick: handleSave, - fontColor: "var(--color-text-with-bg)", + fontColor: "var(--text-with-bg-color)", backgroundColor: "var(--main-color)", isClickable: true, }, @@ -99,8 +99,8 @@ const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { { label: "-", isClickable: false, - fontColor: "var(--gray-tmp-1)", - backgroundColor: "var(--gray-tmp-1)", + fontColor: "var(--shared-gray-color-100)", + backgroundColor: "var(--shared-gray-color-100)", }, ] } diff --git a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx b/frontend/src/components/Card/ProfileCard/ProfileCard.tsx index c9aaa5f33..c02184d7a 100644 --- a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx +++ b/frontend/src/components/Card/ProfileCard/ProfileCard.tsx @@ -48,7 +48,7 @@ const LogoStyled = styled.div` height: 60px; border-radius: 10px; margin-right: 20px; - background-color: var(--color-card-content-bg); + background-color: var(--card-content-bg-color); `; const CabiLogoStyled = styled.div` @@ -75,7 +75,7 @@ const ProfileDetail = styled.div` const EmailDetail = styled(ProfileDetail)` font-size: 0.9rem; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); `; export default ProfileCard; diff --git a/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx b/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx index e201d3a5e..58ed6a4eb 100644 --- a/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx @@ -18,14 +18,14 @@ const ColorPicker = ({ color, onChange, customColors }: ColorPickerProps) => { styles={{ default: { card: { - background: "var(--color-background)", - boxShadow: "var(--border-shadow-200) 0px 1px 4px", + background: "var(--bg-color)", + boxShadow: "var(--border-shadow-color-200) 0px 1px 4px", }, input: { - boxShadow: "var(--gray-tmp-2) 0px 0px 0px 1px inset", + boxShadow: "var(--shared-gray-color-200) 0px 0px 0px 1px inset", }, hash: { - background: "var(--gray-tmp-2)", + background: "var(--shared-gray-color-200)", }, }, }} diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx index a0662bd6f..74838244c 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx @@ -38,7 +38,7 @@ const ThemeColorCardContainer = () => { setMineColor(mine); root.style.setProperty("--main-color", main); root.style.setProperty("--sub-color", sub); - root.style.setProperty("--mine", mine); + root.style.setProperty("--mine-color", mine); localStorage.setItem("main-color", main); localStorage.setItem("sub-color", sub); localStorage.setItem("mine-color", mine); @@ -77,7 +77,7 @@ const ThemeColorCardContainer = () => { useEffect(() => { root.style.setProperty("--main-color", mainColor); - root.style.setProperty("--mine", mineColor); + root.style.setProperty("--mine-color", mineColor); const confirmBeforeUnload = (e: BeforeUnloadEvent) => { if ( mainColor !== savedMainColor || diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx index c90636c7e..07002d3ed 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -51,7 +51,7 @@ const ThemeColorCard = ({ { label: "저장", onClick: handleSave, - fontColor: "var(--color-text-with-bg)", + fontColor: "var(--text-with-bg-color)", backgroundColor: "var(--main-color)", isClickable: true, }, @@ -110,7 +110,7 @@ const BackgroundOverlayStyled = styled.div` left: 0; width: 100%; height: 100%; - background: var(--bg-shadow-100); + background: var(--bg-shadow-color-100); z-index: 1; `; diff --git a/frontend/src/components/Club/AdminClubLog.tsx b/frontend/src/components/Club/AdminClubLog.tsx index 2eecbbed8..2c1fb5a83 100644 --- a/frontend/src/components/Club/AdminClubLog.tsx +++ b/frontend/src/components/Club/AdminClubLog.tsx @@ -21,7 +21,9 @@ const AdminClubLog = ({ changePageOnClickIndexButton(i)} className="cabiButton" @@ -74,7 +76,7 @@ const SectionPaginationStyled = styled.div` padding: 10px 0; position: sticky; top: 0; - background: var(--color-background); + background: var(--bg-color); opacity: 0.8; z-index: 1; `; diff --git a/frontend/src/components/Club/ClubInfo.tsx b/frontend/src/components/Club/ClubInfo.tsx index 1580909e1..03ed9430b 100644 --- a/frontend/src/components/Club/ClubInfo.tsx +++ b/frontend/src/components/Club/ClubInfo.tsx @@ -59,7 +59,7 @@ const EmptyClubCabinetTextStyled = styled.div` justify-content: center; align-items: center; font-size: 1.125rem; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); `; const SadCcabiStyled = styled.div` @@ -76,7 +76,7 @@ const SadCcabiStyled = styled.div` } & > svg > path { - fill: var(--gray-tmp-5); + fill: var(--shared-gray-color-500); transform: scale(0.6); } `; diff --git a/frontend/src/components/Club/ClubLogTable.tsx b/frontend/src/components/Club/ClubLogTable.tsx index dbe1aff10..ae376d53e 100644 --- a/frontend/src/components/Club/ClubLogTable.tsx +++ b/frontend/src/components/Club/ClubLogTable.tsx @@ -63,7 +63,7 @@ const LogTableWrapperStyled = styled.div` const LogTableStyled = styled.table` width: 100%; - background: var(--color-background); + background: var(--bg-color); overflow: scroll; border-spacing: 0 0.3em; border-collapse: separate; @@ -73,16 +73,16 @@ const TheadStyled = styled.thead` width: 100%; height: 50px; line-height: 50px; - background: var(--color-background); + background: var(--bg-color); `; const TbodyStyled = styled.tbody` - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); & > tr { text-align: center; height: 50px; cursor: pointer; - background: var(--purple-tmp-1); + background: var(--shared-purple-color-100); } & > tr td:first-of-type { border-radius: 8px 0 0 8px; @@ -98,7 +98,7 @@ const TbodyStyled = styled.tbody` & > tr:hover, & > tr.selected { background-color: var(--sub-color); - color: var(--color-background); + color: var(--bg-color); } `; diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx index 2980304d3..887d8d7fc 100644 --- a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx +++ b/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -55,7 +55,10 @@ const ClubMemberInfoArea = ({ - + 동아리를
선택해주세요
@@ -66,7 +69,7 @@ const ClubMemberInfoArea = ({ {/* */} {selectedClubInfo!.clubName} @@ -75,7 +78,7 @@ const ClubMemberInfoArea = ({ {isMasterSelected ? : } - + {selectedClubMemberInfo!.userName || "-"} @@ -149,9 +152,9 @@ const ClubMemberInfoAreaStyled = styled.div` display: flex; flex-direction: column; align-items: center; - background: var(--color-background); - box-shadow: 0 0 40px 0 var(--border-shadow-200); - border-left: 1px solid var(--color-line); + background: var(--bg-color); + box-shadow: 0 0 40px 0 var(--border-shadow-color-200); + border-left: 1px solid var(--line-color); &.on { transform: translateX(0%); } @@ -216,7 +219,7 @@ const ClubMemberIconStyled = styled.div<{ isMasterSelected: boolean }>` } & > svg > path { - stroke: var(--color-text-normal); + stroke: var(--normal-text-color); transform: ${(props) => props.isMasterSelected ? "scale(1.3)" : "scale(1.0)"}; } diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx b/frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx index 13bb8a359..ca82600d4 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx +++ b/frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx @@ -150,7 +150,7 @@ const UserCountIconStyled = styled.div` height: 24px; & > svg > path { - stroke: var(--color-text-normal); + stroke: var(--normal-text-color); } `; @@ -198,7 +198,7 @@ const MoreButtonStyled = styled.button<{ margin: 20px auto; border: 1px solid var(--main-color); border-radius: 30px; - background-color: var(--color-background); + background-color: var(--bg-color); color: var(--main-color); position: relative; `; diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx b/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx index 5135f7e4f..8d5ba0dfe 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx +++ b/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx @@ -42,7 +42,7 @@ const MemberListItemContainerStyled = styled.div<{ width: 80px; height: 80px; background-color: ${(props) => - props.bgColor ? props.bgColor : "var(--gray-tmp-1)"}; + props.bgColor ? props.bgColor : "var(--shared-gray-color-100)"}; border-radius: 1rem; margin: 7px; padding: 10px; @@ -57,8 +57,8 @@ const MemberListItemContainerStyled = styled.div<{ css` opacity: 0.9; transform: scale(1.05); - box-shadow: inset 5px 5px 5px var(--border-shadow-200), - 0px 4px 4px var(--border-shadow-200); + box-shadow: inset 5px 5px 5px var(--border-shadow-color-200), + 0px 4px 4px var(--border-shadow-color-200); `} @media (hover: hover) and (pointer: fine) { @@ -84,8 +84,8 @@ const MemberListItemStyled = styled.div<{ isMaster: boolean }>` & > svg > path { stroke: ${(props) => props.isMaster - ? "var(--color-text-with-bg)" - : "var(--color-text-normal)"}; + ? "var(--text-with-bg-color)" + : "var(--normal-text-color)"}; transform: ${(props) => (props.isMaster ? "" : "scale(0.7)")}; } `; @@ -96,7 +96,7 @@ const MemberNameStyled = styled.p<{ line-height: 28px; height: 28px; font-size: 14px; - color: ${(props) => (props.isMaster ? "var(--color-text-with-bg)" : "")}; + color: ${(props) => (props.isMaster ? "var(--text-with-bg-color)" : "")}; `; export default memo(ClubMemberListItem); diff --git a/frontend/src/components/Common/Button.tsx b/frontend/src/components/Common/Button.tsx index ec787ab26..a7b667a28 100644 --- a/frontend/src/components/Common/Button.tsx +++ b/frontend/src/components/Common/Button.tsx @@ -43,38 +43,38 @@ const ButtonContainerStyled = styled.button` props.theme === "fill" && css` background: var(--main-color); - color: var(--color-text-with-bg); + color: var(--text-with-bg-color); `} ${(props) => props.theme === "line" && css` - background: var(--color-background); - color: var(--color-button-line); - border: 1px solid var(--color-button-line); + background: var(--bg-color); + color: var(--button-line-color); + border: 1px solid var(--button-line-color); `} ${(props) => props.theme === "light-grayLine" && css` - background: var(--color-background); - color: var(--gray-tmp-4); - border: 1px solid var(--color-line); + background: var(--bg-color); + color: var(--shared-gray-color-400); + border: 1px solid var(--line-color); `} ${(props) => props.theme === "grayLine" && css` - background: var(--color-background); - color: var(--gray-tmp-5); - border: 1px solid var(--gray-tmp-5); + background: var(--bg-color); + color: var(--shared-gray-color-500); + border: 1px solid var(--shared-gray-color-500); `} ${(props) => props.theme === "smallGrayLine" && css` max-width: 200px; height: 40px; - background: var(--color-background); - color: var(--gray-tmp-5); + background: var(--bg-color); + color: var(--shared-gray-color-500); font-size: 0.875rem; - border: 1px solid var(--gray-tmp-5); + border: 1px solid var(--shared-gray-color-500); `} @media (max-height: 745px) { diff --git a/frontend/src/components/Common/ClubListDropdown.tsx b/frontend/src/components/Common/ClubListDropdown.tsx index b7cb62dd2..ace767c21 100644 --- a/frontend/src/components/Common/ClubListDropdown.tsx +++ b/frontend/src/components/Common/ClubListDropdown.tsx @@ -71,7 +71,7 @@ const ClubListDropdSelectionBoxStyled = styled.div<{ isOpen: boolean }>` position: relative; display: flex; align-items: center; - border: 1px solid var(--gray-tmp-4); + border: 1px solid var(--shared-gray-color-400); width: 100%; height: 60px; border-radius: 10px; @@ -113,8 +113,8 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` display: flex; align-items: center; background-color: ${({ isSelected }) => - isSelected ? "var(--gray-tmp-2)" : "var(--color-background)"}; - border: 1px solid var(--gray-tmp-4); + isSelected ? "var(--shared-gray-color-200)" : "var(--bg-color)"}; + border: 1px solid var(--shared-gray-color-400); border-width: 0px 1px 1px 1px; width: 100%; height: 60px; @@ -122,7 +122,7 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` padding-left: 20px; font-size: 1.125rem; color: ${({ isSelected }) => - isSelected ? "var(--main-color)" : "var(--color-text-normal)"}; + isSelected ? "var(--main-color)" : "var(--normal-text-color)"}; cursor: pointer; &:first-child { border-radius: 10px 10px 0px 0px; @@ -132,7 +132,7 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` border-radius: 0px 0px 10px 10px; } &:hover { - background-color: var(--gray-tmp-2); + background-color: var(--shared-gray-color-200); } `; diff --git a/frontend/src/components/Common/Dropdown.tsx b/frontend/src/components/Common/Dropdown.tsx index b2767b762..44e4226a7 100644 --- a/frontend/src/components/Common/Dropdown.tsx +++ b/frontend/src/components/Common/Dropdown.tsx @@ -74,7 +74,7 @@ const DropdownSelectionBoxStyled = styled.div<{ isOpen: boolean }>` position: relative; display: flex; align-items: center; - border: 1px solid var(--gray-tmp-4); + border: 1px solid var(--shared-gray-color-400); width: 100%; height: 60px; border-radius: 10px; @@ -116,8 +116,8 @@ const DropdownItemStyled = styled.div<{ isSelected: boolean }>` display: flex; align-items: center; background-color: ${({ isSelected }) => - isSelected ? "var(--gray-tmp-2)" : "var(--color-background)"}; - border: 1px solid var(--gray-tmp-4); + isSelected ? "var(--shared-gray-color-200)" : "var(--bg-color)"}; + border: 1px solid var(--shared-gray-color-400); border-width: 0px 1px 1px 1px; width: 100%; height: 60px; @@ -125,7 +125,7 @@ const DropdownItemStyled = styled.div<{ isSelected: boolean }>` padding-left: 20px; font-size: 1.125rem; color: ${({ isSelected }) => - isSelected ? "var(--main-color)" : "var(--color-text-normal)"}; + isSelected ? "var(--main-color)" : "var(--normal-text-color)"}; cursor: pointer; &:first-child { border-radius: 10px 10px 0px 0px; @@ -135,7 +135,7 @@ const DropdownItemStyled = styled.div<{ isSelected: boolean }>` border-radius: 0px 0px 10px 10px; } &:hover { - background-color: var(--gray-tmp-2); + background-color: var(--shared-gray-color-200); } `; @@ -148,7 +148,7 @@ const OptionsImgStyled = styled.div<{ isSelected?: boolean }>` height: 18px; } & > svg > path { - stroke: var(--color-text-normal); + stroke: var(--normal-text-color); transform: scale(0.8); } `; diff --git a/frontend/src/components/Common/MultiSelectButton.tsx b/frontend/src/components/Common/MultiSelectButton.tsx index 5baf39a3b..281c355c4 100644 --- a/frontend/src/components/Common/MultiSelectButton.tsx +++ b/frontend/src/components/Common/MultiSelectButton.tsx @@ -39,12 +39,12 @@ const MultiSelectButtonContainerStyled = styled.button` props.theme === "fill" && css` background: var(--main-color); - color: var(--color-background); + color: var(--bg-color); `} ${(props) => props.theme === "line" && css` - background: var(--color-background); + background: var(--bg-color); color: var(--main-color); border: 1px solid var(--main-color); `} diff --git a/frontend/src/components/Common/MultiSelectFilterButton.tsx b/frontend/src/components/Common/MultiSelectFilterButton.tsx index ad4d26545..f7e5a63be 100644 --- a/frontend/src/components/Common/MultiSelectFilterButton.tsx +++ b/frontend/src/components/Common/MultiSelectFilterButton.tsx @@ -50,7 +50,7 @@ const FilterTextWrapperStyled = styled.div<{ isClicked: boolean }>` justify-content: center; align-items: center; color: ${({ isClicked }) => - isClicked ? "var(--main-color)" : "var(--gray-tmp-4)"}; + isClicked ? "var(--main-color)" : "var(--shared-gray-color-400)"}; font-size: 1rem; `; diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx index efc28ad02..20dfd0f04 100644 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/components/Common/MultiToggleSwitch.tsx @@ -24,7 +24,7 @@ const MultiToggleSwitch = ({ buttons?.forEach((button) => { if (button.className === initialState) { - button.style.color = "var(--color-text-with-bg)"; + button.style.color = "var(--text-with-bg-color)"; button.style.backgroundColor = "var(--main-color)"; } }); @@ -38,11 +38,11 @@ const MultiToggleSwitch = ({ const buttons = wrapperRef.current?.querySelectorAll("button"); buttons?.forEach((button) => { - button.style.color = "var(--color-text-normal)"; + button.style.color = "var(--normal-text-color)"; button.style.backgroundColor = "transparent"; }); - target.style.color = "var(--color-background)"; + target.style.color = "var(--bg-color)"; target.style.backgroundColor = "var(--main-color)"; setState(target.className as React.SetStateAction); @@ -63,7 +63,7 @@ const WrapperStyled = styled.div` width: fit-content; display: flex; align-items: center; - background-color: var(--gray-tmp-1); + background-color: var(--shared-gray-color-100); border-radius: 10px; button { display: flex; @@ -76,7 +76,7 @@ const WrapperStyled = styled.div` height: 30px; font-weight: 500; background-color: transparent; - color: var(--color-text-normal); + color: var(--normal-text-color); padding: 4px 12px; } `; diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx index b2c4e5b67..08e612881 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx @@ -24,7 +24,7 @@ const MultiToggleSwitchSeparated = ({ buttons?.forEach((button) => { if (button.className === `${initialState}`) { - button.style.color = "var(--color-background)"; + button.style.color = "var(--bg-color)"; button.style.backgroundColor = "var(--main-color)"; } }); @@ -39,11 +39,11 @@ const MultiToggleSwitchSeparated = ({ const buttons = wrapperRef.current?.querySelectorAll("button"); buttons?.forEach((button) => { - button.style.color = "var(--color-text-normal)"; - button.style.backgroundColor = "var(--gray-tmp-1)"; + button.style.color = "var(--normal-text-color)"; + button.style.backgroundColor = "var(--shared-gray-color-100)"; }); - target.style.color = "var(--color-background)"; + target.style.color = "var(--bg-color)"; target.style.backgroundColor = "var(--main-color)"; setState(target.className as React.SetStateAction); @@ -76,8 +76,8 @@ const WrapperStyled = styled.div` font-size: 0.9rem; height: 30px; font-weight: 500; - background-color: var(--gray-tmp-1); - color: var(--color-text-normal); + background-color: var(--shared-gray-color-100); + color: var(--normal-text-color); padding: 4px 12px; margin: 0px 4px; } diff --git a/frontend/src/components/Common/PillButton.tsx b/frontend/src/components/Common/PillButton.tsx index 2d16ab559..0742e000c 100644 --- a/frontend/src/components/Common/PillButton.tsx +++ b/frontend/src/components/Common/PillButton.tsx @@ -31,9 +31,9 @@ const PillButtonContainerStyled = styled.button<{ background-color: ${({ isSelected }) => isSelected ? "var(--main-color)" : "transparent"}; border: ${({ isSelected }) => - isSelected ? "1px solid var(--main-color)" : "1px solid var(--color-line)"}; + isSelected ? "1px solid var(--main-color)" : "1px solid var(--line-color)"}; color: ${({ isSelected }) => - isSelected ? "var(--color-background)" : "var(--color-text-normal)"}; + isSelected ? "var(--bg-color)" : "var(--normal-text-color)"}; padding: 2px 16px 4px 16px; text-align: center; text-decoration: none; @@ -46,7 +46,7 @@ const PillButtonContainerStyled = styled.button<{ transition: background-color 0.3s ease-in-out; &:hover { background-color: var(--main-color); - color: var(--color-background); + color: var(--bg-color); } `; diff --git a/frontend/src/components/Common/ToggleSwitch.tsx b/frontend/src/components/Common/ToggleSwitch.tsx index 722d1b9b3..1935fc1b9 100644 --- a/frontend/src/components/Common/ToggleSwitch.tsx +++ b/frontend/src/components/Common/ToggleSwitch.tsx @@ -57,7 +57,7 @@ const ToggleSwitchStyled = styled.label<{ display: inline-block; position: relative; background: ${(props) => - props.checked ? "var(--main-color)" : "var(--gray-tmp-4)"}; + props.checked ? "var(--main-color)" : "var(--shared-gray-color-400)"}; width: 56px; height: 28px; border-radius: 50px; @@ -82,7 +82,7 @@ const ToggleKnobStyled = styled.span<{ checked: boolean }>` width: 24px; height: 24px; border-radius: 50%; - background: var(--color-text-with-bg); + background: var(--text-with-bg-color); transition: transform 0.2s; transform: ${(props) => props.checked ? "translateX(28px)" : "translateX(0)"}; diff --git a/frontend/src/components/Common/WarningNotification.tsx b/frontend/src/components/Common/WarningNotification.tsx index 1fbc83d01..f2ec7edb3 100644 --- a/frontend/src/components/Common/WarningNotification.tsx +++ b/frontend/src/components/Common/WarningNotification.tsx @@ -58,11 +58,11 @@ const WarningWrapper = styled.div<{ isVisible: boolean }>` justify-content: center; & ${WarningIcon}:hover + ${WarningBox} { visibility: visible; - color: var(--color-text-normal); - background-color: var(--bg-shadow-400); + color: var(--normal-text-color); + background-color: var(--bg-shadow-color-400); &:before { - border-color: transparent transparent var(--bg-shadow-400) - var(--bg-shadow-400); + border-color: transparent transparent var(--bg-shadow-color-400) + var(--bg-shadow-color-400); } } `; diff --git a/frontend/src/components/Home/ManualContentBox.tsx b/frontend/src/components/Home/ManualContentBox.tsx index f2a655bc9..5bb2148a0 100644 --- a/frontend/src/components/Home/ManualContentBox.tsx +++ b/frontend/src/components/Home/ManualContentBox.tsx @@ -70,7 +70,7 @@ const MaunalContentBoxStyled = styled.div<{ flex-direction: column; align-items: flex-start; font-size: 1.75rem; - color: var(--color-text-with-bg); + color: var(--text-with-bg-color); padding: 25px; font-weight: bold; cursor: pointer; @@ -89,8 +89,8 @@ const MaunalContentBoxStyled = styled.div<{ & > path { stroke: ${(props) => props.contentStatus === ContentStatus.EXTENSION - ? "var(--color-text-normal)" - : "var(--color-text-with-bg)"}; + ? "var(--normal-text-color)" + : "var(--text-with-bg-color)"}; transform: ${(props) => props.contentStatus === ContentStatus.EXTENSION ? "scale(1.4)" @@ -111,7 +111,7 @@ const MaunalContentBoxStyled = styled.div<{ contentStatus === ContentStatus.PENDING && css` border: 5px double var(--main-color); - box-shadow: inset 0px 0px 0px 5px var(--color-background); + box-shadow: inset 0px 0px 0px 5px var(--bg-color); `} ${({ contentStatus }) => @@ -125,7 +125,7 @@ const MaunalContentBoxStyled = styled.div<{ contentStatus === ContentStatus.EXTENSION && css` width: 900px; - color: var(--color-text-normal); + color: var(--normal-text-color); @media screen and (max-width: 1000px) { width: 280px; .peopleImg { @@ -155,8 +155,8 @@ const MaunalContentBoxStyled = styled.div<{ props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" : props.contentStatus === ContentStatus.EXTENSION - ? "var(--color-text-normal)" - : "var(--color-text-with-bg)"}; + ? "var(--normal-text-color)" + : "var(--text-with-bg-color)"}; cursor: pointer; } @@ -166,11 +166,11 @@ const MaunalContentBoxStyled = styled.div<{ contentStatus === ContentStatus.PENDING ? css` border: 5px double var(--main-color); - box-shadow: inset 0px 0px 0px 5px var(--color-background), - 10px 10px 25px 0 var(--border-shadow-200); + box-shadow: inset 0px 0px 0px 5px var(--bg-color), + 10px 10px 25px 0 var(--border-shadow-color-200); ` : css` - box-shadow: 10px 10px 25px 0 var(--border-shadow-200); + box-shadow: 10px 10px 25px 0 var(--border-shadow-color-200); `} p { transition: all 0.3s ease-in-out; diff --git a/frontend/src/components/Home/ServiceManual.tsx b/frontend/src/components/Home/ServiceManual.tsx index 6e9533fc1..78d266e6d 100644 --- a/frontend/src/components/Home/ServiceManual.tsx +++ b/frontend/src/components/Home/ServiceManual.tsx @@ -121,7 +121,7 @@ const TitleContainerStyled = styled.div` display: flex; justify-content: space-between; align-items: center; - border-bottom: 2px solid var(--gray-tmp-3); + border-bottom: 2px solid var(--shared-gray-color-300); margin-bottom: 70px; color: var(--main-color); font-weight: 700; @@ -136,7 +136,7 @@ const TitleContainerStyled = styled.div` margin-bottom: 20px; } .title > span { - color: var(--color-text-normal); + color: var(--normal-text-color); } `; @@ -145,11 +145,11 @@ const NotionBtn = styled.button` height: 40px; border-radius: 8px; font-size: 0.875rem; - color: var(--gray-tmp-7); - background: var(--color-background); - border: 1px solid var(--gray-tmp-4); + color: var(--shared-gray-color-700); + background: var(--bg-color); + border: 1px solid var(--shared-gray-color-400); :hover { - color: var(--color-text-normal); + color: var(--normal-text-color); font-weight: 400; } `; @@ -162,7 +162,7 @@ const WrapSectionStyled = styled.div` line-height: 1.4; text-align: left; font-weight: bold; - color: var(--color-text-normal); + color: var(--normal-text-color); } `; @@ -199,7 +199,7 @@ const InfoSectionStyled = styled.section` text-align: center; } .redColor { - color: var(--orange-color); + color: var(--orange); margin-top: 15px; } .article > p > span { diff --git a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx index 393184752..7cf9a5e01 100644 --- a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx +++ b/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx @@ -20,12 +20,24 @@ const ColorTableItemContainer = ({ const CabinetColorTable = () => { return ( - - - - - - + + + + + + ); }; @@ -36,7 +48,7 @@ const ColorTableStyled = styled.div` left: 30px; width: auto; overflow: hidden; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); `; const ColorTableItemStyled = styled.div<{ color: string }>` @@ -52,10 +64,10 @@ const ColorTableItemStyled = styled.div<{ color: string }>` margin-right: 5px; background-color: ${({ color }) => color}; ${({ color }) => - color === "var(--pending)" && + color === "var(--pending-color)" && css` border: 1px double var(--main-color); - box-shadow: inset 0px 0px 0px 1px var(--color-background); + box-shadow: inset 0px 0px 0px 1px var(--bg-color); `} } `; diff --git a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx index c830b9d23..567781117 100644 --- a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx +++ b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -88,7 +88,7 @@ const LeftMainNav = ({ } onClick={onClickSearchButton} > - + Search @@ -97,7 +97,7 @@ const LeftMainNav = ({ target="_blank" title="슬랙 캐비닛 채널 새창으로 열기" > - + Contact @@ -109,14 +109,14 @@ const LeftMainNav = ({ } onClick={onClickAdminClubButton} > - + Club - + Logout @@ -131,7 +131,7 @@ const LeftMainNav = ({ } onClick={onClickMainClubButton} > - + Clubs - + Profile @@ -158,7 +162,7 @@ const LeftNavStyled = styled.nav` min-width: 90px; position: relative; height: 100%; - border-right: 1px solid var(--color-line); + border-right: 1px solid var(--line-color); position: relative; display: flex; flex-direction: column; @@ -182,14 +186,14 @@ const TopBtnStyled = styled.li` font-weight: 300; margin-bottom: 2.5vh; border-radius: 10px; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); cursor: pointer; &:last-child { margin-bottom: 0; } @media (hover: hover) and (pointer: fine) { &:hover { - color: var(--color-text-with-bg); + color: var(--text-with-bg-color); background-color: var(--main-color); } } @@ -208,7 +212,7 @@ const BottomSectionStyled = styled.section` margin: 0 auto; width: 56px; height: 1px; - background-color: var(--gray-tmp-4); + background-color: var(--shared-gray-color-400); } `; const BottomBtnsStyled = styled.ul` @@ -223,7 +227,7 @@ const BottomBtnStyled = styled.li` font-weight: 300; margin-top: 2.5vh; border-radius: 10px; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); cursor: pointer; display: flex; flex-direction: column; @@ -232,7 +236,7 @@ const BottomBtnStyled = styled.li` margin-top: 0; } & a { - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); } & div { width: 24px; @@ -241,9 +245,9 @@ const BottomBtnStyled = styled.li` margin-bottom: 4px; } &.active { - color: var(--color-button-line); + color: var(--button-line-color); svg { - stroke: var(--color-button-line); + stroke: var(--button-line-color); } } svg { @@ -251,12 +255,12 @@ const BottomBtnStyled = styled.li` } @media (hover: hover) and (pointer: fine) { &:hover { - color: var(--color-button-line); + color: var(--button-line-color); svg { - stroke: var(--color-button-line); + stroke: var(--button-line-color); } a { - color: var(--color-button-line); + color: var(--button-line-color); } } } diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index 761811566..04030dac5 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -76,14 +76,14 @@ const LeftSectionNav = ({ title="슬랙 캐비닛 채널 새창으로 열기" > 문의하기 - + onClickClubForm()} title="동아리 사물함 사용 신청서 새창으로 열기" > 동아리 신청서 - + {isClub && } @@ -98,7 +98,7 @@ const LeftNavOptionStyled = styled.div<{ min-width: 240px; height: 100%; padding: 32px 10px; - border-right: 1px solid var(--color-line); + border-right: 1px solid var(--line-color); font-weight: 300; position: relative; `; @@ -110,13 +110,13 @@ const ProfileLeftNavOptionStyled = styled.div<{ min-width: 240px; height: 100%; padding: 32px 10px; - border-right: 1px solid var(--color-line); + border-right: 1px solid var(--line-color); font-weight: 300; position: relative; & hr { width: 80%; height: 1px; - background-color: var(--gray-tmp-3); + background-color: var(--shared-gray-color-300); border: 0; margin-top: 20px; margin-bottom: 20px; @@ -129,13 +129,13 @@ export const FloorSectionStyled = styled.div` line-height: 40px; border-radius: 10px; text-indent: 20px; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); margin: 2px 0; cursor: pointer; @media (hover: hover) and (pointer: fine) { &:hover { background-color: var(--main-color); - color: var(--color-text-with-bg); + color: var(--text-with-bg-color); } } `; @@ -150,7 +150,7 @@ const SectionLinkStyled = styled.div` cursor: pointer; display: flex; align-items: center; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); #linknImg { width: 15px; @@ -160,10 +160,10 @@ const SectionLinkStyled = styled.div` @media (hover: hover) and (pointer: fine) { &:hover { - color: var(--color-button-line); + color: var(--button-line-color); svg { - stroke: var(--color-button-line); + stroke: var(--button-line-color); } } } diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx index 700a74635..9e91560a5 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx +++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx @@ -46,13 +46,13 @@ const ClubLeftNavOptionStyled = styled.div` min-width: 240px; height: 100%; padding: 20px 10px 32px; - border-right: 1px solid var(--color-line); + border-right: 1px solid var(--line-color); font-weight: 300; position: relative; & hr { width: 80%; height: 1px; - background-color: var(--gray-tmp-3); + background-color: var(--shared-gray-color-300); border: 0; margin-top: 20px; margin-bottom: 20px; @@ -60,7 +60,7 @@ const ClubLeftNavOptionStyled = styled.div` `; const ListTitleStyled = styled.div` - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); font-size: 0.9rem; margin: 0.5rem 0.75rem; `; diff --git a/frontend/src/components/LentLog/AdminCabinetLentLog.tsx b/frontend/src/components/LentLog/AdminCabinetLentLog.tsx index 40cc86731..eee8c22da 100644 --- a/frontend/src/components/LentLog/AdminCabinetLentLog.tsx +++ b/frontend/src/components/LentLog/AdminCabinetLentLog.tsx @@ -79,7 +79,11 @@ const PageButtonStyled = styled.div<{ position: absolute; opacity: 0.5; transition: opacity 0.5s; - background: linear-gradient(to left, transparent, var(--border-shadow-300)); + background: linear-gradient( + to left, + transparent, + var(--border-shadow-color-300) + ); display: ${({ page, totalPage, type }) => { if (type == "prev" && page == 0) return "none"; if (type == "next" && (totalPage == 0 || page == totalPage - 1)) diff --git a/frontend/src/components/LentLog/AdminLentLog.tsx b/frontend/src/components/LentLog/AdminLentLog.tsx index 2091186fa..9df21be64 100644 --- a/frontend/src/components/LentLog/AdminLentLog.tsx +++ b/frontend/src/components/LentLog/AdminLentLog.tsx @@ -96,11 +96,11 @@ const AdminLentLogStyled = styled.div` z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; - box-shadow: 0 0 40px 0 var(--border-shadow-300); + box-shadow: 0 0 40px 0 var(--border-shadow-color-300); display: flex; flex-direction: column; align-items: center; - background: var(--color-background); + background: var(--bg-color); &.on { transform: translateX(0); } diff --git a/frontend/src/components/LentLog/AdminUserLentLog.tsx b/frontend/src/components/LentLog/AdminUserLentLog.tsx index e1649d6d0..3b41a72bf 100644 --- a/frontend/src/components/LentLog/AdminUserLentLog.tsx +++ b/frontend/src/components/LentLog/AdminUserLentLog.tsx @@ -78,7 +78,11 @@ const PageButtonStyled = styled.div<{ position: absolute; opacity: 0.5; transition: opacity 0.5s; - background: linear-gradient(to left, transparent, var(--border-shadow-300)); + background: linear-gradient( + to left, + transparent, + var(--border-shadow-color-300) + ); display: ${({ page, totalPage, type }) => { if (type == "prev" && page == 0) return "none"; if (type == "next" && (totalPage == 0 || page == totalPage - 1)) diff --git a/frontend/src/components/LentLog/LentLog.tsx b/frontend/src/components/LentLog/LentLog.tsx index 1978a03d3..b363fae48 100644 --- a/frontend/src/components/LentLog/LentLog.tsx +++ b/frontend/src/components/LentLog/LentLog.tsx @@ -104,11 +104,11 @@ const LentLogStyled = styled.div` z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; - box-shadow: 0 0 40px 0 var(--border-shadow-200); + box-shadow: 0 0 40px 0 var(--border-shadow-color-200); display: flex; flex-direction: column; align-items: center; - background: var(--color-background); + background: var(--bg-color); &.on { transform: translateX(0); } diff --git a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx b/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx index bea113d17..d4c53be72 100644 --- a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx +++ b/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx @@ -63,12 +63,12 @@ const LogTableWrapperstyled = styled.div` border-radius: 10px; overflow: hidden; margin: 0 auto; - box-shadow: 0 0 10px 0 var(--border-shadow-100); + box-shadow: 0 0 10px 0 var(--border-shadow-color-100); `; const LogTableStyled = styled.table` width: 100%; - background: var(--color-background); + background: var(--bg-color); overflow: scroll; `; @@ -77,7 +77,7 @@ const TheadStyled = styled.thead` height: 50px; line-height: 50px; background-color: var(--main-color); - color: var(--color-background); + color: var(--bg-color); & > tr > th:first-child { padding-left: 20px; } @@ -98,7 +98,7 @@ const TbodyStyled = styled.tbody` width: 33.3%; } & > tr:nth-child(2n) { - background: var(--purple-tmp-1); + background: var(--shared-purple-color-100); } & > tr > td:first-child { padding-left: 20px; diff --git a/frontend/src/components/LentLog/LogTable/LogTable.tsx b/frontend/src/components/LentLog/LogTable/LogTable.tsx index 29017305f..a6a3891a6 100644 --- a/frontend/src/components/LentLog/LogTable/LogTable.tsx +++ b/frontend/src/components/LentLog/LogTable/LogTable.tsx @@ -62,12 +62,12 @@ const LogTableWrapperstyled = styled.div` border-radius: 10px; overflow: hidden; margin: 0 auto; - box-shadow: 0 0 10px 0 var(--border-shadow-100); + box-shadow: 0 0 10px 0 var(--border-shadow-color-100); `; const LogTableStyled = styled.table` width: 100%; - background: var(--color-background); + background: var(--bg-color); overflow: scroll; `; @@ -76,8 +76,8 @@ const TheadStyled = styled.thead` height: 50px; line-height: 50px; background-color: var(--main-color); - color: var(--color-text-with-bg); - + color: var(--text-with-bg-color); + & > tr > th:first-child { padding-left: 20px; } @@ -98,7 +98,7 @@ const TbodyStyled = styled.tbody` width: 33.3%; } & > tr:nth-child(2n) { - background: var(--purple-tmp-1); + background: var(--shared-purple-color-100); } & > tr > td:first-child { padding-left: 20px; diff --git a/frontend/src/components/Login/AdminLoginTemplate.tsx b/frontend/src/components/Login/AdminLoginTemplate.tsx index 660bb180c..1e5bad114 100644 --- a/frontend/src/components/Login/AdminLoginTemplate.tsx +++ b/frontend/src/components/Login/AdminLoginTemplate.tsx @@ -60,7 +60,7 @@ const AdminLoginTemplate = (props: { - + 42서울 캐비닛 서비스 @@ -208,7 +208,7 @@ const LoginCardStyled = styled.div` align-items: center; flex-direction: column; padding: 85px 40px; - background-color: var(--color-background); + background-color: var(--bg-color); `; const CardLogoStyled = styled.div` @@ -252,19 +252,19 @@ const CardInputStyled = styled.input<{ isFocus: boolean }>` letter-spacing: 0.05rem; width: 100%; height: 48px; - background-color: var(--color-background); + background-color: var(--bg-color); border-radius: 8px; margin-bottom: 8px; border: ${(props) => props.isFocus ? "1px solid var(--main-color)" - : "1px solid var(--gray-tmp-4)"}; - color: var(--color-text-normal); + : "1px solid var(--shared-gray-color-400)"}; + color: var(--normal-text-color); `; const CardGoogleOauthStyled = styled.button` - background-color: var(--color-background); - color: var(--color-text-normal); + background-color: var(--bg-color); + color: var(--normal-text-color); font-style: oblique; height: 30px; margin-top: 10px; diff --git a/frontend/src/components/Login/LoginTemplate.tsx b/frontend/src/components/Login/LoginTemplate.tsx index 1e469bd5c..bbd71fb26 100644 --- a/frontend/src/components/Login/LoginTemplate.tsx +++ b/frontend/src/components/Login/LoginTemplate.tsx @@ -17,7 +17,7 @@ const LoginTemplate = (props: { - + 42서울 캐비닛 서비스 @@ -124,7 +124,7 @@ const LoginCardStyled = styled.div` align-items: center; flex-direction: column; padding: 85px 0; - background-color: var(--color-background); + background-color: var(--bg-color); `; const CardLogoStyled = styled.div` diff --git a/frontend/src/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx b/frontend/src/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx index 25c3678ce..f62ec3802 100644 --- a/frontend/src/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx +++ b/frontend/src/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx @@ -32,7 +32,7 @@ const MapFloorSelect = ({ floor, setFloor, floorInfo }: IMapFloorSelect) => { const MapFloorSelectStyled = styled.div` background-color: var(--main-color); - color: var(--color-text-with-bg); + color: var(--text-with-bg-color); cursor: pointer; width: 65px; height: 40px; @@ -51,7 +51,7 @@ const MapFloorSelectStyled = styled.div` } & > svg > path { - stroke: var(--color-text-with-bg); + stroke: var(--text-with-bg-color); stroke-width: 1.5; } `; diff --git a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx b/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx index 89bde7f92..8c2aa80a2 100644 --- a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx +++ b/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx @@ -23,9 +23,9 @@ const OptionWrapperStyled = styled.div` position: absolute; left: 0; top: 75px; - background: var(--color-background); + background: var(--bg-color); border-radius: 10px; - box-shadow: 0 0 10px 0 var(--border-shadow-100); + box-shadow: 0 0 10px 0 var(--border-shadow-color-100); overflow: hidden; z-index: 99; display: none; @@ -37,12 +37,12 @@ const OptionWrapperStyled = styled.div` const OptionStyled = styled.div` width: 65px; height: 40px; - border-bottom: 1px solid var(--gray-tmp-2); + border-bottom: 1px solid var(--shared-gray-color-200); display: flex; justify-content: center; align-items: center; - background: var(--color-background); - color: var(--color-text-normal); + background: var(--bg-color); + color: var(--normal-text-color); cursor: pointer; `; diff --git a/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx b/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx index bcb1812d0..555958c5a 100644 --- a/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx +++ b/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx @@ -36,7 +36,7 @@ const MapGridStyled = styled.div` text-align: center; max-height: 580px; height: 100%; - background: var(--gray-tmp-2); + background: var(--shared-gray-color-200); display: grid; grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(8, 1fr); diff --git a/frontend/src/components/MapInfo/MapInfo.tsx b/frontend/src/components/MapInfo/MapInfo.tsx index deeecf69d..91b1f8ae3 100644 --- a/frontend/src/components/MapInfo/MapInfo.tsx +++ b/frontend/src/components/MapInfo/MapInfo.tsx @@ -48,7 +48,7 @@ const HeaderStyled = styled.div` justify-content: space-between; width: 100%; align-items: center; - color: var(--color-text-normal); + color: var(--normal-text-color); font-weight: bold; `; @@ -63,12 +63,12 @@ const MapInfoContainerStyled = styled.div` z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; - box-shadow: 0 0 40px 0 var(--border-shadow-200); + box-shadow: 0 0 40px 0 var(--border-shadow-color-200); display: flex; flex-direction: column; align-items: center; - background: var(--color-background); - border-left: 1px solid var(--color-line); + background: var(--bg-color); + border-left: 1px solid var(--line-color); &.on { transform: translateX(0%); } diff --git a/frontend/src/components/MapInfo/MapItem/MapItem.tsx b/frontend/src/components/MapInfo/MapItem/MapItem.tsx index aeefbe779..2a2a447cf 100644 --- a/frontend/src/components/MapInfo/MapItem/MapItem.tsx +++ b/frontend/src/components/MapInfo/MapItem/MapItem.tsx @@ -56,8 +56,8 @@ const ItemStyled = styled.div<{ cursor: ${({ info }) => (info.type === "floorInfo" ? "default" : "pointer")}; color: ${({ info }) => info.type === "floorInfo" - ? "var(--gray-tmp-4)" - : "var(--color-text-with-bg)"}; + ? "var(--shared-gray-color-400)" + : "var(--text-with-bg-color)"}; display: flex; justify-content: center; align-items: center; @@ -72,7 +72,7 @@ const ItemStyled = styled.div<{ ? "var(--main-color)" : info.type === "floorInfo" ? "transparent" - : "var(--gray-tmp-4)"}; + : "var(--shared-gray-color-400)"}; &:hover { opacity: ${({ info }) => (info.type === "cabinet" ? 0.9 : 1)}; } diff --git a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx b/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx index 20f286dd4..ad0afbdb2 100644 --- a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx +++ b/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx @@ -58,7 +58,7 @@ const ModalContainerStyled = styled.div<{ type: string }>` top: 50%; left: 50%; width: 360px; - background: var(--color-background); + background: var(--bg-color); z-index: 1000; border-radius: 10px; transform: translate(-50%, -50%); @@ -103,7 +103,7 @@ const ContentItemTitleStyled = styled.h3` `; const ContentItemInputStyled = styled.input` - border: 1px solid var(--color-line); + border: 1px solid var(--line-color); width: 100%; height: 60px; border-radius: 10px; @@ -111,9 +111,9 @@ const ContentItemInputStyled = styled.input` text-indent: 20px; font-size: 1.125rem; cursor: "input"; - color: "var(--color-text-normal)"; + color: "var(--normal-text-color)"; &::placeholder { - color: "var(--gray-tmp-4)"; + color: "var(--shared-gray-color-400)"; } `; @@ -123,7 +123,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-100); + background: var(--bg-shadow-color-100); z-index: 1000; `; diff --git a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx b/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx index f313ee2bf..d65d1859f 100644 --- a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx +++ b/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx @@ -68,7 +68,7 @@ const ModalContainerStyled = styled.div<{ type: string }>` top: 50%; left: 50%; width: 360px; - background: var(--color-background); + background: var(--bg-color); z-index: 1000; border-radius: 10px; transform: translate(-50%, -50%); @@ -108,25 +108,25 @@ const ContentItemTextAreaStyled = styled.textarea<{ box-sizing: border-box; padding: 15px; width: 100%; - border: 1px solid var(--color-line); + border: 1px solid var(--line-color); height: 100%; border-radius: 10px; font-size: 1.125rem; - color: var(--color-text-normal); + color: var(--normal-text-color); overflow-y: auto; word-break: break-all; white-space: pre-wrap; line-height: 1.2rem; letter-spacing: 0.8px; resize: none; - background-color: var(--color-background); + background-color: var(--bg-color); cursor: ${({ mode }) => (mode === "read" ? "default" : "input")}; color: ${({ mode }) => - mode === "read" ? "var(--main-color)" : "var(--color-text-normal)"}; + mode === "read" ? "var(--main-color)" : "var(--normal-text-color)"}; &::placeholder { color: ${({ mode }) => - mode === "read" ? "var(--main-color)" : "var(--color-line)"}; + mode === "read" ? "var(--main-color)" : "var(--line-color)"}; } ::-webkit-scrollbar { width: 20px; @@ -134,7 +134,7 @@ const ContentItemTextAreaStyled = styled.textarea<{ } ::-webkit-scrollbar-thumb { - background: var(--color-line); + background: var(--line-color); border-radius: 50px; border: 6px solid transparent; background-clip: padding-box; @@ -148,7 +148,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-100); + background: var(--bg-shadow-color-100); z-index: 1000; `; diff --git a/frontend/src/components/Modals/ClubModal/ClubModal.tsx b/frontend/src/components/Modals/ClubModal/ClubModal.tsx index 435585b42..2b9cea4dc 100644 --- a/frontend/src/components/Modals/ClubModal/ClubModal.tsx +++ b/frontend/src/components/Modals/ClubModal/ClubModal.tsx @@ -220,7 +220,7 @@ const ModalContainerStyled = styled.div` top: 50%; left: 50%; width: 460px; - background: var(--color-background); + background: var(--bg-color); z-index: 1000; border-radius: 10px; transform: translate(-50%, -50%); @@ -271,7 +271,7 @@ const ContentItemTitleStyled = styled.h3` `; const ContentItemInputStyled = styled.input` - border: 1px solid var(--color-line); + border: 1px solid var(--line-color); width: 100%; height: 60px; border-radius: 10px; @@ -279,10 +279,10 @@ const ContentItemInputStyled = styled.input` text-indent: 20px; font-size: 1.125rem; /* cursor: input; */ - color: var(--color-text-normal); + color: var(--normal-text-color); &::placeholder { - color: var(--gray-tmp-4); + color: var(--shared-gray-color-400); } `; @@ -292,7 +292,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-100); + background: var(--bg-shadow-color-100); z-index: 1000; `; diff --git a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx b/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx index b5890c020..44a8422f7 100644 --- a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx +++ b/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx @@ -109,7 +109,7 @@ const ModalStyled = styled.div` top: 50%; left: 50%; width: 360px; - background: var(--color-background); + background: var(--bg-color); z-index: 1000; border-radius: 10px; transform: translate(-50%, -50%); @@ -166,7 +166,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-100); + background: var(--bg-shadow-color-100); animation: fadeInBg 0.5s; @keyframes fadeInBg { 0% { diff --git a/frontend/src/components/Modals/ManualModal/ManualModal.tsx b/frontend/src/components/Modals/ManualModal/ManualModal.tsx index c91682192..d3c500451 100644 --- a/frontend/src/components/Modals/ManualModal/ManualModal.tsx +++ b/frontend/src/components/Modals/ManualModal/ManualModal.tsx @@ -163,7 +163,7 @@ const ModalWrapper = styled.div<{ : "none"}; box-shadow: ${(props) => props.contentStatus === ContentStatus.PENDING && - "inset 0px 0px 0px 5px var(--color-background);"}; + "inset 0px 0px 0px 5px var(--bg-color);"}; border-bottom: none; @media screen and (max-width: 700px) { width: 100%; @@ -182,8 +182,8 @@ const ModalContent = styled.div<{ props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" : props.contentStatus === ContentStatus.EXTENSION - ? "var(--color-text-normal)" - : "var(--color-text-with-bg)"}; + ? "var(--normal-text-color)" + : "var(--text-with-bg-color)"}; font-size: 2.5rem; font-weight: bold; align-items: flex-start; @@ -204,8 +204,8 @@ const ModalContent = styled.div<{ props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" : props.contentStatus === ContentStatus.EXTENSION - ? "var(--color-text-normal)" - : "var(--color-text-with-bg)"}; + ? "var(--normal-text-color)" + : "var(--text-with-bg-color)"}; } `; @@ -226,8 +226,8 @@ const CloseButton = styled.div<{ props.contentStatus === ContentStatus.IN_SESSION ? "var(--main-color)" : props.contentStatus === ContentStatus.EXTENSION - ? "var(--color-text-normal)" - : "var(--color-background)"}; + ? "var(--normal-text-color)" + : "var(--bg-color)"}; } :hover { transform: translateX(-16px); @@ -249,7 +249,7 @@ const BoxInfoWrap = styled.div` const BoxInfo1 = styled.div` width: 100px; height: 80px; - border: 1px solid var(--color-text-with-bg); + border: 1px solid var(--text-with-bg-color); border-radius: 15px; font-size: 0.875rem; font-weight: 400; @@ -267,7 +267,7 @@ const BoxInfo1 = styled.div` const BoxInfo2 = styled.div` width: 80px; height: 80px; - border: 1px solid var(--color-text-with-bg); + border: 1px solid var(--text-with-bg-color); border-radius: 15px; font-size: 0.875rem; font-weight: 400; @@ -337,8 +337,8 @@ const ContentImgStyled = styled.div<{ & > path { stroke: ${(props) => props.contentStatus === ContentStatus.EXTENSION - ? "var(--color-text-normal)" - : "var(--color-text-with-bg)"}; + ? "var(--normal-text-color)" + : "var(--text-with-bg-color)"}; transform: ${(props) => props.contentStatus === ContentStatus.EXTENSION ? "scale(1.4)" diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/components/Modals/MemoModal/MemoModal.tsx index 328317d31..721d4857a 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/components/Modals/MemoModal/MemoModal.tsx @@ -118,7 +118,7 @@ const ModalContainerStyled = styled.div<{ type: string }>` top: 50%; left: 50%; width: 360px; - background: var(--color-background); + background: var(--bg-color); z-index: 1000; border-radius: 10px; transform: translate(-50%, -50%); @@ -171,7 +171,7 @@ const ContentItemTitleStyled = styled.h3` const ContentItemInputStyled = styled.input<{ mode: string; }>` - border: 1px solid var(--color-line); + border: 1px solid var(--line-color); width: 100%; height: 60px; border-radius: 10px; @@ -180,10 +180,10 @@ const ContentItemInputStyled = styled.input<{ font-size: 1.125rem; cursor: ${({ mode }) => (mode === "read" ? "default" : "input")}; color: ${({ mode }) => - mode === "read" ? "var(--main-color)" : "var(--color-text-normal)"}; + mode === "read" ? "var(--main-color)" : "var(--normal-text-color)"}; &::placeholder { color: ${({ mode }) => - mode === "read" ? "var(--main-color)" : "var(--gray-tmp-4)"}; + mode === "read" ? "var(--main-color)" : "var(--shared-gray-color-400)"}; } `; @@ -193,7 +193,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-100); + background: var(--bg-shadow-color-100); z-index: 1000; `; diff --git a/frontend/src/components/Modals/Modal.tsx b/frontend/src/components/Modals/Modal.tsx index a5c855303..b5ccda32b 100644 --- a/frontend/src/components/Modals/Modal.tsx +++ b/frontend/src/components/Modals/Modal.tsx @@ -127,7 +127,7 @@ const ModalStyled = styled.div` top: 50%; left: 50%; width: 360px; - background: var(--color-background); + background: var(--bg-color); z-index: 1000; border-radius: 10px; transform: translate(-50%, -50%); @@ -145,7 +145,7 @@ const ModalStyled = styled.div` align-items: center; text-align: center; padding: 40px 20px; - color: var(--color-text-normal); + color: var(--normal-text-color); `; const ModalIconImgStyled = styled.div<{ iconScaleEffect: boolean | undefined }>` @@ -191,7 +191,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-100); + background: var(--bg-shadow-color-100); animation: fadeInBg 0.5s; @keyframes fadeInBg { 0% { @@ -219,7 +219,7 @@ const DropdownStyled = styled.select` `; const Option = styled.option` - background-color: var(--expired); + background-color: var(--expired-color); `; export default Modal; diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx b/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx index 1f00feda0..6737fa5d2 100644 --- a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx +++ b/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx @@ -90,7 +90,7 @@ const ModalStyled = styled.div` top: 50%; left: 50%; width: 360px; - background: var(--color-background); + background: var(--bg-color); z-index: 1000; border-radius: 10px; transform: translate(-50%, -50%); @@ -152,7 +152,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-100); + background: var(--bg-shadow-color-100); animation: fadeInBg 0.5s; @keyframes fadeInBg { 0% { diff --git a/frontend/src/components/Modals/StatusModal/StatusModal.tsx b/frontend/src/components/Modals/StatusModal/StatusModal.tsx index 52ecee768..422370182 100644 --- a/frontend/src/components/Modals/StatusModal/StatusModal.tsx +++ b/frontend/src/components/Modals/StatusModal/StatusModal.tsx @@ -168,7 +168,7 @@ const ModalContainerStyled = styled.div<{ type: string }>` top: 50%; left: 50%; width: 360px; - background: var(--color-background); + background: var(--bg-color); z-index: 1000; border-radius: 10px; transform: translate(-50%, -50%); @@ -224,7 +224,7 @@ const ContentItemContainerStyled = styled.div<{ mode: string }>` position: relative; display: flex; align-items: center; - border: 1px solid var(--gray-tmp-4); + border: 1px solid var(--shared-gray-color-400); width: 100%; height: 60px; border-radius: 10px; @@ -248,7 +248,7 @@ const ContentItemIconStyled = styled.div` & > svg path { transform: scale(0.8); - stroke: var(--color-text-normal); + stroke: var(--normal-text-color); } `; @@ -258,7 +258,7 @@ const BackgroundStyled = styled.div` left: 0; right: 0; bottom: 0; - background: var(--bg-shadow-100); + background: var(--bg-shadow-color-100); z-index: 1000; `; diff --git a/frontend/src/components/Search/NoSearch.tsx b/frontend/src/components/Search/NoSearch.tsx index 8fe98057d..7bd8de0b1 100644 --- a/frontend/src/components/Search/NoSearch.tsx +++ b/frontend/src/components/Search/NoSearch.tsx @@ -28,7 +28,7 @@ const NoSearchStyled = styled.div` & > p { margin-top: 10px; font-size: 1.125rem; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); } `; diff --git a/frontend/src/components/Search/SearchDefault.tsx b/frontend/src/components/Search/SearchDefault.tsx index 3527eed44..8386fadb0 100644 --- a/frontend/src/components/Search/SearchDefault.tsx +++ b/frontend/src/components/Search/SearchDefault.tsx @@ -29,7 +29,7 @@ const SearchDefaultStyled = styled.div` & > p { margin-top: 10px; font-size: 1.125rem; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); text-align: center; line-height: 1.75rem; } diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/components/Search/SearchItemByIntraId.tsx index 4ea4b9b18..c0bfe2590 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/components/Search/SearchItemByIntraId.tsx @@ -131,7 +131,7 @@ const WrapperStyled = styled.div<{ isSelected: boolean }>` height: 110px; border-radius: 10px; padding: 25px; - background-color: var(--gray-tmp-1); + background-color: var(--shared-gray-color-100); display: flex; align-items: center; transition: transform 0.2s, opacity 0.2s; @@ -141,8 +141,8 @@ const WrapperStyled = styled.div<{ isSelected: boolean }>` css` opacity: 0.9; transform: scale(1.02); - box-shadow: inset 4px 4px 4px var(--border-shadow-100), - 2px 2px 4px var(--border-shadow-100); + box-shadow: inset 4px 4px 4px var(--border-shadow-color-100), + 2px 2px 4px var(--border-shadow-color-100); `} @media (hover: hover) and (pointer: fine) { &:hover { @@ -161,14 +161,14 @@ const RectangleStyled = styled.div<{ border-radius: 10px; background-color: ${(props) => props.banned - ? "var(--expired)" + ? "var(--expired-color)" : props.status ? cabinetStatusColorMap[props.status] - : "var(--full)"}; + : "var(--full-color)"}; font-size: 1.625rem; color: ${(props) => props.banned - ? "var(--color-background)" + ? "var(--bg-color)" : props.status ? cabinetLabelColorMap[props.status] : "var(--black)"}; @@ -187,7 +187,7 @@ const TextWrapper = styled.div` const LocationStyled = styled.p` font-size: 0.875rem; line-height: 28px; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); `; const NameWrapperStyled = styled.div` diff --git a/frontend/src/components/Search/SearchItemByNum.tsx b/frontend/src/components/Search/SearchItemByNum.tsx index 517df4077..9a6e3e1d2 100644 --- a/frontend/src/components/Search/SearchItemByNum.tsx +++ b/frontend/src/components/Search/SearchItemByNum.tsx @@ -81,7 +81,7 @@ const WrapperStyled = styled.div<{ isSelected: boolean }>` height: 110px; border-radius: 10px; padding: 25px; - background-color: var(--gray-tmp-1); + background-color: var(--shared-gray-color-100); display: flex; align-items: center; transition: transform 0.2s, opacity 0.2s; @@ -91,8 +91,8 @@ const WrapperStyled = styled.div<{ isSelected: boolean }>` css` opacity: 0.9; transform: scale(1.02); - box-shadow: inset 4px 4px 4px var(--border-shadow-100), - 2px 2px 4px var(--border-shadow-100); + box-shadow: inset 4px 4px 4px var(--border-shadow-color-100), + 2px 2px 4px var(--border-shadow-color-100); `} @media (hover: hover) and (pointer: fine) { &:hover { @@ -111,7 +111,7 @@ const RectangleStyled = styled.div<{ status: CabinetStatus }>` color: ${(props) => props.status ? cabinetLabelColorMap[props.status] - : "var(--color-text-normal)"}; + : "var(--normal-text-color)"}; display: flex; justify-content: center; align-items: center; @@ -126,7 +126,7 @@ const TextWrapper = styled.div` const LocationStyled = styled.p` font-size: 0.875rem; line-height: 28px; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); `; const NameWrapperStyled = styled.div` diff --git a/frontend/src/components/SectionPagination/SectionPagination.tsx b/frontend/src/components/SectionPagination/SectionPagination.tsx index 67970637e..c4f1bc892 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.tsx +++ b/frontend/src/components/SectionPagination/SectionPagination.tsx @@ -24,7 +24,7 @@ const SectionPagination: React.FC<{ filledColor={ sectionName === currentSectionName ? "var(--main-color)" - : "var(--gray-tmp-3)" + : "var(--shared-gray-color-300)" } onClick={() => changeSectionOnClickIndexButton(index)} className="cabiButton" @@ -61,7 +61,7 @@ const SectionPaginationStyled = styled.div` padding: 10px 0; position: sticky; top: 0; - background: var(--color-background); + background: var(--bg-color); z-index: 1; `; @@ -94,7 +94,7 @@ const SectionNameTextStyled = styled.div` min-width: 220px; font-size: 1rem; text-align: center; - color: var(--gray-tmp-5); + color: var(--shared-gray-color-500); `; const SectionIndexStyled = styled.div` diff --git a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx index d964d7014..24328b716 100644 --- a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx +++ b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx @@ -10,9 +10,7 @@ const DarkMode: React.FC<{}> = () => { - - ); +export const lightValues = css` + --text: var(--blue); +`; + +// set up dark theme CSS variables +export const darkValues = css` + --text: var(--pink); +`; + +const GlobalStyle = createGlobalStyle` + :root { + // define light theme values as the defaults within the root selector + ${lightValues} + + [color-theme="true"] { + ${darkValues} + } + } +`; + +const DarkMode = () => { + const [darkMode, setDarkMode] = useRecoilState(darkModeState); + const onClickHandler = () => { + setDarkMode((prev) => { + return prev === "light" ? "dark" : "light"; + }); + }; + + useEffect(() => { + document.body.setAttribute("color-theme", darkMode); + }, [darkMode]); + + return ; }; export default DarkMode; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index d2a7cc819..7185198df 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,11 +1,10 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { RecoilRoot } from "recoil"; - import App from "./App"; +import "./assets/css/media.css"; import "./assets/css/reset.css"; import "./index.css"; -import "./assets/css/media.css"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/pages/Layout.tsx index bb9e3fa43..4c07025dc 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/pages/Layout.tsx @@ -23,7 +23,6 @@ import { ClubResponseDto, } from "@/types/dto/club.dto"; import { UserDto, UserInfo } from "@/types/dto/user.dto"; -import ColorType from "@/types/enum/color.type.enum"; import { axiosMyClubList, axiosMyInfo } from "@/api/axios/axios.custom"; import { getCookie } from "@/api/react_cookie/cookies"; import useMenu from "@/hooks/useMenu"; @@ -128,7 +127,6 @@ const Layout = (): JSX.Element => { ) : ( - {/* */} {isValidToken && } {isLoading ? ( @@ -156,22 +154,12 @@ const Layout = (): JSX.Element => { )} )} - {/* */} ); }; export default Layout; -const ContainerStyled = styled.div<{ darkMode: string }>` - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - overflow: hidden; - background-color: ${(props) => props.darkMode}; -`; - const WrapperStyled = styled.div` width: 100%; height: 100%; diff --git a/frontend/src/recoil/atoms.ts b/frontend/src/recoil/atoms.ts index 7638b9d0c..7d52ab73f 100644 --- a/frontend/src/recoil/atoms.ts +++ b/frontend/src/recoil/atoms.ts @@ -209,5 +209,5 @@ export const targetClubUserInfoState = atom({ export const darkModeState = atom({ key: "darkMode", - default: "var(--normal-text-color)", + default: "light", }); From 244fc3654563b269a92702b31f5a9c077756bbf4 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Mon, 15 Apr 2024 21:22:59 +0900 Subject: [PATCH 0571/1029] =?UTF-8?q?[FE]=20FEAT:=20body=20attribute=20?= =?UTF-8?q?=EA=B0=92=EC=97=90=20=EB=94=B0=EB=9D=BC=20css=20=EC=A0=84?= =?UTF-8?q?=EC=97=AD=EB=B3=80=EC=88=98=20=EA=B0=92=20=EB=B3=80=EA=B2=BD#15?= =?UTF-8?q?51?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/index.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/index.css b/frontend/src/index.css index 242fa94b9..4d18e5976 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -120,6 +120,10 @@ color: var(--normal-text-color); background-color: var(--bg-color); + + [color-theme="dark"] { + --main-color: var(--pink-100); + } } /* main color variable */ From 08c43f15c90de79242128573ac26ac22707381a7 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 16 Apr 2024 15:54:00 +0900 Subject: [PATCH 0572/1029] =?UTF-8?q?[FE]=20REFACTOR:=20color-theme=20?= =?UTF-8?q?=EA=B0=92=EC=9D=B4=20dark=EC=9D=BC=EB=95=8C=EC=99=80=20?= =?UTF-8?q?=EA=B8=B0=EA=B8=B0=20=EC=84=A4=EC=A0=95=EC=9D=B4=20=EB=8B=A4?= =?UTF-8?q?=ED=81=AC=EB=AA=A8=EB=93=9C=EB=A1=9C=20=EB=8F=BC=EC=9E=88?= =?UTF-8?q?=EC=9D=84=EB=95=8C=EB=A5=BC=20=EA=B7=B8=EB=A3=B9=EC=97=B0?= =?UTF-8?q?=EC=82=B0=EC=9E=90=EB=A1=9C=20=EB=AC=B6=EC=9D=8C#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/index.css | 95 ++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index 4d18e5976..ebe55ab01 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -121,56 +121,51 @@ color: var(--normal-text-color); background-color: var(--bg-color); - [color-theme="dark"] { - --main-color: var(--pink-100); - } -} - -/* main color variable */ -/* cabinet color variable */ -@media (prefers-color-scheme: dark) { - :root { - --bg-color: var(--gray-900); - --line-color: var(--shared-gray-color-300); - --normal-text-color: var(--gray-100); - --text-with-bg-color: var(--gray-100); - --card-content-bg-color: var(--gray-700); - --button-line-color: var(--default-sub-color); - - --main-color: var(--purple-600); - --sub-color: var(--purple-300); - - --default-main-color: var(--purple-600); - --default-sub-color: var(--purple-300); - --default-mine-color: var(--green-200); - - --mine-color: var(--green-200); - --available-color: var(--main-color); - --pending-color: var(--main-color); - --full-color: var(--gray-200); - --expired-color: var(--red-200); - --banned-color: var(--gray-600); - - --bg-shadow-color-100: var(--black-shadow-200); - --bg-shadow-color-200: var(--black-shadow-300); - --bg-shadow-color-300: var(--black-shadow-400); - --bg-shadow-color-400: var(--black-shadow-400); - --border-shadow-color-100: var(--black-shadow-200); - --border-shadow-color-200: var(--black-shadow-300); - --border-shadow-color-300: var(--black-shadow-400); - - --shared-gray-color-100: var(--gray-800); - --shared-gray-color-200: var(--gray-700); - --shared-gray-color-300: var(--gray-600); - --shared-gray-color-400: var(--gray-500); - --shared-gray-color-500: var(--gray-400); - --shared-gray-color-600: var(--gray-300); - --shared-gray-color-700: var(--gray-200); - - --shared-purple-color-100: var(--purple-700); - - color: var(--normal-text-color); - background-color: var(--bg-color); + [color-theme="dark"], + &.no-js { + @media (prefers-color-scheme: dark) { + --bg-color: var(--gray-900); + --line-color: var(--shared-gray-color-300); + --normal-text-color: var(--gray-100); + --text-with-bg-color: var(--gray-100); + --card-content-bg-color: var(--gray-700); + --button-line-color: var(--default-sub-color); + + --main-color: var(--purple-600); + --sub-color: var(--purple-300); + + --default-main-color: var(--purple-600); + --default-sub-color: var(--purple-300); + --default-mine-color: var(--green-200); + + --mine-color: var(--green-200); + --available-color: var(--main-color); + --pending-color: var(--main-color); + --full-color: var(--gray-200); + --expired-color: var(--red-200); + --banned-color: var(--gray-600); + + --bg-shadow-color-100: var(--black-shadow-200); + --bg-shadow-color-200: var(--black-shadow-300); + --bg-shadow-color-300: var(--black-shadow-400); + --bg-shadow-color-400: var(--black-shadow-400); + --border-shadow-color-100: var(--black-shadow-200); + --border-shadow-color-200: var(--black-shadow-300); + --border-shadow-color-300: var(--black-shadow-400); + + --shared-gray-color-100: var(--gray-800); + --shared-gray-color-200: var(--gray-700); + --shared-gray-color-300: var(--gray-600); + --shared-gray-color-400: var(--gray-500); + --shared-gray-color-500: var(--gray-400); + --shared-gray-color-600: var(--gray-300); + --shared-gray-color-700: var(--gray-200); + + --shared-purple-color-100: var(--purple-700); + + color: var(--normal-text-color); + background-color: var(--bg-color); + } } } From 067785e8892f44b720af0ad72464d9fb47cdf1a5 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 16 Apr 2024 15:54:43 +0900 Subject: [PATCH 0573/1029] =?UTF-8?q?[FE]=20FEAT:=20=ED=98=84=EC=9E=AC=20?= =?UTF-8?q?=EA=B8=B0=EA=B8=B0=20=EC=84=A4=EC=A0=95=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20color-theme=20=EA=B0=92=EC=9D=84=20=EB=B0=94?= =?UTF-8?q?=EA=BF=88#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/TopNav/DarkMode/DarkMode.tsx | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx index 102b1dd23..1aaeede5d 100644 --- a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx +++ b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx @@ -1,28 +1,7 @@ import { useEffect } from "react"; import { useRecoilState } from "recoil"; -import { createGlobalStyle, css } from "styled-components"; import { darkModeState } from "@/recoil/atoms"; -export const lightValues = css` - --text: var(--blue); -`; - -// set up dark theme CSS variables -export const darkValues = css` - --text: var(--pink); -`; - -const GlobalStyle = createGlobalStyle` - :root { - // define light theme values as the defaults within the root selector - ${lightValues} - - [color-theme="true"] { - ${darkValues} - } - } -`; - const DarkMode = () => { const [darkMode, setDarkMode] = useRecoilState(darkModeState); const onClickHandler = () => { @@ -31,6 +10,14 @@ const DarkMode = () => { }); }; + var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + + useEffect(() => { + setDarkMode(() => { + return darkModeQuery.matches ? "dark" : "light"; + }); + }, []); + useEffect(() => { document.body.setAttribute("color-theme", darkMode); }, [darkMode]); From c9992f56d40fb13da9624b2c46ac7eec52d34198 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 16 Apr 2024 15:55:29 +0900 Subject: [PATCH 0574/1029] =?UTF-8?q?[FE]=20HOTFIX:=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=20=EC=8A=AC=EB=9E=99=20=EC=95=8C=EB=A6=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=AA=A8=ED=8B=B0=EC=BD=98=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Cabinet/assets/data/SlackAlarm.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/src/Cabinet/assets/data/SlackAlarm.ts b/frontend/src/Cabinet/assets/data/SlackAlarm.ts index b3e586e40..c971b2504 100644 --- a/frontend/src/Cabinet/assets/data/SlackAlarm.ts +++ b/frontend/src/Cabinet/assets/data/SlackAlarm.ts @@ -39,7 +39,7 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ 안녕하세요. Cabi 팀입니다! :happy_ccabi: 현시간부로 서비스 이용이 정상화되었습니다. :portal_blue_parrot: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :portal_orange_parrot: - :파일_수납장: 서비스 개선과 관련한 사항은 Cabi 채널 문의주세요! :파일_수납장: + :file_cabinet: 서비스 개선과 관련한 사항은 Cabi 채널 문의주세요! :file_cabinet: :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:기다려주셔서 감사합니다! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, }, { @@ -53,23 +53,23 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ }, { title: "이용 안내서", - content: `:파일_수납장: Cabi 이용 안내서 :파일_수납장: + content: `:file_cabinet: Cabi 이용 안내서 :file_cabinet: :embarrassed_cabi: 42seoul의 사물함 대여 서비스를 운영중인 Cabi 팀입니다.:embarrassed_cabi: 자세한 이용 방법은 Cabi 가입 후 홈페이지의 이용 안내서를 참고해 주세요! - :오른쪽을_가리키는_손_모양: https://cabi.42seoul.io/home + :point_right: https://cabi.42seoul.io/home :alert: Cabi FAQ :alert: - :압정: 사물함의 물리적인 문제가 있습니다 (고장 났거나 잠겨있는 경우) + :pushpin: 사물함의 물리적인 문제가 있습니다 (고장 났거나 잠겨있는 경우) :happy_ccabi: 사물함의 물리적인 문제는 데스크에 문의 부탁드립니다! - :압정: 사물함 비밀번호를 모릅니다 (잊어버렸습니다). + :pushpin: 사물함 비밀번호를 모릅니다 (잊어버렸습니다). :happy_ccabi: 저희 서비스에서 대여한 화면과 슬랙 화면을 준비해서 데스크에 문의해주시기 바랍니다! - :압정: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. + :pushpin: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. :happy_ccabi: 사물함 안이 꽉 차거나 제대로 닫히지 않은 경우에 발생하는데, 문을 누른 상태로 비밀번호를 입력해 보시고, 그래도 되지 않는다면 데스크에 문의 부탁드립니다! - :압정: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. + :pushpin: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! - :압정: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? + :pushpin: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! - :압정: 사물함을 연체 했는데 패널티는 무엇인가요? - :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:울음을_참는_얼굴:`, + :pushpin: 사물함을 연체 했는데 패널티는 무엇인가요? + :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:face_holding_back_tears:`, }, { title: "모집 공고", @@ -88,7 +88,7 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: :four_leaf_clover::arrow_right:지금 바로 지원하기:arrow_left::four_leaf_clover: :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: - :절하는_남성: 상세한 정보는 구글 폼을 참고해주시고, 이외에 모집과 관련한 문의는 + :man-bowing: 상세한 정보는 구글 폼을 참고해주시고, 이외에 모집과 관련한 문의는 @sanan 에게 DM 부탁드립니다! :man-bowing:`, }, From bb32a6543316f9f32b844cf1d2400c63ac8f9f56 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 16 Apr 2024 16:03:42 +0900 Subject: [PATCH 0575/1029] =?UTF-8?q?[FE]=20HOTFIX:=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=20=EC=8A=AC=EB=9E=99=20=EC=95=8C=EB=A6=BC=20=EB=9D=84?= =?UTF-8?q?=EC=96=B4=EC=93=B0=EA=B8=B0,=20=EC=A4=84=EB=B0=94=EA=BF=88=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Cabinet/assets/data/SlackAlarm.ts | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/frontend/src/Cabinet/assets/data/SlackAlarm.ts b/frontend/src/Cabinet/assets/data/SlackAlarm.ts index c971b2504..123470f87 100644 --- a/frontend/src/Cabinet/assets/data/SlackAlarm.ts +++ b/frontend/src/Cabinet/assets/data/SlackAlarm.ts @@ -38,9 +38,9 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ content: `:dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby: 안녕하세요. Cabi 팀입니다! :happy_ccabi: 현시간부로 서비스 이용이 정상화되었습니다. - :portal_blue_parrot: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :portal_orange_parrot: + :portal_blue_parrot: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :portal_orange_parrot: :file_cabinet: 서비스 개선과 관련한 사항은 Cabi 채널 문의주세요! :file_cabinet: - :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:기다려주셔서 감사합니다! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, + :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:기다려주셔서 감사합니다! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, }, { title: "업데이트", @@ -56,20 +56,20 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ content: `:file_cabinet: Cabi 이용 안내서 :file_cabinet: :embarrassed_cabi: 42seoul의 사물함 대여 서비스를 운영중인 Cabi 팀입니다.:embarrassed_cabi: 자세한 이용 방법은 Cabi 가입 후 홈페이지의 이용 안내서를 참고해 주세요! - :point_right: https://cabi.42seoul.io/home + :point_right: https://cabi.42seoul.io/home :alert: Cabi FAQ :alert: :pushpin: 사물함의 물리적인 문제가 있습니다 (고장 났거나 잠겨있는 경우) :happy_ccabi: 사물함의 물리적인 문제는 데스크에 문의 부탁드립니다! :pushpin: 사물함 비밀번호를 모릅니다 (잊어버렸습니다). - :happy_ccabi: 저희 서비스에서 대여한 화면과 슬랙 화면을 준비해서 데스크에 문의해주시기 바랍니다! - :pushpin: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. - :happy_ccabi: 사물함 안이 꽉 차거나 제대로 닫히지 않은 경우에 발생하는데, 문을 누른 상태로 비밀번호를 입력해 보시고, 그래도 되지 않는다면 데스크에 문의 부탁드립니다! - :pushpin: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. - :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! - :pushpin: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? - :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! - :pushpin: 사물함을 연체 했는데 패널티는 무엇인가요? - :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:face_holding_back_tears:`, + :happy_ccabi: 저희 서비스에서 대여한 화면과 슬랙 화면을 준비해서 데스크에 문의해주시기 바랍니다! + :pushpin: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. + :happy_ccabi: 사물함 안이 꽉 차거나 제대로 닫히지 않은 경우에 발생하는데, 문을 누른 상태로 비밀번호를 입력해 보시고, 그래도 되지 않는다면 데스크에 문의 부탁드립니다! + :pushpin: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. + :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! + :pushpin: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? + :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! + :pushpin: 사물함을 연체 했는데 패널티는 무엇인가요? + :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:face_holding_back_tears:`, }, { title: "모집 공고", @@ -89,8 +89,7 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ :four_leaf_clover::arrow_right:지금 바로 지원하기:arrow_left::four_leaf_clover: :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: :man-bowing: 상세한 정보는 구글 폼을 참고해주시고, 이외에 모집과 관련한 문의는 - @sanan - 에게 DM 부탁드립니다! :man-bowing:`, + @jpark2 에게 DM 부탁드립니다! :man-bowing:`, }, { title: "동아리 사물함", From c8141998bfcc9fa6abc4c56f0c9e3b9aa1c9265b Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 16 Apr 2024 16:37:47 +0900 Subject: [PATCH 0576/1029] =?UTF-8?q?[COMMON]=20squash=20merge=20=EC=9D=B4?= =?UTF-8?q?=EC=A0=84=EC=9C=BC=EB=A1=9C=20rollback(DEV=20TO=20MAIN=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=8D=AE=EC=96=B4=EC=93=B0=EA=B8=B0=20=EC=98=88?= =?UTF-8?q?=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/front-cicd.yaml | 12 +- .../AdminPresentationController.java | 44 ++ .../cabinet/auth/service/TokenValidator.java | 2 + .../cabinet/dto/InvalidDateResponseDto.java | 15 + .../cabinet/dto/PresentationFormData.java | 24 + .../dto/PresentationFormRequestDto.java | 33 ++ .../dto/PresentationFormResponseDto.java | 10 + .../cabinet/dto/PresentationMainData.java | 11 + .../cabinet/dto/PresentationMyPageDto.java | 19 + .../dto/PresentationMyPagePaginationDto.java | 11 + .../cabinet/dto/PresentationUpdateDto.java | 15 + .../cabinet/exception/ExceptionStatus.java | 6 + .../cabinet/log/AllRequestLogInterceptor.java | 4 +- .../cabinet/mapper/PresentationMapper.java | 23 + .../controller/PresentationController.java | 79 +++ .../cabinet/presentation/domain/Category.java | 6 + .../presentation/domain/Presentation.java | 87 +++ .../domain/PresentationLocation.java | 6 + .../domain/PresentationStatus.java | 5 + .../presentation/domain/PresentationTime.java | 5 + .../repository/PresentationRepository.java | 29 + .../service/PresentationPolicyService.java | 55 ++ .../service/PresentationQueryService.java | 52 ++ .../service/PresentationService.java | 229 ++++++++ config | 2 +- dev/configure.conf | 2 +- frontend/.prettierrc | 27 +- frontend/index.html | 10 +- frontend/package-lock.json | 17 + frontend/package.json | 1 + frontend/src/App.tsx | 60 ++- .../{ => Cabinet}/api/axios/axios.custom.ts | 12 +- .../{ => Cabinet}/api/axios/axios.instance.ts | 4 +- .../{ => Cabinet}/api/react_cookie/cookies.ts | 0 .../src/{ => Cabinet}/assets/css/homePage.css | 0 .../{ => Cabinet}/assets/css/loginPage.css | 0 .../src/{ => Cabinet}/assets/css/media.css | 12 +- .../src/{ => Cabinet}/assets/css/reset.css | 0 .../assets/data/ManualContent.ts | 12 +- .../assets/data/mapPositionData.ts | 2 +- .../src/{ => Cabinet}/assets/data/maps.ts | 10 +- .../assets/data/sectionColNumData.ts | 0 .../assets/images/LeftSectionButton.svg | 0 .../assets/images/PresentationAcademic.svg | 32 ++ .../assets/images/PresentationDevelop.svg | 45 ++ .../assets/images/PresentationEmpty.svg | 68 +++ .../Cabinet/assets/images/PresentationEtc.svg | 38 ++ .../assets/images/PresentationFortyTwo.svg | 130 +++++ .../assets/images/PresentationHobby.svg | 33 ++ .../Cabinet/assets/images/PresentationJob.svg | 44 ++ .../assets/images/adminLoginImg.svg | 0 .../src/{ => Cabinet}/assets/images/alarm.svg | 0 .../{ => Cabinet}/assets/images/cabinet.svg | 0 .../src/Cabinet/assets/images/calendar.svg | 12 + .../assets/images/cautionSign.svg | 0 .../{ => Cabinet}/assets/images/checkIcon.svg | 0 .../src/{ => Cabinet}/assets/images/clock.svg | 0 .../assets/images/close-circle.svg | 0 .../assets/images/close-square.svg | 0 .../{ => Cabinet}/assets/images/clubIcon.svg | 0 .../assets/images/clubIconGray.svg | 0 .../src/{ => Cabinet}/assets/images/crown.svg | 0 .../assets/images/desktopLogo.png | Bin .../Cabinet/assets/images/dropdownChevron.svg | 3 + .../src/{ => Cabinet}/assets/images/edit.svg | 0 .../{ => Cabinet}/assets/images/errorIcon.svg | 0 .../assets/images/exitButton.svg | 0 .../{ => Cabinet}/assets/images/extension.svg | 0 .../assets/images/extensionTicket.svg | 0 .../assets/images/happyCcabi.png | Bin .../src/Cabinet/assets/images/happyCcabi.svg | 53 ++ .../assets/images/happyCcabiWhite.png | Bin .../{ => Cabinet}/assets/images/leader.svg | 0 .../src/{ => Cabinet}/assets/images/link.svg | 0 .../src/{ => Cabinet}/assets/images/lock.svg | 0 .../src/{ => Cabinet}/assets/images/log.svg | 0 .../{ => Cabinet}/assets/images/loginImg.svg | 0 .../src/{ => Cabinet}/assets/images/logo.ico | Bin .../src/{ => Cabinet}/assets/images/logo.png | Bin .../src/{ => Cabinet}/assets/images/logo.svg | 0 .../{ => Cabinet}/assets/images/logoBlack.svg | 0 .../assets/images/manualPeople.svg | 0 .../src/{ => Cabinet}/assets/images/map.svg | 0 .../src/{ => Cabinet}/assets/images/more.svg | 0 .../assets/images/moveButton.svg | 0 .../assets/images/myCabinetIcon.svg | 0 .../assets/images/notificationSign.svg | 0 .../assets/images/notificationSign_grey.svg | 0 .../assets/images/privateIcon.svg | 0 .../assets/images/proceedMultiSelect.svg | 0 .../assets/images/profile-circle.svg | 0 .../{ => Cabinet}/assets/images/refresh.svg | 0 .../assets/images/rotateRight.svg | 0 .../{ => Cabinet}/assets/images/sadCcabi.png | Bin .../src/Cabinet/assets/images/sadCcabi.svg | 8 + .../assets/images/sadCcabiWhite.png | Bin .../{ => Cabinet}/assets/images/search.svg | 0 .../assets/images/searchWhite.svg | 0 .../{ => Cabinet}/assets/images/select.svg | 0 .../assets/images/selectFilterIconOff.svg | 0 .../assets/images/selectFilterIconOn.svg | 0 .../assets/images/selectMaincolor.svg | 0 .../assets/images/selectPurple.svg | 0 .../{ => Cabinet}/assets/images/shareIcon.svg | 0 .../src/{ => Cabinet}/assets/images/slack.svg | 0 .../{ => Cabinet}/assets/images/stairs.svg | 0 .../{ => Cabinet}/assets/images/subtract.svg | 0 frontend/src/Cabinet/assets/images/timer.svg | 3 + .../assets/images/warningTriangleIcon.svg | 0 .../components/AdminInfo/Chart/BarChart.tsx | 0 .../components/AdminInfo/Chart/LineChart.tsx | 2 +- .../components/AdminInfo/Chart/PieChart.tsx | 0 .../components/AdminInfo/Table/AdminTable.tsx | 4 +- .../components/AdminInfo/Table/Pagination.tsx | 0 .../components/Announce/AnnounceTemplate.tsx | 4 +- .../Available/AvailableCountdown.tsx | 4 +- .../components/Available/FloorContainer.tsx | 14 +- .../CabinetInfoArea/AdminCabinetInfoArea.tsx | 24 +- .../CabinetInfoArea.container.tsx | 25 +- .../CabinetInfoArea/CabinetInfoArea.tsx | 36 +- .../CabinetInfoArea/CountTime/CodeAndTime.tsx | 8 +- .../CountTime/CountTime.container.tsx | 13 +- .../CabinetInfoArea/CountTime/CountTime.tsx | 2 +- .../CabinetList/CabinetList.container.tsx | 21 +- .../components/CabinetList/CabinetList.tsx | 15 +- .../CabinetListItem/AdminCabinetListItem.tsx | 19 +- .../CabinetListItem/CabinetListItem.tsx | 16 +- .../CabinetList/EmptySection/EmptySection.tsx | 2 +- .../RealViewNotification.tsx | 2 +- .../{ => Cabinet}/components/Card/Card.tsx | 2 +- .../components/Card/CardStyles.ts | 2 +- .../ClubCabinetInfoCard.tsx | 12 +- .../Card/ClubNoticeCard/ClubNoticeCard.tsx | 8 +- .../ExtensionCard/ExtensionCard.container.tsx | 12 +- .../Card/ExtensionCard/ExtensionCard.tsx | 12 +- .../LentInfoCard/LentInfoCard.container.tsx | 16 +- .../Card/LentInfoCard/LentInfoCard.tsx | 14 +- .../NotificationCard.container.tsx | 12 +- .../NotificationCard/NotificationCard.tsx | 8 +- .../ProfileCard/ProfileCard.container.tsx | 6 +- .../Card/ProfileCard/ProfileCard.tsx | 4 +- .../Card/ThemeColorCard/ColorPicker.tsx | 0 .../ThemeColorCard.container.tsx | 4 +- .../Card/ThemeColorCard/ThemeColorCard.tsx | 10 +- .../Card/ThemeColorCard/colorInfo.ts | 2 +- .../Club/AdminClubLog.container.tsx | 8 +- .../components/Club/AdminClubLog.tsx | 39 +- .../components/Club/ClubInfo.tsx | 20 +- .../components/Club/ClubLogTable.tsx | 8 +- .../ClubMemberInfoArea.container.tsx | 6 +- .../ClubMemberInfoArea/ClubMemberInfoArea.tsx | 24 +- .../ClubMemberList.container.tsx | 11 +- .../Club/ClubMemberList/ClubMemberList.tsx | 18 +- .../ClubMemberListItem/ClubMemberListItem.tsx | 6 +- .../components/Common/Button.tsx | 1 - .../components/Common/ClubListDropdown.tsx | 2 +- .../components/Common/Dropdown.tsx | 15 +- .../components/Common/LoadingAnimation.tsx | 0 .../components/Common/MultiSelectButton.tsx | 0 .../Common/MultiSelectFilterButton.tsx | 4 +- .../components/Common/MultiToggleSwitch.tsx | 2 +- .../Common/MultiToggleSwitchSeparated.tsx | 65 ++- .../components/Common/PillButton.tsx | 0 .../components/Common/Selector.tsx | 2 +- .../components/Common/ToggleSwitch.tsx | 0 .../components/Common/WarningNotification.tsx | 8 +- .../components/Home/ManualContentBox.tsx | 12 +- .../components/Home/ServiceManual.tsx | 6 +- .../CabinetColorTable/CabinetColorTable.tsx | 0 .../LeftMainNav/LeftMainNav.container.tsx | 27 +- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 74 +-- .../components/LeftNav/LeftNav.tsx | 4 +- .../LeftSectionNav.container.tsx | 8 +- .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 6 +- .../LeftSectionNav/LeftSectionNavClubs.tsx | 8 +- .../LentLog/AdminCabinetLentLog.container.tsx | 13 +- .../LentLog/AdminCabinetLentLog.tsx | 14 +- .../components/LentLog/AdminLentLog.tsx | 11 +- .../LentLog/AdminUserLentLog.container.tsx | 14 +- .../components/LentLog/AdminUserLentLog.tsx | 14 +- .../components/LentLog/LentLog.container.tsx | 13 +- .../components/LentLog/LentLog.tsx | 8 +- .../LentLog/LogTable/AdminCabinetLogTable.tsx | 18 +- .../components/LentLog/LogTable/LogTable.tsx | 6 +- .../components/Login/AdminLoginTemplate.tsx | 12 +- .../components/Login/LoginTemplate.tsx | 7 +- .../Login/__tests__/LoginTemplate.test.tsx | 4 +- .../MapInfo/MapFloorSelect/MapFloorSelect.tsx | 6 +- .../MapFloorSelectOption.tsx | 0 .../components/MapInfo/MapGrid/MapGrid.tsx | 6 +- .../components/MapInfo/MapInfo.container.tsx | 11 +- .../components/MapInfo/MapInfo.tsx | 11 +- .../components/MapInfo/MapItem/MapItem.tsx | 12 +- .../components/Modals/BanModal/BanModal.tsx | 32 +- .../Modals/CancelModal/CancelModal.tsx | 34 +- .../AddClubMemberModal.container.tsx | 10 +- .../Modals/ClubModal/AddClubMemberModal.tsx | 2 +- .../ClubModal/ClubMemoModal.container.tsx | 10 +- .../Modals/ClubModal/ClubMemoModal.tsx | 6 +- .../components/Modals/ClubModal/ClubModal.tsx | 26 +- .../ClubModal/ClubPasswordModal.container.tsx | 18 +- .../Modals/ClubModal/ClubPasswordModal.tsx | 6 +- .../ClubModal/DeleteClubMemberModal.tsx | 18 +- .../ClubModal/MandateClubMemberModal.tsx | 12 +- .../Modals/ExtendModal/ExtendModal.tsx | 36 +- .../InvitationCodeModal.container.tsx | 36 +- .../Modals/LentModal/ClubLentModal.tsx | 30 +- .../components/Modals/LentModal/LentModal.tsx | 40 +- .../Modals/ManualModal/ManualModal.tsx | 6 +- .../Modals/MemoModal/MemoModal.container.tsx | 11 +- .../components/Modals/MemoModal/MemoModal.tsx | 6 +- .../{ => Cabinet}/components/Modals/Modal.tsx | 14 +- .../components/Modals/ModalPortal.tsx | 0 .../NotificationModal/NotificationModal.tsx | 10 +- .../OverduePenaltyModal.tsx | 14 +- .../PasswordCheckModal.container.tsx | 38 +- .../PasswordCheckModal/PasswordCheckModal.tsx | 8 +- .../PasswordCheckModal/PasswordContainer.tsx | 0 .../Modals/ResponseModal/ResponseModal.tsx | 12 +- .../Modals/ReturnModal/AdminReturnModal.tsx | 78 +-- .../Modals/ReturnModal/ReturnModal.tsx | 38 +- .../StatusModal/StatusModal.container.tsx | 16 +- .../Modals/StatusModal/StatusModal.tsx | 12 +- .../components/Modals/SwapModal/SwapModal.tsx | 22 +- .../UnavailableModal/UnavailableModal.tsx | 12 +- .../components/Search/NoSearch.tsx | 2 +- .../components/Search/SearchDefault.tsx | 2 +- .../components/Search/SearchItemByIntraId.tsx | 16 +- .../components/Search/SearchItemByNum.tsx | 16 +- .../SectionPagination.container.tsx | 10 +- .../SectionPagination/SectionPagination.tsx | 4 +- .../TopNav/AdminTopNav.container.tsx | 25 +- .../components/TopNav/SearchBar/SearchBar.tsx | 11 +- .../SearchBar/SearchBarList/SearchBarList.tsx | 4 +- .../SearchBar/SearchListItem/ChangeToHTML.tsx | 0 .../SearchListItem/SearchListItem.tsx | 6 +- .../components/TopNav/TopNav.container.tsx | 28 +- .../components/TopNav/TopNav.tsx | 119 +++-- .../TopNavButton/TopNavButton.tsx | 3 +- .../TopNavButtonGroup/TopNavButtonGroup.tsx | 30 +- .../TopNavDomainGroup/TopNavDomainGroup.tsx | 108 ++++ .../UserInfoArea/UserInfoArea.container.tsx | 8 +- .../components/UserInfoArea/UserInfoArea.tsx | 16 +- .../src/{ => Cabinet}/constants/StatusCode.ts | 0 .../firebase/firebase-messaging-sw.ts | 0 .../{ => Cabinet}/hooks/useAdminHomeApi.ts | 6 +- .../hooks/useCabinetListRefresh.ts | 6 +- .../src/{ => Cabinet}/hooks/useClubInfo.ts | 10 +- .../src/{ => Cabinet}/hooks/useDebounce.tsx | 0 .../src/{ => Cabinet}/hooks/useIsMount.ts | 0 frontend/src/{ => Cabinet}/hooks/useMenu.ts | 4 +- .../src/{ => Cabinet}/hooks/useMultiSelect.ts | 9 +- .../{ => Cabinet}/hooks/useOutsideClick.ts | 0 .../src/{ => Cabinet}/pages/AvailablePage.tsx | 23 +- frontend/src/{ => Cabinet}/pages/ClubPage.tsx | 10 +- frontend/src/{ => Cabinet}/pages/HomePage.tsx | 8 +- frontend/src/{ => Cabinet}/pages/Layout.tsx | 61 ++- frontend/src/{ => Cabinet}/pages/LogPage.tsx | 10 +- .../{ => Cabinet}/pages/LoginFailurePage.tsx | 2 +- .../src/{ => Cabinet}/pages/LoginPage.tsx | 6 +- frontend/src/{ => Cabinet}/pages/MainPage.tsx | 16 +- .../src/{ => Cabinet}/pages/NotFoundPage.tsx | 2 +- .../src/{ => Cabinet}/pages/PostLogin.tsx | 15 +- .../src/{ => Cabinet}/pages/ProfilePage.tsx | 23 +- .../pages/admin/AdminClubPage.tsx | 12 +- .../pages/admin/AdminHomePage.tsx | 20 +- .../{ => Cabinet}/pages/admin/AdminLayout.tsx | 23 +- .../pages/admin/AdminLoginFailurePage.tsx | 2 +- .../pages/admin/AdminLoginPage.tsx | 6 +- .../pages/admin/AdminMainPage.tsx | 23 +- .../pages/admin/AdminPagination.tsx | 0 .../{ => Cabinet}/pages/admin/SearchPage.tsx | 18 +- frontend/src/{ => Cabinet}/recoil/atoms.ts | 14 +- .../src/{ => Cabinet}/recoil/selectors.ts | 9 +- .../src/{ => Cabinet}/types/dto/admin.dto.ts | 0 .../src/{ => Cabinet}/types/dto/alarm.dto.ts | 0 .../{ => Cabinet}/types/dto/cabinet.dto.ts | 6 +- .../src/{ => Cabinet}/types/dto/club.dto.ts | 2 +- .../src/{ => Cabinet}/types/dto/lent.dto.ts | 2 +- .../src/{ => Cabinet}/types/dto/user.dto.ts | 6 +- .../types/enum/AnnounceType.enum.ts | 0 .../types/enum/cabinet.status.enum.ts | 0 .../types/enum/cabinet.type.enum.ts | 0 .../types/enum/color.type.enum.ts | 0 .../types/enum/content.status.enum.ts | 0 .../types/enum/icon.type.enum.ts | 0 .../{ => Cabinet}/types/enum/map.type.enum.ts | 0 .../src/{ => Cabinet}/types/enum/time.enum.ts | 0 frontend/src/{ => Cabinet}/utils/dateUtils.ts | 69 ++- frontend/src/Cabinet/utils/paginationUtils.ts | 3 + .../{ => Cabinet}/utils/recoilPersistUtils.ts | 0 .../src/{ => Cabinet}/utils/tableUtils.ts | 2 +- .../Presentation/api/axios/axios.custom.ts | 116 ++++ frontend/src/Presentation/assets/data/maps.ts | 61 +++ .../src/Presentation/assets/images/logo.svg | 10 + .../Details/DetailContent.container.tsx | 172 ++++++ .../components/Details/DetailContent.tsx | 115 ++++ .../DetailTable/DetailTable.container.tsx | 113 ++++ .../Details/DetailTable/DetailTable.tsx | 79 +++ .../DetailTable/DetailTableBodyItem.tsx | 113 ++++ .../DetailTableBodyItemBottomTr.tsx | 72 +++ .../DetailTableBodyItemMiddleTr.tsx | 100 ++++ .../DetailTable/DetailTableBodyItemTopTr.tsx | 218 ++++++++ .../Details/DetailTable/DetailTableHead.tsx | 76 +++ .../Details/DetailTable/NoEventTableRow.tsx | 96 ++++ .../Home/PresentationCard.container.tsx | 107 ++++ .../components/Home/PresentationCard.tsx | 156 ++++++ .../Home/PresentationCardMobile.tsx | 225 ++++++++ .../components/Home/RecentPresentation.tsx | 147 ++++++ .../LeftMainNav/LeftMainNav.container.tsx | 76 +++ .../LeftNav/LeftMainNav/LeftMainNav.tsx | 210 ++++++++ .../components/LeftNav/LeftNav.tsx | 20 + .../EditStatusModal/EditStatusModal.tsx | 297 +++++++++++ .../Modals/RegisterModal/RegisterModal.tsx | 108 ++++ .../components/PresentationLog/LogTable.tsx | 126 +++++ .../components/Register/DropdownDateMenu.tsx | 204 +++++++ .../components/Register/DropdownTimeMenu.tsx | 202 +++++++ .../components/Register/InputField.tsx | 112 ++++ .../TopNav/AdminTopNav.container.tsx | 14 + .../components/TopNav/TopNav.container.tsx | 37 ++ .../Presentation/components/TopNav/TopNav.tsx | 88 +++ .../Presentation/constants/dayOfTheWeek.ts | 7 + frontend/src/Presentation/constants/policy.ts | 11 + .../src/Presentation/hooks/useClickOutside.ts | 22 + frontend/src/Presentation/hooks/useInput.ts | 38 ++ .../src/Presentation/hooks/useInvalidDates.ts | 22 + .../src/Presentation/hooks/useIsMobile.ts | 18 + .../src/Presentation/pages/DetailPage.tsx | 7 + .../src/Presentation/pages/DropdownMenu.tsx | 113 ++++ frontend/src/Presentation/pages/HomePage.tsx | 13 + frontend/src/Presentation/pages/Layout.tsx | 95 ++++ frontend/src/Presentation/pages/LogPage.tsx | 70 +++ .../src/Presentation/pages/RegisterPage.tsx | 499 ++++++++++++++++++ .../Presentation/pages/admin/AdminLayout.tsx | 77 +++ frontend/src/Presentation/recoil/atoms.ts | 15 + .../types/dto/presentation.dto.ts | 56 ++ .../types/enum/presentation.type.enum.ts | 28 + frontend/src/Presentation/utils/dateUtils.ts | 167 ++++++ .../src/assets/images/dropdownChevron.svg | 3 - frontend/src/assets/images/sadCcabi.svg | 9 - frontend/src/components/Club/ClubList.tsx | 56 -- frontend/src/index.css | 7 + frontend/src/main.tsx | 15 +- 343 files changed, 7414 insertions(+), 1174 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/admin/presentation/controller/AdminPresentationController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/InvalidDateResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormData.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormRequestDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormResponseDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationMainData.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPageDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPagePaginationDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/dto/PresentationUpdateDto.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/mapper/PresentationMapper.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/Category.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationLocation.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationStatus.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationTime.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/repository/PresentationRepository.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationPolicyService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationQueryService.java create mode 100644 backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java rename frontend/src/{ => Cabinet}/api/axios/axios.custom.ts (97%) rename frontend/src/{ => Cabinet}/api/axios/axios.instance.ts (88%) rename frontend/src/{ => Cabinet}/api/react_cookie/cookies.ts (100%) rename frontend/src/{ => Cabinet}/assets/css/homePage.css (100%) rename frontend/src/{ => Cabinet}/assets/css/loginPage.css (100%) rename frontend/src/{ => Cabinet}/assets/css/media.css (92%) rename frontend/src/{ => Cabinet}/assets/css/reset.css (100%) rename frontend/src/{ => Cabinet}/assets/data/ManualContent.ts (94%) rename frontend/src/{ => Cabinet}/assets/data/mapPositionData.ts (98%) rename frontend/src/{ => Cabinet}/assets/data/maps.ts (94%) rename frontend/src/{ => Cabinet}/assets/data/sectionColNumData.ts (100%) rename frontend/src/{ => Cabinet}/assets/images/LeftSectionButton.svg (100%) create mode 100644 frontend/src/Cabinet/assets/images/PresentationAcademic.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationDevelop.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationEmpty.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationEtc.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationFortyTwo.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationHobby.svg create mode 100644 frontend/src/Cabinet/assets/images/PresentationJob.svg rename frontend/src/{ => Cabinet}/assets/images/adminLoginImg.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/alarm.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/cabinet.svg (100%) create mode 100644 frontend/src/Cabinet/assets/images/calendar.svg rename frontend/src/{ => Cabinet}/assets/images/cautionSign.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/checkIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/clock.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/close-circle.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/close-square.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/clubIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/clubIconGray.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/crown.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/desktopLogo.png (100%) create mode 100644 frontend/src/Cabinet/assets/images/dropdownChevron.svg rename frontend/src/{ => Cabinet}/assets/images/edit.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/errorIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/exitButton.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/extension.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/extensionTicket.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/happyCcabi.png (100%) create mode 100644 frontend/src/Cabinet/assets/images/happyCcabi.svg rename frontend/src/{ => Cabinet}/assets/images/happyCcabiWhite.png (100%) rename frontend/src/{ => Cabinet}/assets/images/leader.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/link.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/lock.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/log.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/loginImg.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/logo.ico (100%) rename frontend/src/{ => Cabinet}/assets/images/logo.png (100%) rename frontend/src/{ => Cabinet}/assets/images/logo.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/logoBlack.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/manualPeople.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/map.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/more.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/moveButton.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/myCabinetIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/notificationSign.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/notificationSign_grey.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/privateIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/proceedMultiSelect.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/profile-circle.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/refresh.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/rotateRight.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/sadCcabi.png (100%) create mode 100644 frontend/src/Cabinet/assets/images/sadCcabi.svg rename frontend/src/{ => Cabinet}/assets/images/sadCcabiWhite.png (100%) rename frontend/src/{ => Cabinet}/assets/images/search.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/searchWhite.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/select.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/selectFilterIconOff.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/selectFilterIconOn.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/selectMaincolor.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/selectPurple.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/shareIcon.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/slack.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/stairs.svg (100%) rename frontend/src/{ => Cabinet}/assets/images/subtract.svg (100%) create mode 100644 frontend/src/Cabinet/assets/images/timer.svg rename frontend/src/{ => Cabinet}/assets/images/warningTriangleIcon.svg (100%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Chart/BarChart.tsx (100%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Chart/LineChart.tsx (97%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Chart/PieChart.tsx (100%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Table/AdminTable.tsx (95%) rename frontend/src/{ => Cabinet}/components/AdminInfo/Table/Pagination.tsx (100%) rename frontend/src/{ => Cabinet}/components/Announce/AnnounceTemplate.tsx (94%) rename frontend/src/{ => Cabinet}/components/Available/AvailableCountdown.tsx (94%) rename frontend/src/{ => Cabinet}/components/Available/FloorContainer.tsx (84%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/AdminCabinetInfoArea.tsx (92%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CabinetInfoArea.container.tsx (93%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CabinetInfoArea.tsx (89%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CountTime/CodeAndTime.tsx (93%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CountTime/CountTime.container.tsx (88%) rename frontend/src/{ => Cabinet}/components/CabinetInfoArea/CountTime/CountTime.tsx (94%) rename frontend/src/{ => Cabinet}/components/CabinetList/CabinetList.container.tsx (69%) rename frontend/src/{ => Cabinet}/components/CabinetList/CabinetList.tsx (74%) rename frontend/src/{ => Cabinet}/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx (93%) rename frontend/src/{ => Cabinet}/components/CabinetList/CabinetListItem/CabinetListItem.tsx (95%) rename frontend/src/{ => Cabinet}/components/CabinetList/EmptySection/EmptySection.tsx (91%) rename frontend/src/{ => Cabinet}/components/CabinetList/RealViewNotification/RealViewNotification.tsx (97%) rename frontend/src/{ => Cabinet}/components/Card/Card.tsx (97%) rename frontend/src/{ => Cabinet}/components/Card/CardStyles.ts (93%) rename frontend/src/{ => Cabinet}/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx (91%) rename frontend/src/{ => Cabinet}/components/Card/ClubNoticeCard/ClubNoticeCard.tsx (87%) rename frontend/src/{ => Cabinet}/components/Card/ExtensionCard/ExtensionCard.container.tsx (85%) rename frontend/src/{ => Cabinet}/components/Card/ExtensionCard/ExtensionCard.tsx (84%) rename frontend/src/{ => Cabinet}/components/Card/LentInfoCard/LentInfoCard.container.tsx (82%) rename frontend/src/{ => Cabinet}/components/Card/LentInfoCard/LentInfoCard.tsx (92%) rename frontend/src/{ => Cabinet}/components/Card/NotificationCard/NotificationCard.container.tsx (90%) rename frontend/src/{ => Cabinet}/components/Card/NotificationCard/NotificationCard.tsx (82%) rename frontend/src/{ => Cabinet}/components/Card/ProfileCard/ProfileCard.container.tsx (86%) rename frontend/src/{ => Cabinet}/components/Card/ProfileCard/ProfileCard.tsx (91%) rename frontend/src/{ => Cabinet}/components/Card/ThemeColorCard/ColorPicker.tsx (100%) rename frontend/src/{ => Cabinet}/components/Card/ThemeColorCard/ThemeColorCard.container.tsx (95%) rename frontend/src/{ => Cabinet}/components/Card/ThemeColorCard/ThemeColorCard.tsx (93%) rename frontend/src/{ => Cabinet}/components/Card/ThemeColorCard/colorInfo.ts (91%) rename frontend/src/{ => Cabinet}/components/Club/AdminClubLog.container.tsx (85%) rename frontend/src/{ => Cabinet}/components/Club/AdminClubLog.tsx (75%) rename frontend/src/{ => Cabinet}/components/Club/ClubInfo.tsx (78%) rename frontend/src/{ => Cabinet}/components/Club/ClubLogTable.tsx (89%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx (89%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx (88%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberList/ClubMemberList.container.tsx (90%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberList/ClubMemberList.tsx (88%) rename frontend/src/{ => Cabinet}/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx (90%) rename frontend/src/{ => Cabinet}/components/Common/Button.tsx (98%) rename frontend/src/{ => Cabinet}/components/Common/ClubListDropdown.tsx (97%) rename frontend/src/{ => Cabinet}/components/Common/Dropdown.tsx (92%) rename frontend/src/{ => Cabinet}/components/Common/LoadingAnimation.tsx (100%) rename frontend/src/{ => Cabinet}/components/Common/MultiSelectButton.tsx (100%) rename frontend/src/{ => Cabinet}/components/Common/MultiSelectFilterButton.tsx (90%) rename frontend/src/{ => Cabinet}/components/Common/MultiToggleSwitch.tsx (98%) rename frontend/src/{ => Cabinet}/components/Common/MultiToggleSwitchSeparated.tsx (55%) rename frontend/src/{ => Cabinet}/components/Common/PillButton.tsx (100%) rename frontend/src/{ => Cabinet}/components/Common/Selector.tsx (93%) rename frontend/src/{ => Cabinet}/components/Common/ToggleSwitch.tsx (100%) rename frontend/src/{ => Cabinet}/components/Common/WarningNotification.tsx (91%) rename frontend/src/{ => Cabinet}/components/Home/ManualContentBox.tsx (90%) rename frontend/src/{ => Cabinet}/components/Home/ServiceManual.tsx (95%) rename frontend/src/{ => Cabinet}/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx (100%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx (88%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftMainNav/LeftMainNav.tsx (77%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftNav.tsx (66%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftSectionNav/LeftSectionNav.container.tsx (86%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx (93%) rename frontend/src/{ => Cabinet}/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx (86%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminCabinetLentLog.container.tsx (79%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminCabinetLentLog.tsx (84%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminLentLog.tsx (87%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminUserLentLog.container.tsx (79%) rename frontend/src/{ => Cabinet}/components/LentLog/AdminUserLentLog.tsx (85%) rename frontend/src/{ => Cabinet}/components/LentLog/LentLog.container.tsx (74%) rename frontend/src/{ => Cabinet}/components/LentLog/LentLog.tsx (93%) rename frontend/src/{ => Cabinet}/components/LentLog/LogTable/AdminCabinetLogTable.tsx (84%) rename frontend/src/{ => Cabinet}/components/LentLog/LogTable/LogTable.tsx (92%) rename frontend/src/{ => Cabinet}/components/Login/AdminLoginTemplate.tsx (93%) rename frontend/src/{ => Cabinet}/components/Login/LoginTemplate.tsx (93%) rename frontend/src/{ => Cabinet}/components/Login/__tests__/LoginTemplate.test.tsx (82%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx (84%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx (100%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapGrid/MapGrid.tsx (86%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapInfo.container.tsx (86%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapInfo.tsx (83%) rename frontend/src/{ => Cabinet}/components/MapInfo/MapItem/MapItem.tsx (85%) rename frontend/src/{ => Cabinet}/components/Modals/BanModal/BanModal.tsx (85%) rename frontend/src/{ => Cabinet}/components/Modals/CancelModal/CancelModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/AddClubMemberModal.container.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/AddClubMemberModal.tsx (98%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubMemoModal.container.tsx (91%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubMemoModal.tsx (97%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubModal.tsx (95%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubPasswordModal.container.tsx (88%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/ClubPasswordModal.tsx (93%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/DeleteClubMemberModal.tsx (80%) rename frontend/src/{ => Cabinet}/components/Modals/ClubModal/MandateClubMemberModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/ExtendModal/ExtendModal.tsx (88%) rename frontend/src/{ => Cabinet}/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx (85%) rename frontend/src/{ => Cabinet}/components/Modals/LentModal/ClubLentModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/LentModal/LentModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/ManualModal/ManualModal.tsx (96%) rename frontend/src/{ => Cabinet}/components/Modals/MemoModal/MemoModal.container.tsx (89%) rename frontend/src/{ => Cabinet}/components/Modals/MemoModal/MemoModal.tsx (97%) rename frontend/src/{ => Cabinet}/components/Modals/Modal.tsx (91%) rename frontend/src/{ => Cabinet}/components/Modals/ModalPortal.tsx (100%) rename frontend/src/{ => Cabinet}/components/Modals/NotificationModal/NotificationModal.tsx (57%) rename frontend/src/{ => Cabinet}/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx (79%) rename frontend/src/{ => Cabinet}/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx (83%) rename frontend/src/{ => Cabinet}/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx (93%) rename frontend/src/{ => Cabinet}/components/Modals/PasswordCheckModal/PasswordContainer.tsx (100%) rename frontend/src/{ => Cabinet}/components/Modals/ResponseModal/ResponseModal.tsx (74%) rename frontend/src/{ => Cabinet}/components/Modals/ReturnModal/AdminReturnModal.tsx (81%) rename frontend/src/{ => Cabinet}/components/Modals/ReturnModal/ReturnModal.tsx (86%) rename frontend/src/{ => Cabinet}/components/Modals/StatusModal/StatusModal.container.tsx (90%) rename frontend/src/{ => Cabinet}/components/Modals/StatusModal/StatusModal.tsx (95%) rename frontend/src/{ => Cabinet}/components/Modals/SwapModal/SwapModal.tsx (93%) rename frontend/src/{ => Cabinet}/components/Modals/UnavailableModal/UnavailableModal.tsx (56%) rename frontend/src/{ => Cabinet}/components/Search/NoSearch.tsx (88%) rename frontend/src/{ => Cabinet}/components/Search/SearchDefault.tsx (91%) rename frontend/src/{ => Cabinet}/components/Search/SearchItemByIntraId.tsx (92%) rename frontend/src/{ => Cabinet}/components/Search/SearchItemByNum.tsx (89%) rename frontend/src/{ => Cabinet}/components/SectionPagination/SectionPagination.container.tsx (91%) rename frontend/src/{ => Cabinet}/components/SectionPagination/SectionPagination.tsx (94%) rename frontend/src/{ => Cabinet}/components/TopNav/AdminTopNav.container.tsx (71%) rename frontend/src/{ => Cabinet}/components/TopNav/SearchBar/SearchBar.tsx (95%) rename frontend/src/{ => Cabinet}/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx (91%) rename frontend/src/{ => Cabinet}/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx (100%) rename frontend/src/{ => Cabinet}/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx (88%) rename frontend/src/{ => Cabinet}/components/TopNav/TopNav.container.tsx (74%) rename frontend/src/{ => Cabinet}/components/TopNav/TopNav.tsx (57%) rename frontend/src/{ => Cabinet}/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx (87%) rename frontend/src/{ => Cabinet}/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx (80%) create mode 100644 frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx rename frontend/src/{ => Cabinet}/components/UserInfoArea/UserInfoArea.container.tsx (75%) rename frontend/src/{ => Cabinet}/components/UserInfoArea/UserInfoArea.tsx (89%) rename frontend/src/{ => Cabinet}/constants/StatusCode.ts (100%) rename frontend/src/{ => Cabinet}/firebase/firebase-messaging-sw.ts (100%) rename frontend/src/{ => Cabinet}/hooks/useAdminHomeApi.ts (93%) rename frontend/src/{ => Cabinet}/hooks/useCabinetListRefresh.ts (95%) rename frontend/src/{ => Cabinet}/hooks/useClubInfo.ts (90%) rename frontend/src/{ => Cabinet}/hooks/useDebounce.tsx (100%) rename frontend/src/{ => Cabinet}/hooks/useIsMount.ts (100%) rename frontend/src/{ => Cabinet}/hooks/useMenu.ts (99%) rename frontend/src/{ => Cabinet}/hooks/useMultiSelect.ts (94%) rename frontend/src/{ => Cabinet}/hooks/useOutsideClick.ts (100%) rename frontend/src/{ => Cabinet}/pages/AvailablePage.tsx (88%) rename frontend/src/{ => Cabinet}/pages/ClubPage.tsx (79%) rename frontend/src/{ => Cabinet}/pages/HomePage.tsx (67%) rename frontend/src/{ => Cabinet}/pages/Layout.tsx (81%) rename frontend/src/{ => Cabinet}/pages/LogPage.tsx (80%) rename frontend/src/{ => Cabinet}/pages/LoginFailurePage.tsx (84%) rename frontend/src/{ => Cabinet}/pages/LoginPage.tsx (66%) rename frontend/src/{ => Cabinet}/pages/MainPage.tsx (88%) rename frontend/src/{ => Cabinet}/pages/NotFoundPage.tsx (86%) rename frontend/src/{ => Cabinet}/pages/PostLogin.tsx (79%) rename frontend/src/{ => Cabinet}/pages/ProfilePage.tsx (70%) rename frontend/src/{ => Cabinet}/pages/admin/AdminClubPage.tsx (86%) rename frontend/src/{ => Cabinet}/pages/admin/AdminHomePage.tsx (90%) rename frontend/src/{ => Cabinet}/pages/admin/AdminLayout.tsx (82%) rename frontend/src/{ => Cabinet}/pages/admin/AdminLoginFailurePage.tsx (85%) rename frontend/src/{ => Cabinet}/pages/admin/AdminLoginPage.tsx (58%) rename frontend/src/{ => Cabinet}/pages/admin/AdminMainPage.tsx (86%) rename frontend/src/{ => Cabinet}/pages/admin/AdminPagination.tsx (100%) rename frontend/src/{ => Cabinet}/pages/admin/SearchPage.tsx (90%) rename frontend/src/{ => Cabinet}/recoil/atoms.ts (91%) rename frontend/src/{ => Cabinet}/recoil/selectors.ts (96%) rename frontend/src/{ => Cabinet}/types/dto/admin.dto.ts (100%) rename frontend/src/{ => Cabinet}/types/dto/alarm.dto.ts (100%) rename frontend/src/{ => Cabinet}/types/dto/cabinet.dto.ts (87%) rename frontend/src/{ => Cabinet}/types/dto/club.dto.ts (92%) rename frontend/src/{ => Cabinet}/types/dto/lent.dto.ts (95%) rename frontend/src/{ => Cabinet}/types/dto/user.dto.ts (84%) rename frontend/src/{ => Cabinet}/types/enum/AnnounceType.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/cabinet.status.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/cabinet.type.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/color.type.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/content.status.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/icon.type.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/map.type.enum.ts (100%) rename frontend/src/{ => Cabinet}/types/enum/time.enum.ts (100%) rename frontend/src/{ => Cabinet}/utils/dateUtils.ts (51%) create mode 100644 frontend/src/Cabinet/utils/paginationUtils.ts rename frontend/src/{ => Cabinet}/utils/recoilPersistUtils.ts (100%) rename frontend/src/{ => Cabinet}/utils/tableUtils.ts (97%) create mode 100644 frontend/src/Presentation/api/axios/axios.custom.ts create mode 100644 frontend/src/Presentation/assets/data/maps.ts create mode 100644 frontend/src/Presentation/assets/images/logo.svg create mode 100644 frontend/src/Presentation/components/Details/DetailContent.container.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailContent.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTable.container.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTable.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItem.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/DetailTableHead.tsx create mode 100644 frontend/src/Presentation/components/Details/DetailTable/NoEventTableRow.tsx create mode 100644 frontend/src/Presentation/components/Home/PresentationCard.container.tsx create mode 100644 frontend/src/Presentation/components/Home/PresentationCard.tsx create mode 100644 frontend/src/Presentation/components/Home/PresentationCardMobile.tsx create mode 100644 frontend/src/Presentation/components/Home/RecentPresentation.tsx create mode 100644 frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx create mode 100644 frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.tsx create mode 100644 frontend/src/Presentation/components/LeftNav/LeftNav.tsx create mode 100644 frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx create mode 100644 frontend/src/Presentation/components/Modals/RegisterModal/RegisterModal.tsx create mode 100644 frontend/src/Presentation/components/PresentationLog/LogTable.tsx create mode 100644 frontend/src/Presentation/components/Register/DropdownDateMenu.tsx create mode 100644 frontend/src/Presentation/components/Register/DropdownTimeMenu.tsx create mode 100644 frontend/src/Presentation/components/Register/InputField.tsx create mode 100644 frontend/src/Presentation/components/TopNav/AdminTopNav.container.tsx create mode 100644 frontend/src/Presentation/components/TopNav/TopNav.container.tsx create mode 100644 frontend/src/Presentation/components/TopNav/TopNav.tsx create mode 100644 frontend/src/Presentation/constants/dayOfTheWeek.ts create mode 100644 frontend/src/Presentation/constants/policy.ts create mode 100644 frontend/src/Presentation/hooks/useClickOutside.ts create mode 100644 frontend/src/Presentation/hooks/useInput.ts create mode 100644 frontend/src/Presentation/hooks/useInvalidDates.ts create mode 100644 frontend/src/Presentation/hooks/useIsMobile.ts create mode 100644 frontend/src/Presentation/pages/DetailPage.tsx create mode 100644 frontend/src/Presentation/pages/DropdownMenu.tsx create mode 100644 frontend/src/Presentation/pages/HomePage.tsx create mode 100644 frontend/src/Presentation/pages/Layout.tsx create mode 100644 frontend/src/Presentation/pages/LogPage.tsx create mode 100644 frontend/src/Presentation/pages/RegisterPage.tsx create mode 100644 frontend/src/Presentation/pages/admin/AdminLayout.tsx create mode 100644 frontend/src/Presentation/recoil/atoms.ts create mode 100644 frontend/src/Presentation/types/dto/presentation.dto.ts create mode 100644 frontend/src/Presentation/types/enum/presentation.type.enum.ts create mode 100644 frontend/src/Presentation/utils/dateUtils.ts delete mode 100644 frontend/src/assets/images/dropdownChevron.svg delete mode 100644 frontend/src/assets/images/sadCcabi.svg delete mode 100644 frontend/src/components/Club/ClubList.tsx diff --git a/.github/workflows/front-cicd.yaml b/.github/workflows/front-cicd.yaml index 96ed63ba7..7c25c902b 100644 --- a/.github/workflows/front-cicd.yaml +++ b/.github/workflows/front-cicd.yaml @@ -62,8 +62,10 @@ jobs: if: ${{ github.ref == 'refs/heads/dev' }} run: | cd frontend - mkdir -p dist/src/assets - cp -r src/assets/images dist/src/assets + mkdir -p dist/src/Cabinet/assets + mkdir -p dist/src/Presentation/assets + cp -r src/Cabinet/assets/images dist/src/Cabinet/assets + cp -r src/Presentation/assets/images dist/src/Presentation/assets aws s3 sync ./dist s3://dev.cabi aws cloudfront create-invalidation --distribution-id EWPTW52IH5L5C --paths '/*' @@ -71,7 +73,9 @@ jobs: if: ${{ github.ref == 'refs/heads/main' }} run: | cd frontend - mkdir -p dist/src/assets - cp -r src/assets/images dist/src/assets + mkdir -p dist/src/Cabinet/assets + mkdir -p dist/src/Presentation/assets + cp -r src/Cabinet/assets/images dist/src/Cabinet/assets + cp -r src/Presentation/assets/images dist/src/Presentation/assets aws s3 sync ./dist s3://42cabi aws cloudfront create-invalidation --distribution-id E12WMB9HCNB1DT --paths '/*' diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/presentation/controller/AdminPresentationController.java b/backend/src/main/java/org/ftclub/cabinet/admin/presentation/controller/AdminPresentationController.java new file mode 100644 index 000000000..7b94a0e95 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/presentation/controller/AdminPresentationController.java @@ -0,0 +1,44 @@ +package org.ftclub.cabinet.admin.presentation.controller; + +import static org.ftclub.cabinet.auth.domain.AuthLevel.ADMIN_ONLY; + +import java.time.YearMonth; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.dto.PresentationFormResponseDto; +import org.ftclub.cabinet.dto.PresentationUpdateDto; +import org.ftclub.cabinet.presentation.service.PresentationService; +import org.springframework.format.annotation.DateTimeFormat; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("/v5/admin/presentation") +@RequiredArgsConstructor +public class AdminPresentationController { + + private final PresentationService presentationService; + + @PatchMapping("/{formId}/update") + @AuthGuard(level = ADMIN_ONLY) + public void updatePresentationByFormId( + @PathVariable("formId") Long formId, + @Valid @RequestBody PresentationUpdateDto dto) { + presentationService.updatePresentationByFormId(formId, dto); + } + + @GetMapping("/schedule") + @AuthGuard(level = ADMIN_ONLY) + public PresentationFormResponseDto adminSchedulePage(@RequestParam(value = "yearMonth") + @DateTimeFormat(pattern = "yyyy-MM") + YearMonth yearMonth) { + return presentationService.getAdminPresentationSchedule(yearMonth); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenValidator.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenValidator.java index 89aa4e9c5..3ad35179f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenValidator.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/TokenValidator.java @@ -96,6 +96,8 @@ public boolean isTokenValid(String token, Key key) { } catch (IllegalArgumentException e) { log.info("JWT 토큰이 잘못되었습니다."); } catch (Exception e) { + log.error("token error = {}", e.toString()); + log.error("token error message = {}", e.getMessage()); log.info("JWT 토큰 검사 중 알 수 없는 오류가 발생했습니다."); } return false; diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/InvalidDateResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/InvalidDateResponseDto.java new file mode 100644 index 000000000..f0621ef39 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/InvalidDateResponseDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + + +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class InvalidDateResponseDto { + + private List invalidDateList; + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormData.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormData.java new file mode 100644 index 000000000..f021780a5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormData.java @@ -0,0 +1,24 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import lombok.Data; +import org.ftclub.cabinet.presentation.domain.Category; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; +import org.ftclub.cabinet.presentation.domain.PresentationTime; + +@Data +public class PresentationFormData { + + private final Long id; + private final PresentationStatus presentationStatus; + private final PresentationTime presentationTime; + private final PresentationLocation presentationLocation; + private final String subject; + private final String summary; + private final String detail; + private final Category category; + private final LocalDateTime dateTime; + private final String userName; + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormRequestDto.java new file mode 100644 index 000000000..f8d1fd22a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormRequestDto.java @@ -0,0 +1,33 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import javax.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.ftclub.cabinet.presentation.domain.Category; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationTime; +import org.hibernate.validator.constraints.Length; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@ToString +public class PresentationFormRequestDto { + + private Category category; + private PresentationTime presentationTime; + private PresentationLocation presentationLocation; + private LocalDateTime dateTime; + @NotBlank + @Length(min = 1, max = 25) + private String subject; + @NotBlank + @Length(min = 1, max = 40) + private String summary; + @NotBlank + @Length(min = 1, max = 500) + private String detail; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormResponseDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormResponseDto.java new file mode 100644 index 000000000..dea3f5ed8 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationFormResponseDto.java @@ -0,0 +1,10 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class PresentationFormResponseDto { + + private final List forms; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMainData.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMainData.java new file mode 100644 index 000000000..3be344329 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMainData.java @@ -0,0 +1,11 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class PresentationMainData { + + private final List past; + private final List upcoming; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPageDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPageDto.java new file mode 100644 index 000000000..60d69f0e6 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPageDto.java @@ -0,0 +1,19 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import lombok.Data; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; + +/** + * 날짜, 제목, 상태, 장소 + */ +@Data +public class PresentationMyPageDto { + + private final Integer id; + private final String subject; + private final LocalDateTime dateTime; + private final PresentationStatus presentationStatus; + private final PresentationLocation presentationLocation; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPagePaginationDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPagePaginationDto.java new file mode 100644 index 000000000..b6e391acd --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationMyPagePaginationDto.java @@ -0,0 +1,11 @@ +package org.ftclub.cabinet.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class PresentationMyPagePaginationDto { + + private final List result; + private final Long totalLength; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/PresentationUpdateDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationUpdateDto.java new file mode 100644 index 000000000..9dc671dfc --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/dto/PresentationUpdateDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.dto; + +import java.time.LocalDateTime; +import javax.validation.constraints.NotEmpty; +import lombok.Data; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; + +@Data +public class PresentationUpdateDto { + + private final LocalDateTime dateTime; + private final PresentationStatus status; + private final PresentationLocation location; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index 6cf1251a9..26f2fa8ce 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -70,6 +70,12 @@ public enum ExceptionStatus { NOT_CLUB_MASTER(HttpStatus.BAD_REQUEST, "동아리 장이 아닙니다."), INVALID_CLUB_MASTER(HttpStatus.BAD_REQUEST, "동아리에 동아리 장이 없습니다."), NOT_FOUND_CLUB_LENT_HISTORY(HttpStatus.NOT_FOUND, "동아리가 대여한 사물함이 없습니다."), + INVALID_PRESENTATION_CATEGORY(HttpStatus.BAD_REQUEST, "발표회에 정의된 카테고리가 아닙니다."), + INVALID_DATE(HttpStatus.BAD_REQUEST, "잘못된 날짜입니다."), + PRESENTATION_ALREADY_EXISTED(HttpStatus.CONFLICT, "이미 예약된 발표 날짜입니다"), + NOT_FOUND_FORM(HttpStatus.NOT_FOUND, "신청서가 존재하지 않습니다."), + INVALID_FORM_ID(HttpStatus.BAD_REQUEST, "잘못된 신청번호입니다."), + INVALID_LOCATION(HttpStatus.BAD_REQUEST, "잘못된 장소입니다."), ; final private int statusCode; diff --git a/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java b/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java index aef5737a9..7157942d1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java +++ b/backend/src/main/java/org/ftclub/cabinet/log/AllRequestLogInterceptor.java @@ -78,7 +78,7 @@ private String getUserId(HttpServletRequest request) { throw ExceptionStatus.JSON_PROCESSING_EXCEPTION.asControllerException(); } - if (payloadJson == null || isValidPayLoadName(payloadJson)) { + if (payloadJson == null || !isValidPayLoadName(payloadJson)) { String uuid = UUID.randomUUID().toString(); return uuid.substring(uuid.length() - 12); } @@ -92,6 +92,6 @@ private boolean isValidPayLoadName(JsonNode payloadJson) { if (payloadJson.get(PAYLOAD_NAME) == null) { return false; } - return payloadJson.get(PAYLOAD_NAME).asText().isEmpty(); + return !payloadJson.get(PAYLOAD_NAME).asText().isEmpty(); } } \ No newline at end of file diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/PresentationMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/PresentationMapper.java new file mode 100644 index 000000000..7d36f41b5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/PresentationMapper.java @@ -0,0 +1,23 @@ +package org.ftclub.cabinet.mapper; + +import java.util.List; +import org.ftclub.cabinet.dto.PresentationFormData; +import org.ftclub.cabinet.dto.PresentationMainData; +import org.ftclub.cabinet.dto.PresentationMyPageDto; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface PresentationMapper { + + @Mapping(target = "userName", source = "user.name") + PresentationFormData toPresentationFormDataDto(Presentation presentation); + + + PresentationMyPageDto toPresentationMyPageDto(Presentation presentation); + + PresentationMainData toPresentationMainData(List past, + List upcoming); + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java b/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java new file mode 100644 index 000000000..971665968 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/controller/PresentationController.java @@ -0,0 +1,79 @@ +package org.ftclub.cabinet.presentation.controller; + + +import java.time.YearMonth; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.auth.domain.AuthGuard; +import org.ftclub.cabinet.auth.domain.AuthLevel; +import org.ftclub.cabinet.dto.InvalidDateResponseDto; +import org.ftclub.cabinet.dto.PresentationFormRequestDto; +import org.ftclub.cabinet.dto.PresentationFormResponseDto; +import org.ftclub.cabinet.dto.PresentationMainData; +import org.ftclub.cabinet.dto.PresentationMyPagePaginationDto; +import org.ftclub.cabinet.dto.UserSessionDto; +import org.ftclub.cabinet.presentation.service.PresentationService; +import org.ftclub.cabinet.user.domain.UserSession; +import org.springframework.data.domain.Pageable; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("/v5/presentation") +@RequiredArgsConstructor +public class PresentationController { + + private final PresentationService presentationService; + + @PostMapping("/form") + @AuthGuard(level = AuthLevel.USER_ONLY) + public void createPresentationForm( + @UserSession UserSessionDto user, + @Valid @RequestBody PresentationFormRequestDto dto) { + presentationService.createPresentationFrom(user.getUserId(), dto); + } + + @GetMapping("/form/invalid-date") + public InvalidDateResponseDto getInvalidDate() { + return presentationService.getInvalidDate(); + } + + @GetMapping("") + @AuthGuard(level = AuthLevel.USER_ONLY) + public PresentationMainData getMainData( + @RequestParam(value = "pastFormCount") Integer pastFormCount, + @RequestParam(value = "upcomingFormCount") Integer upcomingFormCount) { + return presentationService.getPastAndUpcomingPresentations(pastFormCount, + upcomingFormCount); + } + + @GetMapping("/schedule") + public PresentationFormResponseDto getPresentationSchedule( + @RequestParam(value = "yearMonth") + @DateTimeFormat(pattern = "yyyy-MM") + YearMonth yearMonth) { + return presentationService.getUserPresentationSchedule(yearMonth); + } + + /** + * 자신의 수요지식회 발표 현황을 조회합니다. + * + * @param user + * @param pageable + * @return + */ + @GetMapping("/me/histories") + @AuthGuard(level = AuthLevel.USER_ONLY) + public PresentationMyPagePaginationDto getUserPresentation( + @UserSession UserSessionDto user, + Pageable pageable + ) { + return presentationService.getUserPresentations(user.getUserId(), pageable); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Category.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Category.java new file mode 100644 index 000000000..1370c758c --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Category.java @@ -0,0 +1,6 @@ +package org.ftclub.cabinet.presentation.domain; + +public enum Category { + DEVELOP, HOBBY, JOB, ETC, TASK, STUDY + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java new file mode 100644 index 000000000..6913b08b5 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/Presentation.java @@ -0,0 +1,87 @@ +package org.ftclub.cabinet.presentation.domain; + +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.ftclub.cabinet.user.domain.User; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Presentation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID") + private Long id; + + @Enumerated(value = EnumType.STRING) + @Column(name = "PRESENTATION_STATUS") + private PresentationStatus presentationStatus; + + @Enumerated(value = EnumType.STRING) + @Column(name = "PRESENTATION_TIME") + private PresentationTime presentationTime; + + @Column(name = "SUBJECT", length = 25) + private String subject; + + @Column(name = "SUMMARY", length = 40) + private String summary; + + @Column(name = "DETAIL", length = 500) + private String detail; + + @Enumerated(value = EnumType.STRING) + @Column(name = "CATEGORY") + private Category category; + + @Column(name = "DATE_TIME") + private LocalDateTime dateTime; + + @Enumerated(value = EnumType.STRING) + private PresentationLocation presentationLocation; + + @Setter + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "USER_ID") + private User user; + + protected Presentation(Category category, LocalDateTime dateTime, + PresentationTime presentationTime, String subject, String summary, String detail) { + this.category = category; + this.dateTime = dateTime; + this.presentationTime = presentationTime; + this.subject = subject; + this.detail = detail; + this.summary = summary; + this.presentationStatus = PresentationStatus.EXPECTED; + this.presentationLocation = PresentationLocation.THIRD; + } + + public static Presentation of(Category category, LocalDateTime dateTime, + PresentationTime presentationTime, String subject, String summary, String detail) { + + return new Presentation(category, dateTime, presentationTime, subject, + summary, detail); + } + + public void adminUpdate(PresentationStatus newStatus, LocalDateTime newDateTime, + PresentationLocation newLocation) { + this.presentationStatus = newStatus; + this.dateTime = newDateTime; + this.presentationLocation = newLocation; + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationLocation.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationLocation.java new file mode 100644 index 000000000..7e6cdd32a --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationLocation.java @@ -0,0 +1,6 @@ +package org.ftclub.cabinet.presentation.domain; + +public enum PresentationLocation { + BASEMENT, FIRST, THIRD + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationStatus.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationStatus.java new file mode 100644 index 000000000..be32e48f3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationStatus.java @@ -0,0 +1,5 @@ +package org.ftclub.cabinet.presentation.domain; + +public enum PresentationStatus { + CANCEL, DONE, EXPECTED +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationTime.java b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationTime.java new file mode 100644 index 000000000..66c6266e3 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/domain/PresentationTime.java @@ -0,0 +1,5 @@ +package org.ftclub.cabinet.presentation.domain; + +public enum PresentationTime { + HALF, HOUR, HOUR_HALF, TWO_HOUR +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/repository/PresentationRepository.java b/backend/src/main/java/org/ftclub/cabinet/presentation/repository/PresentationRepository.java new file mode 100644 index 000000000..13198aaab --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/repository/PresentationRepository.java @@ -0,0 +1,29 @@ +package org.ftclub.cabinet.presentation.repository; + +import java.time.LocalDateTime; +import java.util.List; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface PresentationRepository extends JpaRepository { + + @EntityGraph(attributePaths = "user") + List findAllByDateTimeBetweenOrderByDateTime(LocalDateTime start, + LocalDateTime end); + + List findAllByDateTimeBetween(LocalDateTime start, LocalDateTime end); + + @EntityGraph(attributePaths = "user") + List findByDateTimeBetween(@Param("start") LocalDateTime start, + @Param("end") LocalDateTime end, Pageable pageable); + + @Query("SELECT p " + + "FROM Presentation p " + + "WHERE p.user.id = :userId") + Page findPaginationById(@Param("userId") Long userId, Pageable pageable); +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationPolicyService.java new file mode 100644 index 000000000..dbd06243e --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationPolicyService.java @@ -0,0 +1,55 @@ +package org.ftclub.cabinet.presentation.service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; +import org.ftclub.cabinet.presentation.repository.PresentationRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PresentationPolicyService { + + private static final Integer MAXIMUM_MONTH = 3; + private final PresentationRepository presentationRepository; + + /** + * 발표 날짜에 대해 예약 가능한지 검증한다. + *

+ * 범위 내의 날짜(등록일 기준 3개월 이내), 예약 날짜인지 검증 + * + * @param localDateTime 원하는 발표 날짜 + */ + public void verifyReservationDate(LocalDateTime localDateTime) { + LocalDate now = LocalDate.now(); + LocalDate reservationDate = localDateTime.toLocalDate(); + + LocalDateTime startOfDay = reservationDate.atStartOfDay(); + LocalDateTime endOfDay = startOfDay.plusDays(1); + + if (isOverRangeDate(reservationDate, now) + || isAlreadyRegisteredDate(startOfDay, + endOfDay)) { + throw ExceptionStatus.INVALID_DATE.asServiceException(); + } + } + + private boolean isAlreadyRegisteredDate(LocalDateTime startOfDay, LocalDateTime endOfDay) { + List presentations = + presentationRepository.findAllByDateTimeBetween(startOfDay, endOfDay); + + return presentations.stream() + .anyMatch(presentation -> + !presentation.getPresentationStatus().equals(PresentationStatus.CANCEL)); + } + + private boolean isOverRangeDate(LocalDate reservationDate, LocalDate now) { + return reservationDate.isBefore(now) || + reservationDate.isAfter(now.plusMonths(MAXIMUM_MONTH)); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationQueryService.java b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationQueryService.java new file mode 100644 index 000000000..636fe0179 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationQueryService.java @@ -0,0 +1,52 @@ +package org.ftclub.cabinet.presentation.service; + +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; +import org.ftclub.cabinet.presentation.repository.PresentationRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PresentationQueryService { + + private static final Integer START_DAY = 1; + + private final PresentationRepository presentationRepository; + + public List getRegisteredPresentations(LocalDateTime start, LocalDateTime end) { + List presentations = + presentationRepository.findAllByDateTimeBetween(start, end); + + return presentations.stream() + .filter(presentation -> + !presentation.getPresentationStatus().equals(PresentationStatus.CANCEL)) + .collect(Collectors.toList()); + } + + public List getPresentationsBetweenWithPageRequest(LocalDateTime start, + LocalDateTime end, + PageRequest pageRequest) { + return presentationRepository.findByDateTimeBetween(start, end, pageRequest); + } + + public List getPresentationsByYearMonth(YearMonth yearMonth) { + LocalDateTime startDate = yearMonth.atDay(START_DAY).atStartOfDay(); + LocalDateTime endDayDate = yearMonth.atEndOfMonth().atTime(23, 59, 59); + + return presentationRepository.findAllByDateTimeBetweenOrderByDateTime(startDate, + endDayDate); + } + + public Page getPresentationsById(Long id, Pageable pageable) { + return presentationRepository.findPaginationById(id, pageable); + } + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java new file mode 100644 index 000000000..4c08f928f --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/presentation/service/PresentationService.java @@ -0,0 +1,229 @@ +package org.ftclub.cabinet.presentation.service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ftclub.cabinet.dto.InvalidDateResponseDto; +import org.ftclub.cabinet.dto.PresentationFormData; +import org.ftclub.cabinet.dto.PresentationFormRequestDto; +import org.ftclub.cabinet.dto.PresentationFormResponseDto; +import org.ftclub.cabinet.dto.PresentationMainData; +import org.ftclub.cabinet.dto.PresentationMyPageDto; +import org.ftclub.cabinet.dto.PresentationMyPagePaginationDto; +import org.ftclub.cabinet.dto.PresentationUpdateDto; +import org.ftclub.cabinet.exception.ExceptionStatus; +import org.ftclub.cabinet.mapper.PresentationMapper; +import org.ftclub.cabinet.presentation.domain.Presentation; +import org.ftclub.cabinet.presentation.domain.PresentationLocation; +import org.ftclub.cabinet.presentation.domain.PresentationStatus; +import org.ftclub.cabinet.presentation.repository.PresentationRepository; +import org.ftclub.cabinet.user.domain.User; +import org.ftclub.cabinet.user.service.UserQueryService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class PresentationService { + + private static final Integer START_DAY = 1; + private static final Integer DEFAULT_PAGE = 0; + // 쿼리로 받? + private static final Integer MAX_MONTH = 3; + private static final String DATE_TIME = "dateTime"; + private final PresentationRepository presentationRepository; + private final PresentationPolicyService presentationPolicyService; + private final PresentationMapper presentationMapper; + private final PresentationQueryService presentationQueryService; + private final UserQueryService userQueryService; + + /** + * 수요 지식회 폼 저장 + * + * @param userId 토큰 파싱을 통해 받아온 userId + * @param dto 신청서 작성에 필요한 정보들 + */ + @Transactional + public void createPresentationFrom(Long userId, PresentationFormRequestDto dto) { + presentationPolicyService.verifyReservationDate(dto.getDateTime()); + + Presentation presentation = + Presentation.of(dto.getCategory(), dto.getDateTime(), + dto.getPresentationTime(), dto.getSubject(), dto.getSummary(), + dto.getDetail()); + User user = userQueryService.getUser(userId); + + presentation.setUser(user); + + presentationRepository.save(presentation); + } + + /** + * 예약 불가능한 날짜들을 반환합니다. + * + * @return + */ + public InvalidDateResponseDto getInvalidDate() { + LocalDate now = LocalDate.now(); + LocalDateTime start = now.atStartOfDay(); + LocalDateTime end = start.plusMonths(MAX_MONTH); + + List invalidDates = + presentationQueryService.getRegisteredPresentations(start, end) + .stream() + .map(Presentation::getDateTime) + .collect(Collectors.toList()); + + return new InvalidDateResponseDto(invalidDates); + } + + /** + * 요청 날짜 기준 과거의 신청서 중 가장 최신 Date의 정보를 개수만큼 반환 + * + * @param count + * @return + */ + public List getLatestPastPresentations(int count) { + LocalDate now = LocalDate.now(); + LocalDateTime limit = now.atStartOfDay(); + LocalDateTime start = limit.minusYears(10); + PageRequest pageRequest = PageRequest.of(DEFAULT_PAGE, count, + Sort.by(DATE_TIME).descending()); + + List presentations = + presentationQueryService.getPresentationsBetweenWithPageRequest(start, limit, + pageRequest); + + return presentations.stream() + .filter(presentation -> + presentation.getPresentationStatus().equals(PresentationStatus.DONE)) + .collect(Collectors.toList()); + } + + /** + * 요청 날짜 기준 발표 예정의 신청서들을 개수만큼 반환 + * + * @param count + * @return + */ + public List getLatestUpcomingPresentations(int count) { + LocalDate now = LocalDate.now(); + LocalDateTime start = now.atStartOfDay(); + LocalDateTime end = start.plusMonths(MAX_MONTH); + PageRequest pageRequest = PageRequest.of(DEFAULT_PAGE, count, + Sort.by(DATE_TIME).ascending()); + + List presentations = presentationQueryService. + getPresentationsBetweenWithPageRequest(start, end, pageRequest); + + return presentations.stream() + .filter(presentation -> + presentation.getPresentationStatus().equals(PresentationStatus.EXPECTED)) + .collect(Collectors.toList()); + } + + /** + * 과거, 예정 신청서의 데이터들을 Dto 형식으로 반환 + * + * @param pastFormCount 가장 가까운 과거 신청서의 개수 + * @param upcomingFormCount 가장 가까운 미래 신청서의 개수 + * @return + */ + public PresentationMainData getPastAndUpcomingPresentations( + int pastFormCount, int upcomingFormCount) { + List pastPresentations = getLatestPastPresentations(pastFormCount); + List upcomingPresentations = getLatestUpcomingPresentations( + upcomingFormCount); + + List past = pastPresentations.stream() + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); + List upcoming = upcomingPresentations.stream() + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); + + return presentationMapper.toPresentationMainData(past, upcoming); + } + + /** + * 입력받은 기한 내의 신청서들을 반환합니다. + * + * @param yearMonth yyyy-MM 타입 + * @return + */ + public PresentationFormResponseDto getUserPresentationSchedule(YearMonth yearMonth) { + + List result = + presentationQueryService.getPresentationsByYearMonth(yearMonth) + .stream() + .filter(presentation -> + !presentation.getPresentationStatus().equals(PresentationStatus.CANCEL)) + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); + + return new PresentationFormResponseDto(result); + } + + public PresentationFormResponseDto getAdminPresentationSchedule(YearMonth yearMonth) { + + List result = + presentationQueryService.getPresentationsByYearMonth(yearMonth) + .stream() + .map(presentationMapper::toPresentationFormDataDto) + .collect(Collectors.toList()); + + return new PresentationFormResponseDto(result); + } + + + /** + * 해당 id 를 가진 발표의 상태를 변경합니다. + *

+ * **** 추가 고려사항 -> 발표 3일전엔 확정 되어 update 불가인 정책이 있는지..?? -> 확인후 추가 해야할듯 + *

+ * + * @Pathvariable Long formId; + * @RequestBody { LocalDateTime dateTime; // new Date().toISOString() String status; // [예정, 완료, + * 취소] String location; // [3층 회의실, 지하 1층 오픈스튜디오] } + */ + @Transactional + public void updatePresentationByFormId(Long formId, PresentationUpdateDto dto) { + Presentation presentationToUpdate = + presentationRepository.findById(formId) + .orElseThrow(ExceptionStatus.INVALID_FORM_ID::asServiceException); + //날짜 변경시에만 유효성 검증 + if (!presentationToUpdate.getDateTime().isEqual(dto.getDateTime())) { + presentationPolicyService.verifyReservationDate(dto.getDateTime()); + } + + presentationToUpdate.adminUpdate(dto.getStatus(), dto.getDateTime(), dto.getLocation()); + } + + /** + * Page 타입으로 유저의 Presentation 객체들을 조회 + * + * @param userId + * @param pageable + * @return + */ + public PresentationMyPagePaginationDto getUserPresentations(Long userId, Pageable pageable) { + Page presentations = presentationQueryService.getPresentationsById(userId, + pageable); + + List result = presentations.stream() + .map(presentationMapper::toPresentationMyPageDto) + .collect(Collectors.toList()); + + return new PresentationMyPagePaginationDto(result, presentations.getTotalElements()); + } +} diff --git a/config b/config index dcf6e4379..24f8a0ac5 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit dcf6e437959da4094013321fbc15f8856159c758 +Subproject commit 24f8a0ac525652379356018047bc1c358b344d39 diff --git a/dev/configure.conf b/dev/configure.conf index 05f7ccfde..8c5368ec6 100644 --- a/dev/configure.conf +++ b/dev/configure.conf @@ -73,7 +73,7 @@ server { proxy_read_timeout 3600s; } - location ^~ /v4 { + location ~ ^/(v4|v5) { proxy_pass http://host.docker.internal:2424; proxy_set_header Host $host; } diff --git a/frontend/.prettierrc b/frontend/.prettierrc index a73181b6b..d55a2894b 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -8,15 +8,24 @@ "arrowParens": "always", "importOrder": [ "", - "^@/recoil/(.*)$", - "^@/pages/(.*)$", - "^@/components/(.*)$", - "^@/assets/(.*)$", - "^@/types/(.*)$", - "^@/api/(.*)$", - "^@/hooks/(.*)$", - "^@/utils/(.*)$", - "^@/constants/(.*)$", + "^@/Cabinet/recoil/(.*)$", + "^@/Cabinet/pages/(.*)$", + "^@/Cabinet/components/(.*)$", + "^@/Cabinet/assets/(.*)$", + "^@/Cabinet/types/(.*)$", + "^@/Cabinet/api/(.*)$", + "^@/Cabinet/hooks/(.*)$", + "^@/Cabinet/utils/(.*)$", + "^@/Cabinet/constants/(.*)$", + "^@/Presentation/recoil/(.*)$", + "^@/Presentation/pages/(.*)$", + "^@/Presentation/components/(.*)$", + "^@/Presentation/assets/(.*)$", + "^@/Presentation/types/(.*)$", + "^@/Presentation/api/(.*)$", + "^@/Presentation/hooks/(.*)$", + "^@/Presentation/utils/(.*)$", + "^@/Presentation/constants/(.*)$", "^[./]" ], "importOrderSeparation": false, diff --git a/frontend/index.html b/frontend/index.html index 5406b186c..4975eb9aa 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,13 @@ - - - + + + =12" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -14947,6 +14958,12 @@ "whatwg-url": "^11.0.0" } }, + "date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5bf787c66..5a8a4daae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,6 +44,7 @@ "@typescript-eslint/eslint-plugin": "^5.46.1", "@typescript-eslint/parser": "^5.46.1", "@vitejs/plugin-react": "^3.0.0", + "date-fns": "^3.6.0", "eslint": "^8.30.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^8.5.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6e725cfb2..b839e00aa 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,28 +1,36 @@ +import PageTracker from "@/api/analytics/PageTracker"; import React, { Suspense, lazy } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; -import AvailablePage from "@/pages/AvailablePage"; -import ClubPage from "@/pages/ClubPage"; -import HomePage from "@/pages/HomePage"; -import Layout from "@/pages/Layout"; -import LogPage from "@/pages/LogPage"; -import LoginPage from "@/pages/LoginPage"; -import MainPage from "@/pages/MainPage"; -import PostLogin from "@/pages/PostLogin"; -import ProfilePage from "@/pages/ProfilePage"; -import AdminMainPage from "@/pages/admin/AdminMainPage"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import PageTracker from "@/api/analytics/PageTracker"; +import AvailablePage from "@/Cabinet/pages/AvailablePage"; +import ClubPage from "@/Cabinet/pages/ClubPage"; +import HomePage from "@/Cabinet/pages/HomePage"; +import Layout from "@/Cabinet/pages/Layout"; +import LogPage from "@/Cabinet/pages/LogPage"; +import LoginPage from "@/Cabinet/pages/LoginPage"; +import MainPage from "@/Cabinet/pages/MainPage"; +import PostLogin from "@/Cabinet/pages/PostLogin"; +import ProfilePage from "@/Cabinet/pages/ProfilePage"; +import AdminMainPage from "@/Cabinet/pages/admin/AdminMainPage"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import DetailPage from "@/Presentation/pages/DetailPage"; +import PresentationHomePage from "@/Presentation/pages/HomePage"; +import PresentationLayout from "@/Presentation/pages/Layout"; +import PresentationLogPage from "@/Presentation/pages/LogPage"; +import RegisterPage from "@/Presentation/pages/RegisterPage"; +import AdminPresentationLayout from "@/Presentation/pages/admin/AdminLayout"; -const NotFoundPage = lazy(() => import("@/pages/NotFoundPage")); -const LoginFailurePage = lazy(() => import("@/pages/LoginFailurePage")); -const AdminLayout = lazy(() => import("@/pages/admin/AdminLayout")); -const AdminLoginPage = lazy(() => import("@/pages/admin/AdminLoginPage")); -const SearchPage = lazy(() => import("@/pages/admin/SearchPage")); -const AdminClubPage = lazy(() => import("@/pages/admin/AdminClubPage")); +const NotFoundPage = lazy(() => import("@/Cabinet/pages/NotFoundPage")); +const LoginFailurePage = lazy(() => import("@/Cabinet/pages/LoginFailurePage")); +const AdminLayout = lazy(() => import("@/Cabinet/pages/admin/AdminLayout")); +const AdminLoginPage = lazy( + () => import("@/Cabinet/pages/admin/AdminLoginPage") +); +const SearchPage = lazy(() => import("@/Cabinet/pages/admin/SearchPage")); +const AdminClubPage = lazy(() => import("@/Cabinet/pages/admin/AdminClubPage")); const AdminLoginFailurePage = lazy( - () => import("@/pages/admin/AdminLoginFailurePage") + () => import("@/Cabinet/pages/admin/AdminLoginFailurePage") ); -const AdminHomePage = lazy(() => import("@/pages/admin/AdminHomePage")); +const AdminHomePage = lazy(() => import("@/Cabinet/pages/admin/AdminHomePage")); function App(): React.ReactElement { return ( @@ -41,6 +49,12 @@ function App(): React.ReactElement { } /> } /> + }> + } /> + } /> + } /> + } /> + {/* admin용 라우터 */} }> } /> @@ -50,6 +64,12 @@ function App(): React.ReactElement { } /> } /> + } + > + } /> + } /> => { @@ -341,7 +341,7 @@ export const axiosAdminAuthLogin = async ( } }; -const axiosAdminCabinetInfoByIdURL = "/v4/admin/cabinets/"; +const axiosAdminCabinetInfoByIdURL = "/v4/cabinets/"; export const axiosAdminCabinetInfoByCabinetId = async ( cabinetId: number | null ): Promise => { diff --git a/frontend/src/api/axios/axios.instance.ts b/frontend/src/Cabinet/api/axios/axios.instance.ts similarity index 88% rename from frontend/src/api/axios/axios.instance.ts rename to frontend/src/Cabinet/api/axios/axios.instance.ts index a06986608..4017a2bbd 100644 --- a/frontend/src/api/axios/axios.instance.ts +++ b/frontend/src/Cabinet/api/axios/axios.instance.ts @@ -1,6 +1,6 @@ import axios from "axios"; -import { getCookie, removeCookie } from "@/api/react_cookie/cookies"; -import { STATUS_401_UNAUTHORIZED } from "@/constants/StatusCode"; +import { getCookie, removeCookie } from "@/Cabinet/api/react_cookie/cookies"; +import { STATUS_401_UNAUTHORIZED } from "@/Cabinet/constants/StatusCode"; axios.defaults.withCredentials = true; diff --git a/frontend/src/api/react_cookie/cookies.ts b/frontend/src/Cabinet/api/react_cookie/cookies.ts similarity index 100% rename from frontend/src/api/react_cookie/cookies.ts rename to frontend/src/Cabinet/api/react_cookie/cookies.ts diff --git a/frontend/src/assets/css/homePage.css b/frontend/src/Cabinet/assets/css/homePage.css similarity index 100% rename from frontend/src/assets/css/homePage.css rename to frontend/src/Cabinet/assets/css/homePage.css diff --git a/frontend/src/assets/css/loginPage.css b/frontend/src/Cabinet/assets/css/loginPage.css similarity index 100% rename from frontend/src/assets/css/loginPage.css rename to frontend/src/Cabinet/assets/css/loginPage.css diff --git a/frontend/src/assets/css/media.css b/frontend/src/Cabinet/assets/css/media.css similarity index 92% rename from frontend/src/assets/css/media.css rename to frontend/src/Cabinet/assets/css/media.css index 612f68ee4..b4195d39b 100644 --- a/frontend/src/assets/css/media.css +++ b/frontend/src/Cabinet/assets/css/media.css @@ -2,10 +2,10 @@ /* left navigation */ #leftNavWrap { position: fixed; - top: 80px; + top: 120px; left: 0; background-color: var(--white); - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(-100%); transition: transform 0.3s ease-in-out; @@ -33,9 +33,9 @@ /* cabinetDetailArea(rightSection) */ #cabinetDetailArea { position: fixed; - top: 80px; + top: 120px; right: 0; - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; @@ -54,9 +54,9 @@ /* ClubMemberInfoArea(rightSection) */ /* #clubMemberInfoArea { position: fixed; - top: 80px; + top: 120px; right: 0; - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; diff --git a/frontend/src/assets/css/reset.css b/frontend/src/Cabinet/assets/css/reset.css similarity index 100% rename from frontend/src/assets/css/reset.css rename to frontend/src/Cabinet/assets/css/reset.css diff --git a/frontend/src/assets/data/ManualContent.ts b/frontend/src/Cabinet/assets/data/ManualContent.ts similarity index 94% rename from frontend/src/assets/data/ManualContent.ts rename to frontend/src/Cabinet/assets/data/ManualContent.ts index 11d7623ec..070a182b3 100644 --- a/frontend/src/assets/data/ManualContent.ts +++ b/frontend/src/Cabinet/assets/data/ManualContent.ts @@ -1,4 +1,4 @@ -import ContentStatus from "@/types/enum/content.status.enum"; +import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface ContentStatusData { contentTitle: string; @@ -13,7 +13,7 @@ interface ContentStatusData { export const manualContentData: Record = { [ContentStatus.PRIVATE]: { contentTitle: "개인 사물함", - imagePath: "/src/assets/images/privateIcon.svg", + imagePath: "/src/Cabinet/assets/images/privateIcon.svg", background: "linear-gradient(to bottom, #A17BF3, #8337E5)", rentalPeriod: `${import.meta.env.VITE_PRIVATE_LENT_PERIOD}일`, capacity: "1인", @@ -34,7 +34,7 @@ export const manualContentData: Record = { }, [ContentStatus.SHARE]: { contentTitle: "공유 사물함", - imagePath: "/src/assets/images/shareIcon.svg", + imagePath: "/src/Cabinet/assets/images/shareIcon.svg", background: "linear-gradient(to bottom, #7EBFFB, #406EE4)", rentalPeriod: `${import.meta.env.VITE_SHARE_LENT_PERIOD}일 + n * ${ import.meta.env.VITE_SHARE_BONUS_PER_PERSON @@ -63,7 +63,7 @@ export const manualContentData: Record = { }, [ContentStatus.CLUB]: { contentTitle: "동아리 사물함", - imagePath: "/src/assets/images/clubIcon.svg", + imagePath: "/src/Cabinet/assets/images/clubIcon.svg", background: "linear-gradient(to bottom, #F473B1, #D72766)", rentalPeriod: "상세내용 참조", capacity: "동아리", @@ -106,7 +106,7 @@ export const manualContentData: Record = { }, [ContentStatus.IN_SESSION]: { contentTitle: "대기중", - imagePath: "/src/assets/images/clock.svg", + imagePath: "/src/Cabinet/assets/images/clock.svg", background: "#F5F5F7", contentText: `◦ 상세 내용

공유 사물함 대여 시 10분간의 대기 시간이 발생합니다.
@@ -126,7 +126,7 @@ export const manualContentData: Record = { }, [ContentStatus.EXTENSION]: { contentTitle: "연장권 이용방법 안내서", - imagePath: "/src/assets/images/extension.svg", + imagePath: "/src/Cabinet/assets/images/extension.svg", background: "#F5F5F7", contentText: `◦ 연장권 취득 조건
diff --git a/frontend/src/assets/data/mapPositionData.ts b/frontend/src/Cabinet/assets/data/mapPositionData.ts similarity index 98% rename from frontend/src/assets/data/mapPositionData.ts rename to frontend/src/Cabinet/assets/data/mapPositionData.ts index df708d49e..72287ecd8 100644 --- a/frontend/src/assets/data/mapPositionData.ts +++ b/frontend/src/Cabinet/assets/data/mapPositionData.ts @@ -1,4 +1,4 @@ -import SectionType from "@/types/enum/map.type.enum"; +import SectionType from "@/Cabinet/types/enum/map.type.enum"; /** * @interface diff --git a/frontend/src/assets/data/maps.ts b/frontend/src/Cabinet/assets/data/maps.ts similarity index 94% rename from frontend/src/assets/data/maps.ts rename to frontend/src/Cabinet/assets/data/maps.ts index 450756f56..5780c2528 100644 --- a/frontend/src/assets/data/maps.ts +++ b/frontend/src/Cabinet/assets/data/maps.ts @@ -1,5 +1,5 @@ -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; export enum additionalModalType { MODAL_RETURN = "MODAL_RETURN", @@ -18,9 +18,9 @@ export enum additionalModalType { } export const cabinetIconSrcMap = { - [CabinetType.PRIVATE]: "/src/assets/images/privateIcon.svg", - [CabinetType.SHARE]: "/src/assets/images/shareIcon.svg", - [CabinetType.CLUB]: "/src/assets/images/clubIcon.svg", + [CabinetType.PRIVATE]: "/src/Cabinet/assets/images/privateIcon.svg", + [CabinetType.SHARE]: "/src/Cabinet/assets/images/shareIcon.svg", + [CabinetType.CLUB]: "/src/Cabinet/assets/images/clubIcon.svg", }; export const cabinetLabelColorMap = { diff --git a/frontend/src/assets/data/sectionColNumData.ts b/frontend/src/Cabinet/assets/data/sectionColNumData.ts similarity index 100% rename from frontend/src/assets/data/sectionColNumData.ts rename to frontend/src/Cabinet/assets/data/sectionColNumData.ts diff --git a/frontend/src/assets/images/LeftSectionButton.svg b/frontend/src/Cabinet/assets/images/LeftSectionButton.svg similarity index 100% rename from frontend/src/assets/images/LeftSectionButton.svg rename to frontend/src/Cabinet/assets/images/LeftSectionButton.svg diff --git a/frontend/src/Cabinet/assets/images/PresentationAcademic.svg b/frontend/src/Cabinet/assets/images/PresentationAcademic.svg new file mode 100644 index 000000000..019970b4a --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationAcademic.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationDevelop.svg b/frontend/src/Cabinet/assets/images/PresentationDevelop.svg new file mode 100644 index 000000000..501dce15d --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationDevelop.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationEmpty.svg b/frontend/src/Cabinet/assets/images/PresentationEmpty.svg new file mode 100644 index 000000000..c89f6e1d1 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationEmpty.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationEtc.svg b/frontend/src/Cabinet/assets/images/PresentationEtc.svg new file mode 100644 index 000000000..46830a335 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationEtc.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationFortyTwo.svg b/frontend/src/Cabinet/assets/images/PresentationFortyTwo.svg new file mode 100644 index 000000000..f1d5205e7 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationFortyTwo.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationHobby.svg b/frontend/src/Cabinet/assets/images/PresentationHobby.svg new file mode 100644 index 000000000..723c4039f --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationHobby.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/PresentationJob.svg b/frontend/src/Cabinet/assets/images/PresentationJob.svg new file mode 100644 index 000000000..8105bb49e --- /dev/null +++ b/frontend/src/Cabinet/assets/images/PresentationJob.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/adminLoginImg.svg b/frontend/src/Cabinet/assets/images/adminLoginImg.svg similarity index 100% rename from frontend/src/assets/images/adminLoginImg.svg rename to frontend/src/Cabinet/assets/images/adminLoginImg.svg diff --git a/frontend/src/assets/images/alarm.svg b/frontend/src/Cabinet/assets/images/alarm.svg similarity index 100% rename from frontend/src/assets/images/alarm.svg rename to frontend/src/Cabinet/assets/images/alarm.svg diff --git a/frontend/src/assets/images/cabinet.svg b/frontend/src/Cabinet/assets/images/cabinet.svg similarity index 100% rename from frontend/src/assets/images/cabinet.svg rename to frontend/src/Cabinet/assets/images/cabinet.svg diff --git a/frontend/src/Cabinet/assets/images/calendar.svg b/frontend/src/Cabinet/assets/images/calendar.svg new file mode 100644 index 000000000..0e94e5a5b --- /dev/null +++ b/frontend/src/Cabinet/assets/images/calendar.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/src/assets/images/cautionSign.svg b/frontend/src/Cabinet/assets/images/cautionSign.svg similarity index 100% rename from frontend/src/assets/images/cautionSign.svg rename to frontend/src/Cabinet/assets/images/cautionSign.svg diff --git a/frontend/src/assets/images/checkIcon.svg b/frontend/src/Cabinet/assets/images/checkIcon.svg similarity index 100% rename from frontend/src/assets/images/checkIcon.svg rename to frontend/src/Cabinet/assets/images/checkIcon.svg diff --git a/frontend/src/assets/images/clock.svg b/frontend/src/Cabinet/assets/images/clock.svg similarity index 100% rename from frontend/src/assets/images/clock.svg rename to frontend/src/Cabinet/assets/images/clock.svg diff --git a/frontend/src/assets/images/close-circle.svg b/frontend/src/Cabinet/assets/images/close-circle.svg similarity index 100% rename from frontend/src/assets/images/close-circle.svg rename to frontend/src/Cabinet/assets/images/close-circle.svg diff --git a/frontend/src/assets/images/close-square.svg b/frontend/src/Cabinet/assets/images/close-square.svg similarity index 100% rename from frontend/src/assets/images/close-square.svg rename to frontend/src/Cabinet/assets/images/close-square.svg diff --git a/frontend/src/assets/images/clubIcon.svg b/frontend/src/Cabinet/assets/images/clubIcon.svg similarity index 100% rename from frontend/src/assets/images/clubIcon.svg rename to frontend/src/Cabinet/assets/images/clubIcon.svg diff --git a/frontend/src/assets/images/clubIconGray.svg b/frontend/src/Cabinet/assets/images/clubIconGray.svg similarity index 100% rename from frontend/src/assets/images/clubIconGray.svg rename to frontend/src/Cabinet/assets/images/clubIconGray.svg diff --git a/frontend/src/assets/images/crown.svg b/frontend/src/Cabinet/assets/images/crown.svg similarity index 100% rename from frontend/src/assets/images/crown.svg rename to frontend/src/Cabinet/assets/images/crown.svg diff --git a/frontend/src/assets/images/desktopLogo.png b/frontend/src/Cabinet/assets/images/desktopLogo.png similarity index 100% rename from frontend/src/assets/images/desktopLogo.png rename to frontend/src/Cabinet/assets/images/desktopLogo.png diff --git a/frontend/src/Cabinet/assets/images/dropdownChevron.svg b/frontend/src/Cabinet/assets/images/dropdownChevron.svg new file mode 100644 index 000000000..2917ac0ff --- /dev/null +++ b/frontend/src/Cabinet/assets/images/dropdownChevron.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/edit.svg b/frontend/src/Cabinet/assets/images/edit.svg similarity index 100% rename from frontend/src/assets/images/edit.svg rename to frontend/src/Cabinet/assets/images/edit.svg diff --git a/frontend/src/assets/images/errorIcon.svg b/frontend/src/Cabinet/assets/images/errorIcon.svg similarity index 100% rename from frontend/src/assets/images/errorIcon.svg rename to frontend/src/Cabinet/assets/images/errorIcon.svg diff --git a/frontend/src/assets/images/exitButton.svg b/frontend/src/Cabinet/assets/images/exitButton.svg similarity index 100% rename from frontend/src/assets/images/exitButton.svg rename to frontend/src/Cabinet/assets/images/exitButton.svg diff --git a/frontend/src/assets/images/extension.svg b/frontend/src/Cabinet/assets/images/extension.svg similarity index 100% rename from frontend/src/assets/images/extension.svg rename to frontend/src/Cabinet/assets/images/extension.svg diff --git a/frontend/src/assets/images/extensionTicket.svg b/frontend/src/Cabinet/assets/images/extensionTicket.svg similarity index 100% rename from frontend/src/assets/images/extensionTicket.svg rename to frontend/src/Cabinet/assets/images/extensionTicket.svg diff --git a/frontend/src/assets/images/happyCcabi.png b/frontend/src/Cabinet/assets/images/happyCcabi.png similarity index 100% rename from frontend/src/assets/images/happyCcabi.png rename to frontend/src/Cabinet/assets/images/happyCcabi.png diff --git a/frontend/src/Cabinet/assets/images/happyCcabi.svg b/frontend/src/Cabinet/assets/images/happyCcabi.svg new file mode 100644 index 000000000..60568953e --- /dev/null +++ b/frontend/src/Cabinet/assets/images/happyCcabi.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/happyCcabiWhite.png b/frontend/src/Cabinet/assets/images/happyCcabiWhite.png similarity index 100% rename from frontend/src/assets/images/happyCcabiWhite.png rename to frontend/src/Cabinet/assets/images/happyCcabiWhite.png diff --git a/frontend/src/assets/images/leader.svg b/frontend/src/Cabinet/assets/images/leader.svg similarity index 100% rename from frontend/src/assets/images/leader.svg rename to frontend/src/Cabinet/assets/images/leader.svg diff --git a/frontend/src/assets/images/link.svg b/frontend/src/Cabinet/assets/images/link.svg similarity index 100% rename from frontend/src/assets/images/link.svg rename to frontend/src/Cabinet/assets/images/link.svg diff --git a/frontend/src/assets/images/lock.svg b/frontend/src/Cabinet/assets/images/lock.svg similarity index 100% rename from frontend/src/assets/images/lock.svg rename to frontend/src/Cabinet/assets/images/lock.svg diff --git a/frontend/src/assets/images/log.svg b/frontend/src/Cabinet/assets/images/log.svg similarity index 100% rename from frontend/src/assets/images/log.svg rename to frontend/src/Cabinet/assets/images/log.svg diff --git a/frontend/src/assets/images/loginImg.svg b/frontend/src/Cabinet/assets/images/loginImg.svg similarity index 100% rename from frontend/src/assets/images/loginImg.svg rename to frontend/src/Cabinet/assets/images/loginImg.svg diff --git a/frontend/src/assets/images/logo.ico b/frontend/src/Cabinet/assets/images/logo.ico similarity index 100% rename from frontend/src/assets/images/logo.ico rename to frontend/src/Cabinet/assets/images/logo.ico diff --git a/frontend/src/assets/images/logo.png b/frontend/src/Cabinet/assets/images/logo.png similarity index 100% rename from frontend/src/assets/images/logo.png rename to frontend/src/Cabinet/assets/images/logo.png diff --git a/frontend/src/assets/images/logo.svg b/frontend/src/Cabinet/assets/images/logo.svg similarity index 100% rename from frontend/src/assets/images/logo.svg rename to frontend/src/Cabinet/assets/images/logo.svg diff --git a/frontend/src/assets/images/logoBlack.svg b/frontend/src/Cabinet/assets/images/logoBlack.svg similarity index 100% rename from frontend/src/assets/images/logoBlack.svg rename to frontend/src/Cabinet/assets/images/logoBlack.svg diff --git a/frontend/src/assets/images/manualPeople.svg b/frontend/src/Cabinet/assets/images/manualPeople.svg similarity index 100% rename from frontend/src/assets/images/manualPeople.svg rename to frontend/src/Cabinet/assets/images/manualPeople.svg diff --git a/frontend/src/assets/images/map.svg b/frontend/src/Cabinet/assets/images/map.svg similarity index 100% rename from frontend/src/assets/images/map.svg rename to frontend/src/Cabinet/assets/images/map.svg diff --git a/frontend/src/assets/images/more.svg b/frontend/src/Cabinet/assets/images/more.svg similarity index 100% rename from frontend/src/assets/images/more.svg rename to frontend/src/Cabinet/assets/images/more.svg diff --git a/frontend/src/assets/images/moveButton.svg b/frontend/src/Cabinet/assets/images/moveButton.svg similarity index 100% rename from frontend/src/assets/images/moveButton.svg rename to frontend/src/Cabinet/assets/images/moveButton.svg diff --git a/frontend/src/assets/images/myCabinetIcon.svg b/frontend/src/Cabinet/assets/images/myCabinetIcon.svg similarity index 100% rename from frontend/src/assets/images/myCabinetIcon.svg rename to frontend/src/Cabinet/assets/images/myCabinetIcon.svg diff --git a/frontend/src/assets/images/notificationSign.svg b/frontend/src/Cabinet/assets/images/notificationSign.svg similarity index 100% rename from frontend/src/assets/images/notificationSign.svg rename to frontend/src/Cabinet/assets/images/notificationSign.svg diff --git a/frontend/src/assets/images/notificationSign_grey.svg b/frontend/src/Cabinet/assets/images/notificationSign_grey.svg similarity index 100% rename from frontend/src/assets/images/notificationSign_grey.svg rename to frontend/src/Cabinet/assets/images/notificationSign_grey.svg diff --git a/frontend/src/assets/images/privateIcon.svg b/frontend/src/Cabinet/assets/images/privateIcon.svg similarity index 100% rename from frontend/src/assets/images/privateIcon.svg rename to frontend/src/Cabinet/assets/images/privateIcon.svg diff --git a/frontend/src/assets/images/proceedMultiSelect.svg b/frontend/src/Cabinet/assets/images/proceedMultiSelect.svg similarity index 100% rename from frontend/src/assets/images/proceedMultiSelect.svg rename to frontend/src/Cabinet/assets/images/proceedMultiSelect.svg diff --git a/frontend/src/assets/images/profile-circle.svg b/frontend/src/Cabinet/assets/images/profile-circle.svg similarity index 100% rename from frontend/src/assets/images/profile-circle.svg rename to frontend/src/Cabinet/assets/images/profile-circle.svg diff --git a/frontend/src/assets/images/refresh.svg b/frontend/src/Cabinet/assets/images/refresh.svg similarity index 100% rename from frontend/src/assets/images/refresh.svg rename to frontend/src/Cabinet/assets/images/refresh.svg diff --git a/frontend/src/assets/images/rotateRight.svg b/frontend/src/Cabinet/assets/images/rotateRight.svg similarity index 100% rename from frontend/src/assets/images/rotateRight.svg rename to frontend/src/Cabinet/assets/images/rotateRight.svg diff --git a/frontend/src/assets/images/sadCcabi.png b/frontend/src/Cabinet/assets/images/sadCcabi.png similarity index 100% rename from frontend/src/assets/images/sadCcabi.png rename to frontend/src/Cabinet/assets/images/sadCcabi.png diff --git a/frontend/src/Cabinet/assets/images/sadCcabi.svg b/frontend/src/Cabinet/assets/images/sadCcabi.svg new file mode 100644 index 000000000..b82e3eda9 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/sadCcabi.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/src/assets/images/sadCcabiWhite.png b/frontend/src/Cabinet/assets/images/sadCcabiWhite.png similarity index 100% rename from frontend/src/assets/images/sadCcabiWhite.png rename to frontend/src/Cabinet/assets/images/sadCcabiWhite.png diff --git a/frontend/src/assets/images/search.svg b/frontend/src/Cabinet/assets/images/search.svg similarity index 100% rename from frontend/src/assets/images/search.svg rename to frontend/src/Cabinet/assets/images/search.svg diff --git a/frontend/src/assets/images/searchWhite.svg b/frontend/src/Cabinet/assets/images/searchWhite.svg similarity index 100% rename from frontend/src/assets/images/searchWhite.svg rename to frontend/src/Cabinet/assets/images/searchWhite.svg diff --git a/frontend/src/assets/images/select.svg b/frontend/src/Cabinet/assets/images/select.svg similarity index 100% rename from frontend/src/assets/images/select.svg rename to frontend/src/Cabinet/assets/images/select.svg diff --git a/frontend/src/assets/images/selectFilterIconOff.svg b/frontend/src/Cabinet/assets/images/selectFilterIconOff.svg similarity index 100% rename from frontend/src/assets/images/selectFilterIconOff.svg rename to frontend/src/Cabinet/assets/images/selectFilterIconOff.svg diff --git a/frontend/src/assets/images/selectFilterIconOn.svg b/frontend/src/Cabinet/assets/images/selectFilterIconOn.svg similarity index 100% rename from frontend/src/assets/images/selectFilterIconOn.svg rename to frontend/src/Cabinet/assets/images/selectFilterIconOn.svg diff --git a/frontend/src/assets/images/selectMaincolor.svg b/frontend/src/Cabinet/assets/images/selectMaincolor.svg similarity index 100% rename from frontend/src/assets/images/selectMaincolor.svg rename to frontend/src/Cabinet/assets/images/selectMaincolor.svg diff --git a/frontend/src/assets/images/selectPurple.svg b/frontend/src/Cabinet/assets/images/selectPurple.svg similarity index 100% rename from frontend/src/assets/images/selectPurple.svg rename to frontend/src/Cabinet/assets/images/selectPurple.svg diff --git a/frontend/src/assets/images/shareIcon.svg b/frontend/src/Cabinet/assets/images/shareIcon.svg similarity index 100% rename from frontend/src/assets/images/shareIcon.svg rename to frontend/src/Cabinet/assets/images/shareIcon.svg diff --git a/frontend/src/assets/images/slack.svg b/frontend/src/Cabinet/assets/images/slack.svg similarity index 100% rename from frontend/src/assets/images/slack.svg rename to frontend/src/Cabinet/assets/images/slack.svg diff --git a/frontend/src/assets/images/stairs.svg b/frontend/src/Cabinet/assets/images/stairs.svg similarity index 100% rename from frontend/src/assets/images/stairs.svg rename to frontend/src/Cabinet/assets/images/stairs.svg diff --git a/frontend/src/assets/images/subtract.svg b/frontend/src/Cabinet/assets/images/subtract.svg similarity index 100% rename from frontend/src/assets/images/subtract.svg rename to frontend/src/Cabinet/assets/images/subtract.svg diff --git a/frontend/src/Cabinet/assets/images/timer.svg b/frontend/src/Cabinet/assets/images/timer.svg new file mode 100644 index 000000000..20b760c7e --- /dev/null +++ b/frontend/src/Cabinet/assets/images/timer.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/warningTriangleIcon.svg b/frontend/src/Cabinet/assets/images/warningTriangleIcon.svg similarity index 100% rename from frontend/src/assets/images/warningTriangleIcon.svg rename to frontend/src/Cabinet/assets/images/warningTriangleIcon.svg diff --git a/frontend/src/components/AdminInfo/Chart/BarChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/BarChart.tsx similarity index 100% rename from frontend/src/components/AdminInfo/Chart/BarChart.tsx rename to frontend/src/Cabinet/components/AdminInfo/Chart/BarChart.tsx diff --git a/frontend/src/components/AdminInfo/Chart/LineChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/LineChart.tsx similarity index 97% rename from frontend/src/components/AdminInfo/Chart/LineChart.tsx rename to frontend/src/Cabinet/components/AdminInfo/Chart/LineChart.tsx index f7b2fe829..b83f9e80e 100644 --- a/frontend/src/components/AdminInfo/Chart/LineChart.tsx +++ b/frontend/src/Cabinet/components/AdminInfo/Chart/LineChart.tsx @@ -1,6 +1,6 @@ +import { IMonthlyData } from "@/Cabinet/types/dto/admin.dto"; import { ResponsiveLine } from "@nivo/line"; import styled from "styled-components"; -import { IMonthlyData } from "@/types/dto/admin.dto"; interface IChartInfo { x: string; diff --git a/frontend/src/components/AdminInfo/Chart/PieChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/PieChart.tsx similarity index 100% rename from frontend/src/components/AdminInfo/Chart/PieChart.tsx rename to frontend/src/Cabinet/components/AdminInfo/Chart/PieChart.tsx diff --git a/frontend/src/components/AdminInfo/Table/AdminTable.tsx b/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx similarity index 95% rename from frontend/src/components/AdminInfo/Table/AdminTable.tsx rename to frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx index 27def6ccb..2f7c2b570 100644 --- a/frontend/src/components/AdminInfo/Table/AdminTable.tsx +++ b/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx @@ -1,7 +1,7 @@ -import { ITableData } from "src/types/dto/admin.dto"; import { useState } from "react"; import styled from "styled-components"; -import Pagination from "./Pagination"; +import Pagination from "@/Cabinet/components/AdminInfo/Table/Pagination"; +import { ITableData } from "@/Cabinet/types/dto/admin.dto"; const AdminTable = ({ data, diff --git a/frontend/src/components/AdminInfo/Table/Pagination.tsx b/frontend/src/Cabinet/components/AdminInfo/Table/Pagination.tsx similarity index 100% rename from frontend/src/components/AdminInfo/Table/Pagination.tsx rename to frontend/src/Cabinet/components/AdminInfo/Table/Pagination.tsx diff --git a/frontend/src/components/Announce/AnnounceTemplate.tsx b/frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx similarity index 94% rename from frontend/src/components/Announce/AnnounceTemplate.tsx rename to frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx index f4c831efc..a83d05469 100644 --- a/frontend/src/components/Announce/AnnounceTemplate.tsx +++ b/frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx @@ -36,9 +36,9 @@ const AnnounceTemplate = (props: Itext) => { {title} {type === "ERROR" ? ( - + ) : ( - + )} {subTitle} diff --git a/frontend/src/components/Available/AvailableCountdown.tsx b/frontend/src/Cabinet/components/Available/AvailableCountdown.tsx similarity index 94% rename from frontend/src/components/Available/AvailableCountdown.tsx rename to frontend/src/Cabinet/components/Available/AvailableCountdown.tsx index d2c38f4fe..d8f37f3ea 100644 --- a/frontend/src/components/Available/AvailableCountdown.tsx +++ b/frontend/src/Cabinet/components/Available/AvailableCountdown.tsx @@ -1,8 +1,8 @@ +import { serverTimeState } from "@/Cabinet/recoil/atoms"; +import Time from "@/Cabinet/types/enum/time.enum"; import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { serverTimeState } from "@/recoil/atoms"; -import Time from "@/types/enum/time.enum"; const openTime = new Date(); openTime.setHours(13, 0, 0, 0); // 오픈 시간인 13:00:00(오후 1시)로 설정 diff --git a/frontend/src/components/Available/FloorContainer.tsx b/frontend/src/Cabinet/components/Available/FloorContainer.tsx similarity index 84% rename from frontend/src/components/Available/FloorContainer.tsx rename to frontend/src/Cabinet/components/Available/FloorContainer.tsx index 6be4547f2..c314c6419 100644 --- a/frontend/src/components/Available/FloorContainer.tsx +++ b/frontend/src/Cabinet/components/Available/FloorContainer.tsx @@ -1,9 +1,9 @@ import { useState } from "react"; import { useLocation } from "react-router-dom"; import styled from "styled-components"; -import AdminCabinetListItem from "@/components/CabinetList/CabinetListItem/AdminCabinetListItem"; -import CabinetListItem from "@/components/CabinetList/CabinetListItem/CabinetListItem"; -import { CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; +import AdminCabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem"; +import CabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem"; +import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; // 하나의 층에 대한 타이틀과 캐비넷 리스트를 담고 있는 컴포넌트 const FloorContainer = ({ @@ -40,7 +40,10 @@ const FloorContainer = ({ ) : (

해당 층에는 사용 가능한 사물함이 없습니다

- noAvailable + noAvailable
)} @@ -67,7 +70,8 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` z-index: 2; height: 30px; width: 20px; - background: url(/src/assets/images/select.svg) no-repeat center center; + background: url(/src/Cabinet/assets/images/select.svg) no-repeat center + center; transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; } diff --git a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx similarity index 92% rename from frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 120f66351..1c7f72070 100644 --- a/frontend/src/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -4,27 +4,27 @@ import styled, { css } from "styled-components"; import { currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { IAdminCurrentModalStateInfo, IMultiSelectTargetInfo, ISelectedCabinetInfo, TAdminModalState, -} from "@/components/CabinetInfoArea/CabinetInfoArea.container"; -import ButtonContainer from "@/components/Common/Button"; -import ClubLentModal from "@/components/Modals/LentModal/ClubLentModal"; -import AdminReturnModal from "@/components/Modals/ReturnModal/AdminReturnModal"; -import StatusModalContainer from "@/components/Modals/StatusModal/StatusModal.container"; +} from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; +import ButtonContainer from "@/Cabinet/components/Common/Button"; +import ClubLentModal from "@/Cabinet/components/Modals/LentModal/ClubLentModal"; +import AdminReturnModal from "@/Cabinet/components/Modals/ReturnModal/AdminReturnModal"; +import StatusModalContainer from "@/Cabinet/components/Modals/StatusModal/StatusModal.container"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; -import { CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/assets/data/maps"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; const AdminCabinetInfoArea: React.FC<{ selectedCabinetInfo: ISelectedCabinetInfo | null; diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container.tsx similarity index 93% rename from frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container.tsx index fd3f8b575..52431b660 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container.tsx @@ -5,20 +5,20 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import AdminCabinetInfoArea from "@/components/CabinetInfoArea/AdminCabinetInfoArea"; -import CabinetInfoArea from "@/components/CabinetInfoArea/CabinetInfoArea"; -import AdminLentLog from "@/components/LentLog/AdminLentLog"; +} from "@/Cabinet/recoil/atoms"; +import AdminCabinetInfoArea from "@/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea"; +import CabinetInfoArea from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea"; +import AdminLentLog from "@/Cabinet/components/LentLog/AdminLentLog"; import { CabinetInfo, CabinetPreviewInfo, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; -import { UserDto } from "@/types/dto/user.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import useMenu from "@/hooks/useMenu"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/types/dto/cabinet.dto"; +import { UserDto } from "@/Cabinet/types/dto/user.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; export interface ISelectedCabinetInfo { floor: number; @@ -134,8 +134,9 @@ const getCalcualtedTimeString = (expireTime: Date) => { const getCabinetUserList = (selectedCabinetInfo: CabinetInfo): string => { // 동아리 사물함인 경우 cabinet_title에 있는 동아리 이름 반환 - const { lentType, title, maxUser, lents } = selectedCabinetInfo; - if (lentType === "CLUB" && title) return title; + const { lentType, title, maxUser, lents, status } = selectedCabinetInfo; + if (lentType === "CLUB" && title && status == CabinetStatus.FULL) + return title; else if (maxUser === 0) return ""; // 그 외에는 유저리스트 반환 diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx similarity index 89% rename from frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx index 2bd273d96..ad97002b5 100644 --- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -4,29 +4,29 @@ import { ICurrentModalStateInfo, ISelectedCabinetInfo, TModalState, -} from "@/components/CabinetInfoArea/CabinetInfoArea.container"; -import CountTimeContainer from "@/components/CabinetInfoArea/CountTime/CountTime.container"; -import ButtonContainer from "@/components/Common/Button"; -import CancelModal from "@/components/Modals/CancelModal/CancelModal"; -import ExtendModal from "@/components/Modals/ExtendModal/ExtendModal"; -import InvitationCodeModalContainer from "@/components/Modals/InvitationCodeModal/InvitationCodeModal.container"; -import LentModal from "@/components/Modals/LentModal/LentModal"; -import MemoModalContainer from "@/components/Modals/MemoModal/MemoModal.container"; -import PasswordCheckModalContainer from "@/components/Modals/PasswordCheckModal/PasswordCheckModal.container"; -import ReturnModal from "@/components/Modals/ReturnModal/ReturnModal"; -import SwapModal from "@/components/Modals/SwapModal/SwapModal"; -import UnavailableModal from "@/components/Modals/UnavailableModal/UnavailableModal"; +} from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; +import CountTimeContainer from "@/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container"; +import ButtonContainer from "@/Cabinet/components/Common/Button"; +import CancelModal from "@/Cabinet/components/Modals/CancelModal/CancelModal"; +import ExtendModal from "@/Cabinet/components/Modals/ExtendModal/ExtendModal"; +import InvitationCodeModalContainer from "@/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container"; +import LentModal from "@/Cabinet/components/Modals/LentModal/LentModal"; +import MemoModalContainer from "@/Cabinet/components/Modals/MemoModal/MemoModal.container"; +import PasswordCheckModalContainer from "@/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.container"; +import ReturnModal from "@/Cabinet/components/Modals/ReturnModal/ReturnModal"; +import SwapModal from "@/Cabinet/components/Modals/SwapModal/SwapModal"; +import UnavailableModal from "@/Cabinet/components/Modals/UnavailableModal/UnavailableModal"; import { additionalModalType, cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import alertImg from "@/assets/images/cautionSign.svg"; -import { ReactComponent as ExtensionImg } from "@/assets/images/extensionTicket.svg"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/assets/data/maps"; +import alertImg from "@/Cabinet/assets/images/cautionSign.svg"; +import { ReactComponent as ExtensionImg } from "@/Cabinet/assets/images/extensionTicket.svg"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; const CabinetInfoArea: React.FC<{ selectedCabinetInfo: ISelectedCabinetInfo | null; diff --git a/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CodeAndTime.tsx similarity index 93% rename from frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CodeAndTime.tsx index 67b2c16cd..fec19ac5c 100644 --- a/frontend/src/components/CabinetInfoArea/CountTime/CodeAndTime.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CodeAndTime.tsx @@ -1,10 +1,10 @@ import { useState } from "react"; import { useRecoilValue } from "recoil"; import styled from "styled-components"; -import { myCabinetInfoState } from "@/recoil/atoms"; -import alertImg from "@/assets/images/cautionSign.svg"; -import { ReactComponent as ClockImg } from "@/assets/images/clock.svg"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; +import { myCabinetInfoState } from "@/Cabinet/recoil/atoms"; +import alertImg from "@/Cabinet/assets/images/cautionSign.svg"; +import { ReactComponent as ClockImg } from "@/Cabinet/assets/images/clock.svg"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; interface CountTimeProps { minutes: string; diff --git a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.container.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx similarity index 88% rename from frontend/src/components/CabinetInfoArea/CountTime/CountTime.container.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx index 1931a8686..b69de2ce9 100644 --- a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.container.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx @@ -4,11 +4,14 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import CodeAndTime from "@/components/CabinetInfoArea/CountTime/CodeAndTime"; -import CountTime from "@/components/CabinetInfoArea/CountTime/CountTime"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import { axiosCabinetById, axiosMyLentInfo } from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import CodeAndTime from "@/Cabinet/components/CabinetInfoArea/CountTime/CodeAndTime"; +import CountTime from "@/Cabinet/components/CabinetInfoArea/CountTime/CountTime"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import { + axiosCabinetById, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; const returnCountTime = (countDown: number) => { const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)) diff --git a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.tsx similarity index 94% rename from frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx rename to frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.tsx index b9a989d9e..f409efa4f 100644 --- a/frontend/src/components/CabinetInfoArea/CountTime/CountTime.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import { ReactComponent as ClockImg } from "@/assets/images/clock.svg"; +import { ReactComponent as ClockImg } from "@/Cabinet/assets/images/clock.svg"; interface CountTimeProps { minutes: string; diff --git a/frontend/src/components/CabinetList/CabinetList.container.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx similarity index 69% rename from frontend/src/components/CabinetList/CabinetList.container.tsx rename to frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx index e3ad681f6..64515e003 100644 --- a/frontend/src/components/CabinetList/CabinetList.container.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx @@ -1,17 +1,20 @@ import React from "react"; import { useRecoilValue } from "recoil"; -import { currentSectionNameState, isMultiSelectState } from "@/recoil/atoms"; +import { + currentSectionNameState, + isMultiSelectState, +} from "@/Cabinet/recoil/atoms"; import { currentSectionCabinetState, currentSectionColNumState, -} from "@/recoil/selectors"; -import CabinetList from "@/components/CabinetList/CabinetList"; -import EmptySection from "@/components/CabinetList/EmptySection/EmptySection"; -import RealViewNotification from "@/components/CabinetList/RealViewNotification/RealViewNotification"; -import MultiSelectFilterButton from "@/components/Common/MultiSelectFilterButton"; -import { CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import SectionType from "@/types/enum/map.type.enum"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/recoil/selectors"; +import CabinetList from "@/Cabinet/components/CabinetList/CabinetList"; +import EmptySection from "@/Cabinet/components/CabinetList/EmptySection/EmptySection"; +import RealViewNotification from "@/Cabinet/components/CabinetList/RealViewNotification/RealViewNotification"; +import MultiSelectFilterButton from "@/Cabinet/components/Common/MultiSelectFilterButton"; +import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import SectionType from "@/Cabinet/types/enum/map.type.enum"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; interface ICabinetListContainer { isAdmin: boolean; diff --git a/frontend/src/components/CabinetList/CabinetList.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetList.tsx similarity index 74% rename from frontend/src/components/CabinetList/CabinetList.tsx rename to frontend/src/Cabinet/components/CabinetList/CabinetList.tsx index ec26837fc..58802c633 100644 --- a/frontend/src/components/CabinetList/CabinetList.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetList.tsx @@ -1,9 +1,12 @@ import styled from "styled-components"; -import AdminCabinetListItem from "@/components/CabinetList/CabinetListItem/AdminCabinetListItem"; -import CabinetListItem from "@/components/CabinetList/CabinetListItem/CabinetListItem"; -import { CabinetInfo, CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import useMenu from "@/hooks/useMenu"; -import useMultiSelect from "@/hooks/useMultiSelect"; +import AdminCabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem"; +import CabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem"; +import { + CabinetInfo, + CabinetPreviewInfo, +} from "@/Cabinet/types/dto/cabinet.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; interface CabinetListInterface { colNum: number; @@ -43,7 +46,7 @@ const AdminToggleButtonStyled = styled.div` bottom: 40px; right: 40px; cursor: pointer; - background-image: url("/src/assets/images/proceedMultiSelect.svg"); + background-image: url("/src/Cabinet/assets/images/proceedMultiSelect.svg"); background-repeat: no-repeat; background-size: 100% 100%; `; diff --git a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx similarity index 93% rename from frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx rename to frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index ddcb1f3b6..7fb703ba2 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -4,19 +4,22 @@ import { currentCabinetIdState, selectedTypeOnSearchState, targetCabinetInfoState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { cabinetFilterMap, cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { CabinetInfo, CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { axiosCabinetById } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/assets/data/maps"; +import { + CabinetInfo, + CabinetPreviewInfo, +} from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { axiosCabinetById } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; const AdminCabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { const [currentCabinetId, setCurrentCabinetId] = useRecoilState( diff --git a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx similarity index 95% rename from frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx rename to frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx index fc0291034..ddc9d449e 100644 --- a/frontend/src/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -9,27 +9,27 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import UnavailableModal from "@/components/Modals/UnavailableModal/UnavailableModal"; +} from "@/Cabinet/recoil/atoms"; +import UnavailableModal from "@/Cabinet/components/Modals/UnavailableModal/UnavailableModal"; import { cabinetFilterMap, cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; +} from "@/Cabinet/assets/data/maps"; import { CabinetInfo, CabinetPreviewInfo, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import { axiosCabinetByBuildingFloor, axiosCabinetById, axiosMyLentInfo, -} from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const CabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { const myCabinetInfo = diff --git a/frontend/src/components/CabinetList/EmptySection/EmptySection.tsx b/frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx similarity index 91% rename from frontend/src/components/CabinetList/EmptySection/EmptySection.tsx rename to frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx index 1e1df8b71..5f570a72a 100644 --- a/frontend/src/components/CabinetList/EmptySection/EmptySection.tsx +++ b/frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx @@ -4,7 +4,7 @@ const EmptySection = ({ message }: { message: string }): JSX.Element => { return ( {message} diff --git a/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx b/frontend/src/Cabinet/components/CabinetList/RealViewNotification/RealViewNotification.tsx similarity index 97% rename from frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx rename to frontend/src/Cabinet/components/CabinetList/RealViewNotification/RealViewNotification.tsx index 67d12969b..28ebe8e99 100644 --- a/frontend/src/components/CabinetList/RealViewNotification/RealViewNotification.tsx +++ b/frontend/src/Cabinet/components/CabinetList/RealViewNotification/RealViewNotification.tsx @@ -33,7 +33,7 @@ const RealViewNotification: React.FC<{ colNum: number }> = (props) => { }; const ToolTipIcon = styled.div<{ hasEnoughWidth: boolean }>` - background-image: url("/src/assets/images/cautionSign.svg"); + background-image: url("/src/Cabinet/assets/images/cautionSign.svg"); width: 24px; height: 24px; margin: 0px auto; diff --git a/frontend/src/components/Card/Card.tsx b/frontend/src/Cabinet/components/Card/Card.tsx similarity index 97% rename from frontend/src/components/Card/Card.tsx rename to frontend/src/Cabinet/components/Card/Card.tsx index 7d55a85fb..c1a0272e3 100644 --- a/frontend/src/components/Card/Card.tsx +++ b/frontend/src/Cabinet/components/Card/Card.tsx @@ -97,7 +97,7 @@ export const CardTitleStyled = styled.div` `; const ToolTipIcon = styled.div` - background-image: url("/src/assets/images/notificationSign_grey.svg"); + background-image: url("/src/Cabinet/assets/images/notificationSign_grey.svg"); background-size: contain; width: 16px; height: 16px; diff --git a/frontend/src/components/Card/CardStyles.ts b/frontend/src/Cabinet/components/Card/CardStyles.ts similarity index 93% rename from frontend/src/components/Card/CardStyles.ts rename to frontend/src/Cabinet/components/Card/CardStyles.ts index de068c48e..80d830937 100644 --- a/frontend/src/components/Card/CardStyles.ts +++ b/frontend/src/Cabinet/components/Card/CardStyles.ts @@ -1,5 +1,5 @@ +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import styled from "styled-components"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; export const CardContentWrapper = styled.div` background-color: var(--white); diff --git a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx b/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx similarity index 91% rename from frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx rename to frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx index 4a3c08a74..10ed068a1 100644 --- a/frontend/src/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx +++ b/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx @@ -1,12 +1,12 @@ import { useState } from "react"; import styled from "styled-components"; -import Card from "@/components/Card/Card"; +import Card from "@/Cabinet/components/Card/Card"; import { CardContentStyled, ContentDetailStyled, -} from "@/components/Card/CardStyles"; -import ClubPasswordModalContainer from "@/components/Modals/ClubModal/ClubPasswordModal.container"; -import { ClubInfoResponseDto } from "@/types/dto/club.dto"; +} from "@/Cabinet/components/Card/CardStyles"; +import ClubPasswordModalContainer from "@/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container"; +import { ClubInfoResponseDto } from "@/Cabinet/types/dto/club.dto"; const ClubCabinetInfoCard = ({ clubInfo, @@ -35,7 +35,7 @@ const ClubCabinetInfoCard = ({ ? [ { onClick: handleLockLogoClick, - icon: "/src/assets/images/lock.svg", + icon: "/src/Cabinet/assets/images/lock.svg", isClickable: true, }, ] @@ -150,7 +150,7 @@ const CabinetIconStyled = styled.div` width: 22px; height: 18px; margin-right: 0.5rem; - background-image: url("/src/assets/images/leader.svg"); + background-image: url("/src/Cabinet/assets/images/leader.svg"); background-size: contain; background-repeat: no-repeat; `; diff --git a/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx b/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx similarity index 87% rename from frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx rename to frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx index dcd426897..e70d7ed0a 100644 --- a/frontend/src/components/Card/ClubNoticeCard/ClubNoticeCard.tsx +++ b/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import styled from "styled-components"; -import Card from "@/components/Card/Card"; -import { CardContentWrapper } from "@/components/Card/CardStyles"; -import ClubMemoModalContainer from "@/components/Modals/ClubModal/ClubMemoModal.container"; +import Card from "@/Cabinet/components/Card/Card"; +import { CardContentWrapper } from "@/Cabinet/components/Card/CardStyles"; +import ClubMemoModalContainer from "@/Cabinet/components/Modals/ClubModal/ClubMemoModal.container"; const ClubNoticeCard = ({ notice, @@ -30,7 +30,7 @@ const ClubNoticeCard = ({ ? [ { onClick: openModal, - icon: "/src/assets/images/edit.svg", + icon: "/src/Cabinet/assets/images/edit.svg", isClickable: true, }, ] diff --git a/frontend/src/components/Card/ExtensionCard/ExtensionCard.container.tsx b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.container.tsx similarity index 85% rename from frontend/src/components/Card/ExtensionCard/ExtensionCard.container.tsx rename to frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.container.tsx index 6f2af76fd..18958f830 100644 --- a/frontend/src/components/Card/ExtensionCard/ExtensionCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.container.tsx @@ -4,12 +4,12 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import ExtensionCard from "@/components/Card/ExtensionCard/ExtensionCard"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentDto, LentExtensionDto } from "@/types/dto/lent.dto"; -import { axiosCabinetById } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import ExtensionCard from "@/Cabinet/components/Card/ExtensionCard/ExtensionCard"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentDto, LentExtensionDto } from "@/Cabinet/types/dto/lent.dto"; +import { axiosCabinetById } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const ExtensionCardContainer = ({ extensionInfo, diff --git a/frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx similarity index 84% rename from frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx rename to frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx index a5b865571..1aec4650e 100644 --- a/frontend/src/components/Card/ExtensionCard/ExtensionCard.tsx +++ b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx @@ -1,14 +1,14 @@ -import { useState } from "react"; -import Card, { IButtonProps } from "@/components/Card/Card"; +import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, ContentDetailStyled, ContentInfoStyled, -} from "@/components/Card/CardStyles"; -import { NotificationModal } from "@/components/Modals/NotificationModal/NotificationModal"; -import { LentExtensionDto } from "@/types/dto/lent.dto"; -import { formatDate } from "@/utils/dateUtils"; +} from "@/Cabinet/components/Card/CardStyles"; +import { NotificationModal } from "@/Cabinet/components/Modals/NotificationModal/NotificationModal"; +import { LentExtensionDto } from "@/Cabinet/types/dto/lent.dto"; +import { formatDate } from "@/Cabinet/utils/dateUtils"; +import { useState } from "react"; interface ExtensionProps { extensionInfo: LentExtensionDto | null; diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx similarity index 82% rename from frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx rename to frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx index 57b6bc12f..c0f48e9d4 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx @@ -1,12 +1,12 @@ +import LentInfoCard from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard"; +import { getDefaultCabinetInfo } from "@/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; +import { myCabinetInfoState } from "@/Cabinet/recoil/atoms"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { getRemainingTime } from "@/Cabinet/utils/dateUtils"; import { useRecoilValue } from "recoil"; -import { myCabinetInfoState } from "@/recoil/atoms"; -import LentInfoCard from "@/components/Card/LentInfoCard/LentInfoCard"; -import { getDefaultCabinetInfo } from "@/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentDto } from "@/types/dto/lent.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { getRemainingTime } from "@/utils/dateUtils"; export interface MyCabinetInfo { name: string | null; diff --git a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx similarity index 92% rename from frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx rename to frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx index 559e9446d..2a441d12b 100644 --- a/frontend/src/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx @@ -1,16 +1,16 @@ import styled from "styled-components"; -import Card from "@/components/Card/Card"; +import Card from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, ContentDetailStyled, ContentInfoStyled, -} from "@/components/Card/CardStyles"; -import { MyCabinetInfo } from "@/components/Card/LentInfoCard/LentInfoCard.container"; -import { cabinetIconSrcMap } from "@/assets/data/maps"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { formatDate } from "@/utils/dateUtils"; +} from "@/Cabinet/components/Card/CardStyles"; +import { MyCabinetInfo } from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard.container"; +import { cabinetIconSrcMap } from "@/Cabinet/assets/data/maps"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { formatDate } from "@/Cabinet/utils/dateUtils"; const calculateFontSize = (userCount: number): string => { const baseSize = 1; diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx similarity index 90% rename from frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx rename to frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx index 6539a4dcb..6a3d148f9 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx @@ -1,19 +1,19 @@ import { deleteFcmToken, requestFcmAndGetDeviceToken, -} from "@/firebase/firebase-messaging-sw"; +} from "@/Cabinet/firebase/firebase-messaging-sw"; import { useEffect, useMemo, useState } from "react"; -import NotificationCard from "@/components/Card/NotificationCard/NotificationCard"; -import ModalPortal from "@/components/Modals/ModalPortal"; +import NotificationCard from "@/Cabinet/components/Card/NotificationCard/NotificationCard"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { AlarmInfo } from "@/types/dto/alarm.dto"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { AlarmInfo } from "@/Cabinet/types/dto/alarm.dto"; import { axiosUpdateAlarm, axiosUpdateDeviceToken, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { const [showResponseModal, setShowResponseModal] = useState(false); diff --git a/frontend/src/components/Card/NotificationCard/NotificationCard.tsx b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.tsx similarity index 82% rename from frontend/src/components/Card/NotificationCard/NotificationCard.tsx rename to frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.tsx index 3b9a9d56c..c07812669 100644 --- a/frontend/src/components/Card/NotificationCard/NotificationCard.tsx +++ b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.tsx @@ -1,11 +1,11 @@ -import Card, { IButtonProps } from "@/components/Card/Card"; +import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, ContentInfoStyled, -} from "@/components/Card/CardStyles"; -import ToggleSwitch from "@/components/Common/ToggleSwitch"; -import { AlarmInfo } from "@/types/dto/alarm.dto"; +} from "@/Cabinet/components/Card/CardStyles"; +import ToggleSwitch from "@/Cabinet/components/Common/ToggleSwitch"; +import { AlarmInfo } from "@/Cabinet/types/dto/alarm.dto"; interface NotificationCardProps { alarm: AlarmInfo; diff --git a/frontend/src/components/Card/ProfileCard/ProfileCard.container.tsx b/frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.container.tsx similarity index 86% rename from frontend/src/components/Card/ProfileCard/ProfileCard.container.tsx rename to frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.container.tsx index f62303f92..ffebf6f60 100644 --- a/frontend/src/components/Card/ProfileCard/ProfileCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.container.tsx @@ -4,9 +4,9 @@ import { currentBuildingNameState, currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; -import ProfileCard from "@/components/Card/ProfileCard/ProfileCard"; -import { removeCookie } from "@/api/react_cookie/cookies"; +} from "@/Cabinet/recoil/atoms"; +import ProfileCard from "@/Cabinet/components/Card/ProfileCard/ProfileCard"; +import { removeCookie } from "@/Cabinet/api/react_cookie/cookies"; const ProfileCardContainer = ({ name }: { name: string | null }) => { const navigator = useNavigate(); diff --git a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx b/frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.tsx similarity index 91% rename from frontend/src/components/Card/ProfileCard/ProfileCard.tsx rename to frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.tsx index 92efc4b8b..18a96e3fe 100644 --- a/frontend/src/components/Card/ProfileCard/ProfileCard.tsx +++ b/frontend/src/Cabinet/components/Card/ProfileCard/ProfileCard.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; -import Card, { IButtonProps } from "@/components/Card/Card"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; +import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; type ProfileProps = { name: string | null; diff --git a/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx b/frontend/src/Cabinet/components/Card/ThemeColorCard/ColorPicker.tsx similarity index 100% rename from frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx rename to frontend/src/Cabinet/components/Card/ThemeColorCard/ColorPicker.tsx diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx b/frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.container.tsx similarity index 95% rename from frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx rename to frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.container.tsx index a0662bd6f..6a12343e8 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.container.tsx @@ -1,6 +1,6 @@ +import ThemeColorCard from "@/Cabinet/components/Card/ThemeColorCard/ThemeColorCard"; +import ColorType from "@/Cabinet/types/enum/color.type.enum"; import { useEffect, useState } from "react"; -import ThemeColorCard from "@/components/Card/ThemeColorCard/ThemeColorCard"; -import ColorType from "@/types/enum/color.type.enum"; const ThemeColorCardContainer = () => { const savedMainColor = diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.tsx similarity index 93% rename from frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx rename to frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.tsx index 30358665b..8a74cdd87 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -1,15 +1,15 @@ -import styled, { css } from "styled-components"; -import Card from "@/components/Card/Card"; +import Card from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, ContentInfoStyled, -} from "@/components/Card/CardStyles"; -import ColorPicker from "@/components/Card/ThemeColorCard/ColorPicker"; +} from "@/Cabinet/components/Card/CardStyles"; +import ColorPicker from "@/Cabinet/components/Card/ThemeColorCard/ColorPicker"; import { customColors, themeColorData, -} from "@/components/Card/ThemeColorCard/colorInfo"; +} from "@/Cabinet/components/Card/ThemeColorCard/colorInfo"; +import styled, { css } from "styled-components"; interface ThemeColorProps { showColorPicker: boolean; diff --git a/frontend/src/components/Card/ThemeColorCard/colorInfo.ts b/frontend/src/Cabinet/components/Card/ThemeColorCard/colorInfo.ts similarity index 91% rename from frontend/src/components/Card/ThemeColorCard/colorInfo.ts rename to frontend/src/Cabinet/components/Card/ThemeColorCard/colorInfo.ts index 66b284607..fe643ca98 100644 --- a/frontend/src/components/Card/ThemeColorCard/colorInfo.ts +++ b/frontend/src/Cabinet/components/Card/ThemeColorCard/colorInfo.ts @@ -1,4 +1,4 @@ -import ColorType from "@/types/enum/color.type.enum"; +import ColorType from "@/Cabinet/types/enum/color.type.enum"; interface ColorData { title: string; diff --git a/frontend/src/components/Club/AdminClubLog.container.tsx b/frontend/src/Cabinet/components/Club/AdminClubLog.container.tsx similarity index 85% rename from frontend/src/components/Club/AdminClubLog.container.tsx rename to frontend/src/Cabinet/components/Club/AdminClubLog.container.tsx index ae487b13a..95b7235f9 100644 --- a/frontend/src/components/Club/AdminClubLog.container.tsx +++ b/frontend/src/Cabinet/components/Club/AdminClubLog.container.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -import AdminClubLog from "@/components/Club/AdminClubLog"; -import { ClubLogResponseType, ClubUserDto } from "@/types/dto/lent.dto"; -import { axiosGetClubUserLog } from "@/api/axios/axios.custom"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import AdminClubLog from "@/Cabinet/components/Club/AdminClubLog"; +import { ClubLogResponseType, ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import { axiosGetClubUserLog } from "@/Cabinet/api/axios/axios.custom"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const AdminClubLogContainer = (props: any) => { const [logs, setLogs] = useState(undefined); diff --git a/frontend/src/components/Club/AdminClubLog.tsx b/frontend/src/Cabinet/components/Club/AdminClubLog.tsx similarity index 75% rename from frontend/src/components/Club/AdminClubLog.tsx rename to frontend/src/Cabinet/components/Club/AdminClubLog.tsx index f9ed15e56..2f7f41493 100644 --- a/frontend/src/components/Club/AdminClubLog.tsx +++ b/frontend/src/Cabinet/components/Club/AdminClubLog.tsx @@ -1,7 +1,8 @@ import styled, { css } from "styled-components"; -import ClubLogTable from "@/components/Club/ClubLogTable"; -import LeftSectionButton from "@/assets/images/LeftSectionButton.svg"; -import { IClubLog } from "@/types/dto/lent.dto"; +import ClubLogTable from "@/Cabinet/components/Club/ClubLogTable"; +import { MoveSectionButtonStyled } from "@/Cabinet/components/SectionPagination/SectionPagination"; +import LeftSectionButton from "@/Cabinet/assets/images/LeftSectionButton.svg"; +import { IClubLog } from "@/Cabinet/types/dto/lent.dto"; const AdminClubLog = ({ logs, @@ -84,22 +85,22 @@ const SectionBarStyled = styled.div` align-items: center; `; -const MoveSectionButtonStyled = styled.img<{ arrowReversed?: boolean }>` - width: 24px; - height: 24px; - margin: 0px 15px; - opacity: 70%; - cursor: pointer; - transform: rotate(${(props) => (props.arrowReversed ? "180deg" : "0")}); - transition: all 0.2s; - @media (hover: hover) and (pointer: fine) { - &:hover { - opacity: 100%; - transform: rotate(${(props) => (props.arrowReversed ? "180deg" : "0")}) - scale(1.3); - } - } -`; +// const MoveSectionButtonStyled = styled.img<{ arrowReversed?: boolean }>` +// width: 24px; +// height: 24px; +// margin: 0px 15px; +// opacity: 70%; +// cursor: pointer; +// transform: rotate(${(props) => (props.arrowReversed ? "180deg" : "0")}); +// transition: all 0.2s; +// @media (hover: hover) and (pointer: fine) { +// &:hover { +// opacity: 100%; +// transform: rotate(${(props) => (props.arrowReversed ? "180deg" : "0")}) +// scale(1.3); +// } +// } +// `; const SectionIndexStyled = styled.div` width: 100%; diff --git a/frontend/src/components/Club/ClubInfo.tsx b/frontend/src/Cabinet/components/Club/ClubInfo.tsx similarity index 78% rename from frontend/src/components/Club/ClubInfo.tsx rename to frontend/src/Cabinet/components/Club/ClubInfo.tsx index cdb38d83a..33b933dff 100644 --- a/frontend/src/components/Club/ClubInfo.tsx +++ b/frontend/src/Cabinet/components/Club/ClubInfo.tsx @@ -1,15 +1,15 @@ import { useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; import styled from "styled-components"; -import { userState } from "@/recoil/atoms"; -import ClubCabinetInfoCard from "@/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard"; -import ClubNoticeCard from "@/components/Card/ClubNoticeCard/ClubNoticeCard"; -import ClubMemberListContainer from "@/components/Club/ClubMemberList/ClubMemberList.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { ClubInfoResponseDto } from "@/types/dto/club.dto"; -import useClubInfo from "@/hooks/useClubInfo"; -import useMenu from "@/hooks/useMenu"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { userState } from "@/Cabinet/recoil/atoms"; +import ClubCabinetInfoCard from "@/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard"; +import ClubNoticeCard from "@/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard"; +import ClubMemberListContainer from "@/Cabinet/components/Club/ClubMemberList/ClubMemberList.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ClubInfoResponseDto } from "@/Cabinet/types/dto/club.dto"; +import useClubInfo from "@/Cabinet/hooks/useClubInfo"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const ClubInfo = () => { const myInfo = useRecoilValue(userState); @@ -34,7 +34,7 @@ const ClubInfo = () => { 동아리 사물함이 없어요 - + ) : ( diff --git a/frontend/src/components/Club/ClubLogTable.tsx b/frontend/src/Cabinet/components/Club/ClubLogTable.tsx similarity index 89% rename from frontend/src/components/Club/ClubLogTable.tsx rename to frontend/src/Cabinet/components/Club/ClubLogTable.tsx index 8e661e434..66103f15a 100644 --- a/frontend/src/components/Club/ClubLogTable.tsx +++ b/frontend/src/Cabinet/components/Club/ClubLogTable.tsx @@ -1,9 +1,9 @@ import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { selectedClubInfoState } from "@/recoil/atoms"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { ClubLogResponseType, ClubUserDto } from "@/types/dto/lent.dto"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { selectedClubInfoState } from "@/Cabinet/recoil/atoms"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ClubLogResponseType, ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const ClubLogTable = ({ ClubList }: { ClubList: ClubLogResponseType }) => { const [selectedClubInfo, setSelectedClubInfo] = useRecoilState( diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx similarity index 89% rename from frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx index 134e28477..4c7ceef2f 100644 --- a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container.tsx @@ -5,9 +5,9 @@ import { targetClubInfoState, targetClubUserInfoState, userState, -} from "@/recoil/atoms"; -import ClubMemberInfoArea from "@/components/Club/ClubMemberInfoArea/ClubMemberInfoArea"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import ClubMemberInfoArea from "@/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea"; +import useMenu from "@/Cabinet/hooks/useMenu"; export type TClubModalState = "deleteModal" | "mandateModal"; diff --git a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx similarity index 88% rename from frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx index 43b9d58ca..2dff4552a 100644 --- a/frontend/src/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -2,23 +2,23 @@ import styled from "styled-components"; import { ICurrentClubModalStateInfo, TClubModalState, -} from "@/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; -import Button from "@/components/Common/Button"; -import DeleteClubMemberModal from "@/components/Modals/ClubModal/DeleteClubMemberModal"; -import MandateClubMemberModal from "@/components/Modals/ClubModal/MandateClubMemberModal"; +} from "@/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; +import Button from "@/Cabinet/components/Common/Button"; +import DeleteClubMemberModal from "@/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal"; +import MandateClubMemberModal from "@/Cabinet/components/Modals/ClubModal/MandateClubMemberModal"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; +} from "@/Cabinet/assets/data/maps"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; import { ClubCabinetInfo, ClubResponseDto, ClubUserResponseDto, -} from "@/types/dto/club.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/types/dto/club.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; interface ClubMemberInfoAreaProps { selectedClubInfo: ClubResponseDto; @@ -132,11 +132,11 @@ const CabiLogoStyled = styled.div` `; const ClubMemberInfoAreaStyled = styled.div` position: fixed; - top: 80px; + top: 120px; right: 0; min-width: 330px; width: 330px; - height: calc(100% - 80px); + height: calc(100% - 120px); padding: 40px; z-index: 9; transform: translateX(120%); @@ -193,7 +193,7 @@ const ClubMasterIconStyled = styled.div` min-width: 24px; min-height: 24px; margin-bottom: 10px; - background-image: url("/src/assets/images/leader.svg"); + background-image: url("/src/Cabinet/assets/images/leader.svg"); background-size: contain; background-repeat: no-repeat; `; diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.container.tsx similarity index 90% rename from frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.container.tsx index b61bb24f1..7e7facac4 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberList.container.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.container.tsx @@ -1,9 +1,12 @@ import { useEffect, useState } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; -import { targetClubUserInfoState, userState } from "@/recoil/atoms"; -import ClubMemberList from "@/components/Club/ClubMemberList/ClubMemberList"; -import { ClubInfoResponseDto, ClubUserResponseDto } from "@/types/dto/club.dto"; -import useMenu from "@/hooks/useMenu"; +import { targetClubUserInfoState, userState } from "@/Cabinet/recoil/atoms"; +import ClubMemberList from "@/Cabinet/components/Club/ClubMemberList/ClubMemberList"; +import { + ClubInfoResponseDto, + ClubUserResponseDto, +} from "@/Cabinet/types/dto/club.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; export type TClubMemberModalState = "addModal"; diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx similarity index 88% rename from frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx index 19d368da7..f03696686 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberList.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx @@ -1,16 +1,16 @@ import { useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; import styled from "styled-components"; -import { targetClubUserInfoState, userState } from "@/recoil/atoms"; +import { targetClubUserInfoState, userState } from "@/Cabinet/recoil/atoms"; import { ICurrentClubMemberModalStateInfo, TClubMemberModalState, -} from "@/components/Club/ClubMemberList/ClubMemberList.container"; -import ClubMemberListItem from "@/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import AddClubMemberModalContainer from "@/components/Modals/ClubModal/AddClubMemberModal.container"; -import { ReactComponent as Select } from "@/assets/images/selectMaincolor.svg"; -import { ClubUserResponseDto } from "@/types/dto/club.dto"; +} from "@/Cabinet/components/Club/ClubMemberList/ClubMemberList.container"; +import ClubMemberListItem from "@/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import AddClubMemberModalContainer from "@/Cabinet/components/Modals/ClubModal/AddClubMemberModal.container"; +import { ReactComponent as Select } from "@/Cabinet/assets/images/selectMaincolor.svg"; +import { ClubUserResponseDto } from "@/Cabinet/types/dto/club.dto"; interface ClubMemberListProps { isLoading: boolean; @@ -46,7 +46,9 @@ const ClubMemberList = ({

동아리 멤버

- + {clubUserCount} diff --git a/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx similarity index 90% rename from frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx rename to frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx index 66807ec52..0870e1627 100644 --- a/frontend/src/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx @@ -1,8 +1,8 @@ import { memo } from "react"; import styled, { css } from "styled-components"; -import { ReactComponent as CrownImg } from "@/assets/images/crown.svg"; -import { ReactComponent as UserImg } from "@/assets/images/privateIcon.svg"; -import { ClubUserResponseDto } from "@/types/dto/club.dto"; +import { ReactComponent as CrownImg } from "@/Cabinet/assets/images/crown.svg"; +import { ReactComponent as UserImg } from "@/Cabinet/assets/images/privateIcon.svg"; +import { ClubUserResponseDto } from "@/Cabinet/types/dto/club.dto"; interface ClubMemberListItemProps { bgColor?: string; diff --git a/frontend/src/components/Common/Button.tsx b/frontend/src/Cabinet/components/Common/Button.tsx similarity index 98% rename from frontend/src/components/Common/Button.tsx rename to frontend/src/Cabinet/components/Common/Button.tsx index c5818725f..b799efdc3 100644 --- a/frontend/src/components/Common/Button.tsx +++ b/frontend/src/Cabinet/components/Common/Button.tsx @@ -1,4 +1,3 @@ -import React from "react"; import styled, { css } from "styled-components"; interface ButtonInterface { diff --git a/frontend/src/components/Common/ClubListDropdown.tsx b/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx similarity index 97% rename from frontend/src/components/Common/ClubListDropdown.tsx rename to frontend/src/Cabinet/components/Common/ClubListDropdown.tsx index 65569c513..51ad382c6 100644 --- a/frontend/src/components/Common/ClubListDropdown.tsx +++ b/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx @@ -30,7 +30,7 @@ const ClubListDropd = ({
)}

{currentName}

- + {options?.map((option) => { diff --git a/frontend/src/components/Common/Dropdown.tsx b/frontend/src/Cabinet/components/Common/Dropdown.tsx similarity index 92% rename from frontend/src/components/Common/Dropdown.tsx rename to frontend/src/Cabinet/components/Common/Dropdown.tsx index b1733efef..42826a742 100644 --- a/frontend/src/components/Common/Dropdown.tsx +++ b/frontend/src/Cabinet/components/Common/Dropdown.tsx @@ -1,8 +1,14 @@ import { useState } from "react"; import styled, { css } from "styled-components"; -interface IDropdown { - options: { name: string; value: any; imageSrc?: string }[]; +export interface IDropdownOptions { + name: string; + value: any; + imageSrc?: string; +} + +export interface IDropdown { + options: IDropdownOptions[]; defaultValue: string; defaultImageSrc?: string; onChangeValue?: (param: any) => any; @@ -12,6 +18,7 @@ const Dropdown = ({ options, defaultValue, onChangeValue }: IDropdown) => { const [currentName, setCurrentName] = useState(defaultValue); const [isOpen, setIsOpen] = useState(false); const selectedIdx = options.findIndex((op) => op.name === currentName) ?? 0; + return ( { }} isOpen={isOpen} > - {options[selectedIdx].imageSrc && ( + {options[selectedIdx]?.imageSrc?.length && (
)}

{currentName}

- +
{options?.map((option) => { diff --git a/frontend/src/components/Common/LoadingAnimation.tsx b/frontend/src/Cabinet/components/Common/LoadingAnimation.tsx similarity index 100% rename from frontend/src/components/Common/LoadingAnimation.tsx rename to frontend/src/Cabinet/components/Common/LoadingAnimation.tsx diff --git a/frontend/src/components/Common/MultiSelectButton.tsx b/frontend/src/Cabinet/components/Common/MultiSelectButton.tsx similarity index 100% rename from frontend/src/components/Common/MultiSelectButton.tsx rename to frontend/src/Cabinet/components/Common/MultiSelectButton.tsx diff --git a/frontend/src/components/Common/MultiSelectFilterButton.tsx b/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx similarity index 90% rename from frontend/src/components/Common/MultiSelectFilterButton.tsx rename to frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx index 223af324b..8f97cbd02 100644 --- a/frontend/src/components/Common/MultiSelectFilterButton.tsx +++ b/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx @@ -17,8 +17,8 @@ const MultiSelectFilterButton = ({ diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx similarity index 98% rename from frontend/src/components/Common/MultiToggleSwitch.tsx rename to frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx index 8fd8da98e..44a335380 100644 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx @@ -73,7 +73,7 @@ const WrapperStyled = styled.div` width: fit-content; min-width: 50px; border-radius: 10px; - font-size: 0.9rem; + font-size: 0.875rem; height: 30px; font-weight: 500; background-color: transparent; diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx similarity index 55% rename from frontend/src/components/Common/MultiToggleSwitchSeparated.tsx rename to frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx index 7770bd0b2..cc3c9db9b 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx @@ -3,19 +3,21 @@ import styled from "styled-components"; export interface toggleItem { name: string; - key: number; + key: string; } interface MultiToggleSwitchProps { initialState: T; setState: React.Dispatch>; toggleList: toggleItem[]; + fontSize?: string; } const MultiToggleSwitchSeparated = ({ initialState, setState, toggleList, + fontSize = "0.875rem", }: MultiToggleSwitchProps) => { const wrapperRef = useRef(null); @@ -23,36 +25,31 @@ const MultiToggleSwitchSeparated = ({ const buttons = wrapperRef.current?.querySelectorAll("button"); buttons?.forEach((button) => { - if (button.className === `${initialState}`) { - button.style.color = "white"; - button.style.backgroundColor = "var(--main-color)"; + if (button.classList.contains(`${initialState}`)) { + button.classList.add("selected"); + } else { + button.classList.add("categoryButton"); } }); }, [initialState]); function switchToggle(e: any) { const target = e.target as HTMLButtonElement; - if (target === e.currentTarget) return; - // setPage(0); + const selectedKey = target.className.split(" ")[0]; const buttons = wrapperRef.current?.querySelectorAll("button"); - buttons?.forEach((button) => { - button.style.color = "black"; - button.style.backgroundColor = "var(--lightgray-color)"; + button.classList.remove("selected"); }); - - target.style.color = "white"; - target.style.backgroundColor = "var(--main-color)"; - - setState(target.className as React.SetStateAction); + target.classList.add("selected"); + setState(selectedKey as React.SetStateAction); } return ( - + {toggleList.map((item) => ( - ))} @@ -60,26 +57,46 @@ const MultiToggleSwitchSeparated = ({ ); }; -const WrapperStyled = styled.div` +const WrapperStyled = styled.div<{ + fontSize: string; +}>` width: 100%; display: flex; + flex-wrap: wrap; + justify-content: space-between; align-items: center; - border-radius: 10px; + gap: 10px; button { display: flex; justify-content: center; align-items: center; - width: fit-content; - min-width: 50px; + width: calc(16.66% - 10px); + height: 36px; + min-width: 40px; border-radius: 10px; - font-size: 0.9rem; - height: 30px; + font-size: ${(props) => props.fontSize}; font-weight: 500; background-color: var(--lightgray-color); - color: black; + color: gray; padding: 4px 12px; - margin: 0px 4px; + box-sizing: border-box; + } + + @media (max-width: 560px) { + button { + width: calc(33.333% - 10px); + } + } + + button.categoryButton { + color: black; + background-color: var(--white); + } + + button.selected { + color: white; + background-color: #3f69fd; } `; diff --git a/frontend/src/components/Common/PillButton.tsx b/frontend/src/Cabinet/components/Common/PillButton.tsx similarity index 100% rename from frontend/src/components/Common/PillButton.tsx rename to frontend/src/Cabinet/components/Common/PillButton.tsx diff --git a/frontend/src/components/Common/Selector.tsx b/frontend/src/Cabinet/components/Common/Selector.tsx similarity index 93% rename from frontend/src/components/Common/Selector.tsx rename to frontend/src/Cabinet/components/Common/Selector.tsx index 400bbafac..170761990 100644 --- a/frontend/src/components/Common/Selector.tsx +++ b/frontend/src/Cabinet/components/Common/Selector.tsx @@ -1,5 +1,5 @@ +import PillButton from "@/Cabinet/components/Common/PillButton"; import styled from "styled-components"; -import PillButton from "@/components/Common/PillButton"; interface ISelectorProps { iconSrc?: string; diff --git a/frontend/src/components/Common/ToggleSwitch.tsx b/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx similarity index 100% rename from frontend/src/components/Common/ToggleSwitch.tsx rename to frontend/src/Cabinet/components/Common/ToggleSwitch.tsx diff --git a/frontend/src/components/Common/WarningNotification.tsx b/frontend/src/Cabinet/components/Common/WarningNotification.tsx similarity index 91% rename from frontend/src/components/Common/WarningNotification.tsx rename to frontend/src/Cabinet/components/Common/WarningNotification.tsx index 213da972e..cefd1ff05 100644 --- a/frontend/src/components/Common/WarningNotification.tsx +++ b/frontend/src/Cabinet/components/Common/WarningNotification.tsx @@ -20,7 +20,7 @@ const WarningNotification: React.FC = ({ const WarningIcon = styled.div<{ isVisible: boolean }>` display: ${({ isVisible }) => (isVisible ? "block" : "none")}; - background-image: url("/src/assets/images/warningTriangleIcon.svg"); + background-image: url("/src/Cabinet/assets/images/warningTriangleIcon.svg"); width: 24px; height: 24px; margin: 0px auto; @@ -47,7 +47,7 @@ const WarningBox = styled.div` white-space: pre-line; z-index: 100; transition: visibility 0.5s, color 0.5s, background-color 0.5s, width 0.5s, - padding 0.5s ease-in-out; + padding 0.5s ease-in-out; `; const WarningWrapper = styled.div<{ isVisible: boolean }>` @@ -62,9 +62,9 @@ const WarningWrapper = styled.div<{ isVisible: boolean }>` background-color: rgba(0, 0, 0, 0.8); &:before { border-color: transparent transparent rgba(0, 0, 0, 0.8) - rgba(0, 0, 0, 0.8); + rgba(0, 0, 0, 0.8); } } `; - + export default WarningNotification; diff --git a/frontend/src/components/Home/ManualContentBox.tsx b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx similarity index 90% rename from frontend/src/components/Home/ManualContentBox.tsx rename to frontend/src/Cabinet/components/Home/ManualContentBox.tsx index 62c405ff8..1d224aef2 100644 --- a/frontend/src/components/Home/ManualContentBox.tsx +++ b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx @@ -1,9 +1,9 @@ import styled, { css, keyframes } from "styled-components"; -import { manualContentData } from "@/assets/data/ManualContent"; -import { ReactComponent as ClockImg } from "@/assets/images/clock.svg"; -import { ReactComponent as ManualPeopleImg } from "@/assets/images/manualPeople.svg"; -import { ReactComponent as MoveBtnImg } from "@/assets/images/moveButton.svg"; -import ContentStatus from "@/types/enum/content.status.enum"; +import { manualContentData } from "@/Cabinet/assets/data/ManualContent"; +import { ReactComponent as ClockImg } from "@/Cabinet/assets/images/clock.svg"; +import { ReactComponent as ManualPeopleImg } from "@/Cabinet/assets/images/manualPeople.svg"; +import { ReactComponent as MoveBtnImg } from "@/Cabinet/assets/images/moveButton.svg"; +import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface MaunalContentBoxProps { contentStatus: ContentStatus; @@ -111,7 +111,7 @@ const MaunalContentBoxStyled = styled.div<{ `} p { - margin-top: 80px; + margin-top: 120px; ${({ contentStatus }) => (contentStatus === ContentStatus.PENDING || contentStatus === ContentStatus.IN_SESSION) && diff --git a/frontend/src/components/Home/ServiceManual.tsx b/frontend/src/Cabinet/components/Home/ServiceManual.tsx similarity index 95% rename from frontend/src/components/Home/ServiceManual.tsx rename to frontend/src/Cabinet/components/Home/ServiceManual.tsx index 4ff428e69..1d05ef481 100644 --- a/frontend/src/components/Home/ServiceManual.tsx +++ b/frontend/src/Cabinet/components/Home/ServiceManual.tsx @@ -1,8 +1,8 @@ +import MaunalContentBox from "@/Cabinet/components/Home/ManualContentBox"; +import ManualModal from "@/Cabinet/components/Modals/ManualModal/ManualModal"; +import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; import { useState } from "react"; import styled from "styled-components"; -import MaunalContentBox from "@/components/Home/ManualContentBox"; -import ManualModal from "@/components/Modals/ManualModal/ManualModal"; -import ContentStatus from "@/types/enum/content.status.enum"; const ServiceManual = ({ lentStartHandler, diff --git a/frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx b/frontend/src/Cabinet/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx similarity index 100% rename from frontend/src/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx rename to frontend/src/Cabinet/components/LeftNav/CabinetColorTable/CabinetColorTable.tsx diff --git a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx similarity index 88% rename from frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx rename to frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx index eab36d48e..acdd21610 100644 --- a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -14,19 +14,19 @@ import { currentSectionNameState, myCabinetInfoState, numberOfAdminWorkState, - userState, -} from "@/recoil/atoms"; -import { currentBuildingFloorState } from "@/recoil/selectors"; -import LeftMainNav from "@/components/LeftNav/LeftMainNav/LeftMainNav"; +} from "@/Cabinet/recoil/atoms"; +import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; +import LeftMainNav from "@/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav"; import { CabinetInfoByBuildingFloorDto, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; -import { axiosCabinetByBuildingFloor } from "@/api/axios/axios.custom"; -import { removeCookie } from "@/api/react_cookie/cookies"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/types/dto/cabinet.dto"; +import { axiosCabinetByBuildingFloor } from "@/Cabinet/api/axios/axios.custom"; +import { removeCookie } from "@/Cabinet/api/react_cookie/cookies"; +import useMenu from "@/Cabinet/hooks/useMenu"; const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { + const currentBuildingName = useRecoilValue(currentBuildingNameState); const floors = useRecoilValue>(currentBuildingFloorState); const [currentFloor, setCurrentFloor] = useRecoilState( currentFloorNumberState @@ -68,7 +68,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { currentSectionFromPersist && sections.includes(currentSectionFromPersist) ? setCurrentSection(currentSectionFromPersist) - : setCurrentSection(response.data[0].section); + : setCurrentSection(response.data[0]?.section); }) .catch((error) => { console.error(error); @@ -104,11 +104,6 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { closeAll(); }; - const onClickLentLogButton = () => { - navigator("log"); - closeAll(); - }; - const onClickSearchButton = () => { navigator("search"); closeAll(); @@ -150,16 +145,16 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { resetBuilding(); resetCurrentFloor(); resetCurrentSection(); - navigator("login"); + navigator("/login"); }; return ( - - Home - - {floors && - floors.map((floor, index) => ( + {currentBuildingName === "새롬관" && ( + <> + + Home + + {floors && + floors.map((floor, index) => ( + onClickFloorButton(floor)} + key={index} + > + {floor + "층"} + + ))} onClickFloorButton(floor)} - key={index} + onClick={onClickAvailableButton} > - {floor + "층"} + 사용가능 - ))} - - 사용가능 - + + )} @@ -121,7 +125,7 @@ const LeftMainNav = ({ )} - {!isAdmin && ( + {!isAdmin && currentBuildingName === "새롬관" && ( <> { const floorSection = useRecoilValue>(currentFloorSectionState); diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx similarity index 93% rename from frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx rename to frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index d291f4bfe..f5adb344b 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; -import CabinetColorTable from "@/components/LeftNav/CabinetColorTable/CabinetColorTable"; -import LeftSectionNavClubs from "@/components/LeftNav/LeftSectionNav/LeftSectionNavClubs"; -import { ReactComponent as LinkImg } from "@/assets/images/link.svg"; +import CabinetColorTable from "@/Cabinet/components/LeftNav/CabinetColorTable/CabinetColorTable"; +import LeftSectionNavClubs from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs"; +import { ReactComponent as LinkImg } from "@/Cabinet/assets/images/link.svg"; interface ILeftSectionNav { isVisible: boolean; diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx similarity index 86% rename from frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx rename to frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx index fe0ceb52e..6c425b127 100644 --- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx @@ -1,12 +1,12 @@ import { useRecoilState, useRecoilValue } from "recoil"; import styled from "styled-components"; -import { myClubListState, targetClubInfoState } from "@/recoil/atoms"; -import { FloorSectionStyled } from "@/components/LeftNav/LeftSectionNav/LeftSectionNav"; +import { myClubListState, targetClubInfoState } from "@/Cabinet/recoil/atoms"; +import { FloorSectionStyled } from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav"; import { ClubPaginationResponseDto, ClubResponseDto, -} from "@/types/dto/club.dto"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/types/dto/club.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; const LeftSectionNavClubs = () => { const clubList = useRecoilValue(myClubListState); diff --git a/frontend/src/components/LentLog/AdminCabinetLentLog.container.tsx b/frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.container.tsx similarity index 79% rename from frontend/src/components/LentLog/AdminCabinetLentLog.container.tsx rename to frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.container.tsx index cbfde1e8b..d001b3cae 100644 --- a/frontend/src/components/LentLog/AdminCabinetLentLog.container.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.container.tsx @@ -1,12 +1,11 @@ import { useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; -import { currentCabinetIdState } from "@/recoil/atoms"; -import AdminCabinetLentLog from "@/components/LentLog/AdminCabinetLentLog"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { axiosGetCabinetLentLog } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import { getTotalPage } from "@/utils/dateUtils"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { currentCabinetIdState } from "@/Cabinet/recoil/atoms"; +import AdminCabinetLentLog from "@/Cabinet/components/LentLog/AdminCabinetLentLog"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { axiosGetCabinetLentLog } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const AdminCabinetLentLogContainer = () => { const { closeLent } = useMenu(); diff --git a/frontend/src/components/LentLog/AdminCabinetLentLog.tsx b/frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.tsx similarity index 84% rename from frontend/src/components/LentLog/AdminCabinetLentLog.tsx rename to frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.tsx index 465278fb9..62cfb8e14 100644 --- a/frontend/src/components/LentLog/AdminCabinetLentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminCabinetLentLog.tsx @@ -1,6 +1,6 @@ import styled, { css } from "styled-components"; -import AdminCabinetLogTable from "@/components/LentLog/LogTable/AdminCabinetLogTable"; -import { ILentLog } from "@/types/dto/lent.dto"; +import AdminCabinetLogTable from "@/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable"; +import { ILentLog } from "@/Cabinet/types/dto/lent.dto"; const AdminCabinetLentLog = ({ closeLent, @@ -23,7 +23,10 @@ const AdminCabinetLentLog = ({ > - + @@ -36,7 +39,10 @@ const AdminCabinetLentLog = ({ > - + diff --git a/frontend/src/components/LentLog/AdminLentLog.tsx b/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx similarity index 87% rename from frontend/src/components/LentLog/AdminLentLog.tsx rename to frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx index 14b93ca70..d1eb6884c 100644 --- a/frontend/src/components/LentLog/AdminLentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; import styled from "styled-components"; -import AdminCabinetLentLogContainer from "@/components/LentLog/AdminCabinetLentLog.container"; -import AdminUserLentLogContainer from "@/components/LentLog/AdminUserLentLog.container"; -import useMenu from "@/hooks/useMenu"; +import AdminCabinetLentLogContainer from "@/Cabinet/components/LentLog/AdminCabinetLentLog.container"; +import AdminUserLentLogContainer from "@/Cabinet/components/LentLog/AdminUserLentLog.container"; +import useMenu from "@/Cabinet/hooks/useMenu"; const AdminLentLog = ({ lentType }: { lentType: string }) => { const { closeLent } = useMenu(); @@ -36,7 +36,10 @@ const AdminLentLog = ({ lentType }: { lentType: string }) => { {isSearchPage && ( - + )} {getLentTypeText(togglelentType)} 대여 기록 diff --git a/frontend/src/components/LentLog/AdminUserLentLog.container.tsx b/frontend/src/Cabinet/components/LentLog/AdminUserLentLog.container.tsx similarity index 79% rename from frontend/src/components/LentLog/AdminUserLentLog.container.tsx rename to frontend/src/Cabinet/components/LentLog/AdminUserLentLog.container.tsx index c3d81056a..637e1cc8a 100644 --- a/frontend/src/components/LentLog/AdminUserLentLog.container.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminUserLentLog.container.tsx @@ -1,13 +1,11 @@ import { useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; -import { targetUserInfoState } from "@/recoil/atoms"; -import AdminUserLentLog from "@/components/LentLog/AdminUserLentLog"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { axiosGetUserLentLog } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import { getTotalPage } from "@/utils/dateUtils"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; - +import { targetUserInfoState } from "@/Cabinet/recoil/atoms"; +import AdminUserLentLog from "@/Cabinet/components/LentLog/AdminUserLentLog"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { axiosGetUserLentLog } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const AdminUserLentLogContainer = () => { const { closeLent } = useMenu(); diff --git a/frontend/src/components/LentLog/AdminUserLentLog.tsx b/frontend/src/Cabinet/components/LentLog/AdminUserLentLog.tsx similarity index 85% rename from frontend/src/components/LentLog/AdminUserLentLog.tsx rename to frontend/src/Cabinet/components/LentLog/AdminUserLentLog.tsx index fb5f7b388..4891d092b 100644 --- a/frontend/src/components/LentLog/AdminUserLentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminUserLentLog.tsx @@ -1,6 +1,6 @@ import styled, { css } from "styled-components"; -import LogTable from "@/components/LentLog/LogTable/LogTable"; -import { ILentLog } from "@/types/dto/lent.dto"; +import LogTable from "@/Cabinet/components/LentLog/LogTable/LogTable"; +import { ILentLog } from "@/Cabinet/types/dto/lent.dto"; const AdminUserLentLog = ({ closeLent, @@ -23,7 +23,10 @@ const AdminUserLentLog = ({ > - + @@ -36,7 +39,10 @@ const AdminUserLentLog = ({ > - + diff --git a/frontend/src/components/LentLog/LentLog.container.tsx b/frontend/src/Cabinet/components/LentLog/LentLog.container.tsx similarity index 74% rename from frontend/src/components/LentLog/LentLog.container.tsx rename to frontend/src/Cabinet/components/LentLog/LentLog.container.tsx index aab412598..7ebed068b 100644 --- a/frontend/src/components/LentLog/LentLog.container.tsx +++ b/frontend/src/Cabinet/components/LentLog/LentLog.container.tsx @@ -1,11 +1,10 @@ import { useEffect, useState } from "react"; -import LentLog from "@/components/LentLog/LentLog"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { axiosMyLentLog } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import { getTotalPage } from "@/utils/dateUtils"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; - +import LentLog from "@/Cabinet/components/LentLog/LentLog"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { axiosMyLentLog } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { getTotalPage } from "@/Cabinet/utils/paginationUtils"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const LentLogContainer = () => { const { closeLent } = useMenu(); diff --git a/frontend/src/components/LentLog/LentLog.tsx b/frontend/src/Cabinet/components/LentLog/LentLog.tsx similarity index 93% rename from frontend/src/components/LentLog/LentLog.tsx rename to frontend/src/Cabinet/components/LentLog/LentLog.tsx index 506498117..1ae64d62f 100644 --- a/frontend/src/components/LentLog/LentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/LentLog.tsx @@ -1,6 +1,6 @@ import styled, { css } from "styled-components"; -import LogTable from "@/components/LentLog/LogTable/LogTable"; -import { ILentLog } from "@/types/dto/lent.dto"; +import LogTable from "@/Cabinet/components/LentLog/LogTable/LogTable"; +import { ILentLog } from "@/Cabinet/types/dto/lent.dto"; const LentLog = ({ closeLent, @@ -95,10 +95,10 @@ const TitleStyled = styled.h1` const LentLogStyled = styled.div` position: fixed; - top: 80px; + top: 120px; right: 0; min-width: 330px; - height: calc(100% - 80px); + height: calc(100% - 120px); padding: 40px 20px; z-index: 9; transform: translateX(120%); diff --git a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx b/frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx similarity index 84% rename from frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx rename to frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx index 8296cb404..f459bf18d 100644 --- a/frontend/src/components/LentLog/LogTable/AdminCabinetLogTable.tsx +++ b/frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const dateOptions: Intl.DateTimeFormatOptions = { year: "2-digit", @@ -35,10 +35,14 @@ const AdminCabinetLogTable = ({ {new Date(startedAt).toLocaleString("ko-KR", dateOptions)} - - {endedAt - ? new Date(endedAt).toLocaleString("ko-KR", dateOptions) - : '-'} + + {endedAt + ? new Date(endedAt).toLocaleString("ko-KR", dateOptions) + : "-"} ) diff --git a/frontend/src/components/LentLog/LogTable/LogTable.tsx b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx similarity index 92% rename from frontend/src/components/LentLog/LogTable/LogTable.tsx rename to frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx index 94e162a7f..502ba75e7 100644 --- a/frontend/src/components/LentLog/LogTable/LogTable.tsx +++ b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const dateOptions: Intl.DateTimeFormatOptions = { year: "2-digit", diff --git a/frontend/src/components/Login/AdminLoginTemplate.tsx b/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx similarity index 93% rename from frontend/src/components/Login/AdminLoginTemplate.tsx rename to frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx index 6d6cab21a..76b8ac459 100644 --- a/frontend/src/components/Login/AdminLoginTemplate.tsx +++ b/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx @@ -2,12 +2,12 @@ import axios from "axios"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import UnavailableModal from "@/components/Modals/UnavailableModal/UnavailableModal"; -import { additionalModalType } from "@/assets/data/maps"; -import { ReactComponent as AdminLoginImg } from "@/assets/images/adminLoginImg.svg"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; -import { axiosAdminAuthLogin } from "@/api/axios/axios.custom"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import UnavailableModal from "@/Cabinet/components/Modals/UnavailableModal/UnavailableModal"; +import { additionalModalType } from "@/Cabinet/assets/data/maps"; +import { ReactComponent as AdminLoginImg } from "@/Cabinet/assets/images/adminLoginImg.svg"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import { axiosAdminAuthLogin } from "@/Cabinet/api/axios/axios.custom"; const AdminLoginTemplate = (props: { url: string; diff --git a/frontend/src/components/Login/LoginTemplate.tsx b/frontend/src/Cabinet/components/Login/LoginTemplate.tsx similarity index 93% rename from frontend/src/components/Login/LoginTemplate.tsx rename to frontend/src/Cabinet/components/Login/LoginTemplate.tsx index ff55945b2..2ca9cb8d4 100644 --- a/frontend/src/components/Login/LoginTemplate.tsx +++ b/frontend/src/Cabinet/components/Login/LoginTemplate.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import styled from "styled-components"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { ReactComponent as LoginImg } from "@/assets/images/loginImg.svg"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ReactComponent as LoginImg } from "@/Cabinet/assets/images/loginImg.svg"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; const LoginTemplate = (props: { url: string; @@ -62,7 +62,6 @@ const LoginPageStyled = styled.div` display: flex; justify-content: space-between; align-items: center; - height: 80px; width: 100%; height: 100%; `; diff --git a/frontend/src/components/Login/__tests__/LoginTemplate.test.tsx b/frontend/src/Cabinet/components/Login/__tests__/LoginTemplate.test.tsx similarity index 82% rename from frontend/src/components/Login/__tests__/LoginTemplate.test.tsx rename to frontend/src/Cabinet/components/Login/__tests__/LoginTemplate.test.tsx index 7c929655f..e6da197ab 100644 --- a/frontend/src/components/Login/__tests__/LoginTemplate.test.tsx +++ b/frontend/src/Cabinet/components/Login/__tests__/LoginTemplate.test.tsx @@ -1,13 +1,13 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it, test } from "vitest"; -import LoginTemplate from "@/components/Login/LoginTemplate"; +import LoginTemplate from "@/Cabinet/components/Login/LoginTemplate"; describe("Login Template test", () => { it("should render properly", () => { const url = `${import.meta.env.VITE_BE_HOST}/v4/auth/login`; const pageTitle = "42cabi"; const pageSubTitle = "여러분의 일상을 가볍게"; - const imgSrc = "/src/assets/images/loginImg.svg"; + const imgSrc = "/src/Cabinet/assets/images/loginImg.svg"; render( { }; const MapFloorSelectStyled = styled.div` - background: url("/src/assets/images/select.svg") var(--main-color) no-repeat - 80% 55%; + background: url("/src/Cabinet/assets/images/select.svg") var(--main-color) + no-repeat 80% 55%; color: white; cursor: pointer; width: 65px; diff --git a/frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx b/frontend/src/Cabinet/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx similarity index 100% rename from frontend/src/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx rename to frontend/src/Cabinet/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx diff --git a/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx b/frontend/src/Cabinet/components/MapInfo/MapGrid/MapGrid.tsx similarity index 86% rename from frontend/src/components/MapInfo/MapGrid/MapGrid.tsx rename to frontend/src/Cabinet/components/MapInfo/MapGrid/MapGrid.tsx index 5ec8c0e7e..7036f21e8 100644 --- a/frontend/src/components/MapInfo/MapGrid/MapGrid.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapGrid/MapGrid.tsx @@ -3,9 +3,9 @@ import styled from "styled-components"; import { currentSectionNameState, isCurrentSectionRenderState, -} from "@/recoil/atoms"; -import MapItem from "@/components/MapInfo/MapItem/MapItem"; -import { mapPostionData } from "@/assets/data/mapPositionData"; +} from "@/Cabinet/recoil/atoms"; +import MapItem from "@/Cabinet/components/MapInfo/MapItem/MapItem"; +import { mapPostionData } from "@/Cabinet/assets/data/mapPositionData"; const MapGrid = ({ floor }: { floor: number }) => { const setSection = useSetRecoilState(currentSectionNameState); diff --git a/frontend/src/components/MapInfo/MapInfo.container.tsx b/frontend/src/Cabinet/components/MapInfo/MapInfo.container.tsx similarity index 86% rename from frontend/src/components/MapInfo/MapInfo.container.tsx rename to frontend/src/Cabinet/components/MapInfo/MapInfo.container.tsx index bfee7ffb1..e64181718 100644 --- a/frontend/src/components/MapInfo/MapInfo.container.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapInfo.container.tsx @@ -1,9 +1,12 @@ import React, { useEffect, useRef, useState } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; -import { currentFloorNumberState, currentMapFloorState } from "@/recoil/atoms"; -import { currentBuildingFloorState } from "@/recoil/selectors"; -import MapInfo from "@/components/MapInfo/MapInfo"; -import useMenu from "@/hooks/useMenu"; +import { + currentFloorNumberState, + currentMapFloorState, +} from "@/Cabinet/recoil/atoms"; +import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; +import MapInfo from "@/Cabinet/components/MapInfo/MapInfo"; +import useMenu from "@/Cabinet/hooks/useMenu"; const MapInfoContainer = () => { const { closeMap } = useMenu(); diff --git a/frontend/src/components/MapInfo/MapInfo.tsx b/frontend/src/Cabinet/components/MapInfo/MapInfo.tsx similarity index 83% rename from frontend/src/components/MapInfo/MapInfo.tsx rename to frontend/src/Cabinet/components/MapInfo/MapInfo.tsx index 6d7e138f7..e03b7d385 100644 --- a/frontend/src/components/MapInfo/MapInfo.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapInfo.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; -import MapFloorSelect from "@/components/MapInfo/MapFloorSelect/MapFloorSelect"; -import MapGrid from "@/components/MapInfo/MapGrid/MapGrid"; +import MapFloorSelect from "@/Cabinet/components/MapInfo/MapFloorSelect/MapFloorSelect"; +import MapGrid from "@/Cabinet/components/MapInfo/MapGrid/MapGrid"; const MapInfo = ({ touchStart, @@ -28,7 +28,7 @@ const MapInfo = ({ @@ -54,11 +54,11 @@ const HeaderStyled = styled.div` const MapInfoContainerStyled = styled.div` position: fixed; - top: 80px; + top: 120px; right: 0; min-width: 330px; width: 330px; - height: calc(100% - 80px); + height: calc(100% - 120px); padding: 40px; z-index: 9; transform: translateX(120%); @@ -68,6 +68,7 @@ const MapInfoContainerStyled = styled.div` flex-direction: column; align-items: center; background: var(--white); + overflow-y: auto; `; export default MapInfo; diff --git a/frontend/src/components/MapInfo/MapItem/MapItem.tsx b/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx similarity index 85% rename from frontend/src/components/MapInfo/MapItem/MapItem.tsx rename to frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx index a20ddc0a6..fbf51d512 100644 --- a/frontend/src/components/MapInfo/MapItem/MapItem.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx @@ -2,11 +2,11 @@ import React from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { useRecoilValue, useSetRecoilState } from "recoil"; import styled from "styled-components"; -import { currentFloorNumberState } from "@/recoil/atoms"; -import { currentBuildingFloorState } from "@/recoil/selectors"; -import { ISectionInfo } from "@/assets/data/mapPositionData"; -import SectionType from "@/types/enum/map.type.enum"; -import useMenu from "@/hooks/useMenu"; +import { currentFloorNumberState } from "@/Cabinet/recoil/atoms"; +import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; +import { ISectionInfo } from "@/Cabinet/assets/data/mapPositionData"; +import SectionType from "@/Cabinet/types/enum/map.type.enum"; +import useMenu from "@/Cabinet/hooks/useMenu"; const MapItem: React.FC<{ floor: number; @@ -78,7 +78,7 @@ const ItemStyled = styled.div<{ const IconContainerStyled = styled.div` width: 100%; height: 48px; - background-image: url("/src/assets/images/stairs.svg"); + background-image: url("/src/Cabinet/assets/images/stairs.svg"); background-size: contain; background-repeat: no-repeat; `; diff --git a/frontend/src/components/Modals/BanModal/BanModal.tsx b/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx similarity index 85% rename from frontend/src/components/Modals/BanModal/BanModal.tsx rename to frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx index 820ba5c52..838f8fb59 100644 --- a/frontend/src/components/Modals/BanModal/BanModal.tsx +++ b/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx @@ -1,24 +1,24 @@ -import React, { useState } from "react"; -import { useSetRecoilState } from "recoil"; import { - bannedUserListState, - isCurrentSectionRenderState, - numberOfAdminWorkState, - targetUserInfoState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; + axiosDeleteCurrentBanLog, + axiosGetBannedUserList, +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import IconType from "@/types/enum/icon.type.enum"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { - axiosDeleteCurrentBanLog, - axiosGetBannedUserList, -} from "@/api/axios/axios.custom"; -import { handleBannedUserList } from "@/utils/tableUtils"; + bannedUserListState, + isCurrentSectionRenderState, + numberOfAdminWorkState, + targetUserInfoState, +} from "@/Cabinet/recoil/atoms"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { handleBannedUserList } from "@/Cabinet/utils/tableUtils"; +import React, { useState } from "react"; +import { useSetRecoilState } from "recoil"; const BanModal: React.FC<{ userId: number | null; diff --git a/frontend/src/components/Modals/CancelModal/CancelModal.tsx b/frontend/src/Cabinet/components/Modals/CancelModal/CancelModal.tsx similarity index 86% rename from frontend/src/components/Modals/CancelModal/CancelModal.tsx rename to frontend/src/Cabinet/components/Modals/CancelModal/CancelModal.tsx index 1620dab8a..97d7660c4 100644 --- a/frontend/src/components/Modals/CancelModal/CancelModal.tsx +++ b/frontend/src/Cabinet/components/Modals/CancelModal/CancelModal.tsx @@ -1,26 +1,26 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosCancel, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosCancel, - axiosMyLentInfo, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const CancelModal: React.FC<{ lentType: string; diff --git a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.container.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.container.tsx similarity index 86% rename from frontend/src/components/Modals/ClubModal/AddClubMemberModal.container.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.container.tsx index bc05995fc..d8a9e4452 100644 --- a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.container.tsx @@ -3,14 +3,14 @@ import { useRecoilValue, useSetRecoilState } from "recoil"; import { isCurrentSectionRenderState, targetClubInfoState, -} from "@/recoil/atoms"; -import AddClubMemberModal from "@/components/Modals/ClubModal/AddClubMemberModal"; -import ModalPortal from "@/components/Modals/ModalPortal"; +} from "@/Cabinet/recoil/atoms"; +import AddClubMemberModal from "@/Cabinet/components/Modals/ClubModal/AddClubMemberModal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { axiosAddClubMember } from "@/api/axios/axios.custom"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { axiosAddClubMember } from "@/Cabinet/api/axios/axios.custom"; const AddClubMemberModalContainer: React.FC<{ closeModal: React.MouseEventHandler; diff --git a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx similarity index 98% rename from frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx index 5962e3f87..bffce3be1 100644 --- a/frontend/src/components/Modals/ClubModal/AddClubMemberModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx @@ -1,5 +1,5 @@ +import Button from "@/Cabinet/components/Common/Button"; import styled from "styled-components"; -import Button from "@/components/Common/Button"; const MAX_INPUT_LENGTH = 27; diff --git a/frontend/src/components/Modals/ClubModal/ClubMemoModal.container.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.container.tsx similarity index 91% rename from frontend/src/components/Modals/ClubModal/ClubMemoModal.container.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.container.tsx index bfb67014f..01696fadc 100644 --- a/frontend/src/components/Modals/ClubModal/ClubMemoModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.container.tsx @@ -3,14 +3,14 @@ import { useRecoilValue, useSetRecoilState } from "recoil"; import { isCurrentSectionRenderState, targetClubInfoState, -} from "@/recoil/atoms"; -import ModalPortal from "@/components//Modals/ModalPortal"; -import ClubMemoModal from "@/components/Modals/ClubModal/ClubMemoModal"; +} from "@/Cabinet/recoil/atoms"; +import ClubMemoModal from "@/Cabinet/components/Modals/ClubModal/ClubMemoModal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { axiosUpdateClubNotice } from "@/api/axios/axios.custom"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { axiosUpdateClubNotice } from "@/Cabinet/api/axios/axios.custom"; export const CLUB_MEMO_MAX_LENGTH = 100; interface ClubMemoModalContainerInterface { diff --git a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.tsx similarity index 97% rename from frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.tsx index 5307e1172..f2e73bef4 100644 --- a/frontend/src/components/Modals/ClubModal/ClubMemoModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubMemoModal.tsx @@ -1,9 +1,9 @@ -import styled from "styled-components"; -import Button from "@/components/Common/Button"; +import Button from "@/Cabinet/components/Common/Button"; import { CLUB_MEMO_MAX_LENGTH, ClubMemoModalInterface, -} from "@/components/Modals/ClubModal/ClubMemoModal.container"; +} from "@/Cabinet/components/Modals/ClubModal/ClubMemoModal.container"; +import styled from "styled-components"; const ClubMemoModal = ({ clubNotice, diff --git a/frontend/src/components/Modals/ClubModal/ClubModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx similarity index 95% rename from frontend/src/components/Modals/ClubModal/ClubModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx index 32badd0ec..e60ce09af 100644 --- a/frontend/src/components/Modals/ClubModal/ClubModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx @@ -1,20 +1,20 @@ -import React, { useEffect, useRef, useState } from "react"; -import { useRecoilState } from "recoil"; -import styled from "styled-components"; -import { selectedClubInfoState } from "@/recoil/atoms"; -import Button from "@/components/Common/Button"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { ClubUserDto } from "@/types/dto/lent.dto"; import { axiosCreateClubUser, axiosDeleteClubUser, axiosEditClubUser, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Button from "@/Cabinet/components/Common/Button"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { selectedClubInfoState } from "@/Cabinet/recoil/atoms"; +import { ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import React, { useEffect, useRef, useState } from "react"; +import { useRecoilState } from "recoil"; +import styled from "styled-components"; interface ClubModalContainerInterface { type: string; diff --git a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.container.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container.tsx similarity index 88% rename from frontend/src/components/Modals/ClubModal/ClubPasswordModal.container.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container.tsx index e2e456d97..f86267ec7 100644 --- a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container.tsx @@ -3,18 +3,18 @@ import { useRecoilValue, useSetRecoilState } from "recoil"; import { isCurrentSectionRenderState, targetClubInfoState, -} from "@/recoil/atoms"; -import { IModalContents } from "@/components//Modals/Modal"; -import ModalPortal from "@/components//Modals/ModalPortal"; +} from "@/Cabinet/recoil/atoms"; +import ClubPasswordModal from "@/Cabinet/components/Modals/ClubModal/ClubPasswordModal"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components//Modals/ResponseModal/ResponseModal"; -import ClubPasswordModal from "@/components/Modals/ClubModal/ClubPasswordModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import IconType from "@/types/enum/icon.type.enum"; -import { axiosUpdateClubMemo } from "@/api/axios/axios.custom"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { axiosUpdateClubMemo } from "@/Cabinet/api/axios/axios.custom"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; interface ClubPasswordModalContainerInterface { password: string; diff --git a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.tsx similarity index 93% rename from frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.tsx index da107fe15..436d795e4 100644 --- a/frontend/src/components/Modals/ClubModal/ClubPasswordModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubPasswordModal.tsx @@ -1,8 +1,8 @@ import React from "react"; import styled from "styled-components"; -import Button from "@/components/Common/Button"; -import { ClubPasswordModalInterface } from "@/components/Modals/ClubModal/ClubPasswordModal.container"; -import { ReactComponent as CheckIcon } from "@/assets/images/checkIcon.svg"; +import Button from "@/Cabinet/components/Common/Button"; +import { ClubPasswordModalInterface } from "@/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container"; +import { ReactComponent as CheckIcon } from "@/Cabinet/assets/images/checkIcon.svg"; const ClubPasswordModal: React.FC = ({ ClubPwModalContents, diff --git a/frontend/src/components/Modals/ClubModal/DeleteClubMemberModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal.tsx similarity index 80% rename from frontend/src/components/Modals/ClubModal/DeleteClubMemberModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal.tsx index c1ab35e03..62c1bb1d4 100644 --- a/frontend/src/components/Modals/ClubModal/DeleteClubMemberModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal.tsx @@ -1,17 +1,17 @@ import { useState } from "react"; import { useSetRecoilState } from "recoil"; -import { isCurrentSectionRenderState } from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; +import { isCurrentSectionRenderState } from "@/Cabinet/recoil/atoms"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import { ClubUserResponseDto } from "@/types/dto/club.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { axiosDeleteClubMember } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { ClubUserResponseDto } from "@/Cabinet/types/dto/club.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { axiosDeleteClubMember } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const DeleteClubMemberModal: React.FC<{ closeModal: React.MouseEventHandler; diff --git a/frontend/src/components/Modals/ClubModal/MandateClubMemberModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/MandateClubMemberModal.tsx similarity index 86% rename from frontend/src/components/Modals/ClubModal/MandateClubMemberModal.tsx rename to frontend/src/Cabinet/components/Modals/ClubModal/MandateClubMemberModal.tsx index 56beb78fc..b9797e3a4 100644 --- a/frontend/src/components/Modals/ClubModal/MandateClubMemberModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/MandateClubMemberModal.tsx @@ -1,11 +1,11 @@ import { useState } from "react"; import { useSetRecoilState } from "recoil"; -import { isCurrentSectionRenderState } from "@/recoil/atoms"; -import { modalPropsMap } from "@/assets/data/maps"; -import { ClubUserResponseDto } from "@/types/dto/club.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { axiosMandateClubMember } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +import { isCurrentSectionRenderState } from "@/Cabinet/recoil/atoms"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { ClubUserResponseDto } from "@/Cabinet/types/dto/club.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { axiosMandateClubMember } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; import Modal, { IModalContents } from "../Modal"; import ModalPortal from "../ModalPortal"; import { diff --git a/frontend/src/components/Modals/ExtendModal/ExtendModal.tsx b/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx similarity index 88% rename from frontend/src/components/Modals/ExtendModal/ExtendModal.tsx rename to frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx index f200c983c..60ebafbe3 100644 --- a/frontend/src/components/Modals/ExtendModal/ExtendModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx @@ -1,27 +1,27 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosMyLentInfo, + axiosUseExtension, // axiosExtend, // TODO: 연장권 api 생성 후 연결해야 함 +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosMyLentInfo, - axiosUseExtension, // axiosExtend, // TODO: 연장권 api 생성 후 연결해야 함 -} from "@/api/axios/axios.custom"; -import { getExtendedDateString } from "@/utils/dateUtils"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { getExtendedDateString } from "@/Cabinet/utils/dateUtils"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const ExtendModal: React.FC<{ onClose: () => void; diff --git a/frontend/src/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx b/frontend/src/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx similarity index 85% rename from frontend/src/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx rename to frontend/src/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx index d39faded9..5638faf55 100644 --- a/frontend/src/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container.tsx @@ -1,27 +1,27 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosLentShareId, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import PasswordContainer from "@/Cabinet/components/Modals/PasswordCheckModal/PasswordContainer"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import PasswordContainer from "@/components/Modals/PasswordCheckModal/PasswordContainer"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosLentShareId, - axiosMyLentInfo, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import PasswordCheckModal from "../PasswordCheckModal/PasswordCheckModal"; const InvitationCodeModalContainer: React.FC<{ diff --git a/frontend/src/components/Modals/LentModal/ClubLentModal.tsx b/frontend/src/Cabinet/components/Modals/LentModal/ClubLentModal.tsx similarity index 86% rename from frontend/src/components/Modals/LentModal/ClubLentModal.tsx rename to frontend/src/Cabinet/components/Modals/LentModal/ClubLentModal.tsx index 9493920d5..c11fbd791 100644 --- a/frontend/src/components/Modals/LentModal/ClubLentModal.tsx +++ b/frontend/src/Cabinet/components/Modals/LentModal/ClubLentModal.tsx @@ -1,24 +1,24 @@ -import React, { useState } from "react"; -import { useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosLentClubCabinet, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, numberOfAdminWorkState, selectedClubInfoState, targetCabinetInfoState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { - axiosCabinetById, - axiosLentClubCabinet, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import React, { useState } from "react"; +import { useRecoilValue, useSetRecoilState } from "recoil"; const ClubLentModal: React.FC<{ lentType: string; diff --git a/frontend/src/components/Modals/LentModal/LentModal.tsx b/frontend/src/Cabinet/components/Modals/LentModal/LentModal.tsx similarity index 86% rename from frontend/src/components/Modals/LentModal/LentModal.tsx rename to frontend/src/Cabinet/components/Modals/LentModal/LentModal.tsx index 8b978ce9c..614cfaafa 100644 --- a/frontend/src/components/Modals/LentModal/LentModal.tsx +++ b/frontend/src/Cabinet/components/Modals/LentModal/LentModal.tsx @@ -1,29 +1,29 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosLentId, + axiosLentShareId, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosLentId, - axiosLentShareId, - axiosMyLentInfo, -} from "@/api/axios/axios.custom"; -import { getExpireDateString } from "@/utils/dateUtils"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { getExpireDateString } from "@/Cabinet/utils/dateUtils"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const LentModal: React.FC<{ lentType: string; diff --git a/frontend/src/components/Modals/ManualModal/ManualModal.tsx b/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx similarity index 96% rename from frontend/src/components/Modals/ManualModal/ManualModal.tsx rename to frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx index b3755a336..02d415aee 100644 --- a/frontend/src/components/Modals/ManualModal/ManualModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx @@ -1,9 +1,9 @@ import React from "react"; import { useState } from "react"; import styled, { css, keyframes } from "styled-components"; -import { manualContentData } from "@/assets/data/ManualContent"; -import { ReactComponent as MoveBtnImg } from "@/assets/images/moveButton.svg"; -import ContentStatus from "@/types/enum/content.status.enum"; +import { manualContentData } from "@/Cabinet/assets/data/ManualContent"; +import { ReactComponent as MoveBtnImg } from "@/Cabinet/assets/images/moveButton.svg"; +import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface ModalProps { contentStatus: ContentStatus; diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.container.tsx b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.container.tsx similarity index 89% rename from frontend/src/components/Modals/MemoModal/MemoModal.container.tsx rename to frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.container.tsx index e0b32e12d..4f795b5c0 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.container.tsx @@ -1,15 +1,18 @@ import React from "react"; import { useRecoilState } from "recoil"; -import { currentFloorCabinetState, myCabinetInfoState } from "@/recoil/atoms"; -import MemoModal from "@/components/Modals/MemoModal/MemoModal"; +import { + currentFloorCabinetState, + myCabinetInfoState, +} from "@/Cabinet/recoil/atoms"; +import MemoModal from "@/Cabinet/components/Modals/MemoModal/MemoModal"; import { CabinetInfoByBuildingFloorDto, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/types/dto/cabinet.dto"; import { axiosUpdateMyCabinetInfo, // axiosUpdateCabinetMemo, // axiosUpdateCabinetTitle, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; const MemoModalContainer = (props: { onClose: React.MouseEventHandler; diff --git a/frontend/src/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx similarity index 97% rename from frontend/src/components/Modals/MemoModal/MemoModal.tsx rename to frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx index a41c76fcb..e065aee58 100644 --- a/frontend/src/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx @@ -1,8 +1,8 @@ +import Button from "@/Cabinet/components/Common/Button"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import React, { useRef, useState } from "react"; import styled from "styled-components"; -import Button from "@/components/Common/Button"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import CabinetType from "@/types/enum/cabinet.type.enum"; export interface MemoModalInterface { cabinetType: CabinetType; diff --git a/frontend/src/components/Modals/Modal.tsx b/frontend/src/Cabinet/components/Modals/Modal.tsx similarity index 91% rename from frontend/src/components/Modals/Modal.tsx rename to frontend/src/Cabinet/components/Modals/Modal.tsx index 4422d0790..0f8d25869 100644 --- a/frontend/src/components/Modals/Modal.tsx +++ b/frontend/src/Cabinet/components/Modals/Modal.tsx @@ -1,12 +1,12 @@ import React, { ReactElement } from "react"; import styled, { css } from "styled-components"; -import AdminClubLogContainer from "@/components/Club/AdminClubLog.container"; -import Button from "@/components/Common/Button"; -import { ReactComponent as CheckIcon } from "@/assets/images/checkIcon.svg"; -import { ReactComponent as ErrorIcon } from "@/assets/images/errorIcon.svg"; -import { ReactComponent as NotificationIcon } from "@/assets/images/notificationSign.svg"; -import IconType from "@/types/enum/icon.type.enum"; -import useMultiSelect from "@/hooks/useMultiSelect"; +import AdminClubLogContainer from "@/Cabinet/components/Club/AdminClubLog.container"; +import Button from "@/Cabinet/components/Common/Button"; +import { ReactComponent as CheckIcon } from "@/Cabinet/assets/images/checkIcon.svg"; +import { ReactComponent as ErrorIcon } from "@/Cabinet/assets/images/errorIcon.svg"; +import { ReactComponent as NotificationIcon } from "@/Cabinet/assets/images/notificationSign.svg"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; /** * @interface diff --git a/frontend/src/components/Modals/ModalPortal.tsx b/frontend/src/Cabinet/components/Modals/ModalPortal.tsx similarity index 100% rename from frontend/src/components/Modals/ModalPortal.tsx rename to frontend/src/Cabinet/components/Modals/ModalPortal.tsx diff --git a/frontend/src/components/Modals/NotificationModal/NotificationModal.tsx b/frontend/src/Cabinet/components/Modals/NotificationModal/NotificationModal.tsx similarity index 57% rename from frontend/src/components/Modals/NotificationModal/NotificationModal.tsx rename to frontend/src/Cabinet/components/Modals/NotificationModal/NotificationModal.tsx index a997a1095..297cad861 100644 --- a/frontend/src/components/Modals/NotificationModal/NotificationModal.tsx +++ b/frontend/src/Cabinet/components/Modals/NotificationModal/NotificationModal.tsx @@ -1,22 +1,24 @@ -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import IconType from "@/types/enum/icon.type.enum"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; export const NotificationModal = ({ title, detail, closeModal, + iconType = IconType.NOTIFICATIONICON, }: { title: string; detail: string; closeModal: React.MouseEventHandler; + iconType?: IconType; }) => { const modalContents: IModalContents = { type: "noBtn", title: title, detail: detail, closeModal: closeModal, - iconType: IconType.NOTIFICATIONICON, + iconType: iconType, }; return ( diff --git a/frontend/src/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx b/frontend/src/Cabinet/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx similarity index 79% rename from frontend/src/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx rename to frontend/src/Cabinet/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx index 3611f66be..0878099b2 100644 --- a/frontend/src/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx +++ b/frontend/src/Cabinet/components/Modals/OverduePenaltyModal/OverduePenaltyModal.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useState } from "react"; -import Modal from "@/components/Modals/Modal"; -import { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import IconType from "@/types/enum/icon.type.enum"; -import { formatDate } from "@/utils/dateUtils"; +import Modal from "@/Cabinet/components/Modals/Modal"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { formatDate } from "@/Cabinet/utils/dateUtils"; const OverduePenaltyModal: React.FC<{ status: CabinetStatus | additionalModalType; diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx similarity index 83% rename from frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx rename to frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx index 0307005f8..181041663 100644 --- a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.container.tsx @@ -1,28 +1,28 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosMyLentInfo, + axiosSendCabinetPassword, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import PasswordCheckModal from "@/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal"; +import PasswordContainer from "@/Cabinet/components/Modals/PasswordCheckModal/PasswordContainer"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import PasswordCheckModal from "@/components/Modals/PasswordCheckModal/PasswordCheckModal"; -import PasswordContainer from "@/components/Modals/PasswordCheckModal/PasswordContainer"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosMyLentInfo, - axiosSendCabinetPassword, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const PasswordCheckModalContainer: React.FC<{ onClose: () => void; diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx similarity index 93% rename from frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx rename to frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx index 4618d460d..992bd59e2 100644 --- a/frontend/src/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx +++ b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordCheckModal.tsx @@ -1,9 +1,9 @@ import React from "react"; import styled, { css } from "styled-components"; -import Button from "@/components/Common/Button"; -import { IModalContents } from "@/components/Modals/Modal"; -import { ReactComponent as CheckIcon } from "@/assets/images/checkIcon.svg"; -import useMultiSelect from "@/hooks/useMultiSelect"; +import Button from "@/Cabinet/components/Common/Button"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import { ReactComponent as CheckIcon } from "@/Cabinet/assets/images/checkIcon.svg"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; const PasswordCheckModal: React.FC<{ modalContents: IModalContents; diff --git a/frontend/src/components/Modals/PasswordCheckModal/PasswordContainer.tsx b/frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordContainer.tsx similarity index 100% rename from frontend/src/components/Modals/PasswordCheckModal/PasswordContainer.tsx rename to frontend/src/Cabinet/components/Modals/PasswordCheckModal/PasswordContainer.tsx diff --git a/frontend/src/components/Modals/ResponseModal/ResponseModal.tsx b/frontend/src/Cabinet/components/Modals/ResponseModal/ResponseModal.tsx similarity index 74% rename from frontend/src/components/Modals/ResponseModal/ResponseModal.tsx rename to frontend/src/Cabinet/components/Modals/ResponseModal/ResponseModal.tsx index a3eb2b329..e65691b22 100644 --- a/frontend/src/components/Modals/ResponseModal/ResponseModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ResponseModal/ResponseModal.tsx @@ -1,7 +1,7 @@ +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; import React from "react"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import IconType from "@/types/enum/icon.type.enum"; export const SuccessResponseModal: React.FC<{ modalTitle?: string; @@ -15,7 +15,11 @@ export const SuccessResponseModal: React.FC<{ iconType: IconType.CHECKICON, }; - return ; + return ( + + + + ); }; export const FailResponseModal: React.FC<{ diff --git a/frontend/src/components/Modals/ReturnModal/AdminReturnModal.tsx b/frontend/src/Cabinet/components/Modals/ReturnModal/AdminReturnModal.tsx similarity index 81% rename from frontend/src/components/Modals/ReturnModal/AdminReturnModal.tsx rename to frontend/src/Cabinet/components/Modals/ReturnModal/AdminReturnModal.tsx index 60926544a..4d9998cba 100644 --- a/frontend/src/components/Modals/ReturnModal/AdminReturnModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ReturnModal/AdminReturnModal.tsx @@ -9,27 +9,27 @@ import { overdueCabinetListState, targetCabinetInfoState, targetUserInfoState, -} from "@/recoil/atoms"; -import Selector from "@/components/Common/Selector"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; +} from "@/Cabinet/recoil/atoms"; +import Selector from "@/Cabinet/components/Common/Selector"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { UserInfo } from "@/types/dto/user.dto"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import IconType from "@/types/enum/icon.type.enum"; +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { UserInfo } from "@/Cabinet/types/dto/user.dto"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; import { axiosBundleReturn, axiosCabinetById, axiosGetOverdueUserList, axiosReturnByUserId, -} from "@/api/axios/axios.custom"; -import useMultiSelect from "@/hooks/useMultiSelect"; -import { handleOverdueUserList } from "@/utils/tableUtils"; +} from "@/Cabinet/api/axios/axios.custom"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; +import { handleOverdueUserList } from "@/Cabinet/utils/tableUtils"; const AdminReturnModal: React.FC<{ lentType?: CabinetType; @@ -90,7 +90,7 @@ const AdminReturnModal: React.FC<{ const renderSelector = () => ( { return { key: info.userId, value: info.name }; })} @@ -151,31 +151,31 @@ const AdminReturnModal: React.FC<{ return; } await axiosReturnByUserId(selectedUserIds) - .then(async (res) => { - setIsCurrentSectionRender(true); - setNumberOfAdminWork((prev) => prev + 1); - setModalTitle("반납되었습니다"); - const overdueUserData = await axiosGetOverdueUserList(); - setOverdueUserList(handleOverdueUserList(overdueUserData)); - setSelectedUserIds([]); - try { - const { data } = await axiosCabinetById(currentCabinetId); - setTargetCabinetInfo(data); - } catch (error) { - throw error; - } - }) - .catch((error) => { - setHasErrorOnResponse(true); - setModalTitle(error.response.data.message); - }) - .finally(() => { - setShowResponseModal(true); - }); - } catch (error) { - console.error(error); - } -}; + .then(async (res) => { + setIsCurrentSectionRender(true); + setNumberOfAdminWork((prev) => prev + 1); + setModalTitle("반납되었습니다"); + const overdueUserData = await axiosGetOverdueUserList(); + setOverdueUserList(handleOverdueUserList(overdueUserData)); + setSelectedUserIds([]); + try { + const { data } = await axiosCabinetById(currentCabinetId); + setTargetCabinetInfo(data); + } catch (error) { + throw error; + } + }) + .catch((error) => { + setHasErrorOnResponse(true); + setModalTitle(error.response.data.message); + }) + .finally(() => { + setShowResponseModal(true); + }); + } catch (error) { + console.error(error); + } + }; const tryBundleReturnRequest = async (e: React.MouseEvent) => { const returnableCabinetIdList = targetCabinetInfoList diff --git a/frontend/src/components/Modals/ReturnModal/ReturnModal.tsx b/frontend/src/Cabinet/components/Modals/ReturnModal/ReturnModal.tsx similarity index 86% rename from frontend/src/components/Modals/ReturnModal/ReturnModal.tsx rename to frontend/src/Cabinet/components/Modals/ReturnModal/ReturnModal.tsx index 4c3c4744a..ba6bd38c2 100644 --- a/frontend/src/components/Modals/ReturnModal/ReturnModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ReturnModal/ReturnModal.tsx @@ -1,31 +1,31 @@ -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosMyLentInfo, + axiosReturn, +} from "@/Cabinet/api/axios/axios.custom"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { getDefaultCabinetInfo } from "@/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import Modal, { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/components/Modals/ResponseModal/ResponseModal"; -import { getDefaultCabinetInfo } from "@/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosMyLentInfo, - axiosReturn, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; import { getExpireDateString, getShortenedExpireDateString, -} from "@/utils/dateUtils"; +} from "@/Cabinet/utils/dateUtils"; +import React, { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; const ReturnModal: React.FC<{ lentType: string; diff --git a/frontend/src/components/Modals/StatusModal/StatusModal.container.tsx b/frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.container.tsx similarity index 90% rename from frontend/src/components/Modals/StatusModal/StatusModal.container.tsx rename to frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.container.tsx index 2c07ad33e..3d4ba5bb0 100644 --- a/frontend/src/components/Modals/StatusModal/StatusModal.container.tsx +++ b/frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.container.tsx @@ -6,19 +6,19 @@ import { isCurrentSectionRenderState, numberOfAdminWorkState, targetCabinetInfoState, -} from "@/recoil/atoms"; -import StatusModal from "@/components/Modals/StatusModal/StatusModal"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/recoil/atoms"; +import StatusModal from "@/Cabinet/components/Modals/StatusModal/StatusModal"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import { axiosCabinetById, axiosGetBrokenCabinetList, // axiosBundleUpdateCabinetStatus, // axiosBundleUpdateCabinetType, axiosUpdateCabinets, -} from "@/api/axios/axios.custom"; -import useMultiSelect from "@/hooks/useMultiSelect"; -import { handleBrokenCabinetList } from "@/utils/tableUtils"; +} from "@/Cabinet/api/axios/axios.custom"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; +import { handleBrokenCabinetList } from "@/Cabinet/utils/tableUtils"; const StatusModalContainer = (props: { onClose: React.MouseEventHandler; diff --git a/frontend/src/components/Modals/StatusModal/StatusModal.tsx b/frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.tsx similarity index 95% rename from frontend/src/components/Modals/StatusModal/StatusModal.tsx rename to frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.tsx index 8c31e0b45..34244abd0 100644 --- a/frontend/src/components/Modals/StatusModal/StatusModal.tsx +++ b/frontend/src/Cabinet/components/Modals/StatusModal/StatusModal.tsx @@ -1,17 +1,17 @@ import React, { useState } from "react"; import styled from "styled-components"; -import Button from "@/components/Common/Button"; -import Dropdown from "@/components/Common/Dropdown"; +import Button from "@/Cabinet/components/Common/Button"; +import Dropdown from "@/Cabinet/components/Common/Dropdown"; import WarningNotification, { WarningNotificationProps, -} from "@/components/Common/WarningNotification"; +} from "@/Cabinet/components/Common/WarningNotification"; import { cabinetIconSrcMap, cabinetStatusLabelMap, cabinetTypeLabelMap, -} from "@/assets/data/maps"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/assets/data/maps"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import ModalPortal from "../ModalPortal"; export interface StatusModalInterface { diff --git a/frontend/src/components/Modals/SwapModal/SwapModal.tsx b/frontend/src/Cabinet/components/Modals/SwapModal/SwapModal.tsx similarity index 93% rename from frontend/src/components/Modals/SwapModal/SwapModal.tsx rename to frontend/src/Cabinet/components/Modals/SwapModal/SwapModal.tsx index 98aece572..43daf74f4 100644 --- a/frontend/src/components/Modals/SwapModal/SwapModal.tsx +++ b/frontend/src/Cabinet/components/Modals/SwapModal/SwapModal.tsx @@ -1,20 +1,20 @@ -import { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import { + axiosCabinetById, + axiosMyLentInfo, + axiosSwapId, +} from "@/Cabinet/api/axios/axios.custom"; +import { modalPropsMap } from "@/Cabinet/assets/data/maps"; import { currentCabinetIdState, isCurrentSectionRenderState, myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import { modalPropsMap } from "@/assets/data/maps"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import IconType from "@/types/enum/icon.type.enum"; -import { - axiosCabinetById, - axiosMyLentInfo, - axiosSwapId, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/recoil/atoms"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import Modal, { IModalContents } from "../Modal"; import ModalPortal from "../ModalPortal"; import { diff --git a/frontend/src/components/Modals/UnavailableModal/UnavailableModal.tsx b/frontend/src/Cabinet/components/Modals/UnavailableModal/UnavailableModal.tsx similarity index 56% rename from frontend/src/components/Modals/UnavailableModal/UnavailableModal.tsx rename to frontend/src/Cabinet/components/Modals/UnavailableModal/UnavailableModal.tsx index b613e69dd..1fac1ff39 100644 --- a/frontend/src/components/Modals/UnavailableModal/UnavailableModal.tsx +++ b/frontend/src/Cabinet/components/Modals/UnavailableModal/UnavailableModal.tsx @@ -1,10 +1,10 @@ import React from "react"; -import Modal from "@/components/Modals/Modal"; -import { IModalContents } from "@/components/Modals/Modal"; -import ModalPortal from "@/components/Modals/ModalPortal"; -import { additionalModalType, modalPropsMap } from "@/assets/data/maps"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import IconType from "@/types/enum/icon.type.enum"; +import Modal from "@/Cabinet/components/Modals/Modal"; +import { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import IconType from "@/Cabinet/types/enum/icon.type.enum"; const UnavailableModal: React.FC<{ status: CabinetStatus | additionalModalType; diff --git a/frontend/src/components/Search/NoSearch.tsx b/frontend/src/Cabinet/components/Search/NoSearch.tsx similarity index 88% rename from frontend/src/components/Search/NoSearch.tsx rename to frontend/src/Cabinet/components/Search/NoSearch.tsx index 8b7f36302..1a9376ca1 100644 --- a/frontend/src/components/Search/NoSearch.tsx +++ b/frontend/src/Cabinet/components/Search/NoSearch.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; const NoSearch = () => ( - 로고 블랙 + 로고 블랙

검색 결과가 없습니다

diff --git a/frontend/src/components/Search/SearchDefault.tsx b/frontend/src/Cabinet/components/Search/SearchDefault.tsx similarity index 91% rename from frontend/src/components/Search/SearchDefault.tsx rename to frontend/src/Cabinet/components/Search/SearchDefault.tsx index b0fd9fd59..abe268566 100644 --- a/frontend/src/components/Search/SearchDefault.tsx +++ b/frontend/src/Cabinet/components/Search/SearchDefault.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; const SearchDefault = () => ( diff --git a/frontend/src/components/Search/SearchItemByIntraId.tsx b/frontend/src/Cabinet/components/Search/SearchItemByIntraId.tsx similarity index 92% rename from frontend/src/components/Search/SearchItemByIntraId.tsx rename to frontend/src/Cabinet/components/Search/SearchItemByIntraId.tsx index a061463d0..9da747cff 100644 --- a/frontend/src/components/Search/SearchItemByIntraId.tsx +++ b/frontend/src/Cabinet/components/Search/SearchItemByIntraId.tsx @@ -6,18 +6,18 @@ import { selectedTypeOnSearchState, targetCabinetInfoState, targetUserInfoState, -} from "@/recoil/atoms"; -import ChangeToHTML from "@/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; +} from "@/Cabinet/recoil/atoms"; +import ChangeToHTML from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { axiosAdminCabinetInfoByCabinetId } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/assets/data/maps"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { axiosAdminCabinetInfoByCabinetId } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; interface ISearchDetail { name: string; diff --git a/frontend/src/components/Search/SearchItemByNum.tsx b/frontend/src/Cabinet/components/Search/SearchItemByNum.tsx similarity index 89% rename from frontend/src/components/Search/SearchItemByNum.tsx rename to frontend/src/Cabinet/components/Search/SearchItemByNum.tsx index db67bfab6..c92023965 100644 --- a/frontend/src/components/Search/SearchItemByNum.tsx +++ b/frontend/src/Cabinet/components/Search/SearchItemByNum.tsx @@ -4,18 +4,18 @@ import { currentCabinetIdState, selectedTypeOnSearchState, targetCabinetInfoState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentDto } from "@/types/dto/lent.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; -import { axiosAdminCabinetInfoByCabinetId } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/assets/data/maps"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import { axiosAdminCabinetInfoByCabinetId } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const reformIntraId = (lents: LentDto[]) => { if (!lents || lents.length === 0) { diff --git a/frontend/src/components/SectionPagination/SectionPagination.container.tsx b/frontend/src/Cabinet/components/SectionPagination/SectionPagination.container.tsx similarity index 91% rename from frontend/src/components/SectionPagination/SectionPagination.container.tsx rename to frontend/src/Cabinet/components/SectionPagination/SectionPagination.container.tsx index 26fb258eb..b1e6a1718 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.container.tsx +++ b/frontend/src/Cabinet/components/SectionPagination/SectionPagination.container.tsx @@ -1,11 +1,11 @@ -import React from "react"; -import { useRecoilState, useRecoilValue } from "recoil"; +import SectionPagination from "@/Cabinet/components/SectionPagination/SectionPagination"; import { currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; -import { currentFloorSectionState } from "@/recoil/selectors"; -import SectionPagination from "@/components/SectionPagination/SectionPagination"; +} from "@/Cabinet/recoil/atoms"; +import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; +import React from "react"; +import { useRecoilState, useRecoilValue } from "recoil"; const SectionPaginationContainer = (): JSX.Element => { const floor = useRecoilValue(currentFloorNumberState); diff --git a/frontend/src/components/SectionPagination/SectionPagination.tsx b/frontend/src/Cabinet/components/SectionPagination/SectionPagination.tsx similarity index 94% rename from frontend/src/components/SectionPagination/SectionPagination.tsx rename to frontend/src/Cabinet/components/SectionPagination/SectionPagination.tsx index 2e42cedd5..98def7f03 100644 --- a/frontend/src/components/SectionPagination/SectionPagination.tsx +++ b/frontend/src/Cabinet/components/SectionPagination/SectionPagination.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import LeftSectionButton from "@/assets/images/LeftSectionButton.svg"; +import LeftSectionButton from "@/Cabinet/assets/images/LeftSectionButton.svg"; const SectionPagination: React.FC<{ currentSectionName: string; @@ -71,7 +71,7 @@ const SectionBarStyled = styled.div` align-items: center; `; -const MoveSectionButtonStyled = styled.img<{ arrowReversed?: boolean }>` +export const MoveSectionButtonStyled = styled.img<{ arrowReversed?: boolean }>` width: 24px; height: 24px; margin: 0px 15px; diff --git a/frontend/src/components/TopNav/AdminTopNav.container.tsx b/frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx similarity index 71% rename from frontend/src/components/TopNav/AdminTopNav.container.tsx rename to frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx index 6afb724f4..157ac8157 100644 --- a/frontend/src/components/TopNav/AdminTopNav.container.tsx +++ b/frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx @@ -1,10 +1,14 @@ import React, { SetStateAction, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; -import { buildingsFloorState, currentBuildingNameState } from "@/recoil/atoms"; -import { buildingsState } from "@/recoil/selectors"; -import TopNav from "@/components/TopNav/TopNav"; -import { axiosBuildingFloor } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +import { + buildingsFloorState, + currentBuildingNameState, +} from "@/Cabinet/recoil/atoms"; +import { buildingsState } from "@/Cabinet/recoil/selectors"; +import TopNav from "@/Cabinet/components/TopNav/TopNav"; +import { axiosBuildingFloor } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const AdminTopNavContainer: React.FC<{ setIsLoading: React.Dispatch>; @@ -17,6 +21,7 @@ const AdminTopNavContainer: React.FC<{ const buildingsList = useRecoilValue>(buildingsState); const { setIsLoading } = props; const { toggleLeftNav } = useMenu(); + const navigator = useNavigate(); const onClickLogo = () => { toggleLeftNav(); @@ -32,7 +37,7 @@ const AdminTopNavContainer: React.FC<{ try { await setTimeoutPromise(500); const buildingsFloorData = await axiosBuildingFloor(); - setBuildingsFloor(buildingsFloorData.data); + setBuildingsFloor([...buildingsFloorData.data]); } catch (error) { console.log(error); } @@ -43,10 +48,16 @@ const AdminTopNavContainer: React.FC<{ useEffect(() => { if (buildingsList.length === 0) return; - setCurrentBuildingName(buildingsList[0]); }, [buildingsList]); + useEffect(() => { + if (currentBuildingName === undefined) return; + else if (currentBuildingName === "새롬관") { + navigator("/admin/main"); + } + }, [currentBuildingName]); + return ( { const navigate = useNavigate(); @@ -218,7 +218,8 @@ const SearchBarInputStyled = styled.input` `; const SearchButtonStyled = styled.button` - background: url("/src/assets/images/searchWhite.svg") no-repeat 50% 50%; + background: url("/src/Cabinet/assets/images/searchWhite.svg") no-repeat 50% + 50%; width: 32px; height: 32px; position: absolute; diff --git a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx similarity index 91% rename from frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx rename to frontend/src/Cabinet/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx index cc56979f0..e3204e8d4 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx +++ b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBarList/SearchBarList.tsx @@ -1,6 +1,6 @@ +import SearchListItem from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem"; +import { CabinetSimple } from "@/Cabinet/types/dto/cabinet.dto"; import styled from "styled-components"; -import SearchListItem from "@/components/TopNav/SearchBar/SearchListItem/SearchListItem"; -import { CabinetSimple } from "@/types/dto/cabinet.dto"; interface ISearchListByIntraId { name: string; diff --git a/frontend/src/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx similarity index 100% rename from frontend/src/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx rename to frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML.tsx diff --git a/frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx similarity index 88% rename from frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx rename to frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx index 549ccfa0a..225e52838 100644 --- a/frontend/src/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx +++ b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import ChangeToHTML from "@/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; +import ChangeToHTML from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; const SearchListItem = (props: { floor?: number; @@ -21,8 +21,8 @@ const SearchListItem = (props: { const navigate = useNavigate(); const chooseImage = (isCabinet: boolean | undefined) => { - if (isCabinet) return "/src/assets/images/cabinet.svg"; - return "/src/assets/images/privateIcon.svg"; + if (isCabinet) return "/src/Cabinet/assets/images/cabinet.svg"; + return "/src/Cabinet/assets/images/privateIcon.svg"; }; return ( diff --git a/frontend/src/components/TopNav/TopNav.container.tsx b/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx similarity index 74% rename from frontend/src/components/TopNav/TopNav.container.tsx rename to frontend/src/Cabinet/components/TopNav/TopNav.container.tsx index d12cfebfc..23ee2077e 100644 --- a/frontend/src/components/TopNav/TopNav.container.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx @@ -1,15 +1,19 @@ import React, { SetStateAction, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import { buildingsFloorState, currentBuildingNameState, myCabinetInfoState, -} from "@/recoil/atoms"; -import { buildingsState } from "@/recoil/selectors"; -import TopNav from "@/components/TopNav/TopNav"; -import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto"; -import { axiosBuildingFloor, axiosMyLentInfo } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import { buildingsState } from "@/Cabinet/recoil/selectors"; +import TopNav from "@/Cabinet/components/TopNav/TopNav"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; +import { + axiosBuildingFloor, + axiosMyLentInfo, +} from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; const TopNavContainer: React.FC<{ setIsLoading: React.Dispatch>; @@ -24,6 +28,7 @@ const TopNavContainer: React.FC<{ const buildingsList = useRecoilValue>(buildingsState); const { setIsLoading } = props; const { toggleLeftNav } = useMenu(); + const navigator = useNavigate(); const onClickLogo = () => { toggleLeftNav(); @@ -37,8 +42,7 @@ const TopNavContainer: React.FC<{ try { await setTimeoutPromise(500); const buildingsFloorData = await axiosBuildingFloor(); - - setBuildingsFloor(buildingsFloorData.data); + setBuildingsFloor([...buildingsFloorData.data]); } catch (error) { console.log(error); } @@ -60,10 +64,16 @@ const TopNavContainer: React.FC<{ useEffect(() => { if (buildingsList.length === 0) return; - setCurrentBuildingName(buildingsList[0]); }, [buildingsList]); + useEffect(() => { + if (currentBuildingName === undefined) return; + else if (currentBuildingName === "새롬관") { + navigator("/home"); + } + }, [currentBuildingName]); + return ( ; } +interface ITopNavProps { + currentBuildingName: string; + buildingsList: Array; + buildingClicked: boolean; + setBuildingClicked: React.Dispatch; + onClickLogo: React.MouseEventHandler; + setCurrentBuildingName: SetterOrUpdater; + isAdmin?: boolean; +} + const BuildingListItem: React.FC = ({ building, onUpdate, @@ -30,73 +41,75 @@ const BuildingListItem: React.FC = ({ ); }; -const TopNav: React.FC<{ - currentBuildingName: string; - buildingsList: Array; - buildingClicked: boolean; - setBuildingClicked: React.Dispatch; - onClickLogo: React.MouseEventHandler; - setCurrentBuildingName: SetterOrUpdater; - isAdmin?: boolean; -}> = (props) => { - const { - currentBuildingName, - buildingsList, - buildingClicked, - setBuildingClicked, - onClickLogo, - setCurrentBuildingName, - isAdmin, - } = props; - +const TopNav = ({ + currentBuildingName, + buildingsList, + buildingClicked, + setBuildingClicked, + onClickLogo, + setCurrentBuildingName, + isAdmin = false, +}: ITopNavProps): JSX.Element => { const buildingDom = React.useRef(null); useOutsideClick(buildingDom, () => { if (buildingClicked) setBuildingClicked(false); }); return ( - - - {isAdmin && } - + + + + + {isAdmin && } + + ); }; const TopNavContainerStyled = styled.nav` + width: 100%; + display: flex; + flex-direction: column; + background-color: white; + z-index: 10; +`; + +const TopNavWrapperStyled = styled.div` width: 100%; height: 80px; min-height: 80px; display: flex; justify-content: space-between; align-items: center; - background-color: white; - border-bottom: 1px solid #bcbcbc; + border-bottom: 1px solid var(--line-color); padding: 0 28px; color: var(--gray-color); - z-index: 10; `; const LogoStyled = styled.div` @@ -140,7 +153,7 @@ const BuildingSelectBoxStyled = styled.span` transform: translateY(-50%); width: 20px; height: 20px; - background: url(/src/assets/images/select.svg) no-repeat 100%; + background: url(/src/Cabinet/assets/images/select.svg) no-repeat 100%; } `; diff --git a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx similarity index 87% rename from frontend/src/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx rename to frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx index 7e8b6d77b..189acb307 100644 --- a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx @@ -34,7 +34,8 @@ const TopNavButtonStyled = styled.div<{ height: 32px; margin-right: 10px; cursor: pointer; - display: ${({ disable }) => (disable ? "none" : "block")}; + /* display: ${({ disable }) => (disable ? "none" : "block")}; */ + /* visibility: ${({ disable }) => (disable ? "hidden" : "visible")}; */ @media (hover: hover) and (pointer: fine) { &:hover { opacity: 0.9; diff --git a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx similarity index 80% rename from frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx rename to frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx index 4fad185e0..b55572cae 100644 --- a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx @@ -6,17 +6,17 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; -import TopNavButton from "@/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentDto } from "@/types/dto/lent.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/recoil/atoms"; +import TopNavButton from "@/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; import { axiosCabinetById, axiosDeleteCurrentBanLog, -} from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; export const getDefaultCabinetInfo = () => ({ building: "", @@ -47,7 +47,6 @@ const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => { async function setTargetCabinetInfoToMyCabinet() { setCurrentCabinetId(myInfo.cabinetId); - setMyInfo((prev) => ({ ...prev, cabinetId: null })); try { if (!myCabinetInfo?.cabinetId) return; const { data } = await axiosCabinetById(myCabinetInfo.cabinetId); @@ -94,7 +93,7 @@ const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => { {import.meta.env.VITE_UNBAN === "true" && ( axiosDeleteCurrentBanLog(myInfo.userId)} - imgSrc="/src/assets/images/happyCcabiWhite.png" + imgSrc="/src/Cabinet/assets/images/happyCcabiWhite.png" width="32px" height="32px" /> @@ -103,20 +102,23 @@ const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => { )} - {!isAdmin && ( + {!isAdmin && !!myInfo.cabinetId && ( )} - + ); }; diff --git a/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx b/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx new file mode 100644 index 000000000..1cf23e060 --- /dev/null +++ b/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx @@ -0,0 +1,108 @@ +import { useLocation, useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { ReactComponent as CabiLogo } from "@/Cabinet/assets/images/logo.svg"; +import { ReactComponent as PresentationLogo } from "@/Presentation/assets/images/logo.svg"; + +interface ITopNavDomain { + path: string; + adminPath: string; + logo: React.FunctionComponent>; + title: string; + active: (pathname: string) => boolean; +} + +const domains: ITopNavDomain[] = [ + { + path: "/home", + adminPath: "/admin/home", + logo: CabiLogo, + title: "Cabi", + active: (pathname) => !pathname.includes("presentation"), + }, + { + path: "/presentation/home", + adminPath: "/admin/presentation/detail", + logo: PresentationLogo, + title: "수요지식회", + active: (pathname) => pathname.includes("presentation"), + }, +]; + +const TopNavDomainGroup = ({ isAdmin = false }: { isAdmin?: boolean }) => { + const navigator = useNavigate(); + const { pathname } = useLocation(); + return ( + + {domains.map((domain, index) => ( + + navigator(isAdmin ? domain.adminPath : domain.path)} + > + + + + + {domain.title} + + + {index < domains.length - 1 && } + + ))} + + ); +}; + +const DomainGroupContainerStyled = styled.div` + display: flex; + align-items: center; + width: 100%; + height: 40px; + border-bottom: 1px solid var(--line-color); + padding: 0 28px; + color: var(--gray-color); + font-size: 0.875rem; +`; + +const DomainWrapperStyled = styled.div` + display: flex; + align-items: center; +`; + +const DomainContainerStyled = styled.div` + display: flex; + align-items: center; +`; + +const LogoContainerStyled = styled.div` + display: flex; + align-items: center; + width: 14px; + height: 14px; + cursor: pointer; + svg { + .logo_svg__currentPath { + fill: var(--default-main-color); + } + } +`; + +const DomainTitleStyled = styled.div<{ fontWeight: string }>` + display: flex; + align-items: center; + font-size: 0.875rem; + font-weight: ${(props) => props.fontWeight}; + margin-left: 4px; + cursor: pointer; +`; + +const DomainSeparatorStyled = styled.div` + width: 1px; + height: 20px; + margin: 0 8px; + background-color: #d9d9d9; +`; + +export default TopNavDomainGroup; diff --git a/frontend/src/components/UserInfoArea/UserInfoArea.container.tsx b/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.container.tsx similarity index 75% rename from frontend/src/components/UserInfoArea/UserInfoArea.container.tsx rename to frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.container.tsx index 600d098ad..9fd2b7c08 100644 --- a/frontend/src/components/UserInfoArea/UserInfoArea.container.tsx +++ b/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.container.tsx @@ -1,10 +1,10 @@ import { useRecoilValue } from "recoil"; -import { targetUserInfoState } from "@/recoil/atoms"; -import AdminLentLog from "@/components/LentLog/AdminLentLog"; +import { targetUserInfoState } from "@/Cabinet/recoil/atoms"; +import AdminLentLog from "@/Cabinet/components/LentLog/AdminLentLog"; import UserInfoArea, { ISelectedUserInfo, -} from "@/components/UserInfoArea/UserInfoArea"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/components/UserInfoArea/UserInfoArea"; +import useMenu from "@/Cabinet/hooks/useMenu"; const UserInfoAreaContainer = (): JSX.Element => { const targetUserInfo = useRecoilValue(targetUserInfoState); diff --git a/frontend/src/components/UserInfoArea/UserInfoArea.tsx b/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.tsx similarity index 89% rename from frontend/src/components/UserInfoArea/UserInfoArea.tsx rename to frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.tsx index 2ef8ef0aa..8ed3518a5 100644 --- a/frontend/src/components/UserInfoArea/UserInfoArea.tsx +++ b/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.tsx @@ -1,17 +1,17 @@ import React, { useState } from "react"; import styled from "styled-components"; -import ButtonContainer from "@/components/Common/Button"; -import BanModal from "@/components/Modals/BanModal/BanModal"; -import AdminReturnModal from "@/components/Modals/ReturnModal/AdminReturnModal"; -import ChangeToHTML from "@/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; +import ButtonContainer from "@/Cabinet/components/Common/Button"; +import BanModal from "@/Cabinet/components/Modals/BanModal/BanModal"; +import AdminReturnModal from "@/Cabinet/components/Modals/ReturnModal/AdminReturnModal"; +import ChangeToHTML from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; import { cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, -} from "@/assets/data/maps"; -import cabiLogo from "@/assets/images/logo.svg"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +} from "@/Cabinet/assets/data/maps"; +import cabiLogo from "@/Cabinet/assets/images/logo.svg"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; export interface ISelectedUserInfo { name: string; diff --git a/frontend/src/constants/StatusCode.ts b/frontend/src/Cabinet/constants/StatusCode.ts similarity index 100% rename from frontend/src/constants/StatusCode.ts rename to frontend/src/Cabinet/constants/StatusCode.ts diff --git a/frontend/src/firebase/firebase-messaging-sw.ts b/frontend/src/Cabinet/firebase/firebase-messaging-sw.ts similarity index 100% rename from frontend/src/firebase/firebase-messaging-sw.ts rename to frontend/src/Cabinet/firebase/firebase-messaging-sw.ts diff --git a/frontend/src/hooks/useAdminHomeApi.ts b/frontend/src/Cabinet/hooks/useAdminHomeApi.ts similarity index 93% rename from frontend/src/hooks/useAdminHomeApi.ts rename to frontend/src/Cabinet/hooks/useAdminHomeApi.ts index 34f91767e..6a75e83c5 100644 --- a/frontend/src/hooks/useAdminHomeApi.ts +++ b/frontend/src/Cabinet/hooks/useAdminHomeApi.ts @@ -3,19 +3,19 @@ import { ICabinetNumbersPerFloor, IMonthlyData, ITableData, -} from "@/types/dto/admin.dto"; +} from "@/Cabinet/types/dto/admin.dto"; import { axiosGetBannedUserList, axiosGetBrokenCabinetList, axiosGetCabinetNumbersPerFloor, axiosGetOverdueUserList, axiosGetStatistics, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; import { handleBannedUserList, handleBrokenCabinetList, handleOverdueUserList, -} from "@/utils/tableUtils"; +} from "@/Cabinet/utils/tableUtils"; export async function useAdminHomeApi( setMonthlyData: React.Dispatch>, diff --git a/frontend/src/hooks/useCabinetListRefresh.ts b/frontend/src/Cabinet/hooks/useCabinetListRefresh.ts similarity index 95% rename from frontend/src/hooks/useCabinetListRefresh.ts rename to frontend/src/Cabinet/hooks/useCabinetListRefresh.ts index 4c03f7982..f3850cc9c 100644 --- a/frontend/src/hooks/useCabinetListRefresh.ts +++ b/frontend/src/Cabinet/hooks/useCabinetListRefresh.ts @@ -5,17 +5,17 @@ import { myCabinetInfoState, targetCabinetInfoState, userState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { CabinetInfo, CabinetPreview, CabinetPreviewInfo, -} from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/types/dto/cabinet.dto"; import { axiosCabinetByBuildingFloor, axiosCabinetById, axiosMyLentInfo, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; const shouldUpdateTargetCabinetInfo = ( cabinet: CabinetPreviewInfo, diff --git a/frontend/src/hooks/useClubInfo.ts b/frontend/src/Cabinet/hooks/useClubInfo.ts similarity index 90% rename from frontend/src/hooks/useClubInfo.ts rename to frontend/src/Cabinet/hooks/useClubInfo.ts index 32081ba21..152676f21 100644 --- a/frontend/src/hooks/useClubInfo.ts +++ b/frontend/src/Cabinet/hooks/useClubInfo.ts @@ -4,14 +4,14 @@ import { isCurrentSectionRenderState, targetClubCabinetInfoState, targetClubInfoState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { ClubInfoResponseDto, ClubInfoResponseType, -} from "@/types/dto/club.dto"; -import { axiosGetClubInfo } from "@/api/axios/axios.custom"; -import useMenu from "@/hooks/useMenu"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +} from "@/Cabinet/types/dto/club.dto"; +import { axiosGetClubInfo } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const useClubInfo = () => { const [clubState, setClubState] = useState({ clubId: 0, page: 0 }); diff --git a/frontend/src/hooks/useDebounce.tsx b/frontend/src/Cabinet/hooks/useDebounce.tsx similarity index 100% rename from frontend/src/hooks/useDebounce.tsx rename to frontend/src/Cabinet/hooks/useDebounce.tsx diff --git a/frontend/src/hooks/useIsMount.ts b/frontend/src/Cabinet/hooks/useIsMount.ts similarity index 100% rename from frontend/src/hooks/useIsMount.ts rename to frontend/src/Cabinet/hooks/useIsMount.ts diff --git a/frontend/src/hooks/useMenu.ts b/frontend/src/Cabinet/hooks/useMenu.ts similarity index 99% rename from frontend/src/hooks/useMenu.ts rename to frontend/src/Cabinet/hooks/useMenu.ts index 228b228e8..c3fa83f95 100644 --- a/frontend/src/hooks/useMenu.ts +++ b/frontend/src/Cabinet/hooks/useMenu.ts @@ -1,11 +1,11 @@ -import { useResetRecoilState } from "recoil"; import { currentCabinetIdState, currentIntraIdState, targetCabinetInfoState, targetClubUserInfoState, targetUserInfoState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; +import { useResetRecoilState } from "recoil"; const useMenu = () => { const resetTargetCabinetInfo = useResetRecoilState(targetCabinetInfoState); diff --git a/frontend/src/hooks/useMultiSelect.ts b/frontend/src/Cabinet/hooks/useMultiSelect.ts similarity index 94% rename from frontend/src/hooks/useMultiSelect.ts rename to frontend/src/Cabinet/hooks/useMultiSelect.ts index 03010a5d1..1b5a46a21 100644 --- a/frontend/src/hooks/useMultiSelect.ts +++ b/frontend/src/Cabinet/hooks/useMultiSelect.ts @@ -3,9 +3,12 @@ import { isMultiSelectState, selectedTypeOnSearchState, targetCabinetInfoListState, -} from "@/recoil/atoms"; -import { CabinetInfo, CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import { + CabinetInfo, + CabinetPreviewInfo, +} from "@/Cabinet/types/dto/cabinet.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; const useMultiSelect = () => { const [targetCabinetInfoList, setTargetCabinetInfoList] = useRecoilState< diff --git a/frontend/src/hooks/useOutsideClick.ts b/frontend/src/Cabinet/hooks/useOutsideClick.ts similarity index 100% rename from frontend/src/hooks/useOutsideClick.ts rename to frontend/src/Cabinet/hooks/useOutsideClick.ts diff --git a/frontend/src/pages/AvailablePage.tsx b/frontend/src/Cabinet/pages/AvailablePage.tsx similarity index 88% rename from frontend/src/pages/AvailablePage.tsx rename to frontend/src/Cabinet/pages/AvailablePage.tsx index f5a853c6f..0326464e1 100644 --- a/frontend/src/pages/AvailablePage.tsx +++ b/frontend/src/Cabinet/pages/AvailablePage.tsx @@ -1,20 +1,20 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { isCurrentSectionRenderState } from "@/recoil/atoms"; -import AvailableCountdown from "@/components/Available/AvailableCountdown"; -import FloorContainer from "@/components/Available/FloorContainer"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; +import { isCurrentSectionRenderState } from "@/Cabinet/recoil/atoms"; +import AvailableCountdown from "@/Cabinet/components/Available/AvailableCountdown"; +import FloorContainer from "@/Cabinet/components/Available/FloorContainer"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; import MultiToggleSwitch, { toggleItem, -} from "@/components/Common/MultiToggleSwitch"; +} from "@/Cabinet/components/Common/MultiToggleSwitch"; import { AvailableCabinetsInfo, CabinetPreviewInfo, -} from "@/types/dto/cabinet.dto"; -import { axiosGetAvailableCabinets } from "@/api/axios/axios.custom"; -import useDebounce from "@/hooks/useDebounce"; -import { deleteRecoilPersistFloorSection } from "@/utils/recoilPersistUtils"; +} from "@/Cabinet/types/dto/cabinet.dto"; +import { axiosGetAvailableCabinets } from "@/Cabinet/api/axios/axios.custom"; +import useDebounce from "@/Cabinet/hooks/useDebounce"; +import { deleteRecoilPersistFloorSection } from "@/Cabinet/utils/recoilPersistUtils"; enum AvailableCabinetsType { ALL = "ALL", @@ -137,7 +137,10 @@ const AvailablePage = () => { ) : ( <> - 새로고침 + 새로고침 setIsOpenTime(true)} /> )} diff --git a/frontend/src/pages/ClubPage.tsx b/frontend/src/Cabinet/pages/ClubPage.tsx similarity index 79% rename from frontend/src/pages/ClubPage.tsx rename to frontend/src/Cabinet/pages/ClubPage.tsx index 07462db5c..306a3f13a 100644 --- a/frontend/src/pages/ClubPage.tsx +++ b/frontend/src/Cabinet/pages/ClubPage.tsx @@ -1,11 +1,11 @@ import { useEffect } from "react"; import { useRecoilValue } from "recoil"; import styled from "styled-components"; -import { myClubListState } from "@/recoil/atoms"; -import ClubInfo from "@/components/Club/ClubInfo"; -import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; -import { ClubPaginationResponseDto } from "@/types/dto/club.dto"; -import { deleteRecoilPersistFloorSection } from "@/utils/recoilPersistUtils"; +import { myClubListState } from "@/Cabinet/recoil/atoms"; +import ClubInfo from "@/Cabinet/components/Club/ClubInfo"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import { ClubPaginationResponseDto } from "@/Cabinet/types/dto/club.dto"; +import { deleteRecoilPersistFloorSection } from "@/Cabinet/utils/recoilPersistUtils"; const ClubPage = () => { const clubList = useRecoilValue(myClubListState); diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/Cabinet/pages/HomePage.tsx similarity index 67% rename from frontend/src/pages/HomePage.tsx rename to frontend/src/Cabinet/pages/HomePage.tsx index 579f58ac5..114b16dae 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/Cabinet/pages/HomePage.tsx @@ -1,9 +1,9 @@ import { useNavigate } from "react-router-dom"; import { useRecoilValue, useSetRecoilState } from "recoil"; -import { currentFloorNumberState } from "@/recoil/atoms"; -import { currentBuildingFloorState } from "@/recoil/selectors"; -import ServiceManual from "@/components/Home/ServiceManual"; -import "@/assets/css/homePage.css"; +import { currentFloorNumberState } from "@/Cabinet/recoil/atoms"; +import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; +import ServiceManual from "@/Cabinet/components/Home/ServiceManual"; +import "@/Cabinet/assets/css/homePage.css"; const HomePage = () => { const floors = useRecoilValue>(currentBuildingFloorState); diff --git a/frontend/src/pages/Layout.tsx b/frontend/src/Cabinet/pages/Layout.tsx similarity index 81% rename from frontend/src/pages/Layout.tsx rename to frontend/src/Cabinet/pages/Layout.tsx index 5b6e6c440..569bfc95e 100644 --- a/frontend/src/pages/Layout.tsx +++ b/frontend/src/Cabinet/pages/Layout.tsx @@ -8,24 +8,26 @@ import { serverTimeState, targetClubInfoState, userState, -} from "@/recoil/atoms"; -import CabinetInfoAreaContainer from "@/components/CabinetInfoArea/CabinetInfoArea.container"; -import ClubMemberInfoAreaContainer from "@/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import LeftNav from "@/components/LeftNav/LeftNav"; -import MapInfoContainer from "@/components/MapInfo/MapInfo.container"; -import OverduePenaltyModal from "@/components/Modals/OverduePenaltyModal/OverduePenaltyModal"; -import TopNavContainer from "@/components/TopNav/TopNav.container"; -import { additionalModalType } from "@/assets/data/maps"; +} from "@/Cabinet/recoil/atoms"; +import CabinetInfoAreaContainer from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; +import ClubMemberInfoAreaContainer from "@/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import LeftNav from "@/Cabinet/components/LeftNav/LeftNav"; +import MapInfoContainer from "@/Cabinet/components/MapInfo/MapInfo.container"; +import OverduePenaltyModal from "@/Cabinet/components/Modals/OverduePenaltyModal/OverduePenaltyModal"; +import TopNavContainer from "@/Cabinet/components/TopNav/TopNav.container"; +import { additionalModalType } from "@/Cabinet/assets/data/maps"; import { ClubPaginationResponseDto, ClubResponseDto, -} from "@/types/dto/club.dto"; -import { UserDto, UserInfo } from "@/types/dto/user.dto"; -import ColorType from "@/types/enum/color.type.enum"; -import { axiosMyClubList, axiosMyInfo } from "@/api/axios/axios.custom"; -import { getCookie } from "@/api/react_cookie/cookies"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/types/dto/club.dto"; +import { UserDto, UserInfo } from "@/Cabinet/types/dto/user.dto"; +import { axiosMyClubList, axiosMyInfo } from "@/Cabinet/api/axios/axios.custom"; +import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; +import useMenu from "@/Cabinet/hooks/useMenu"; + +const root: HTMLElement = document.documentElement; +const token = getCookie("access_token"); const Layout = (): JSX.Element => { const [isLoading, setIsLoading] = useState(true); @@ -40,12 +42,16 @@ const Layout = (): JSX.Element => { useSetRecoilState(targetClubInfoState); const navigate = useNavigate(); const location = useLocation(); - const token = getCookie("access_token"); + const { closeAll } = useMenu(); const isRootPath: boolean = location.pathname === "/"; const isLoginPage: boolean = location.pathname === "/login"; const isMainPage: boolean = location.pathname === "/main"; + const savedMainColor = localStorage.getItem("main-color"); + const savedSubColor = localStorage.getItem("sub-color"); + const savedMineColor = localStorage.getItem("mine-color"); + const openModal = () => { setIsModalOpen(true); }; @@ -54,6 +60,10 @@ const Layout = (): JSX.Element => { setIsModalOpen(false); }; + const handleClickBg = () => { + closeAll(); + }; + const getMyInfo = async () => { try { const { @@ -91,11 +101,6 @@ const Layout = (): JSX.Element => { } }; - const savedMainColor = localStorage.getItem("main-color"); - const savedSubColor = localStorage.getItem("sub-color"); - const savedMineColor = localStorage.getItem("mine-color"); - const root: HTMLElement = document.documentElement; - useEffect(() => { if (!token && !isLoginPage) navigate("/login"); else if (token) { @@ -116,12 +121,6 @@ const Layout = (): JSX.Element => { root.style.setProperty("--mine", savedMineColor); }, [savedMainColor, savedSubColor, savedMineColor]); - const { closeAll } = useMenu(); - - const handleClickBg = () => { - closeAll(); - }; - return isLoginPage ? ( ) : ( @@ -157,8 +156,6 @@ const Layout = (): JSX.Element => { ); }; -export default Layout; - const WrapperStyled = styled.div` width: 100%; height: 100%; @@ -184,9 +181,9 @@ const DetailInfoContainerStyled = styled.div<{ isHomePage: boolean }>` props.isHomePage && css` position: fixed; - top: 80px; + top: 120px; right: 0; - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; @@ -200,3 +197,5 @@ const DetailInfoContainerStyled = styled.div<{ isHomePage: boolean }>` const MenuBgStyled = styled.div` position: none; `; + +export default Layout; diff --git a/frontend/src/pages/LogPage.tsx b/frontend/src/Cabinet/pages/LogPage.tsx similarity index 80% rename from frontend/src/pages/LogPage.tsx rename to frontend/src/Cabinet/pages/LogPage.tsx index 56ec7f156..8d84d5d79 100644 --- a/frontend/src/pages/LogPage.tsx +++ b/frontend/src/Cabinet/pages/LogPage.tsx @@ -1,10 +1,10 @@ import { useEffect, useState } from "react"; import styled from "styled-components"; -import LogTable from "@/components/LentLog/LogTable/LogTable"; -import { LentHistoryDto } from "@/types/dto/lent.dto"; -import { LentLogResponseType } from "@/types/dto/lent.dto"; -import { axiosMyLentLog } from "@/api/axios/axios.custom"; -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import LogTable from "@/Cabinet/components/LentLog/LogTable/LogTable"; +import { LentHistoryDto } from "@/Cabinet/types/dto/lent.dto"; +import { LentLogResponseType } from "@/Cabinet/types/dto/lent.dto"; +import { axiosMyLentLog } from "@/Cabinet/api/axios/axios.custom"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const LogPage = () => { const [lentLog, setLentLog] = useState(undefined); diff --git a/frontend/src/pages/LoginFailurePage.tsx b/frontend/src/Cabinet/pages/LoginFailurePage.tsx similarity index 84% rename from frontend/src/pages/LoginFailurePage.tsx rename to frontend/src/Cabinet/pages/LoginFailurePage.tsx index 53872545e..527b0cf06 100644 --- a/frontend/src/pages/LoginFailurePage.tsx +++ b/frontend/src/Cabinet/pages/LoginFailurePage.tsx @@ -1,5 +1,5 @@ +import AnnounceTemplate from "@/Cabinet/components/Announce/AnnounceTemplate"; import { useNavigate } from "react-router-dom"; -import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/Cabinet/pages/LoginPage.tsx similarity index 66% rename from frontend/src/pages/LoginPage.tsx rename to frontend/src/Cabinet/pages/LoginPage.tsx index 38479f786..880d5fab1 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/Cabinet/pages/LoginPage.tsx @@ -1,5 +1,5 @@ -import LoginTemplate from "@/components/Login/LoginTemplate"; -import "@/assets/css/loginPage.css"; +import LoginTemplate from "@/Cabinet/components/Login/LoginTemplate"; +import "@/Cabinet/assets/css/loginPage.css"; const LoginPage = () => { const ORIGIN_URL = window.location.origin; @@ -10,7 +10,7 @@ const LoginPage = () => { url={`${url}?redirect=${ORIGIN_URL}/post-login`} pageTitle="Cabi" pageSubTitle="여러분의 일상을 가볍게" - imgSrc="/src/assets/images/loginImg.svg" + imgSrc="/src/Cabinet/assets/images/loginImg.svg" /> ); }; diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/Cabinet/pages/MainPage.tsx similarity index 88% rename from frontend/src/pages/MainPage.tsx rename to frontend/src/Cabinet/pages/MainPage.tsx index 05848c76b..6948d9879 100644 --- a/frontend/src/pages/MainPage.tsx +++ b/frontend/src/Cabinet/pages/MainPage.tsx @@ -8,14 +8,14 @@ import { currentFloorNumberState, currentSectionNameState, targetCabinetInfoState, -} from "@/recoil/atoms"; -import { currentFloorSectionState } from "@/recoil/selectors"; -import CabinetListContainer from "@/components/CabinetList/CabinetList.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import SectionPaginationContainer from "@/components/SectionPagination/SectionPagination.container"; -import SectionType from "@/types/enum/map.type.enum"; -import useCabinetListRefresh from "@/hooks/useCabinetListRefresh"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/recoil/atoms"; +import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; +import CabinetListContainer from "@/Cabinet/components/CabinetList/CabinetList.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import SectionPaginationContainer from "@/Cabinet/components/SectionPagination/SectionPagination.container"; +import SectionType from "@/Cabinet/types/enum/map.type.enum"; +import useCabinetListRefresh from "@/Cabinet/hooks/useCabinetListRefresh"; +import useMenu from "@/Cabinet/hooks/useMenu"; const MainPage = () => { const touchStartPosX = useRef(0); diff --git a/frontend/src/pages/NotFoundPage.tsx b/frontend/src/Cabinet/pages/NotFoundPage.tsx similarity index 86% rename from frontend/src/pages/NotFoundPage.tsx rename to frontend/src/Cabinet/pages/NotFoundPage.tsx index 0222f2fad..a481b69ce 100644 --- a/frontend/src/pages/NotFoundPage.tsx +++ b/frontend/src/Cabinet/pages/NotFoundPage.tsx @@ -1,5 +1,5 @@ +import AnnounceTemplate from "@/Cabinet/components/Announce/AnnounceTemplate"; import { useNavigate } from "react-router-dom"; -import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); diff --git a/frontend/src/pages/PostLogin.tsx b/frontend/src/Cabinet/pages/PostLogin.tsx similarity index 79% rename from frontend/src/pages/PostLogin.tsx rename to frontend/src/Cabinet/pages/PostLogin.tsx index f7a723b17..c840badad 100644 --- a/frontend/src/pages/PostLogin.tsx +++ b/frontend/src/Cabinet/pages/PostLogin.tsx @@ -1,15 +1,18 @@ import { deleteFcmToken, requestFcmAndGetDeviceToken, -} from "@/firebase/firebase-messaging-sw"; +} from "@/Cabinet/firebase/firebase-messaging-sw"; import { useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { useRecoilState, useSetRecoilState } from "recoil"; -import { userState } from "@/recoil/atoms"; -import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; -import { UserDto } from "@/types/dto/user.dto"; -import { axiosMyInfo, axiosUpdateDeviceToken } from "@/api/axios/axios.custom"; -import { getCookie } from "@/api/react_cookie/cookies"; +import { userState } from "@/Cabinet/recoil/atoms"; +import AnnounceTemplate from "@/Cabinet/components/Announce/AnnounceTemplate"; +import { UserDto } from "@/Cabinet/types/dto/user.dto"; +import { + axiosMyInfo, + axiosUpdateDeviceToken, +} from "@/Cabinet/api/axios/axios.custom"; +import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; const PostLogin = (): JSX.Element => { const [isLoading, setIsLoading] = useState(true); diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/Cabinet/pages/ProfilePage.tsx similarity index 70% rename from frontend/src/pages/ProfilePage.tsx rename to frontend/src/Cabinet/pages/ProfilePage.tsx index fb554cc5c..d8ab061e2 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/Cabinet/pages/ProfilePage.tsx @@ -1,19 +1,22 @@ import { deleteFcmToken, requestFcmAndGetDeviceToken, -} from "@/firebase/firebase-messaging-sw"; +} from "@/Cabinet/firebase/firebase-messaging-sw"; import { useEffect, useState } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; import styled from "styled-components"; -import { userState } from "@/recoil/atoms"; -import ExtensionCardContainer from "@/components/Card/ExtensionCard/ExtensionCard.container"; -import LentInfoCardContainer from "@/components/Card/LentInfoCard/LentInfoCard.container"; -import NotificationCardContainer from "@/components/Card/NotificationCard/NotificationCard.container"; -import ProfileCardContainer from "@/components/Card/ProfileCard/ProfileCard.container"; -import ThemeColorCardContainer from "@/components/Card/ThemeColorCard/ThemeColorCard.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import { axiosMyInfo, axiosUpdateDeviceToken } from "@/api/axios/axios.custom"; -import { deleteRecoilPersistFloorSection } from "@/utils/recoilPersistUtils"; +import { userState } from "@/Cabinet/recoil/atoms"; +import ExtensionCardContainer from "@/Cabinet/components/Card/ExtensionCard/ExtensionCard.container"; +import LentInfoCardContainer from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard.container"; +import NotificationCardContainer from "@/Cabinet/components/Card/NotificationCard/NotificationCard.container"; +import ProfileCardContainer from "@/Cabinet/components/Card/ProfileCard/ProfileCard.container"; +import ThemeColorCardContainer from "@/Cabinet/components/Card/ThemeColorCard/ThemeColorCard.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { + axiosMyInfo, + axiosUpdateDeviceToken, +} from "@/Cabinet/api/axios/axios.custom"; +import { deleteRecoilPersistFloorSection } from "@/Cabinet/utils/recoilPersistUtils"; const ProfilePage = () => { const [isLoading, setIsLoading] = useState(true); diff --git a/frontend/src/pages/admin/AdminClubPage.tsx b/frontend/src/Cabinet/pages/admin/AdminClubPage.tsx similarity index 86% rename from frontend/src/pages/admin/AdminClubPage.tsx rename to frontend/src/Cabinet/pages/admin/AdminClubPage.tsx index c831f5474..c5dc710f6 100644 --- a/frontend/src/pages/admin/AdminClubPage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminClubPage.tsx @@ -1,12 +1,12 @@ +import AdminClubLogContainer from "@/Cabinet/components/Club/AdminClubLog.container"; +import Button from "@/Cabinet/components/Common/Button"; +import ClubModal from "@/Cabinet/components/Modals/ClubModal/ClubModal"; +import { selectedClubInfoState } from "@/Cabinet/recoil/atoms"; +import { ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import { deleteRecoilPersistFloorSection } from "@/Cabinet/utils/recoilPersistUtils"; import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; -import { selectedClubInfoState } from "@/recoil/atoms"; -import AdminClubLogContainer from "@/components/Club/AdminClubLog.container"; -import Button from "@/components/Common/Button"; -import ClubModal from "@/components/Modals/ClubModal/ClubModal"; -import { ClubUserDto } from "@/types/dto/lent.dto"; -import { deleteRecoilPersistFloorSection } from "@/utils/recoilPersistUtils"; const AdminClubPage = () => { const [shouldFetchData, setShouldFetchData] = useState(false); diff --git a/frontend/src/pages/admin/AdminHomePage.tsx b/frontend/src/Cabinet/pages/admin/AdminHomePage.tsx similarity index 90% rename from frontend/src/pages/admin/AdminHomePage.tsx rename to frontend/src/Cabinet/pages/admin/AdminHomePage.tsx index 188c2a1f2..cc8d0a296 100644 --- a/frontend/src/pages/admin/AdminHomePage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminHomePage.tsx @@ -9,20 +9,20 @@ import { selectedTypeOnSearchState, targetCabinetInfoState, targetUserInfoState, -} from "@/recoil/atoms"; -import BarChart from "@/components/AdminInfo/Chart/BarChart"; -import LineChart from "@/components/AdminInfo/Chart/LineChart"; -import PieChart from "@/components/AdminInfo/Chart/PieChart"; -import AdminTable from "@/components/AdminInfo/Table/AdminTable"; +} from "@/Cabinet/recoil/atoms"; +import BarChart from "@/Cabinet/components/AdminInfo/Chart/BarChart"; +import LineChart from "@/Cabinet/components/AdminInfo/Chart/LineChart"; +import PieChart from "@/Cabinet/components/AdminInfo/Chart/PieChart"; +import AdminTable from "@/Cabinet/components/AdminInfo/Table/AdminTable"; import { ICabinetNumbersPerFloor, IMonthlyData, ITableData, -} from "@/types/dto/admin.dto"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { axiosCabinetById } from "@/api/axios/axios.custom"; -import { useAdminHomeApi } from "@/hooks/useAdminHomeApi"; -import useMenu from "@/hooks/useMenu"; +} from "@/Cabinet/types/dto/admin.dto"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { axiosCabinetById } from "@/Cabinet/api/axios/axios.custom"; +import { useAdminHomeApi } from "@/Cabinet/hooks/useAdminHomeApi"; +import useMenu from "@/Cabinet/hooks/useMenu"; const AdminHomePage = () => { const [overdueUserList, setOverdueUserList] = useRecoilState( diff --git a/frontend/src/pages/admin/AdminLayout.tsx b/frontend/src/Cabinet/pages/admin/AdminLayout.tsx similarity index 82% rename from frontend/src/pages/admin/AdminLayout.tsx rename to frontend/src/Cabinet/pages/admin/AdminLayout.tsx index e0ec0e3b2..fdc2d84eb 100644 --- a/frontend/src/pages/admin/AdminLayout.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminLayout.tsx @@ -3,16 +3,15 @@ import { Outlet } from "react-router"; import { useLocation, useNavigate } from "react-router-dom"; import { useRecoilValue } from "recoil"; import styled, { css } from "styled-components"; -import { selectedTypeOnSearchState } from "@/recoil/atoms"; -import CabinetInfoAreaContainer from "@/components/CabinetInfoArea/CabinetInfoArea.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import LeftNav from "@/components/LeftNav/LeftNav"; -import MapInfoContainer from "@/components/MapInfo/MapInfo.container"; -import AdminTopNavContainer from "@/components/TopNav/AdminTopNav.container"; -import UserInfoAreaContainer from "@/components/UserInfoArea/UserInfoArea.container"; -import ColorType from "@/types/enum/color.type.enum"; -import { getCookie } from "@/api/react_cookie/cookies"; -import useMenu from "@/hooks/useMenu"; +import { selectedTypeOnSearchState } from "@/Cabinet/recoil/atoms"; +import CabinetInfoAreaContainer from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import LeftNav from "@/Cabinet/components/LeftNav/LeftNav"; +import MapInfoContainer from "@/Cabinet/components/MapInfo/MapInfo.container"; +import AdminTopNavContainer from "@/Cabinet/components/TopNav/AdminTopNav.container"; +import UserInfoAreaContainer from "@/Cabinet/components/UserInfoArea/UserInfoArea.container"; +import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; +import useMenu from "@/Cabinet/hooks/useMenu"; const Layout = (): JSX.Element => { const [isLoading, setIsLoading] = useState(true); @@ -115,9 +114,9 @@ const DetailInfoContainerStyled = styled.div<{ isFloat: boolean }>` props.isFloat && css` position: fixed; - top: 80px; + top: 120px; right: 0; - height: calc(100% - 80px); + height: calc(100% - 120px); z-index: 9; transform: translateX(120%); transition: transform 0.3s ease-in-out; diff --git a/frontend/src/pages/admin/AdminLoginFailurePage.tsx b/frontend/src/Cabinet/pages/admin/AdminLoginFailurePage.tsx similarity index 85% rename from frontend/src/pages/admin/AdminLoginFailurePage.tsx rename to frontend/src/Cabinet/pages/admin/AdminLoginFailurePage.tsx index 2e20c6cb8..3daaaa8a4 100644 --- a/frontend/src/pages/admin/AdminLoginFailurePage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminLoginFailurePage.tsx @@ -1,5 +1,5 @@ +import AnnounceTemplate from "@/Cabinet/components/Announce/AnnounceTemplate"; import { useNavigate } from "react-router-dom"; -import AnnounceTemplate from "@/components/Announce/AnnounceTemplate"; const NotFoundPage = () => { const navigate = useNavigate(); diff --git a/frontend/src/pages/admin/AdminLoginPage.tsx b/frontend/src/Cabinet/pages/admin/AdminLoginPage.tsx similarity index 58% rename from frontend/src/pages/admin/AdminLoginPage.tsx rename to frontend/src/Cabinet/pages/admin/AdminLoginPage.tsx index 0d63e3734..e40391ae4 100644 --- a/frontend/src/pages/admin/AdminLoginPage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminLoginPage.tsx @@ -1,5 +1,5 @@ -import AdminLoginTemplate from "@/components/Login/AdminLoginTemplate"; -import "@/assets/css/loginPage.css"; +import AdminLoginTemplate from "@/Cabinet/components/Login/AdminLoginTemplate"; +import "@/Cabinet/assets/css/loginPage.css"; const LoginPage = () => { const url = `${import.meta.env.VITE_BE_HOST}/v4/admin/auth/login`; @@ -9,7 +9,7 @@ const LoginPage = () => { url={url} pageTitle="Cabi Admin" pageSubTitle="관리자 페이지" - imgSrc="/src/assets/images/adminLoginImg.svg" + imgSrc="/src/Cabinet/assets/images/adminLoginImg.svg" /> ); }; diff --git a/frontend/src/pages/admin/AdminMainPage.tsx b/frontend/src/Cabinet/pages/admin/AdminMainPage.tsx similarity index 86% rename from frontend/src/pages/admin/AdminMainPage.tsx rename to frontend/src/Cabinet/pages/admin/AdminMainPage.tsx index f2d52fe86..4c0cbb376 100644 --- a/frontend/src/pages/admin/AdminMainPage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminMainPage.tsx @@ -5,16 +5,19 @@ import { currentBuildingNameState, currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; -import { currentCabinetIdState, targetCabinetInfoState } from "@/recoil/atoms"; -import { currentFloorSectionState } from "@/recoil/selectors"; -import CabinetListContainer from "@/components/CabinetList/CabinetList.container"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import MultiSelectButton from "@/components/Common/MultiSelectButton"; -import SectionPaginationContainer from "@/components/SectionPagination/SectionPagination.container"; -import useCabinetListRefresh from "@/hooks/useCabinetListRefresh"; -import useMenu from "@/hooks/useMenu"; -import useMultiSelect from "@/hooks/useMultiSelect"; +} from "@/Cabinet/recoil/atoms"; +import { + currentCabinetIdState, + targetCabinetInfoState, +} from "@/Cabinet/recoil/atoms"; +import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; +import CabinetListContainer from "@/Cabinet/components/CabinetList/CabinetList.container"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import MultiSelectButton from "@/Cabinet/components/Common/MultiSelectButton"; +import SectionPaginationContainer from "@/Cabinet/components/SectionPagination/SectionPagination.container"; +import useCabinetListRefresh from "@/Cabinet/hooks/useCabinetListRefresh"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; const AdminMainPage = () => { const touchStartPosX = useRef(0); diff --git a/frontend/src/pages/admin/AdminPagination.tsx b/frontend/src/Cabinet/pages/admin/AdminPagination.tsx similarity index 100% rename from frontend/src/pages/admin/AdminPagination.tsx rename to frontend/src/Cabinet/pages/admin/AdminPagination.tsx diff --git a/frontend/src/pages/admin/SearchPage.tsx b/frontend/src/Cabinet/pages/admin/SearchPage.tsx similarity index 90% rename from frontend/src/pages/admin/SearchPage.tsx rename to frontend/src/Cabinet/pages/admin/SearchPage.tsx index 7e5e2bdc2..e659b7cfd 100644 --- a/frontend/src/pages/admin/SearchPage.tsx +++ b/frontend/src/Cabinet/pages/admin/SearchPage.tsx @@ -6,17 +6,17 @@ import { currentCabinetIdState, currentIntraIdState, numberOfAdminWorkState, -} from "@/recoil/atoms"; -import LoadingAnimation from "@/components/Common/LoadingAnimation"; -import NoSearch from "@/components/Search/NoSearch"; -import SearchDefault from "@/components/Search/SearchDefault"; -import SearchItemByIntraId from "@/components/Search/SearchItemByIntraId"; -import SearchItemByNum from "@/components/Search/SearchItemByNum"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/recoil/atoms"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import NoSearch from "@/Cabinet/components/Search/NoSearch"; +import SearchDefault from "@/Cabinet/components/Search/SearchDefault"; +import SearchItemByIntraId from "@/Cabinet/components/Search/SearchItemByIntraId"; +import SearchItemByNum from "@/Cabinet/components/Search/SearchItemByNum"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; import { axiosSearchByCabinetNum, axiosSearchDetailByIntraId, -} from "@/api/axios/axios.custom"; +} from "@/Cabinet/api/axios/axios.custom"; interface ISearchDetail { name: string; @@ -189,7 +189,7 @@ const MoreButtonStyled = styled.button` transform: translateY(-40%); width: 20px; height: 20px; - background: url(/src/assets/images/selectPurple.svg) no-repeat 100%; + background: url(/src/Cabinet/assets/images/selectPurple.svg) no-repeat 100%; } `; diff --git a/frontend/src/recoil/atoms.ts b/frontend/src/Cabinet/recoil/atoms.ts similarity index 91% rename from frontend/src/recoil/atoms.ts rename to frontend/src/Cabinet/recoil/atoms.ts index 82f32a5c0..888bdc2fc 100644 --- a/frontend/src/recoil/atoms.ts +++ b/frontend/src/Cabinet/recoil/atoms.ts @@ -1,23 +1,23 @@ import { atom } from "recoil"; import { recoilPersist } from "recoil-persist"; -import { staticColNumData } from "@/assets/data/sectionColNumData"; -import { IBuildingColNum } from "@/assets/data/sectionColNumData"; -import { ITableData } from "@/types/dto/admin.dto"; +import { staticColNumData } from "@/Cabinet/assets/data/sectionColNumData"; +import { IBuildingColNum } from "@/Cabinet/assets/data/sectionColNumData"; +import { ITableData } from "@/Cabinet/types/dto/admin.dto"; import { CabinetBuildingFloorDto, CabinetInfo, CabinetInfoByBuildingFloorDto, CabinetPreviewInfo, MyCabinetInfoResponseDto, -} from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/types/dto/cabinet.dto"; import { ClubCabinetInfo, ClubPaginationResponseDto, ClubResponseDto, ClubUserResponseDto, -} from "@/types/dto/club.dto"; -import { ClubUserDto } from "@/types/dto/lent.dto"; -import { UserDto, UserInfo } from "@/types/dto/user.dto"; +} from "@/Cabinet/types/dto/club.dto"; +import { ClubUserDto } from "@/Cabinet/types/dto/lent.dto"; +import { UserDto, UserInfo } from "@/Cabinet/types/dto/user.dto"; const { persistAtom } = recoilPersist(); diff --git a/frontend/src/recoil/selectors.ts b/frontend/src/Cabinet/recoil/selectors.ts similarity index 96% rename from frontend/src/recoil/selectors.ts rename to frontend/src/Cabinet/recoil/selectors.ts index cfd01dcd8..c15744474 100644 --- a/frontend/src/recoil/selectors.ts +++ b/frontend/src/Cabinet/recoil/selectors.ts @@ -6,12 +6,15 @@ import { currentFloorCabinetState, currentFloorNumberState, currentSectionNameState, -} from "@/recoil/atoms"; +} from "@/Cabinet/recoil/atoms"; import { IFloorSectionColNum, ISectionColNum, -} from "@/assets/data/sectionColNumData"; -import { CabinetInfo, CabinetPreviewInfo } from "@/types/dto/cabinet.dto"; +} from "@/Cabinet/assets/data/sectionColNumData"; +import { + CabinetInfo, + CabinetPreviewInfo, +} from "@/Cabinet/types/dto/cabinet.dto"; export const buildingsState = selector>({ key: "Buildings", diff --git a/frontend/src/types/dto/admin.dto.ts b/frontend/src/Cabinet/types/dto/admin.dto.ts similarity index 100% rename from frontend/src/types/dto/admin.dto.ts rename to frontend/src/Cabinet/types/dto/admin.dto.ts diff --git a/frontend/src/types/dto/alarm.dto.ts b/frontend/src/Cabinet/types/dto/alarm.dto.ts similarity index 100% rename from frontend/src/types/dto/alarm.dto.ts rename to frontend/src/Cabinet/types/dto/alarm.dto.ts diff --git a/frontend/src/types/dto/cabinet.dto.ts b/frontend/src/Cabinet/types/dto/cabinet.dto.ts similarity index 87% rename from frontend/src/types/dto/cabinet.dto.ts rename to frontend/src/Cabinet/types/dto/cabinet.dto.ts index 88a5c3ad4..6a65cd4c6 100644 --- a/frontend/src/types/dto/cabinet.dto.ts +++ b/frontend/src/Cabinet/types/dto/cabinet.dto.ts @@ -1,6 +1,6 @@ -import { LentDto } from "@/types/dto/lent.dto"; -import CabinetStatus from "@/types/enum/cabinet.status.enum"; -import CabinetType from "@/types/enum/cabinet.type.enum"; +import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; // TODO :hybae // lentType을 LentType으로 변경 예정 diff --git a/frontend/src/types/dto/club.dto.ts b/frontend/src/Cabinet/types/dto/club.dto.ts similarity index 92% rename from frontend/src/types/dto/club.dto.ts rename to frontend/src/Cabinet/types/dto/club.dto.ts index ae416037f..379462796 100644 --- a/frontend/src/types/dto/club.dto.ts +++ b/frontend/src/Cabinet/types/dto/club.dto.ts @@ -1,4 +1,4 @@ -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; export type ClubListReponseType = | ClubPaginationResponseDto diff --git a/frontend/src/types/dto/lent.dto.ts b/frontend/src/Cabinet/types/dto/lent.dto.ts similarity index 95% rename from frontend/src/types/dto/lent.dto.ts rename to frontend/src/Cabinet/types/dto/lent.dto.ts index 88b71fba5..05f483c44 100644 --- a/frontend/src/types/dto/lent.dto.ts +++ b/frontend/src/Cabinet/types/dto/lent.dto.ts @@ -1,4 +1,4 @@ -import { STATUS_400_BAD_REQUEST } from "@/constants/StatusCode"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; /** * @interface diff --git a/frontend/src/types/dto/user.dto.ts b/frontend/src/Cabinet/types/dto/user.dto.ts similarity index 84% rename from frontend/src/types/dto/user.dto.ts rename to frontend/src/Cabinet/types/dto/user.dto.ts index 64ecffd4e..71336948b 100644 --- a/frontend/src/types/dto/user.dto.ts +++ b/frontend/src/Cabinet/types/dto/user.dto.ts @@ -1,6 +1,6 @@ -import { AlarmInfo } from "@/types/dto/alarm.dto"; -import { CabinetInfo } from "@/types/dto/cabinet.dto"; -import { LentExtensionDto } from "@/types/dto/lent.dto"; +import { AlarmInfo } from "@/Cabinet/types/dto/alarm.dto"; +import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { LentExtensionDto } from "@/Cabinet/types/dto/lent.dto"; /** * @description 유저 정보 diff --git a/frontend/src/types/enum/AnnounceType.enum.ts b/frontend/src/Cabinet/types/enum/AnnounceType.enum.ts similarity index 100% rename from frontend/src/types/enum/AnnounceType.enum.ts rename to frontend/src/Cabinet/types/enum/AnnounceType.enum.ts diff --git a/frontend/src/types/enum/cabinet.status.enum.ts b/frontend/src/Cabinet/types/enum/cabinet.status.enum.ts similarity index 100% rename from frontend/src/types/enum/cabinet.status.enum.ts rename to frontend/src/Cabinet/types/enum/cabinet.status.enum.ts diff --git a/frontend/src/types/enum/cabinet.type.enum.ts b/frontend/src/Cabinet/types/enum/cabinet.type.enum.ts similarity index 100% rename from frontend/src/types/enum/cabinet.type.enum.ts rename to frontend/src/Cabinet/types/enum/cabinet.type.enum.ts diff --git a/frontend/src/types/enum/color.type.enum.ts b/frontend/src/Cabinet/types/enum/color.type.enum.ts similarity index 100% rename from frontend/src/types/enum/color.type.enum.ts rename to frontend/src/Cabinet/types/enum/color.type.enum.ts diff --git a/frontend/src/types/enum/content.status.enum.ts b/frontend/src/Cabinet/types/enum/content.status.enum.ts similarity index 100% rename from frontend/src/types/enum/content.status.enum.ts rename to frontend/src/Cabinet/types/enum/content.status.enum.ts diff --git a/frontend/src/types/enum/icon.type.enum.ts b/frontend/src/Cabinet/types/enum/icon.type.enum.ts similarity index 100% rename from frontend/src/types/enum/icon.type.enum.ts rename to frontend/src/Cabinet/types/enum/icon.type.enum.ts diff --git a/frontend/src/types/enum/map.type.enum.ts b/frontend/src/Cabinet/types/enum/map.type.enum.ts similarity index 100% rename from frontend/src/types/enum/map.type.enum.ts rename to frontend/src/Cabinet/types/enum/map.type.enum.ts diff --git a/frontend/src/types/enum/time.enum.ts b/frontend/src/Cabinet/types/enum/time.enum.ts similarity index 100% rename from frontend/src/types/enum/time.enum.ts rename to frontend/src/Cabinet/types/enum/time.enum.ts diff --git a/frontend/src/utils/dateUtils.ts b/frontend/src/Cabinet/utils/dateUtils.ts similarity index 51% rename from frontend/src/utils/dateUtils.ts rename to frontend/src/Cabinet/utils/dateUtils.ts index bc5736daa..0e6de4d3c 100644 --- a/frontend/src/utils/dateUtils.ts +++ b/frontend/src/Cabinet/utils/dateUtils.ts @@ -1,7 +1,26 @@ +/** + * @description 해당 월, 일의 앞자리를 0으로 채워 두 자리로 만듦 + * + * @param num 날짜 + * + * @returns 두 자리로 만들어진 날짜 + */ export const padTo2Digits = (num: number) => { return num.toString().padStart(2, "0"); }; +/** + * @description 해당 날짜의 년, 월, 일을 divider 로 구분하여 반환 + * + * @param date 날짜 + * @param divider 구분자 + * + * @returns 구분자로 구분된 년, 월, 일 + * + * @example + * const result = formatDate(new Date(), "-") + * //=> "2023-04-14" + */ export const formatDate = (date: Date | null, divider: string) => { if (date === null) return ""; return [ @@ -11,6 +30,14 @@ export const formatDate = (date: Date | null, divider: string) => { ].join(divider); }; +/** + * @description 주어진 lentType에 따라 대여 만료일을 구해 "YYYY/MM/DD" 형식으로 반환. 예정된 대여 만료일이 있다면 그 일자를 반환 + * + * @param lentType 대여 타입 + * @param existExpireDate 예정된 만료일 + * + * @returns "YYYY/MM/DD" 형식의 대여 만료일 + */ export const getExpireDateString = ( lentType: string, existExpireDate?: Date @@ -26,7 +53,15 @@ export const getExpireDateString = ( return formatDate(expireDate, "/"); }; -// 공유 사물함 반납 시 남은 대여일 수 차감 (원래 남은 대여일 수 * (남은 인원 / 원래 있던 인원)) +/** + * @description 공유 사물함 반납 시 남은 대여일 수를 공식 (원래 남은 대여일 수 * (남은 인원 / 원래 있던 인원)) 에 따라 차감해 새로운 만료일을 "YYYY/MM/DD" 형식으로 반환 + * + * @param lentType 대여 타입 + * @param currentNumUsers 현재 대여 중인 인원 + * @param existExpireDate 예정된 만료일 + * + * @returns "YYYY/MM/DD" 형식의 새로운 대여 만료일 + */ export const getShortenedExpireDateString = ( lentType: string, currentNumUsers: number, @@ -43,6 +78,14 @@ export const getShortenedExpireDateString = ( return formatDate(new Date(newExpireDate), "/"); }; +/** + * @description 주어진 날짜를 기준으로 주어진 일자만큼 만료일을 연장하여 "YYYY/MM/DD" 형식으로 반환 + * + * @param existExpireDate 예정된 만료일 + * @param dateToExtend 연장할 일자 + * + * @returns "YYYY/MM/DD" 형식의 연장된 대여 만료일 + */ export const getExtendedDateString = ( existExpireDate: Date | undefined | null, dateToExtend: number | undefined @@ -53,23 +96,27 @@ export const getExtendedDateString = ( return formatDate(expireDate, "/"); }; -export const getTotalPage = (totalLength: number, size: number) => { - return Math.ceil(totalLength / size); -}; - +/** + * @description 주어진 대여 만료일을 기준으로 남은 대여일 수를 계산하여 반환. 만료일이 지났다면 음수로 반환 + * + * @param expireTime 대여 만료일 + * + * @returns 남은 대여일 수 + */ export const calExpiredTime = (expireTime: Date) => Math.floor( (expireTime.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24) ); +/** + * @description 주어진 대여 만료일을 기준으로 남은 대여일 수를 계산하여 반환. 만료일이 지났다면 음수로 반환. expireTime 을 Date 로 변환 후 사용하는 Wrapper 함수 + * + * @param expireTime 대여 만료일 + * + * @returns 남은 대여일 수 + */ export const getRemainingTime = (expireTime: Date | undefined) => { if (!expireTime) return 0; const remainTime = calExpiredTime(new Date(expireTime)); return remainTime < 0 ? -remainTime : remainTime; }; - -export const getExpireDate = (date: Date | undefined) => { - if (!date) return null; - if (date.toString().slice(0, 4) === "9999") return null; - return date.toString().slice(0, 10); -}; diff --git a/frontend/src/Cabinet/utils/paginationUtils.ts b/frontend/src/Cabinet/utils/paginationUtils.ts new file mode 100644 index 000000000..a5e55fc20 --- /dev/null +++ b/frontend/src/Cabinet/utils/paginationUtils.ts @@ -0,0 +1,3 @@ +export const getTotalPage = (totalLength: number, size: number) => { + return Math.ceil(totalLength / size); +}; diff --git a/frontend/src/utils/recoilPersistUtils.ts b/frontend/src/Cabinet/utils/recoilPersistUtils.ts similarity index 100% rename from frontend/src/utils/recoilPersistUtils.ts rename to frontend/src/Cabinet/utils/recoilPersistUtils.ts diff --git a/frontend/src/utils/tableUtils.ts b/frontend/src/Cabinet/utils/tableUtils.ts similarity index 97% rename from frontend/src/utils/tableUtils.ts rename to frontend/src/Cabinet/utils/tableUtils.ts index 55e35641f..383a61293 100644 --- a/frontend/src/utils/tableUtils.ts +++ b/frontend/src/Cabinet/utils/tableUtils.ts @@ -3,7 +3,7 @@ import { BrokenCabinetDto, ITableData, OverdueUserDto, -} from "@/types/dto/admin.dto"; +} from "@/Cabinet/types/dto/admin.dto"; const calcLeftDays = (end: Date) => Math.ceil((end.getTime() - new Date().getTime()) / 1000 / 3600 / 24); diff --git a/frontend/src/Presentation/api/axios/axios.custom.ts b/frontend/src/Presentation/api/axios/axios.custom.ts new file mode 100644 index 000000000..3e90ae9d9 --- /dev/null +++ b/frontend/src/Presentation/api/axios/axios.custom.ts @@ -0,0 +1,116 @@ +import instance from "@/Cabinet/api/axios/axios.instance"; + +/** + * 수요지식회 (구 까비지식회) API + */ +const axiosGetPresentationURL = "/v5/presentation/"; +export const axiosGetPresentation = async () => { + try { + const response = await instance.get(axiosGetPresentationURL, { + params: { pastFormCount: 1, upcomingFormCount: 3 }, + }); + return response; + } catch (error) { + throw error; + } +}; + +const axiosGetPresentationScheduleURL = "/v5/presentation/schedule/"; +export const axiosGetPresentationSchedule = async ( + yearMonth: string +): Promise => { + try { + const response = await instance.get(axiosGetPresentationScheduleURL, { + params: { yearMonth }, + }); + return response; + } catch (error) { + throw error; + } +}; + +const axiosPostPresentationFormURL = "/v5/presentation/form"; +export const axiosPostPresentationForm = async ( + subject: string, + summary: string, + detail: string, + dateTime: Date, + category: string, + presentationTime: string +): Promise => { + try { + const response = await instance.post(axiosPostPresentationFormURL, { + subject, + summary, + detail, + dateTime: dateTime.toISOString(), + category, + presentationTime, + }); + return response; + } catch (error) { + throw error; + } +}; + +const axiosGetInvalidDatesURL = "/v5/presentation/form/invalid-date"; +export const axiosGetInvalidDates = async (): Promise => { + try { + const response = await instance.get(axiosGetInvalidDatesURL); + return response; + } catch (error) { + throw error; + } +}; + +const axiosMyPresentationLogURL = "/v5/presentation/me/histories"; +export const axiosMyPresentationLog = async (page: number): Promise => { + try { + const response = await instance.get( + `${axiosMyPresentationLogURL}?page=${page}&size=10&sort=dateTime,desc` + ); + return response; + } catch (error) { + throw error; + } +}; + +/** + * 수요지식회 (구 까비지식회) Admin API + */ +const axiosUpdatePresentationStatusURL = + "/v5/admin/presentation/{formId}/update"; +export const axiosUpdatePresentationStatus = async ( + formId: number, + dateTime: string, + status: string, + location: string +): Promise => { + try { + const response = await instance.patch( + axiosUpdatePresentationStatusURL.replace("{formId}", formId.toString()), + { + dateTime, + status, + location, + } + ); + return response; + } catch (error) { + throw error; + } +}; + +const axiosGetAdminPresentationScheduleURL = "/v5/admin/presentation/schedule/"; +export const getAdminPresentationSchedule = async ( + yearMonth: string +): Promise => { + try { + const response = await instance.get(axiosGetAdminPresentationScheduleURL, { + params: { yearMonth }, + }); + return response; + } catch (error) { + throw error; + } +}; diff --git a/frontend/src/Presentation/assets/data/maps.ts b/frontend/src/Presentation/assets/data/maps.ts new file mode 100644 index 000000000..de667a4b6 --- /dev/null +++ b/frontend/src/Presentation/assets/data/maps.ts @@ -0,0 +1,61 @@ +import { PresentationTimeKey } from "@/Presentation/pages/RegisterPage"; +import { + PresentationCategoryType, + PresentationLocation, + PresentationPeriodType, + PresentationStatusType, +} from "@/Presentation/types/enum/presentation.type.enum"; + +export const PresentationStatusTypeLabelMap = { + [PresentationStatusType.EXPECTED]: "발표예정", + [PresentationStatusType.DONE]: "발표완료", + [PresentationStatusType.CANCEL]: "발표취소", +}; + +export const PresentationPeriodTypeNumberLabelMap = { + [PresentationPeriodType.NONE]: 0, + [PresentationPeriodType.HALF]: 30, + [PresentationPeriodType.HOUR]: 60, + [PresentationPeriodType.HOUR_HALF]: 90, + [PresentationPeriodType.TWO_HOUR]: 120, +}; + +export const PresentationTimeMap: { + [key in PresentationTimeKey]: PresentationPeriodType; +} = { + "": PresentationPeriodType.NONE, + "30분": PresentationPeriodType.HALF, + "1시간": PresentationPeriodType.HOUR, + "1시간 30분": PresentationPeriodType.HOUR_HALF, + "2시간": PresentationPeriodType.TWO_HOUR, +}; + +export const PresentationLocationLabelMap = { + [PresentationLocation.BASEMENT]: "지하 1층 오픈스튜디오", + [PresentationLocation.FIRST]: "1층 오픈스튜디오", + [PresentationLocation.THIRD]: "3층 세미나실", +}; + +export const PresentationCategoryTypeLabelMap = { + [PresentationCategoryType.DEVELOP]: "개발", + [PresentationCategoryType.STUDY]: "학술", + [PresentationCategoryType.HOBBY]: "취미", + [PresentationCategoryType.JOB]: "취업", + [PresentationCategoryType.TASK]: "42", + [PresentationCategoryType.ETC]: "기타", +}; + +export const presentationCategoryIconMap = { + [PresentationCategoryType.DEVELOP]: + "/src/Cabinet/assets/images/PresentationDevelop.svg", + [PresentationCategoryType.STUDY]: + "/src/Cabinet/assets/images/PresentationAcademic.svg", + [PresentationCategoryType.HOBBY]: + "/src/Cabinet/assets/images/PresentationHobby.svg", + [PresentationCategoryType.JOB]: + "/src/Cabinet/assets/images/PresentationJob.svg", + [PresentationCategoryType.TASK]: + "/src/Cabinet/assets/images/PresentationFortyTwo.svg", + [PresentationCategoryType.ETC]: + "/src/Cabinet/assets/images/PresentationEtc.svg", +}; diff --git a/frontend/src/Presentation/assets/images/logo.svg b/frontend/src/Presentation/assets/images/logo.svg new file mode 100644 index 000000000..1f0bdf423 --- /dev/null +++ b/frontend/src/Presentation/assets/images/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/src/Presentation/components/Details/DetailContent.container.tsx b/frontend/src/Presentation/components/Details/DetailContent.container.tsx new file mode 100644 index 000000000..e1dcfa898 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailContent.container.tsx @@ -0,0 +1,172 @@ +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import { useRecoilState } from "recoil"; +import { isCurrentModalState } from "@/Presentation/recoil/atoms"; +import DetailContent from "@/Presentation/components/Details/DetailContent"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { + axiosGetPresentationSchedule, + getAdminPresentationSchedule, +} from "@/Presentation/api/axios/axios.custom"; +import { + calculateAvailableDaysInWeeks, + makeIDateObj, + toISOStringwithTimeZone, +} from "@/Presentation/utils/dateUtils"; +import { WEDNESDAY } from "@/Presentation/constants/dayOfTheWeek"; +import { + AVAILABLE_WEEKS, + FUTURE_MONTHS_TO_DISPLAY, +} from "@/Presentation/constants/policy"; + +export interface IDate { + year: string; + month: string; + day: string; +} + +const createEmptyPresentation = (day: Date) => ({ + id: null, + subject: null, + summary: null, + detail: null, + dateTime: toISOStringwithTimeZone(day), + category: null, + userName: null, + presentationTime: null, + presentationStatus: null, + presentationLocation: null, +}); + +const findExistingPresentation = ( + presentations: IPresentationScheduleDetailInfo[], + day: Date +) => { + return presentations.find((p) => { + const presentationDate = new Date(p.dateTime); + return ( + presentationDate.getFullYear() === day.getFullYear() && + presentationDate.getMonth() === day.getMonth() && + presentationDate.getDate() === day.getDate() + ); + }); +}; + +const mergePresentationsWithDays = ( + presentations: IPresentationScheduleDetailInfo[], + days: Date[] +) => { + return days.map((day) => { + const existingPresentation = findExistingPresentation(presentations, day); + return existingPresentation || createEmptyPresentation(day); + }); +}; + +const DetailContentContainer = () => { + const [currentDate, setCurrentDate] = useState(null); + const [todayDate, setTodayDate] = useState(null); + const [presentationDetailInfo, setPresentationDetailInfo] = useState< + IPresentationScheduleDetailInfo[] | null + >(null); + const firstPresentationDate: IDate = { year: "2024", month: "4", day: "24" }; + const { pathname } = useLocation(); + const isAdmin = pathname.includes("admin/presentation"); + const [isCurrentRender, setIsCurrentRender] = + useRecoilState(isCurrentModalState); + + useEffect(() => { + const tmpTodayDate = makeIDateObj(new Date()); + if ( + !( + todayDate?.year === tmpTodayDate.year || + todayDate?.month === tmpTodayDate.month || + todayDate?.day === tmpTodayDate.day + ) + ) + setTodayDate(tmpTodayDate); + setCurrentDate(tmpTodayDate); + }, []); + + useEffect(() => { + setIsCurrentRender(false); + if (currentDate) getPresentationSchedule(currentDate); + }, [currentDate, isCurrentRender]); + + const fetchPresentationData = async (year: string, month: string) => { + return !isAdmin + ? await axiosGetPresentationSchedule(year + "-" + month) + : await getAdminPresentationSchedule(year + "-" + month); + }; + + const getPresentationSchedule = async (requestDate: IDate) => { + try { + const response = await fetchPresentationData( + requestDate.year, + requestDate.month + ); + const availableDays = calculateAvailableDaysInWeeks( + new Date( + parseInt(requestDate.year), + parseInt(requestDate.month) - 1, + 1 + ), + AVAILABLE_WEEKS, + WEDNESDAY, + 1 + ); + const mergedPresentationInfo = mergePresentationsWithDays( + response.data.forms as IPresentationScheduleDetailInfo[], + availableDays + ); + setPresentationDetailInfo(mergedPresentationInfo); + } catch (error) { + console.error("Error fetching presentation schedule:", error); + } + }; + + const moveMonth = (direction: string) => { + let requestDate: IDate = { ...currentDate! }; + let currentDateMonth = parseInt(currentDate!.month); + let currentDateYear = parseInt(currentDate!.year); + + if (direction === "left") { + // 현재 페이지 날짜의 월-1 axios 요청 + if (currentDateMonth === 1) { + requestDate.year = (currentDateYear - 1).toString(); + requestDate.month = (12).toString(); + } else { + requestDate.month = (currentDateMonth - 1).toString().padStart(2, "0"); + } + } else { + if (currentDateMonth === 12) { + requestDate.year = (currentDateYear + 1).toString(); + requestDate.month = (1).toString(); + } else { + requestDate.month = (currentDateMonth + 1).toString().padStart(2, "0"); + } + } + + setCurrentDate(requestDate); + }; + + return ( + parseInt(firstPresentationDate.month) + : false + } + canMoveRight={ + currentDate && todayDate + ? parseInt(currentDate.month) < + parseInt(todayDate.month) + FUTURE_MONTHS_TO_DISPLAY - 1 + : false + } + /> + ); +}; + +export default DetailContentContainer; diff --git a/frontend/src/Presentation/components/Details/DetailContent.tsx b/frontend/src/Presentation/components/Details/DetailContent.tsx new file mode 100644 index 000000000..02f864325 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailContent.tsx @@ -0,0 +1,115 @@ +import styled from "styled-components"; +import { MoveSectionButtonStyled } from "@/Cabinet/components/SectionPagination/SectionPagination"; +import LeftSectionButton from "@/Cabinet/assets/images/LeftSectionButton.svg"; +import { IDate } from "@/Presentation/components/Details/DetailContent.container"; +import DetailTableContainer from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +const DetailContent = ({ + moveMonth, + currentDate, + presentationDetailInfo, + + canMoveLeft, + canMoveRight, +}: { + moveMonth: (direction: string) => void; + currentDate: IDate | null; + presentationDetailInfo: IPresentationScheduleDetailInfo[] | null; + + canMoveLeft: boolean; + canMoveRight: boolean; +}) => { + return ( + + + {canMoveLeft ? ( + moveMonth("left")} + className="cabiButton" + /> + ) : ( +
+ )} +
+ {currentDate?.year}년 {currentDate?.month}월 +
+ {canMoveRight ? ( + moveMonth("right")} + arrowReversed={true} + className="cabiButton" + /> + ) : ( +
+ )} +
+ + + +
+ ); +}; + +export default DetailContent; + +const ContainerStyled = styled.div` + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + overflow-y: scroll; +`; + +const HeaderStyled = styled.div` + margin-top: 70px; + text-align: center; + width: 340px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + + & > #headerDate { + width: 200px; + height: 50px; + font-size: 2rem; + line-height: 3rem; + font-weight: 600; + } + + & > img { + width: 2.5rem; + height: 2.5rem; + } + + & > #replaceOfMoveButton { + width: 2.5rem; + height: 2.5rem; + margin: 0px 15px; + } + + @media (max-width: 1150px) { + margin-top: 30px; + } +`; + +const BodyStyled = styled.div` + margin-top: 50px; + margin-bottom: 50px; + width: 80%; + padding: 24px 20px 10px 20px; + background-color: var(--lightgray-color); + border-radius: 10px; + display: flex; + min-width: 375px; + + @media (max-width: 1150px) { + margin-top: 16px; + width: 96%; + padding: 0 16px; + } +`; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTable.container.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTable.container.tsx new file mode 100644 index 000000000..b90bab33d --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTable.container.tsx @@ -0,0 +1,113 @@ +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import DetailTable from "@/Presentation/components/Details/DetailTable/DetailTable"; +import EditStatusModal from "@/Presentation/components/Modals/EditStatusModal/EditStatusModal"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { toISOStringwithTimeZone } from "@/Presentation/utils/dateUtils"; + +export interface IAdminCurrentModalStateInfo { + statusModal: boolean; +} + +export type TAdminModalState = "statusModal"; + +export enum itemType { + EVENT_AVAILABLE = "", + NO_EVENT_CURRENT = "noEventCurrent", + NO_EVENT_PAST = "noEventPast", +} + +const tableHeadArray = { + date: "날짜", + subject: "제목", + userName: "ID", + category: "카테고리", + presentationTime: "시간", + presentationLocation: "장소", +}; + +const adminTableHeadArray = { + date: "날짜", + subject: "제목", + userName: "ID", + category: "카테고리", + presentationTime: "시간", + presentationLocation: "장소", + presentationStatus: "상태", +}; + +const DetailTableContainer = ({ + presentationDetailInfo, +}: { + presentationDetailInfo: IPresentationScheduleDetailInfo[] | null; +}) => { + const [adminModal, setAdminModal] = useState({ + statusModal: false, + }); + const { pathname } = useLocation(); + const isAdmin = pathname.includes("admin/presentation"); + const [list, setList] = useState( + null + ); + const [isMobile, setIsMobile] = useState(false); + const tableHeadEntries = !isAdmin + ? Object.entries(tableHeadArray) + : Object.entries(adminTableHeadArray); + + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < 1150); + }; + window.addEventListener("resize", handleResize); + handleResize(); + + return () => window.removeEventListener("resize", handleResize); + }, []); + + useEffect(() => { + if (presentationDetailInfo) setList(presentationDetailInfo); + }, [presentationDetailInfo]); + + const openAdminModal = (modal: TAdminModalState) => { + setAdminModal({ ...adminModal, [modal]: true }); + }; + + const closeAdminModal = (modal: TAdminModalState) => { + setAdminModal({ ...adminModal, [modal]: false }); + }; + + const groupEvent = (item: IPresentationScheduleDetailInfo) => { + let itemStatus = itemType.EVENT_AVAILABLE; + + // 발표가 없다면 + if (!item.id) { + const date = new Date(); + let dateISO = toISOStringwithTimeZone(date); + const dateObj = new Date(dateISO); + + const itemDateObj = new Date(item.dateTime); + if (dateObj > itemDateObj) itemStatus = itemType.NO_EVENT_PAST; + else itemStatus = itemType.NO_EVENT_CURRENT; + } + + return itemStatus; + }; + + return ( + <> + + {adminModal.statusModal && ( + closeAdminModal("statusModal")} /> + )} + + ); +}; + +export default DetailTableContainer; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTable.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTable.tsx new file mode 100644 index 000000000..18fb9ebf1 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTable.tsx @@ -0,0 +1,79 @@ +import styled from "styled-components"; +import { IDate } from "@/Presentation/components/Details/DetailContent.container"; +import { + TAdminModalState, + itemType, +} from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import DetailTableBodyItem from "@/Presentation/components/Details/DetailTable/DetailTableBodyItem"; +import DetailTableHead from "@/Presentation/components/Details/DetailTable/DetailTableHead"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { makeIDateObj } from "@/Presentation/utils/dateUtils"; + +const DetailTable = ({ + isMobile, + list, + isAdmin, + openAdminModal, + groupEvent, + tableHeadEntries, +}: { + isMobile: boolean; + list: IPresentationScheduleDetailInfo[] | null; + isAdmin: boolean; + openAdminModal: (modal: TAdminModalState) => void; + groupEvent: (item: IPresentationScheduleDetailInfo) => itemType; + tableHeadEntries: [string, string][]; +}) => { + return ( + + + + + {list?.map((item, idx) => { + let itemStatus = groupEvent(item); + const itemInfo = { + item: item, + itemStatus: itemStatus, + itemDateInIDate: makeIDateObj(new Date(item.dateTime)), + hasNoUpcomingEvent: itemStatus === itemType.NO_EVENT_CURRENT, + }; + return ( + + ); + })} + + + ); +}; + +const TableStyled = styled.table` + width: 100%; + table-layout: fixed; + word-break: break-all; +`; + +const TableBodyStyled = styled.tbody` + width: 100%; + + & #selected { + background-color: #91b5fa; + } +`; + +export const WhiteSpaceTrStyled = styled.tr` + height: 24px; + width: 100%; +`; + +export default DetailTable; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItem.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItem.tsx new file mode 100644 index 000000000..288a214b9 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItem.tsx @@ -0,0 +1,113 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useSetRecoilState } from "recoil"; +import { currentPresentationState } from "@/Presentation/recoil/atoms"; +import { IDate } from "@/Presentation/components/Details/DetailContent.container"; +import { WhiteSpaceTrStyled } from "@/Presentation/components/Details/DetailTable/DetailTable"; +import { + TAdminModalState, + itemType, +} from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import DetailTableBodyItemBottomTr from "@/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr"; +import DetailTableBodyItemMiddleTr from "@/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr"; +import DetailTableBodyItemTopTr from "@/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +export interface IItem { + item: IPresentationScheduleDetailInfo; + itemStatus: itemType; + itemDateInIDate: IDate; + hasNoUpcomingEvent: boolean; +} + +const DetailTableBodyItem = ({ + isAdmin, + openAdminModal, + tableHeadEntries, + itemInfo, + isMobile, +}: { + isAdmin: boolean; + openAdminModal: (modal: TAdminModalState) => void; + tableHeadEntries: [string, string][]; + itemInfo: IItem; + isMobile: boolean; +}) => { + const [clickedItem, setClickedItem] = + useState(null); + const navigator = useNavigate(); + const setCurrentPresentation = useSetRecoilState(currentPresentationState); + const [isItemOpen, setIsItemOpen] = useState(false); + const tableHeadEntriesWithoutDate = tableHeadEntries.filter( + (head) => head[0] !== "date" + ); + const tableHeadEntriesWithoutDateAndSubject = tableHeadEntries.filter( + (head) => head[0] !== "date" && head[0] !== "subject" + ); + + useEffect(() => { + if (isItemOpen) { + setIsItemOpen(false); + } + }, [itemInfo]); + + useEffect(() => { + setIsItemOpen(clickedItem?.dateTime === itemInfo.item.dateTime); + }, [clickedItem]); + + const handleItemClick = (item: IPresentationScheduleDetailInfo) => { + if (isAdmin && !itemInfo.itemStatus) { + setCurrentPresentation({ + id: item.id, + dateTime: item.dateTime, + presentationTime: item.presentationTime, + presentationStatus: item.presentationStatus, + presentationLocation: item.presentationLocation, + detail: item.detail, + }); + openAdminModal("statusModal"); + } else { + if (clickedItem?.dateTime === item.dateTime) setClickedItem(null); + else setClickedItem(item); + } + }; + + return ( + <> + + + + + + ); +}; + +export default DetailTableBodyItem; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr.tsx new file mode 100644 index 000000000..c8f2d7b4e --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemBottomTr.tsx @@ -0,0 +1,72 @@ +import styled from "styled-components"; +import { itemType } from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import { IItem } from "@/Presentation/components/Details/DetailTable/DetailTableBodyItem"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +const DetailTableBodyItemBottomTr = ({ + itemInfo, + isItemOpen, + handleItemClick, + isMobile, + tableHeadEntriesWithoutDate, + tableHeadEntriesWithoutDateAndSubject, +}: { + itemInfo: IItem; + isItemOpen: boolean; + handleItemClick: (item: IPresentationScheduleDetailInfo) => void; + isMobile: boolean; + tableHeadEntriesWithoutDate: [string, string][]; + tableHeadEntriesWithoutDateAndSubject: [string, string][]; +}) => { + return ( + <> + {isItemOpen ? ( + { + !itemInfo.itemStatus && handleItemClick(itemInfo.item); + }} + itemStatus={itemInfo.itemStatus} + > + +
{itemInfo.item.detail}
+ +
+ ) : null} + + ); +}; + +export default DetailTableBodyItemBottomTr; + +const BottomTrStyled = styled.tr<{ + itemStatus: itemType; +}>` + background-color: #91b5fa; + width: 100%; + + & > td { + border-radius: 0 0 10px 10px; + padding: 0; + } + + & > td > div { + background-color: var(--white); + border-radius: 10px; + margin: 24px; + margin-top: 0; + line-height: 24px; + padding: 30px 50px; + font-size: 18px; + } + + &:hover { + cursor: ${(props) => (props.itemStatus ? "" : "pointer")}; + background-color: ${(props) => (props.itemStatus ? "" : "#91B5FA")}; + } +`; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr.tsx new file mode 100644 index 000000000..a869c3808 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemMiddleTr.tsx @@ -0,0 +1,100 @@ +import { NavigateFunction } from "react-router-dom"; +import styled from "styled-components"; +import { itemType } from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import { IItem } from "@/Presentation/components/Details/DetailTable/DetailTableBodyItem"; +import NoEventTableRow from "@/Presentation/components/Details/DetailTable/NoEventTableRow"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +const noEventPhraseMobile = { + noEventPast: "발표가 없었습니다", + noEventCurrent: "지금 바로 발표를 신청해보세요", +}; + +const DetailTableBodyItemMiddleTr = ({ + isAdmin, + itemInfo, + isItemOpen, + handleItemClick, + mobileColSpanSize, + navigator, +}: { + isAdmin: boolean; + itemInfo: IItem; + isItemOpen: boolean; + handleItemClick: (item: IPresentationScheduleDetailInfo) => void; + mobileColSpanSize: number; + navigator: NavigateFunction; +}) => { + return ( + { + !itemInfo.itemStatus && handleItemClick(itemInfo.item); + }} + isItemOpen={isItemOpen} + > + {itemInfo.itemStatus ? ( + + ) : ( + +
{itemInfo.item.subject}
+ + )} +
+ ); +}; + +const MobileMiddleTrStysled = styled.tr<{ + itemStatus: itemType; + isItemOpen: boolean; +}>` + background-color: ${(props) => + !props.itemStatus + ? "#dce7fd" + : props.itemStatus === itemType.NO_EVENT_CURRENT + ? "var(--white)" + : "var(--full)"}; + width: 100%; + height: 50px; + font-size: 14px; + line-height: 50px; + text-align: center; + padding: 0 50px; + + &:hover { + cursor: ${(props) => (props.itemStatus ? "" : "pointer")}; + } + + & button { + width: 100px; + height: 36px; + background-color: #3f69fd; + font-weight: bold; + font-size: 1rem; + } + + & > td { + border-radius: ${(props) => (props.isItemOpen ? "" : "0 0 10px 10px")}; + } + + & > td > #mobileSubjectDiv { + padding: 0 10px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + @media (min-width: 1150px) { + display: none; + } +`; + +export default DetailTableBodyItemMiddleTr; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr.tsx new file mode 100644 index 000000000..c0abc21f6 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableBodyItemTopTr.tsx @@ -0,0 +1,218 @@ +import { NavigateFunction } from "react-router-dom"; +import styled from "styled-components"; +import { itemType } from "@/Presentation/components/Details/DetailTable/DetailTable.container"; +import { IItem } from "@/Presentation/components/Details/DetailTable/DetailTableBodyItem"; +import NoEventTableRow from "@/Presentation/components/Details/DetailTable/NoEventTableRow"; +import { + PresentationCategoryTypeLabelMap, + PresentationLocationLabelMap, + PresentationPeriodTypeNumberLabelMap, + PresentationStatusTypeLabelMap, +} from "@/Presentation/assets/data/maps"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; + +const noEventPhraseDesktop = { + noEventPast: "수요지식회가 열리지 않았습니다", + noEventCurrent: + "다양한 관심사를 함께 나누고 싶으신 분은 지금 바로 발표를 신청해보세요", +}; + +const renderTableData = ( + className: string, + key: number, + title: string, + head: string, + item: IPresentationScheduleDetailInfo +) => { + return ( + +
{renderCellDetail(head, item)}
+ + ); +}; + +const renderCellDetail = ( + head: string, + item: IPresentationScheduleDetailInfo +) => { + switch (head) { + case "subject": + return item.subject; + case "userName": + return item.userName; + case "category": + return PresentationCategoryTypeLabelMap[item.category!]; + case "presentationTime": + return ( + PresentationPeriodTypeNumberLabelMap[item.presentationTime!] + "분" + ); + case "presentationLocation": + return PresentationLocationLabelMap[item.presentationLocation!]; + case "presentationStatus": + return PresentationStatusTypeLabelMap[item.presentationStatus!]; + default: + return null; + } +}; + +const DetailTableBodyItemTopTr = ({ + isAdmin, + itemInfo, + isItemOpen, + handleItemClick, + isMobile, + mobileColSpanSize, + navigator, + tableHeadEntriesWithoutDate, + tableHeadEntriesWithoutDateAndSubject, +}: { + isAdmin: boolean; + itemInfo: IItem; + isItemOpen: boolean; + handleItemClick: (item: IPresentationScheduleDetailInfo) => void; + isMobile: boolean; + mobileColSpanSize: number; + navigator: NavigateFunction; + tableHeadEntriesWithoutDate: [string, string][]; + tableHeadEntriesWithoutDateAndSubject: [string, string][]; +}) => { + return ( + <> + { + !itemInfo.itemStatus && handleItemClick(itemInfo.item); + }} + open={isItemOpen} + > + {/* date cell */} + +
+ {itemInfo.itemDateInIDate?.month}월 {itemInfo.itemDateInIDate?.day} + 일 +
+ + {!isMobile ? ( + // 웹 뷰 + <> + {itemInfo.itemStatus && ( + // 발표 없을때 + <> + + + )} + {!itemInfo.itemStatus && ( + // 발표 있을때 + <> + {tableHeadEntriesWithoutDate.map((head, idx) => { + return renderTableData( + (!isAdmin && head[0] === "presentationLocation") || + (isAdmin && head[0] === "presentationStatus") + ? "rightEnd" + : "", + idx, + head[0] === "subject" ? itemInfo.item.subject ?? "" : "", + head[0], + itemInfo.item + ); + })} + + )} + + ) : ( + // 모바일 뷰 + <> + {!isItemOpen && <>} + {isItemOpen && ( + <> + {tableHeadEntriesWithoutDateAndSubject.map((head, idx) => { + return renderTableData( + head[0] === "presentationLocation" ? "rightEnd" : "", + idx, + "", + head[0], + itemInfo.item + ); + })} + + )} + + )} +
+ + ); +}; +export default DetailTableBodyItemTopTr; + +const TopTrStyled = styled.tr<{ + itemStatus: itemType; + open?: boolean; +}>` + width: 100%; + text-align: center; + & .leftEnd { + border-radius: ${(props) => (props.open ? "10px 0 0 0" : "10px 0 0 10px")}; + } + & .rightEnd { + border-radius: ${(props) => (props.open ? "0 10px 0 0" : "0 10px 10px 0")}; + } + @media (min-width: 1150px) { + font-size: 18px; + background-color: ${(props) => + !props.itemStatus + ? "#dce7fd" + : props.itemStatus === itemType.NO_EVENT_CURRENT + ? "var(--white)" + : "var(--full)"}; + height: 70px; + line-height: 70px; + & > td { + padding: 0 10px; + } + & > td > div { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + & button { + width: 120px; + height: 36px; + background-color: #3f69fd; + font-weight: bold; + font-size: 1rem; + } + &:hover { + cursor: ${(props) => (props.itemStatus ? "" : "pointer")}; + background-color: ${(props) => (props.itemStatus ? "" : "#91B5FB")}; + } + } + @media (max-width: 1150px) { + font-size: 16px; + height: 40px; + line-height: 40px; + width: 100%; + background-color: ${(props) => (props.open ? "#91B5FB" : "#3f69fd")}; + & > td { + border-radius: ${(props) => (props.open ? "" : "10px 10px 0 0")}; + } + & > td > #mobileTopDate { + color: ${(props) => (props.open ? "var(--black)" : "var(--white)")}; + text-align: ${(props) => (props.open ? "center" : "start")}; + padding-left: 10px; + } + &:hover { + cursor: ${(props) => (props.itemStatus ? "" : "pointer")}; + } + } +`; diff --git a/frontend/src/Presentation/components/Details/DetailTable/DetailTableHead.tsx b/frontend/src/Presentation/components/Details/DetailTable/DetailTableHead.tsx new file mode 100644 index 000000000..7bae95584 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/DetailTableHead.tsx @@ -0,0 +1,76 @@ +import styled from "styled-components"; + +const DetailTableHead = ({ + isMobile, + headEntries, + isAdmin, +}: { + isMobile: boolean; + headEntries: [string, string][]; + isAdmin: boolean; +}) => { + return ( + <> + {!isMobile ? ( + + + {headEntries.map((head, idx) => { + return ( + + {head[1]} + + ); + })} + + + ) : null} + + ); +}; + +const TableHeadStyled = styled.thead<{ isAdmin: boolean }>` + margin-bottom: 10px; + height: 40px; + line-height: 40px; + background-color: #3f69fd; + color: var(--white); + width: 100%; + + & > td { + font-size: 1rem; + text-align: center; + } + + & #date { + width: 13%; + border-radius: 10px 0 0 10px; + } + + & #subject { + width: ${(props) => (!props.isAdmin ? "42%" : "31%")}; + } + + & #userName { + width: 14%; + } + + & #category { + width: 9%; + } + + & #presentationTime { + width: 8%; + } + + & #presentationLocation { + width: 14%; + border-radius: ${(props) => (!props.isAdmin ? "0 10px 10px 0" : "")}; + } + + & #presentationStatus { + width: 11%; + border-radius: 0 10px 10px 0; + } +`; + +export default DetailTableHead; diff --git a/frontend/src/Presentation/components/Details/DetailTable/NoEventTableRow.tsx b/frontend/src/Presentation/components/Details/DetailTable/NoEventTableRow.tsx new file mode 100644 index 000000000..61e1b3989 --- /dev/null +++ b/frontend/src/Presentation/components/Details/DetailTable/NoEventTableRow.tsx @@ -0,0 +1,96 @@ +import { NavigateFunction } from "react-router-dom"; +import styled from "styled-components"; +import { ReactComponent as HappyCcabiImg } from "@/Cabinet/assets/images/happyCcabi.svg"; +import { ReactComponent as SadCcabiImg } from "@/Cabinet/assets/images/sadCcabi.svg"; +import { itemType } from "@/Presentation/components/Details/DetailTable/DetailTable.container"; + +const NoEventTableRow = ({ + isAdmin, + itemStatus, + hasNoUpcomingEvent, + navigator, + colSpanSize, + phrase, +}: { + isAdmin: boolean; + itemStatus: itemType.NO_EVENT_CURRENT | itemType.NO_EVENT_PAST; + hasNoUpcomingEvent: boolean; + navigator: NavigateFunction; + colSpanSize: number; + phrase: { + noEventPast: string; + noEventCurrent: string; + }; +}) => { + return ( + <> + {/* TODO : event 관련 current -> upcoming */} + + + +
{phrase[itemStatus]}
+ + {hasNoUpcomingEvent ? : } + +
+ {hasNoUpcomingEvent && !isAdmin ? ( + + ) : null} +
+ + + ); +}; + +const NoEventDivStyled = styled.div<{ hasNoUpcomingEvent: boolean }>` + display: flex; + justify-content: ${(props) => + props.hasNoUpcomingEvent ? "space-evenly" : "center"}; + align-items: center; + + @media (max-width: 1150px) { + justify-content: center; + align-items: center; + padding: 0 10px; + } +`; + +const NoEventPhraseStyled = styled.div<{ hasNoUpcomingEvent: boolean }>` + display: flex; + align-items: center; + padding: 0 10px; + + & > div { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } +`; + +const CcabiStyled = styled.div<{ hasNoUpcomingEvent: boolean }>` + width: 30px; + height: 30px; + display: flex; + margin-left: 10px; + + & > svg { + width: 30px; + height: 30px; + } + + & svg > path { + fill: var(--black); + } + + @media (max-width: 1220px) { + display: ${(props) => (props.hasNoUpcomingEvent ? "none" : "")}; + } +`; + +export default NoEventTableRow; diff --git a/frontend/src/Presentation/components/Home/PresentationCard.container.tsx b/frontend/src/Presentation/components/Home/PresentationCard.container.tsx new file mode 100644 index 000000000..3553d6e14 --- /dev/null +++ b/frontend/src/Presentation/components/Home/PresentationCard.container.tsx @@ -0,0 +1,107 @@ +import { useEffect, useMemo, useState } from "react"; +import styled from "styled-components"; +import PresentationCard from "@/Presentation/components/Home/PresentationCard"; +import PresentationCardMobile from "@/Presentation/components/Home/PresentationCardMobile"; +import { presentationCategoryIconMap } from "@/Presentation/assets/data/maps"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { PresentationCategoryType } from "@/Presentation/types/enum/presentation.type.enum"; +import useIsMobile from "@/Presentation/hooks/useIsMobile"; + +const PresentationCardContainer = ({ + currentPresentations, +}: { + currentPresentations: IPresentationScheduleDetailInfo[] | null; +}) => { + const isMobile = useIsMobile(1150); + const [selectIndex, setSelectIndex] = useState(1); + const [slide, setSlide] = useState(0); + + const searchCategory = ( + categoryName?: keyof typeof presentationCategoryIconMap + ): string => { + return categoryName != undefined + ? presentationCategoryIconMap[categoryName] + : "/src/Cabinet/assets/images/PresentationEmpty.svg"; + }; + + const onCardClick = (index: number) => { + if (selectIndex !== index) { + setSelectIndex(index); + setSlide(slide + (selectIndex - index) * 345); + } + }; + + const swipeSection = ( + touchEndPosX: number, + touchEndPosY: number, + touchStartPosX: number, + touchStartPosY: number + ) => { + const touchOffsetX = Math.round(touchEndPosX - touchStartPosX); + const touchOffsetY = Math.round(touchEndPosY - touchStartPosY); + + if ( + Math.abs(touchOffsetX) < 50 || + Math.abs(touchOffsetX) < Math.abs(touchOffsetY) + ) { + return; + } + + if (touchOffsetX > 0) { + slideSectionTo("left"); + } else { + slideSectionTo("right"); + } + }; + + const slideSectionTo = (direction: string) => { + if (direction === "left" && selectIndex !== 0) { + setSelectIndex(selectIndex - 1); + setSlide(slide + 345); + } else if (direction === "right" && selectIndex !== 2) { + setSelectIndex(selectIndex + 1); + setSlide(slide - 345); + } + }; + + const refinePresentations = useMemo(() => { + return currentPresentations?.concat( + new Array(Math.max(3 - (currentPresentations?.length || 0), 0)).fill({ + id: -1, + subject: "예정된 일정이 없습니다. 당신의 이야기를 들려주세요", + category: "", + }) + ); + }, [currentPresentations]); + + return ( + + {isMobile ? ( + + ) : ( + + )} + + ); +}; + +export default PresentationCardContainer; + +const ConTainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + max-width: 1000px; + width: 80%; + height: 550px; +`; diff --git a/frontend/src/Presentation/components/Home/PresentationCard.tsx b/frontend/src/Presentation/components/Home/PresentationCard.tsx new file mode 100644 index 000000000..9786afbd3 --- /dev/null +++ b/frontend/src/Presentation/components/Home/PresentationCard.tsx @@ -0,0 +1,156 @@ +import styled from "styled-components"; +import { presentationCategoryIconMap } from "@/Presentation/assets/data/maps"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { makeIDateObj } from "@/Presentation/utils/dateUtils"; + +const PresentationCard = ({ + searchCategory, + refinePresentations, +}: { + searchCategory: ( + categoryName?: keyof typeof presentationCategoryIconMap + ) => string | undefined; + refinePresentations: IPresentationScheduleDetailInfo[] | undefined; +}) => { + return ( + + {refinePresentations?.map((p, index) => { + const tmpDate = p.id !== -1 ? makeIDateObj(new Date(p.dateTime)) : null; + + return ( + + {p.id !== -1 ? ( + <> + + + + {p.category && } + + + {p.subject} + {p.summary} + + {p.userName} + + + + + + {tmpDate?.month}/{tmpDate?.day} + + + + + + ) : ( + <> + + + + + + 예정된 일정이 없습니다. + 당신의 이야기를 들려주세요 + + )} + + ); + })} + + ); +}; + +export default PresentationCard; + +const Container = styled.div` + width: 100%; + display: flex; + align-items: flex-start; +`; + +const PresentationCardStyled = styled.div` + width: 300px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + margin-right: 50px; +`; + +const DetailStyled = styled.div` + width: 300px; +`; + +const CategoryStyled = styled.div` + width: 300px; + height: 300px; + margin-bottom: 16px; + border-radius: 30px; + background-color: #3f69fd; + display: flex; + justify-content: center; + align-items: center; +`; + +const CategoryIconStyled = styled.div` + width: 300px; + height: 220px; +`; + +const TitleStyled = styled.div` + width: 300px; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1.2rem; + font-weight: 700; + word-break: break-all; + line-height: 1.5; + margin-bottom: 12px; +`; + +const SubTitleStyled = styled.div` + width: 300px; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1.1rem; + word-break: break-all; + line-height: 1.5; + margin-bottom: 12px; + color: #797979; +`; + +const DetailFooterStyled = styled.div` + display: flex; + align-items: center; +`; + +const NameStyled = styled.div` + white-space: nowrap; + margin-right: 5px; + color: #9d9d9d; + font-weight: 500; + + ::after { + content: " ㅣ"; + } +`; + +const CalendarStyled = styled.div` + display: flex; + align-items: flex-end; + justify-content: flex-end; + font-size: 1rem; + & > span { + color: #797979; + } +`; + +const IconStyled = styled.div` + height: 15px; + margin-right: 8px; +`; diff --git a/frontend/src/Presentation/components/Home/PresentationCardMobile.tsx b/frontend/src/Presentation/components/Home/PresentationCardMobile.tsx new file mode 100644 index 000000000..30a727ff6 --- /dev/null +++ b/frontend/src/Presentation/components/Home/PresentationCardMobile.tsx @@ -0,0 +1,225 @@ +import { useRef } from "react"; +import styled from "styled-components"; +import { IDate } from "@/Presentation/components/Details/DetailContent.container"; +import { presentationCategoryIconMap } from "@/Presentation/assets/data/maps"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { makeIDateObj } from "@/Presentation/utils/dateUtils"; + +const PresentationCardMobile = ({ + refinePresentations, + searchCategory, + selectIndex, + slide, + onCardClick, + swipeSection, +}: { + refinePresentations: IPresentationScheduleDetailInfo[] | undefined; + searchCategory: ( + categoryName?: keyof typeof presentationCategoryIconMap + ) => string; + selectIndex: number; + slide: number; + onCardClick: (index: number) => void; + swipeSection: ( + touchEndPosX: number, + touchEndPosY: number, + touchStartPosX: number, + touchStartPosY: number + ) => void; +}) => { + const touchStartPosX = useRef(0); + const touchStartPosY = useRef(0); + const components = []; + + for (let i = 0; i < 3; i++) { + components.push( + onCardClick(i)} + current={i == selectIndex} + > + ); + } + + return ( + <> + + {refinePresentations?.map((p, index) => { + const tmpDate = + p.id !== -1 ? makeIDateObj(new Date(p.dateTime)) : null; + + return ( + onCardClick(index)} + className={index == slide ? "check" : "not-check"} + onTouchStart={(e: React.TouchEvent) => { + touchStartPosX.current = e.changedTouches[0].screenX; + touchStartPosY.current = e.changedTouches[0].screenY; + }} + onTouchEnd={(e: React.TouchEvent) => { + swipeSection( + e.changedTouches[0].screenX, + e.changedTouches[0].screenY, + touchStartPosX.current, + touchStartPosY.current + ); + }} + > + {p.id !== -1 ? ( + <> + + + {p.category && } + + + + {p.subject} + {p.summary} + + {p.userName} + + + + + + {tmpDate?.month}/{tmpDate?.day} + + + + + + ) : ( + <> + + + + + + 예정된 일정이 없습니다. + 당신의 이야기를 들려주세요 + + )} + + ); + })} + + {components} + + ); +}; + +export default PresentationCardMobile; + +const Container = styled.div<{ select: number }>` + overflow-x: hidden; + display: flex; + align-items: flex-start; + width: 100%; + min-width: 1000px; + min-height: 500px; + margin-bottom: 20px; + transform: translateX(${(props) => props.select}px); + transition: all 500ms ease-in-out; +`; + +const PresentationCardStyled = styled.div` + width: 300px; + margin-right: 50px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; +`; + +const CategoryStyled = styled.div` + width: 300px; + height: 300px; + margin-bottom: 16px; + border-radius: 30px; + background-color: #3f69fd; + display: flex; + justify-content: center; + align-items: center; +`; + +const CategoryIconStyled = styled.div` + width: 300px; + height: 220px; +`; + +const DetailStyled = styled.div` + width: 300px; +`; + +const TitleStyled = styled.div` + width: 300px; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1.2rem; + font-weight: 700; + word-break: break-all; + line-height: 1.5; + margin-bottom: 12px; +`; + +const SubTitleStyled = styled.div` + width: 300px; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1.1rem; + word-break: break-all; + line-height: 1.5; + margin-bottom: 12px; + color: #797979; +`; + +const DetailFooterStyled = styled.div` + display: flex; + align-items: center; +`; + +const NameStyled = styled.div` + white-space: nowrap; + margin-right: 5px; + color: #9d9d9d; + font-weight: 500; + + ::after { + content: "ㅣ"; + } +`; + +const CalendarStyled = styled.div` + display: flex; + align-items: flex-end; + justify-content: flex-end; + font-size: 1rem; + + & > span { + color: #797979; + } +`; + +const IconStyled = styled.div` + height: 15px; + margin-right: 8px; +`; + +const PaginationStyled = styled.div` + display: flex; + padding-bottom: 30px; +`; + +const Paginations = styled.div<{ current: boolean }>` + width: 30px; + height: 10px; + background-color: ${(props) => (props.current ? "#5378fd" : "gray")}; + border-radius: 12px; + margin: 0 5px; +`; diff --git a/frontend/src/Presentation/components/Home/RecentPresentation.tsx b/frontend/src/Presentation/components/Home/RecentPresentation.tsx new file mode 100644 index 000000000..23a02b238 --- /dev/null +++ b/frontend/src/Presentation/components/Home/RecentPresentation.tsx @@ -0,0 +1,147 @@ +import { useEffect, useState } from "react"; +import styled from "styled-components"; +import PresentationCardContainer from "@/Presentation/components/Home/PresentationCard.container"; +import { IPresentationScheduleDetailInfo } from "@/Presentation/types/dto/presentation.dto"; +import { axiosGetPresentation } from "@/Presentation/api/axios/axios.custom"; + +const RecentPresentation = ({ + presentButtonHandler, +}: { + presentButtonHandler: () => void; +}) => { + const [currentPresentations, setCurrentPresentations] = useState< + IPresentationScheduleDetailInfo[] | null + >(null); + + const getCurrentPresentation = async () => { + try { + const response = await axiosGetPresentation(); + setCurrentPresentations([ + ...response.data.past, + ...response.data.upcoming, + ]); + } catch (error: any) { + // TODO + } finally { + // TODO + } + }; + + useEffect(() => { + getCurrentPresentation(); + }, []); + + return ( + + + +

수요지식회

+ { + presentButtonHandler(); + }} + > + 발표신청 + +
+ 지식이 일상이 되다. +
+ + + { + presentButtonHandler(); + }} + > + 발표신청 + +
+ ); +}; + +export default RecentPresentation; + +const ContainerStyled = styled.div` + padding-top: 60px; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + overflow-y: scroll; + overflow-x: hidden; +`; + +const HeaderStyled = styled.div` + display: flex; + max-width: 1000px; + width: 80%; + justify-content: space-between; + flex-wrap: wrap; + align-items: flex-end; + + & > span { + margin-top: 10px; + margin-bottom: 40px; + display: block; + font-size: 2rem; + font-weight: 600; + word-break: keep-all; + } + + @media screen and (max-width: 430px) { + & > span { + font-size: 1.25rem; + } + & > div { + margin-bottom: 0px; + padding-bottom: 0px; + align-items: center; + } + } +`; + +const TitleContainerStyled = styled.div` + padding-bottom: 10px; + margin-bottom: 10px; + height: 60px; + display: flex; + justify-content: space-between; + align-items: flex-start; + font-weight: 700; + border-bottom: 2px solid #d9d9d9; + width: 100%; + font-size: 2.5rem; + + @media screen and (max-width: 430px) { + & > p { + font-size: 1.5rem; + padding-bottom: 10px; + } + height: 35px; + } + + & > h1 { + font-size: 2.5rem; + font-weight: 700; + } +`; + +const RegisterButtonStyled = styled.button` + background-color: #3f69fd; + width: 150px; + height: 50px; + @media screen and (max-width: 425px) { + display: none; + } +`; + +const MobileRegisertButtonStyled = styled.button` + background-color: #3f69fd; + width: 80%; + height: 50px; + margin-bottom: 30px; + margin-top: 30px; + @media screen and (min-width: 425px) { + display: none; + } +`; diff --git a/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx new file mode 100644 index 000000000..367f83995 --- /dev/null +++ b/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -0,0 +1,76 @@ +import { useLocation, useNavigate } from "react-router-dom"; +import { useResetRecoilState } from "recoil"; +import { + currentBuildingNameState, + currentFloorNumberState, + currentSectionNameState, +} from "@/Cabinet/recoil/atoms"; +import { removeCookie } from "@/Cabinet/api/react_cookie/cookies"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import LeftMainNav from "@/Presentation/components/LeftNav/LeftMainNav/LeftMainNav"; + +const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { + const resetCurrentFloor = useResetRecoilState(currentFloorNumberState); + const resetCurrentSection = useResetRecoilState(currentSectionNameState); + const resetBuilding = useResetRecoilState(currentBuildingNameState); + const navigator = useNavigate(); + const { pathname } = useLocation(); + + const { closeAll } = useMenu(); + + const onClickPresentationHomeButton = () => { + navigator("/presentation/home"); + closeAll(); + }; + + const onClickPresentationRegisterButton = () => { + navigator("/presentation/register"); + closeAll(); + }; + + const onClickPresentationDetailButton = () => { + if (isAdmin) { + navigator("/presentation/detail"); + } else { + navigator("detail"); + } + closeAll(); + }; + + const onClickPresentationLogButton = () => { + navigator("/presentation/log"); + closeAll(); + }; + + const onClickLogoutButton = (): void => { + const adminToken = isAdmin ? "admin_" : ""; + if (import.meta.env.VITE_IS_LOCAL === "true") { + removeCookie(adminToken + "access_token", { + path: "/", + domain: "localhost", + }); + } else { + removeCookie(adminToken + "access_token", { + path: "/", + domain: "cabi.42seoul.io", + }); + } + resetBuilding(); + resetCurrentFloor(); + resetCurrentSection(); + navigator("/login"); + }; + return ( + + ); +}; + +export default LeftMainNavContainer; diff --git a/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.tsx new file mode 100644 index 000000000..2bee6f4fd --- /dev/null +++ b/frontend/src/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -0,0 +1,210 @@ +import styled from "styled-components"; +import { ReactComponent as LogoutImg } from "@/Cabinet/assets/images/close-square.svg"; + +interface ILeftMainNav { + pathname: string; + onClickLogoutButton: React.MouseEventHandler; + onClickPresentationHomeButton: React.MouseEventHandler; + onClickPresentationRegisterButton: React.MouseEventHandler; + onClickPresentationDetailButton: React.MouseEventHandler; + onClickPresentationLogButton: React.MouseEventHandler; + isAdmin?: boolean; +} + +const LeftMainNav = ({ + pathname, + onClickLogoutButton, + onClickPresentationHomeButton, + onClickPresentationRegisterButton, + onClickPresentationDetailButton, + onClickPresentationLogButton, + isAdmin, +}: ILeftMainNav) => { + return ( + + + + {isAdmin ? ( + + {"일정관리"} + + ) : ( + <> + + Home + + + 발표신청 + + + 발표기록 + + + {isAdmin ? "일정관리" : "상세정보"} + + + )} + + + + + {!isAdmin && ( + + + Logout + + )} + + + + ); +}; + +const LeftNavStyled = styled.nav` + width: 90px; + min-width: 90px; + position: relative; + height: 100%; + border-right: 1px solid var(--line-color); + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; +`; + +const TopSectionStyled = styled.section` + position: relative; + overflow-x: hidden; +`; + +const TopBtnsStyled = styled.ul` + text-align: center; + padding: 30px 10px; +`; + +const TopBtnStyled = styled.li` + width: 100%; + height: 48px; + line-height: 48px; + font-weight: 300; + margin-bottom: 2.5vh; + border-radius: 10px; + color: var(--gray-color); + cursor: pointer; + &:last-child { + margin-bottom: 0; + } + @media (hover: hover) and (pointer: fine) { + &:hover { + color: var(--white); + background-color: var(--main-color); + } + } +`; + +const BottomSectionStyled = styled.section` + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-end; + &::after { + content: ""; + position: absolute; + top: 0; + left: 17px; + margin: 0 auto; + width: 56px; + height: 1px; + background-color: var(--line-color); + } +`; +const BottomBtnsStyled = styled.ul` + padding: 30px 10px; + text-align: center; +`; + +const BottomBtnStyled = styled.li` + width: 100%; + min-height: 48px; + line-height: 1.125rem; + font-weight: 300; + margin-top: 2.5vh; + border-radius: 10px; + color: var(--gray-color); + cursor: pointer; + display: flex; + flex-direction: column; + justify-content: center; + &:first-child { + margin-top: 0; + } + & a { + color: var(--gray-color); + } + & div { + width: 24px; + height: 24px; + margin: 0 auto; + margin-bottom: 4px; + } + &.active { + color: var(--main-color); + svg { + stroke: var(--main-color); + } + } + svg { + margin: 0 auto; + } + @media (hover: hover) and (pointer: fine) { + &:hover { + color: var(--main-color); + svg { + stroke: var(--main-color); + } + a { + color: var(--main-color); + } + } + } +`; + +export default LeftMainNav; diff --git a/frontend/src/Presentation/components/LeftNav/LeftNav.tsx b/frontend/src/Presentation/components/LeftNav/LeftNav.tsx new file mode 100644 index 000000000..5d34e1664 --- /dev/null +++ b/frontend/src/Presentation/components/LeftNav/LeftNav.tsx @@ -0,0 +1,20 @@ +import styled from "styled-components"; +import LeftMainNavContainer from "@/Presentation/components/LeftNav/LeftMainNav/LeftMainNav.container"; + +const LeftNav: React.FC<{ + isVisible: boolean; + isAdmin?: boolean; +}> = ({ isAdmin, isVisible }) => { + return ( + + + {/* */} + + ); +}; + +const LeftNavWrapStyled = styled.div` + display: flex; +`; + +export default LeftNav; diff --git a/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx b/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx new file mode 100644 index 000000000..3fad633e7 --- /dev/null +++ b/frontend/src/Presentation/components/Modals/EditStatusModal/EditStatusModal.tsx @@ -0,0 +1,297 @@ +import { format } from "date-fns"; +import { useEffect, useState } from "react"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import styled from "styled-components"; +import Button from "@/Cabinet/components/Common/Button"; +import Dropdown, { + IDropdown, + IDropdownOptions, +} from "@/Cabinet/components/Common/Dropdown"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { + currentPresentationState, + isCurrentModalState, +} from "@/Presentation/recoil/atoms"; +import { + PresentationLocation, + PresentationStatusType, +} from "@/Presentation/types/enum/presentation.type.enum"; +import { + axiosGetInvalidDates, + axiosUpdatePresentationStatus, +} from "@/Presentation/api/axios/axios.custom"; +import { + calculateAvailableDaysInWeeks, + filterInvalidDates, +} from "@/Presentation/utils/dateUtils"; +import { WEDNESDAY } from "@/Presentation/constants/dayOfTheWeek"; +import { + AVAILABLE_WEEKS, + FUTURE_MONTHS_TO_DISPLAY, +} from "@/Presentation/constants/policy"; + +interface EditStatusModalProps { + closeModal: React.MouseEventHandler; +} + +const statusOptions: IDropdownOptions[] = [ + { name: "발표 예정", value: PresentationStatusType.EXPECTED }, + { name: "발표 완료", value: PresentationStatusType.DONE }, + { name: "발표 취소", value: PresentationStatusType.CANCEL }, +]; + +const floorOptions: IDropdownOptions[] = [ + { name: "지하 1층", value: PresentationLocation.BASEMENT }, + { name: "1층", value: PresentationLocation.FIRST }, + { name: "3층", value: PresentationLocation.THIRD }, +]; + +const EditStatusModal = ({ closeModal }: EditStatusModalProps) => { + const [currentPresentation, setCurrentPresentation] = useRecoilState( + currentPresentationState + ); + const setIsCurrentModalRender = useSetRecoilState(isCurrentModalState); + const [showResponseModal, setShowResponseModal] = useState(false); + const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false); + const [modalTitle, setModalTitle] = useState(""); + const [presentationDate, setPresentationDate] = useState( + currentPresentation?.dateTime + ? new Date(currentPresentation?.dateTime).toISOString() + : "" + ); + const [presentationStatus, setPresentationStatus] = + useState(PresentationStatusType.EXPECTED); + const [location, setLocation] = useState( + PresentationLocation.THIRD + ); + const [invalidDates, setInvalidDates] = useState([]); + const [statusDropdownProps, setStatusDropdownProps] = useState({ + options: statusOptions, + defaultValue: + statusOptions.find( + (option) => option.value === currentPresentation?.presentationStatus + )?.name ?? "발표 예정", + defaultImageSrc: "", + onChangeValue: (val: PresentationStatusType) => { + setPresentationStatus(val); + }, + }); + const [datesDropdownProps, setDatesDropdownProps] = useState({ + options: [], + defaultValue: currentPresentation?.dateTime + ? format(currentPresentation?.dateTime.split("T")[0], "M월 d일") + : "", + defaultImageSrc: "", + onChangeValue: (val: string) => { + setPresentationDate(val); + }, + }); + const [locationDropdownProps, setLocationDropdownProps] = useState( + { + options: floorOptions, + defaultValue: + floorOptions.find( + (option) => option.value === currentPresentation?.presentationLocation + )?.name ?? "3층", + defaultImageSrc: "", + onChangeValue: (val: PresentationLocation) => { + setLocation(val); + }, + } + ); + + const tryEditPresentationStatus = async (e: React.MouseEvent) => { + if (!currentPresentation || !currentPresentation.id) return; + const data = new Date(presentationDate); + // NOTE: Date 객체의 시간은 UTC 기준이므로 한국 시간 (GMT + 9) 으로 변환, 이후 발표 시작 시간인 14시를 더해줌 + data.setHours(9 + 14); + + try { + await axiosUpdatePresentationStatus( + currentPresentation.id, + data.toISOString(), + presentationStatus, + location + ); + setModalTitle("수정이 완료되었습니다"); + setIsCurrentModalRender(true); + } catch (error: any) { + setModalTitle(error.response.data.message); + setHasErrorOnResponse(true); + } finally { + setShowResponseModal(true); + } + }; + + const getInvalidDates = async () => { + try { + const response = await axiosGetInvalidDates(); + setInvalidDates(response.data.invalidDateList); + } catch (error: any) { + setModalTitle(error.response.data.message); + setHasErrorOnResponse(true); + setShowResponseModal(true); + } + }; + + useEffect(() => { + getInvalidDates(); + }, []); + + useEffect(() => { + if (!currentPresentation) return; + // NOTE: 발표 가능한 날짜들을 계산 + const availableDates: Date[] = calculateAvailableDaysInWeeks( + new Date(), + AVAILABLE_WEEKS, + WEDNESDAY, + FUTURE_MONTHS_TO_DISPLAY + ); + // NOTE: 발표 가능한 날짜 중 유효하지 않은 날짜를 필터링 + const availableDatesFiltered: Date[] = filterInvalidDates( + availableDates, + invalidDates + ); + // NOTE: 발표 가능 날짜들을 Dropdown options으로 변환 + const dropdownOptions: IDropdownOptions[] = availableDatesFiltered.map( + (date) => ({ + name: format(date, "M월 d일"), + value: date, + }) + ); + setDatesDropdownProps({ + options: dropdownOptions, + defaultValue: dropdownOptions[0].name, + onChangeValue: (val: string) => { + setPresentationDate(val); + }, + }); + }, [invalidDates]); + + return ( + <> + + {!showResponseModal && ( + <> + + + 일정 관리 + + + + 발표 상태 + + + + 날짜 + + + + 장소 + + + + + + ; + return ( + <> + mode change + + ); }; +const ButtonStyled = styled.button` + background-color: var(--test); +`; + export default DarkMode; diff --git a/frontend/src/index.css b/frontend/src/index.css index ebe55ab01..d802bb86e 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -76,99 +76,6 @@ font-weight: 400; } -:root { - --bg-color: var(--white); - --line-color: var(--shared-gray-color-400); - --normal-text-color: var(--black); - --text-with-bg-color: var(--white); - --card-content-bg-color: var(--white); - --button-line-color: var(--default-main-color); - - /* main color variable */ - --main-color: var(--purple-500); - --sub-color: var(--purple-300); - - --default-main-color: var(--purple-500); - --default-sub-color: var(--purple-300); - --default-mine-color: var(--green-100); - - /* cabinet color variable */ - --mine-color: var(--green-100); - --available-color: var(--main-color); - --pending-color: var(--main-color); - --full-color: var(--gray-200); - --expired-color: var(--red-100); - --banned-color: var(--gray-600); - - --bg-shadow-color-100: var(--black-shadow-100); - --bg-shadow-color-200: var(--black-shadow-200); - --bg-shadow-color-300: var(--black-shadow-300); - --bg-shadow-color-400: var(--black-shadow-400); - --border-shadow-color-100: var(--black-shadow-100); - --border-shadow-color-200: var(--black-shadow-200); - --border-shadow-color-300: var(--black-shadow-300); - - --shared-gray-color-100: var(--gray-100); - --shared-gray-color-200: var(--gray-200); - --shared-gray-color-300: var(--gray-300); - --shared-gray-color-400: var(--gray-400); - --shared-gray-color-500: var(--gray-500); - --shared-gray-color-600: var(--gray-600); - --shared-gray-color-700: var(--gray-800); - - --shared-purple-color-100: var(--purple-100); - - color: var(--normal-text-color); - background-color: var(--bg-color); - - [color-theme="dark"], - &.no-js { - @media (prefers-color-scheme: dark) { - --bg-color: var(--gray-900); - --line-color: var(--shared-gray-color-300); - --normal-text-color: var(--gray-100); - --text-with-bg-color: var(--gray-100); - --card-content-bg-color: var(--gray-700); - --button-line-color: var(--default-sub-color); - - --main-color: var(--purple-600); - --sub-color: var(--purple-300); - - --default-main-color: var(--purple-600); - --default-sub-color: var(--purple-300); - --default-mine-color: var(--green-200); - - --mine-color: var(--green-200); - --available-color: var(--main-color); - --pending-color: var(--main-color); - --full-color: var(--gray-200); - --expired-color: var(--red-200); - --banned-color: var(--gray-600); - - --bg-shadow-color-100: var(--black-shadow-200); - --bg-shadow-color-200: var(--black-shadow-300); - --bg-shadow-color-300: var(--black-shadow-400); - --bg-shadow-color-400: var(--black-shadow-400); - --border-shadow-color-100: var(--black-shadow-200); - --border-shadow-color-200: var(--black-shadow-300); - --border-shadow-color-300: var(--black-shadow-400); - - --shared-gray-color-100: var(--gray-800); - --shared-gray-color-200: var(--gray-700); - --shared-gray-color-300: var(--gray-600); - --shared-gray-color-400: var(--gray-500); - --shared-gray-color-500: var(--gray-400); - --shared-gray-color-600: var(--gray-300); - --shared-gray-color-700: var(--gray-200); - - --shared-purple-color-100: var(--purple-700); - - color: var(--normal-text-color); - background-color: var(--bg-color); - } - } -} - a { color: var(--main-color); -webkit-tap-highlight-color: transparent; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 7185198df..b45567932 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,6 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { RecoilRoot } from "recoil"; +import { GlobalStyle } from "@/components/TopNav/DarkMode/DarkMode"; import App from "./App"; import "./assets/css/media.css"; import "./assets/css/reset.css"; @@ -9,6 +10,7 @@ import "./index.css"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + From ab07791a9d76680558ad4e10ae762e20064d2f11 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 16 Apr 2024 17:00:48 +0900 Subject: [PATCH 0578/1029] =?UTF-8?q?[FE]=20REFACT:=20=EA=B8=B0=EA=B8=B0?= =?UTF-8?q?=EA=B0=80=20=EB=9D=BC=EC=9D=B4=ED=8A=B8=EB=AA=A8=EB=93=9C?= =?UTF-8?q?=EC=9D=B4=EB=93=A0=20=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C?= =?UTF-8?q?=EC=9D=B4=EB=93=A0=20=EB=B2=84=ED=8A=BC=20=EB=88=84=EB=A5=BC?= =?UTF-8?q?=EB=95=8C=EB=A7=88=EB=8B=A4=20=EC=83=89=20=EC=9E=98=20=EB=B0=94?= =?UTF-8?q?=EB=80=9C#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/TopNav/DarkMode/DarkMode.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx index 2c251fe40..b8bc60f75 100644 --- a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx +++ b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx @@ -96,16 +96,11 @@ const darkValues = css` export const GlobalStyle = createGlobalStyle` :root { - // define light theme values as the defaults within the root selector ${lightValues} - // override with dark theme values if theme data attribute is set to dark - [color-theme="dark"], - &.no-js { - @media (prefers-color-scheme: dark) { + [color-theme="dark"] { ${darkValues} } - } } `; From 1ec49c2aed8e6f283bc3e5568d97df8b54dbe9e6 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 16 Apr 2024 17:17:41 +0900 Subject: [PATCH 0579/1029] =?UTF-8?q?[FE]=20=EB=A1=A4=EB=B0=B1=202?= =?UTF-8?q?=EC=B0=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev/configure.conf | 5 + frontend/package-lock.json | 346 +----------------- frontend/src/App.tsx | 2 + .../src/Cabinet/api/axios/axios.custom.ts | 31 ++ .../src/Cabinet/assets/data/SlackAlarm.ts | 134 +++++++ .../assets/images/slack-notification.svg | 5 + .../LeftMainNav/LeftMainNav.container.tsx | 7 + .../LeftNav/LeftMainNav/LeftMainNav.tsx | 14 + .../SlackNoti/SlackNotiSearchBar.tsx | 161 ++++++++ .../SlackNoti/SlackNotiSearchBarList.tsx | 69 ++++ .../SlackNoti/SlackNotiSearchListItem.tsx | 51 +++ .../components/TopNav/SearchBar/SearchBar.tsx | 17 +- .../pages/admin/AdminSlackNotiPage.tsx | 296 +++++++++++++++ 13 files changed, 786 insertions(+), 352 deletions(-) create mode 100644 frontend/src/Cabinet/assets/data/SlackAlarm.ts create mode 100644 frontend/src/Cabinet/assets/images/slack-notification.svg create mode 100644 frontend/src/Cabinet/components/SlackNoti/SlackNotiSearchBar.tsx create mode 100644 frontend/src/Cabinet/components/SlackNoti/SlackNotiSearchBarList.tsx create mode 100644 frontend/src/Cabinet/components/SlackNoti/SlackNotiSearchListItem.tsx create mode 100644 frontend/src/Cabinet/pages/admin/AdminSlackNotiPage.tsx diff --git a/dev/configure.conf b/dev/configure.conf index 8c5368ec6..40ece1c4b 100644 --- a/dev/configure.conf +++ b/dev/configure.conf @@ -82,4 +82,9 @@ server { proxy_pass http://host.docker.internal:2424; proxy_set_header Host $host; } + + location ^~ /slack { + proxy_pass http://host.docker.internal:2424; + proxy_set_header Host $host; + } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5c99f8797..80b634fe4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,7 +1,7 @@ { "name": "cabi_fontend", "version": "0.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -1958,54 +1958,6 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, - "node_modules/@esbuild/android-arm": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.9.tgz", - "integrity": "sha512-kW5ccqWHVOOTGUkkJbtfoImtqu3kA1PFkivM+9QPFSHphPfPBlBalX9eDRqPK+wHCqKhU48/78T791qPgC9e9A==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.9.tgz", - "integrity": "sha512-ndIAZJUeLx4O+4AJbFQCurQW4VRUXjDsUvt1L+nP8bVELOWdmdCEOtlIweCUE6P+hU0uxYbEK2AEP0n5IVQvhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.9.tgz", - "integrity": "sha512-UbMcJB4EHrAVOnknQklREPgclNU2CPet2h+sCBCXmF2mfoYWopBn/CfTfeyOkb/JglOcdEADqAljFndMKnFtOw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.16.9", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.9.tgz", @@ -2022,294 +1974,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.9.tgz", - "integrity": "sha512-LZc+Wlz06AkJYtwWsBM3x2rSqTG8lntDuftsUNQ3fCx9ZttYtvlDcVtgb+NQ6t9s6K5No5zutN3pcjZEC2a4iQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.9.tgz", - "integrity": "sha512-gIj0UQZlQo93CHYouHKkpzP7AuruSaMIm1etcWIxccFEVqCN1xDr6BWlN9bM+ol/f0W9w3hx3HDuEwcJVtGneQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.9.tgz", - "integrity": "sha512-GNors4vaMJ7lzGOuhzNc7jvgsQZqErGA8rsW+nck8N1nYu86CvsJW2seigVrQQWOV4QzEP8Zf3gm+QCjA2hnBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.9.tgz", - "integrity": "sha512-cNx1EF99c2t1Ztn0lk9N+MuwBijGF8mH6nx9GFsB3e0lpUpPkCE/yt5d+7NP9EwJf5uzqdjutgVYoH1SNqzudA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.9.tgz", - "integrity": "sha512-YPxQunReYp8RQ1FvexFrOEqqf+nLbS3bKVZF5FRT2uKM7Wio7BeATqAwO02AyrdSEntt3I5fhFsujUChIa8CZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.9.tgz", - "integrity": "sha512-zb12ixDIKNwFpIqR00J88FFitVwOEwO78EiUi8wi8FXlmSc3GtUuKV/BSO+730Kglt0B47+ZrJN1BhhOxZaVrw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.9.tgz", - "integrity": "sha512-X8te4NLxtHiNT6H+4Pfm5RklzItA1Qy4nfyttihGGX+Koc53Ar20ViC+myY70QJ8PDEOehinXZj/F7QK3A+MKQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.9.tgz", - "integrity": "sha512-ZqyMDLt02c5smoS3enlF54ndK5zK4IpClLTxF0hHfzHJlfm4y8IAkIF8LUW0W7zxcKy7oAwI7BRDqeVvC120SA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.9.tgz", - "integrity": "sha512-k+ca5W5LDBEF3lfDwMV6YNXwm4wEpw9krMnNvvlNz3MrKSD2Eb2c861O0MaKrZkG/buTQAP4vkavbLwgIe6xjg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.9.tgz", - "integrity": "sha512-GuInVdogjmg9DhgkEmNipHkC+3tzkanPJzgzTC2ihsvrruLyFoR1YrTGixblNSMPudQLpiqkcwGwwe0oqfrvfA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.9.tgz", - "integrity": "sha512-49wQ0aYkvwXonGsxc7LuuLNICMX8XtO92Iqmug5Qau0kpnV6SP34jk+jIeu4suHwAbSbRhVFtDv75yRmyfQcHw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.9.tgz", - "integrity": "sha512-Nx4oKEAJ6EcQlt4dK7qJyuZUoXZG7CAeY22R7rqZijFzwFfMOD+gLP56uV7RrV86jGf8PeRY8TBsRmOcZoG42w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.9.tgz", - "integrity": "sha512-d0WnpgJ+FTiMZXEQ1NOv9+0gvEhttbgKEvVqWWAtl1u9AvlspKXbodKHzQ5MLP6YV1y52Xp+p8FMYqj8ykTahg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.9.tgz", - "integrity": "sha512-jccK11278dvEscHFfMk5EIPjF4wv1qGD0vps7mBV1a6TspdR36O28fgPem/SA/0pcsCPHjww5ouCLwP+JNAFlw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.9.tgz", - "integrity": "sha512-OetwTSsv6mIDLqN7I7I2oX9MmHGwG+AP+wKIHvq+6sIHwcPPJqRx+DJB55jy9JG13CWcdcQno/7V5MTJ5a0xfQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.9.tgz", - "integrity": "sha512-tKSSSK6unhxbGbHg+Cc+JhRzemkcsX0tPBvG0m5qsWbkShDK9c+/LSb13L18LWVdOQZwuA55Vbakxmt6OjBDOQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.9.tgz", - "integrity": "sha512-ZTQ5vhNS5gli0KK8I6/s6+LwXmNEfq1ftjnSVyyNm33dBw8zDpstqhGXYUbZSWWLvkqiRRjgxgmoncmi6Yy7Ng==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.16.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.9.tgz", - "integrity": "sha512-C4ZX+YFIp6+lPrru3tpH6Gaapy8IBRHw/e7l63fzGDhn/EaiGpQgbIlT5paByyy+oMvRFQoxxyvC4LE0AjJMqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint/eslintrc": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", @@ -7948,7 +7612,13 @@ "version": "29.4.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", - "dev": true, + " + + + + + + ": true, "dependencies": { "@jest/schemas": "^29.4.0", "ansi-styles": "^5.0.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b839e00aa..f8a66b315 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,6 +29,7 @@ const SearchPage = lazy(() => import("@/Cabinet/pages/admin/SearchPage")); const AdminClubPage = lazy(() => import("@/Cabinet/pages/admin/AdminClubPage")); const AdminLoginFailurePage = lazy( () => import("@/Cabinet/pages/admin/AdminLoginFailurePage") + ); const AdminHomePage = lazy(() => import("@/Cabinet/pages/admin/AdminHomePage")); @@ -62,6 +63,7 @@ function App(): React.ReactElement { } /> } /> } /> + } /> } /> => { throw error; } }; + +const axiosSendSlackNotificationToUserURL = "/slack/send"; +export const axiosSendSlackNotificationToUser = async ( + receiverName: string, + message: string +): Promise => { + try { + const response = await instance.post(axiosSendSlackNotificationToUserURL, { + receiverName: receiverName, + message: message, + }); + return response; + } catch (error) { + throw error; + } +}; + +export const axiosSendSlackNotificationToChannel = async ( + receiverName: string, + message: string, + channel: string | undefined +): Promise => { + try { + await instance.post(axiosSendSlackNotificationToUserURL + `/${channel}`, { + receiverName: receiverName, + message: message, + }); + } catch (error) { + throw error; + } +}; diff --git a/frontend/src/Cabinet/assets/data/SlackAlarm.ts b/frontend/src/Cabinet/assets/data/SlackAlarm.ts new file mode 100644 index 000000000..123470f87 --- /dev/null +++ b/frontend/src/Cabinet/assets/data/SlackAlarm.ts @@ -0,0 +1,134 @@ +export interface ISlackChannel { + title: string; + channelId: string; +} + +export const SlackChannels: ISlackChannel[] = [ + { + title: "#random", + channelId: "CU6MTFBNH", + }, + { + title: "#club_cabinet", + channelId: "C02V6GE8LD7", + }, +]; + +export interface ISlackAlarmTemplate { + title: string; + content: string; +} + +export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ + { + title: "점검 시작", + content: `:alert::alert::alert::alert::alert::alert: + :happy_ccabi: 안녕하세요. Cabi 팀입니다! :happy_ccabi: + :sad_ccabi: 서비스 개선을 위해, 서버를 점검하게 되었습니다. :sad_ccabi: + :file_cabinet: 서비스 개선과 관련한 사항은 Cabi 채널 에서, :file_cabinet: + :hammer_and_wrench: 보관물 관련 사항은 *데스크에 직접 문의*해주세요! :hammer_and_wrench: + + 점검 일자 : 2024년 04월 02일 (화요일) + 점검 시간 : 15시 10분 ~ 완료 공지 시점까지 + + :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:잠시만 기다려주세요! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, + }, + { + title: "점검 완료", + content: `:dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby: + 안녕하세요. Cabi 팀입니다! :happy_ccabi: + 현시간부로 서비스 이용이 정상화되었습니다. + :portal_blue_parrot: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :portal_orange_parrot: + :file_cabinet: 서비스 개선과 관련한 사항은 Cabi 채널 문의주세요! :file_cabinet: + :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:기다려주셔서 감사합니다! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, + }, + { + title: "업데이트", + content: `:dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby: + :happy_ccabi:동아리 장분들의 동아리 기능 사용 방법:happy_ccabi: + =============================================== + 내용 + =============================================== + :point_right: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :point_left:`, + }, + { + title: "이용 안내서", + content: `:file_cabinet: Cabi 이용 안내서 :file_cabinet: + :embarrassed_cabi: 42seoul의 사물함 대여 서비스를 운영중인 Cabi 팀입니다.:embarrassed_cabi: + 자세한 이용 방법은 Cabi 가입 후 홈페이지의 이용 안내서를 참고해 주세요! + :point_right: https://cabi.42seoul.io/home + :alert: Cabi FAQ :alert: + :pushpin: 사물함의 물리적인 문제가 있습니다 (고장 났거나 잠겨있는 경우) + :happy_ccabi: 사물함의 물리적인 문제는 데스크에 문의 부탁드립니다! + :pushpin: 사물함 비밀번호를 모릅니다 (잊어버렸습니다). + :happy_ccabi: 저희 서비스에서 대여한 화면과 슬랙 화면을 준비해서 데스크에 문의해주시기 바랍니다! + :pushpin: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. + :happy_ccabi: 사물함 안이 꽉 차거나 제대로 닫히지 않은 경우에 발생하는데, 문을 누른 상태로 비밀번호를 입력해 보시고, 그래도 되지 않는다면 데스크에 문의 부탁드립니다! + :pushpin: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. + :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! + :pushpin: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? + :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! + :pushpin: 사물함을 연체 했는데 패널티는 무엇인가요? + :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:face_holding_back_tears:`, + }, + { + title: "모집 공고", + content: `:embarrassed_cabi::embarrassed_cabi::embarrassed_cabi: :embarrassed_cabi::embarrassed_cabi::embarrassed_cabi: + 안녕하세요 Cabi 팀입니다! + 새로운 팀원 모집 공고를 올립니다:yesyes: + 많은 관심 부탁드립니다! + ---------------------------------------------------- + :dancing_kirby:모집개요:dancing_kirby: + 오늘 {날짜} 부터, {날짜} 23:59까지! + 까비 팀의 프론트엔드 / 백엔드 6기 신청을 받습니다! + 폼 작성 - 간단한 미팅 - 최종 발표 예정입니다! + ... + ...까비에 할애할 수 있으신 분! + - 열정 넘치시는 분! + :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: + :four_leaf_clover::arrow_right:지금 바로 지원하기:arrow_left::four_leaf_clover: + :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: + :man-bowing: 상세한 정보는 구글 폼을 참고해주시고, 이외에 모집과 관련한 문의는 + @jpark2 에게 DM 부탁드립니다! :man-bowing:`, + }, + { + title: "동아리 사물함", + content: `:happy_ccabi: 안녕하세요 사물함 서비스를 운영중인 Cabi 팀입니다 :happy_ccabi: + 이번 동아리 사물함 모집을 공지드립니다! + 기존 {날짜} 에 신청하셨던 분들도 재신청해주시기를 바랍니다. + 신청 링크 : {링크} + 내용은 아래와 같습니다. + ----------------------------------------------------- + < :hourglass_flowing_sand: 모집 기간 > + 2024년 동아리 사물함 신청은 일주일 동안 진행됩니다. + 3월 15일(금) 부터 + ~ 3월 22일(금) 23:59 까지 입니다. + *이 시간대 외의 신청은 유효하지 않은 신청으로 간주됩니다:disappointed_relieved: + ----------------------------------------------------- + < :pushpin: 선발 기준 > + [스프레드 시트에 등록된] 42 Seoul 동아리 리스트에 포함된 동아리 + 신청자 수가 사물함보다 많을경우 추첨으로 진행됩니다. + ----------------------------------------------------- + < :mantelpiece_clock: 발표일 > + 10월 20일 오후 중 발표 예정입니다! + 해당 발표 결과는 슬랙의 ‘42seoul_club_cabinet’, ‘42seoul_global_random’ 채널에 공지 드릴 예정입니다. + 사물함 배정이 완료된 후 동아리 사물함 대표분들에게, 슬랙 DM으로 메시지가 전송될 예정입니다. + ----------------------------------------------------- + < :man-tipping-hand: 유의사항 > + 1. 기존에 동아리 사물함을 사용중이셨다면, 새로이 사용하는 동아리들의 원활한 이용을 위해서 + 3월 22일(금)까지 사물함을 비워주시기 바랍니다! + 2. 위 모든 내용은 상황에 따라 변경될 수 있으며 차후에도 변경될 수 있습니다. + 이 때에는 재공지될 예정입니다! + ----------------------------------------------------- + < :telephone_receiver: 문의사항 > + 슬랙의 ‘42seoul_club_cabinet’ 채널에 문의해주시면 됩니다! :sunglasses:`, + }, + { + title: "연체", + content: `[CABI] 안녕하세요! :embarrassed_cabi: + 현재 이용 중이신 사물함이 연체인 것으로 확인되어 연락드립니다. + 장기간 연체시 서비스 이용에 대한 페널티, 혹은 :tig:가 부여될 수 있음을 인지해주세요! + 사물함의 대여 기간을 확인하신 후 반납 부탁드립니다. + 항상 저희 서비스를 이용해 주셔서 감사합니다:)`, + }, +]; diff --git a/frontend/src/Cabinet/assets/images/slack-notification.svg b/frontend/src/Cabinet/assets/images/slack-notification.svg new file mode 100644 index 000000000..db62eaca0 --- /dev/null +++ b/frontend/src/Cabinet/assets/images/slack-notification.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx index acdd21610..c0f00fee6 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -114,6 +114,11 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { closeAll(); }; + const onClickSlackNotiButton = () => { + navigator("slack-notification"); + closeAll(); + }; + const onClickMainClubButton = () => { navigator("clubs"); closeAll(); @@ -147,6 +152,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { resetCurrentSection(); navigator("/login"); }; + return ( { onClickFloorButton={onClickFloorButton} onClickSearchButton={onClickSearchButton} onClickLogoutButton={onClickLogoutButton} + onClickSlackNotiButton={onClickSlackNotiButton} onClickAdminClubButton={onClickAdminClubButton} onClickMainClubButton={onClickMainClubButton} onClickProfileButton={onClickProfileButton} diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx index 3d7ed59cf..b7918c37b 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -3,6 +3,7 @@ import { ReactComponent as LogoutImg } from "@/Cabinet/assets/images/close-squar import { ReactComponent as CulbImg } from "@/Cabinet/assets/images/clubIconGray.svg"; import { ReactComponent as ProfileImg } from "@/Cabinet/assets/images/profile-circle.svg"; import { ReactComponent as SearchImg } from "@/Cabinet/assets/images/search.svg"; +import { ReactComponent as SlackNotiImg } from "@/Cabinet/assets/images/slack-notification.svg"; import { ReactComponent as SlackImg } from "@/Cabinet/assets/images/slack.svg"; interface ILeftMainNav { @@ -13,6 +14,7 @@ interface ILeftMainNav { currentFloor: number; onClickFloorButton: Function; onClickLogoutButton: React.MouseEventHandler; + onClickSlackNotiButton: React.MouseEventHandler; onClickSearchButton: React.MouseEventHandler; onClickAdminClubButton: React.MouseEventHandler; onClickMainClubButton: React.MouseEventHandler; @@ -29,6 +31,7 @@ const LeftMainNav = ({ onClickHomeButton, onClickFloorButton, onClickLogoutButton, + onClickSlackNotiButton, onClickSearchButton, onClickAdminClubButton, onClickMainClubButton, @@ -84,6 +87,17 @@ const LeftMainNav = ({ {isAdmin && ( <> + + + Noti + ; + renderReceiverInput: (title: string) => void; +}) => { + const [searchListById, setSearchListById] = useState([]); + const [searchListByChannel, setSearchListByChannel] = useState< + ISlackChannel[] + >([]); + const [totalLength, setTotalLength] = useState(0); + const [onFocus, setOnFocus] = useState(true); + const [targetIndex, setTargetIndex] = useState(-1); + const [searchValue, setSearchValue] = useState(""); + const { debounce } = useDebounce(); + + const typeSearchInput = async () => { + if (searchInput.current) { + const searchValue = searchInput.current.value; + setSearchValue(searchValue); + if (searchValue.length <= 0) { + setSearchListById([]); + setSearchListByChannel([]); + setTotalLength(0); + setTargetIndex(-1); + return; + } + if (searchInput.current!.value[0] === "#") { + // slack channel 검색 + if (searchValue.length <= 0) { + setSearchListByChannel([]); + setTotalLength(0); + setTargetIndex(-1); + } else { + const searchResult = SlackChannels.filter((SlackChannels) => { + return SlackChannels.title.includes(searchValue); + }); + setSearchListById([]); + setSearchListByChannel(searchResult); + setTotalLength(searchResult.length); + } + } else { + // intra_ID 검색 + if (searchValue.length <= 1) { + setSearchListById([]); + setTotalLength(0); + setTargetIndex(-1); + } else { + const searchResult = await axiosSearchByIntraId(searchValue); + setSearchListByChannel([]); + setSearchListById(searchResult.data.result); + setTotalLength(searchResult.data.totalLength); + } + } + } + }; + + // outside click + useOutsideClick(searchInput, () => { + setOnFocus(false); + }); + + const valueChangeHandler = () => { + if (searchInput.current!.value[0] === "#") { + return searchListByChannel[targetIndex].title; + } else return searchListById[targetIndex].name; + }; + + // searchInput value change + useEffect(() => { + if (targetIndex !== -1) { + searchInput.current!.value = valueChangeHandler(); + setSearchValue(searchInput.current!.value); + } + }, [targetIndex]); + + const handleInputKey = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + if (targetIndex !== -1) { + searchInput.current!.value = valueChangeHandler(); + setSearchValue(searchInput.current!.value); + setTotalLength(0); + } + } else if (e.key == "ArrowUp") { + if (totalLength > 0) { + setTargetIndex((prev) => + prev > 0 + ? prev - 1 + : Math.max(searchListById.length, searchListByChannel.length) - 1 + ); + } + } else if (e.key == "ArrowDown") { + if (totalLength > 0) { + setTargetIndex((prev) => + prev < Math.max(searchListById.length, searchListByChannel.length) - 1 + ? prev + 1 + : 0 + ); + } + } + }; + + return ( + <> + + { + setOnFocus(true); + }} + onChange={() => debounce("slackNotiSearch", typeSearchInput, 300)} + onKeyDown={handleInputKey} + /> + {onFocus && searchInput.current?.value && totalLength > 0 && ( + <> + + + )} + + + ); +}; + +const SearchBarWrapStyled = styled.div` + position: relative; +`; + +const FormInputStyled = styled.input` + width: 100%; + height: 40px; + background-color: var(--white); + border-radius: 8px; + border: 1px solid var(--full); + :focus { + border: 1px solid var(--main-color); + } + text-align: left; + padding: 0 10px; + ::placeholder { + color: var(--line-color); + } +`; + +export default SlackNotiSearchBar; diff --git a/frontend/src/Cabinet/components/SlackNoti/SlackNotiSearchBarList.tsx b/frontend/src/Cabinet/components/SlackNoti/SlackNotiSearchBarList.tsx new file mode 100644 index 000000000..773332ee1 --- /dev/null +++ b/frontend/src/Cabinet/components/SlackNoti/SlackNotiSearchBarList.tsx @@ -0,0 +1,69 @@ +import { ISlackChannel } from "@/Cabinet/assets/data/SlackAlarm"; +import styled from "styled-components"; +import SlackNotiSearchListItem from "@/Cabinet/components/SlackNoti/SlackNotiSearchListItem"; + +interface ISearchListByIntraId { + name: string; + userId: number; +} + +const SlackNotiSearchBarList = ({ + searchListById, + searchListByChannel, + searchWord, + targetIndex, + renderReceiverInput, +}: { + searchListById: ISearchListByIntraId[]; + searchListByChannel: ISlackChannel[]; + searchWord: string; + targetIndex?: number; + renderReceiverInput: (title: string) => void; +}) => { + return ( + + {searchListById.map((item, index: number) => { + return ( + + ); + })} + {searchListByChannel.map((item, index: number) => { + return ( + + ); + })} + + ); +}; + +const UlStyled = styled.ul` + position: absolute; + top: 50px; + width: 100%; + border: 1px solid var(--white); + border-radius: 10px; + text-align: left; + padding: 10px; + color: var(--black); + background-color: var(--white); + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); + opacity: 0.9; + overflow: hidden; + @media (max-width: 768px) { + width: 100%; + } +`; + +export default SlackNotiSearchBarList; diff --git a/frontend/src/Cabinet/components/SlackNoti/SlackNotiSearchListItem.tsx b/frontend/src/Cabinet/components/SlackNoti/SlackNotiSearchListItem.tsx new file mode 100644 index 000000000..e3098324e --- /dev/null +++ b/frontend/src/Cabinet/components/SlackNoti/SlackNotiSearchListItem.tsx @@ -0,0 +1,51 @@ +import styled from "styled-components"; +import ChangeToHTML from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; + +const SlackNotiSearchListItem = (props: { + inputText: string; + resultText: string; + isTargetIndex?: boolean; + renderReceiverInput: (title: string) => void; +}) => { + const { resultText, inputText, isTargetIndex, renderReceiverInput } = props; + + return ( + { + renderReceiverInput(resultText); + }} + > + + + ); +}; + +const LiStyled = styled.li` + padding: 12px; + border-radius: 10px; + cursor: pointer; + & strong { + color: var(--main-color); + } + + &.active { + background-color: var(--main-color); + color: var(--white); + } + &.active strong { + color: var(--white); + } + + @media (hover: hover) and (pointer: fine) { + &:hover { + background-color: var(--main-color); + color: var(--white); + } + &:hover strong { + color: var(--white); + } + } +`; + +export default SlackNotiSearchListItem; diff --git a/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBar.tsx b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBar.tsx index 71d79ff5e..304b6132d 100644 --- a/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBar.tsx +++ b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBar.tsx @@ -7,6 +7,7 @@ import { axiosSearchByCabinetNumSimple, axiosSearchByIntraId, } from "@/Cabinet/api/axios/axios.custom"; +import useDebounce from "@/Cabinet/hooks/useDebounce"; import useOutsideClick from "@/Cabinet/hooks/useOutsideClick"; const SearchBar = () => { @@ -20,6 +21,7 @@ const SearchBar = () => { const [targetIndex, setTargetIndex] = useState(-1); const [searchValue, setSearchValue] = useState(""); const [floor, setFloor] = useState(0); + const { debounce } = useDebounce(); const resetSearchState = () => { setSearchListById([]); @@ -55,19 +57,6 @@ const SearchBar = () => { } }; - const debounce = (func: Function, wait: number) => { - let timeout: NodeJS.Timeout; - - return function executedFunction(...args: any[]) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - }; - const typeSearchInput = async () => { if (searchInput.current) { setSearchValue(searchInput.current.value); @@ -172,7 +161,7 @@ const SearchBar = () => { onFocus={() => { setIsFocus(true); }} - onChange={debounce(typeSearchInput, 300)} + onChange={() => debounce("topNavSearch", typeSearchInput, 300)} onKeyDown={handleInputKey} > diff --git a/frontend/src/Cabinet/pages/admin/AdminSlackNotiPage.tsx b/frontend/src/Cabinet/pages/admin/AdminSlackNotiPage.tsx new file mode 100644 index 000000000..0ab9cd395 --- /dev/null +++ b/frontend/src/Cabinet/pages/admin/AdminSlackNotiPage.tsx @@ -0,0 +1,296 @@ +import { useRef, useState } from "react"; +import styled from "styled-components"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import SlackNotiSearchBar from "@/Cabinet/components/SlackNoti/SlackNotiSearchBar"; +import { + ISlackAlarmTemplate, + ISlackChannel, + SlackAlarmTemplates, + SlackChannels, +} from "@/Cabinet/assets/data/SlackAlarm"; +import { + axiosSendSlackNotificationToChannel, + axiosSendSlackNotificationToUser, +} from "@/Cabinet/api/axios/axios.custom"; + +const AdminSlackNotiPage = () => { + const receiverInputRef = useRef(null); + const msgTextAreaRef = useRef(null); + const [showResponseModal, setShowResponseModal] = useState(false); + const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false); + const [modalContent, setModalContent] = useState(""); + const [modalTitle, setModalTitle] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const renderReceiverInput = (title: string) => { + if (receiverInputRef.current) receiverInputRef.current.value = title; + }; + + const renderTemplateTextArea = (title: string) => { + const template = SlackAlarmTemplates.find((template) => { + return template.title === title; + }); + if (msgTextAreaRef.current) + msgTextAreaRef.current.value = template!.content; + }; + + const initializeInputandTextArea = () => { + if (receiverInputRef.current) receiverInputRef.current.value = ""; + if (msgTextAreaRef.current) msgTextAreaRef.current.value = ""; + }; + + const handleSubmitButton = async () => { + if (!receiverInputRef.current?.value) { + return alert("받는이를 입력해주세요."); + } else if (!msgTextAreaRef.current?.value) { + return alert("메시지 내용을 입력해주세요."); + } + + try { + setIsLoading(true); + if (receiverInputRef.current!.value[0] === "#") { + let channelId = SlackChannels.find((channel) => { + return receiverInputRef.current!.value === channel.title; + })?.channelId; + await axiosSendSlackNotificationToChannel( + receiverInputRef.current.value, + msgTextAreaRef.current!.value, + channelId + ); + } else { + await axiosSendSlackNotificationToUser( + receiverInputRef.current.value, + msgTextAreaRef.current!.value + ); + } + setModalTitle("알림이 전송되었습니다"); + } catch (error: any) { + setHasErrorOnResponse(true); + setModalContent(error.response.data.message); + } finally { + setShowResponseModal(true); + setIsLoading(false); + } + }; + + return ( + + +

알림

+
+ + 자주 쓰는 채널 + + {SlackChannels.map((channel: ISlackChannel, idx: number) => { + return ( + renderReceiverInput(channel.title)} + > + {channel.title} + + ); + })} + + + + 자주 쓰는 템플릿 + + {SlackAlarmTemplates.map( + (template: ISlackAlarmTemplate, idx: number) => { + return ( + renderTemplateTextArea(template.title)} + > + {template.title} + + ); + } + )} + + + + 알림 보내기 + + + + 받는이(Intra ID/ Channel)* + + + + + + 메시지 내용* + + + + + + 초기화 + + + 보내기 + + + + + {showResponseModal && + (hasErrorOnResponse ? ( + { + setShowResponseModal(false); + setHasErrorOnResponse(false); + }} + /> + ) : ( + setShowResponseModal(false)} + /> + ))} +
+ ); +}; + +const WrapperStyled = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 60px 0; +`; + +const TitleContainerStyled = styled.div` + width: 80%; + max-width: 1000px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 2px solid #d9d9d9; + margin-bottom: 70px; + font-weight: 700; + + .title { + font-size: 1.25rem; + letter-spacing: -0.02rem; + margin-bottom: 20px; + } +`; + +const ContainerStyled = styled.div` + width: 80%; + max-width: 1000px; + margin-bottom: 40px; +`; + +const SubTitleStyled = styled.h2` + font-size: 1.25rem; + font-weight: 700; + margin-bottom: 20px; +`; + +const CapsuleWrappingStyled = styled.div` + width: 100%; + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +`; + +const CapsuleButtonStyled = styled.span` + display: flex; + justify-content: center; + align-items: center; + padding: 8px 20px; + background: var(--lightgray-color); + border: 1px solid var(--full); + border-radius: 22px; + cursor: pointer; + + :hover { + background: #b08cff5f; + color: var(--main-color); + border: 1px solid var(--main-color); + } +`; + +const FormWappingStyled = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + /* width: 80%; */ + /* max-width: 1000px; */ + background-color: var(--lightgray-color); + border-radius: 10px; + padding: 30px 20px; + gap: 20px; +`; + +const FormContainerStyled = styled.div` + width: 100%; +`; +const FormSubTitleStyled = styled.h3` + font-size: 0.875rem; + color: var(--gray-color); + margin-bottom: 10px; + span { + color: var(--expired); + } +`; + +const FormTextareaStyled = styled.textarea` + box-sizing: border-box; + width: 100%; + min-height: 200px; + background-color: var(--white); + border-radius: 8px; + border: 1px solid var(--full); + resize: none; + outline: none; + :focus { + border: 1px solid var(--main-color); + } + text-align: left; + padding: 10px; + ::placeholder { + color: var(--line-color); + } +`; + +const FormButtonContainerStyled = styled.div` + width: 100%; + display: flex; + justify-content: space-between; +`; + +const FormButtonStyled = styled.button<{ primary?: boolean }>` + width: auto; + height: auto; + padding: 10px 16px; + font-size: 0.875rem; + background-color: ${(props) => + props.primary ? "var(--main-color)" : "var(--white)"}; + color: ${(props) => (props.primary ? "var(--white)" : "var(--black)")}; + font-weight: 700; + border: 1px solid var(--full); + border-radius: 4px; + cursor: pointer; + :hover { + opacity: 0.85; + } +`; + +export default AdminSlackNotiPage; From cc981fd0dd739abf6bf64a8bad0b6e11d56fb279 Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 16 Apr 2024 17:21:35 +0900 Subject: [PATCH 0580/1029] =?UTF-8?q?[FE]=20=EB=A1=A4=EB=B0=B1=203?= =?UTF-8?q?=EC=B0=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 7866 ++++++++++++++++- frontend/src/App.tsx | 4 +- .../LeftMainNav/LeftMainNav.container.tsx | 4 +- 3 files changed, 7868 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9f08481bf..80b634fe4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7612,7 +7612,13 @@ "version": "29.4.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", - "dev": true, + " + + + + + + ": true, "dependencies": { "@jest/schemas": "^29.4.0", "ansi-styles": "^5.0.0", @@ -10525,5 +10531,7863 @@ "url": "https://github.com/sponsors/sindresorhus" } } + }, + "dependencies": { + "@adobe/css-tools": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.1.0.tgz", + "integrity": "sha512-mMVJ/j/GbZ/De4ZHWbQAQO1J6iVnjtZLc9WEdkUQb8S/Bu2cAF2bETXUgMAdvMG3/ngtKmcNBe+Zms9bg6jnQQ==", + "dev": true + }, + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "requires": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + } + }, + "@babel/compat-data": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "dev": true + }, + "@babel/core": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "requires": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "requires": { + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + } + }, + "@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + }, + "@babel/helper-validator-option": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + } + }, + "@babel/helpers": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" + } + }, + "@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", + "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", + "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.15" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "requires": {} + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.2.tgz", + "integrity": "sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", + "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-static-block": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", + "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", + "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", + "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dynamic-import": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", + "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-export-namespace-from": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", + "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", + "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-json-strings": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", + "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", + "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", + "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", + "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", + "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", + "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", + "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", + "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.15" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + } + }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", + "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", + "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", + "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", + "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-constant-elements": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz", + "integrity": "sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", + "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz", + "integrity": "sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/types": "^7.22.15" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz", + "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz", + "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", + "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", + "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz", + "integrity": "sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", + "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/preset-env": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.2.tgz", + "integrity": "sha512-BW3gsuDD+rvHL2VO2SjAUNTBe5YrjsTiDyqamPDWY723na3/yPQ65X5oQkFVJZ0o50/2d+svm1rkPoJeR1KxVQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.23.2", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.23.2", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.23.0", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.11", + "@babel/plugin-transform-classes": "^7.22.15", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.23.0", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.11", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.11", + "@babel/plugin-transform-for-of": "^7.22.15", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.11", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.23.0", + "@babel/plugin-transform-modules-commonjs": "^7.23.0", + "@babel/plugin-transform-modules-systemjs": "^7.23.0", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", + "@babel/plugin-transform-numeric-separator": "^7.22.11", + "@babel/plugin-transform-object-rest-spread": "^7.22.15", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.11", + "@babel/plugin-transform-optional-chaining": "^7.23.0", + "@babel/plugin-transform-parameters": "^7.22.15", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.10", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.10", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "@babel/types": "^7.23.0", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + } + }, + "@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-react": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.15.tgz", + "integrity": "sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-react-display-name": "^7.22.5", + "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.22.5" + } + }, + "@babel/preset-typescript": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.2.tgz", + "integrity": "sha512-u4UJc1XsS1GhIGteM8rnGiIvf9rJpiVgMEeCnwlLA7WJPC+jcXWJAGxYmeqs5hOZD8BbAfnV5ezBOxQbb4OUxA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.23.0", + "@babel/plugin-transform-typescript": "^7.22.15" + } + }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "@babel/runtime": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", + "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/runtime-corejs3": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.6.tgz", + "integrity": "sha512-tqeujPiuEfcH067mx+7otTQWROVMKHXEaOQcAeNV5dDdbPWvPcFA8/W9LXw2NfjNmOetqLl03dfnG2WALPlsRQ==", + "dev": true, + "requires": { + "core-js-pure": "^3.25.1", + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + }, + "@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "@esbuild/android-arm": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.9.tgz", + "integrity": "sha512-kW5ccqWHVOOTGUkkJbtfoImtqu3kA1PFkivM+9QPFSHphPfPBlBalX9eDRqPK+wHCqKhU48/78T791qPgC9e9A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.9.tgz", + "integrity": "sha512-ndIAZJUeLx4O+4AJbFQCurQW4VRUXjDsUvt1L+nP8bVELOWdmdCEOtlIweCUE6P+hU0uxYbEK2AEP0n5IVQvhg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.9.tgz", + "integrity": "sha512-UbMcJB4EHrAVOnknQklREPgclNU2CPet2h+sCBCXmF2mfoYWopBn/CfTfeyOkb/JglOcdEADqAljFndMKnFtOw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.9.tgz", + "integrity": "sha512-d7D7/nrt4CxPul98lx4PXhyNZwTYtbdaHhOSdXlZuu5zZIznjqtMqLac8Bv+IuT6SVHiHUwrkL6ywD7mOgLW+A==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.9.tgz", + "integrity": "sha512-LZc+Wlz06AkJYtwWsBM3x2rSqTG8lntDuftsUNQ3fCx9ZttYtvlDcVtgb+NQ6t9s6K5No5zutN3pcjZEC2a4iQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.9.tgz", + "integrity": "sha512-gIj0UQZlQo93CHYouHKkpzP7AuruSaMIm1etcWIxccFEVqCN1xDr6BWlN9bM+ol/f0W9w3hx3HDuEwcJVtGneQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.9.tgz", + "integrity": "sha512-GNors4vaMJ7lzGOuhzNc7jvgsQZqErGA8rsW+nck8N1nYu86CvsJW2seigVrQQWOV4QzEP8Zf3gm+QCjA2hnBQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.9.tgz", + "integrity": "sha512-cNx1EF99c2t1Ztn0lk9N+MuwBijGF8mH6nx9GFsB3e0lpUpPkCE/yt5d+7NP9EwJf5uzqdjutgVYoH1SNqzudA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.9.tgz", + "integrity": "sha512-YPxQunReYp8RQ1FvexFrOEqqf+nLbS3bKVZF5FRT2uKM7Wio7BeATqAwO02AyrdSEntt3I5fhFsujUChIa8CZg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.9.tgz", + "integrity": "sha512-zb12ixDIKNwFpIqR00J88FFitVwOEwO78EiUi8wi8FXlmSc3GtUuKV/BSO+730Kglt0B47+ZrJN1BhhOxZaVrw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.9.tgz", + "integrity": "sha512-X8te4NLxtHiNT6H+4Pfm5RklzItA1Qy4nfyttihGGX+Koc53Ar20ViC+myY70QJ8PDEOehinXZj/F7QK3A+MKQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.9.tgz", + "integrity": "sha512-ZqyMDLt02c5smoS3enlF54ndK5zK4IpClLTxF0hHfzHJlfm4y8IAkIF8LUW0W7zxcKy7oAwI7BRDqeVvC120SA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.9.tgz", + "integrity": "sha512-k+ca5W5LDBEF3lfDwMV6YNXwm4wEpw9krMnNvvlNz3MrKSD2Eb2c861O0MaKrZkG/buTQAP4vkavbLwgIe6xjg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.9.tgz", + "integrity": "sha512-GuInVdogjmg9DhgkEmNipHkC+3tzkanPJzgzTC2ihsvrruLyFoR1YrTGixblNSMPudQLpiqkcwGwwe0oqfrvfA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.9.tgz", + "integrity": "sha512-49wQ0aYkvwXonGsxc7LuuLNICMX8XtO92Iqmug5Qau0kpnV6SP34jk+jIeu4suHwAbSbRhVFtDv75yRmyfQcHw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.9.tgz", + "integrity": "sha512-Nx4oKEAJ6EcQlt4dK7qJyuZUoXZG7CAeY22R7rqZijFzwFfMOD+gLP56uV7RrV86jGf8PeRY8TBsRmOcZoG42w==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.9.tgz", + "integrity": "sha512-d0WnpgJ+FTiMZXEQ1NOv9+0gvEhttbgKEvVqWWAtl1u9AvlspKXbodKHzQ5MLP6YV1y52Xp+p8FMYqj8ykTahg==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.9.tgz", + "integrity": "sha512-jccK11278dvEscHFfMk5EIPjF4wv1qGD0vps7mBV1a6TspdR36O28fgPem/SA/0pcsCPHjww5ouCLwP+JNAFlw==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.9.tgz", + "integrity": "sha512-OetwTSsv6mIDLqN7I7I2oX9MmHGwG+AP+wKIHvq+6sIHwcPPJqRx+DJB55jy9JG13CWcdcQno/7V5MTJ5a0xfQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.9.tgz", + "integrity": "sha512-tKSSSK6unhxbGbHg+Cc+JhRzemkcsX0tPBvG0m5qsWbkShDK9c+/LSb13L18LWVdOQZwuA55Vbakxmt6OjBDOQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.9.tgz", + "integrity": "sha512-ZTQ5vhNS5gli0KK8I6/s6+LwXmNEfq1ftjnSVyyNm33dBw8zDpstqhGXYUbZSWWLvkqiRRjgxgmoncmi6Yy7Ng==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.9.tgz", + "integrity": "sha512-C4ZX+YFIp6+lPrru3tpH6Gaapy8IBRHw/e7l63fzGDhn/EaiGpQgbIlT5paByyy+oMvRFQoxxyvC4LE0AjJMqQ==", + "dev": true, + "optional": true + }, + "@eslint/eslintrc": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", + "integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + } + } + }, + "@firebase/analytics": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.0.tgz", + "integrity": "sha512-Locv8gAqx0e+GX/0SI3dzmBY5e9kjVDtD+3zCFLJ0tH2hJwuCAiL+5WkHuxKj92rqQj/rvkBUCfA1ewlX2hehg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/analytics-compat": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.6.tgz", + "integrity": "sha512-4MqpVLFkGK7NJf/5wPEEP7ePBJatwYpyjgJ+wQHQGHfzaCDgntOnl9rL2vbVGGKCnRqWtZDIWhctB86UWXaX2Q==", + "requires": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-types": "0.8.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/analytics-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", + "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==" + }, + "@firebase/app": { + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.19.tgz", + "integrity": "sha512-t/SHyZ3xWkR77ZU9VMoobDNFLdDKQ5xqoCAn4o16gTsA1C8sJ6ZOMZ02neMOPxNHuQXVE4tA8ukilnDbnK7uJA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/app-check": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.0.tgz", + "integrity": "sha512-dRDnhkcaC2FspMiRK/Vbp+PfsOAEP6ZElGm9iGFJ9fDqHoPs0HOPn7dwpJ51lCFi1+2/7n5pRPGhqF/F03I97g==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/app-check-compat": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.7.tgz", + "integrity": "sha512-cW682AxsyP1G+Z0/P7pO/WT2CzYlNxoNe5QejVarW2o5ZxeWSSPAiVEwpEpQR/bUlUmdeWThYTMvBWaopdBsqw==", + "requires": { + "@firebase/app-check": "0.8.0", + "@firebase/app-check-types": "0.5.0", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/app-check-interop-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz", + "integrity": "sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg==" + }, + "@firebase/app-check-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", + "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==" + }, + "@firebase/app-compat": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.19.tgz", + "integrity": "sha512-QkJDqYqjhvs4fTMcRVXQkP9hbo5yfoJXDWkhU4VA5Vzs8Qsp76VPzYbqx5SD5OmBy+bz/Ot1UV8qySPGI4aKuw==", + "requires": { + "@firebase/app": "0.9.19", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + }, + "@firebase/auth": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.3.0.tgz", + "integrity": "sha512-vjK4CHbY9aWdiVOrKi6mpa8z6uxeaf7LB/MZTHuZOiGHMcUoTGB6TeMbRShyqk1uaMrxhhZ5Ar/dR0965E1qyA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/auth-compat": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.4.6.tgz", + "integrity": "sha512-pKp1d4fSf+yoy1EBjTx9ISxlunqhW0vTICk0ByZ3e+Lp6ZIXThfUy4F1hAJlEafD/arM0oepRiAh7LXS1xn/BA==", + "requires": { + "@firebase/auth": "1.3.0", + "@firebase/auth-types": "0.12.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" + }, + "@firebase/auth-types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", + "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", + "requires": {} + }, + "@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "requires": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/database": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.1.tgz", + "integrity": "sha512-VAhF7gYwunW4Lw/+RQZvW8dlsf2r0YYqV9W0Gi2Mz8+0TGg1mBJWoUtsHfOr8kPJXhcLsC4eP/z3x6L/Fvjk/A==", + "requires": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/database-compat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.1.tgz", + "integrity": "sha512-ky82yLIboLxtAIWyW/52a6HLMVTzD2kpZlEilVDok73pNPLjkJYowj8iaIWK5nTy7+6Gxt7d00zfjL6zckGdXQ==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/database": "1.0.1", + "@firebase/database-types": "1.0.0", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/database-types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.0.tgz", + "integrity": "sha512-SjnXStoE0Q56HcFgNQ+9SsmJc0c8TqGARdI/T44KXy+Ets3r6x/ivhQozT66bMnCEjJRywYoxNurRTMlZF8VNg==", + "requires": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "@firebase/firestore": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.2.0.tgz", + "integrity": "sha512-iKZqIdOBJpJUcwY5airLX0W04TLrQSJuActOP1HG5WoIY5oyGTQE4Ml7hl5GW7mBqFieT4ojtUuDXj6MLrn1lA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "@firebase/webchannel-wrapper": "0.10.3", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/firestore-compat": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.18.tgz", + "integrity": "sha512-hkqv4mb1oScKbEtzfcK8Go8c0VpDWmbAvbD6B6XnphLqi27pkXgo9Rp+aSKlD7cBL29VMEekP5bEm9lSVfZpNw==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/firestore": "4.2.0", + "@firebase/firestore-types": "3.0.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/firestore-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.0.tgz", + "integrity": "sha512-Meg4cIezHo9zLamw0ymFYBD4SMjLb+ZXIbuN7T7ddXN6MGoICmOTq3/ltdCGoDCS2u+H1XJs2u/cYp75jsX9Qw==", + "requires": {} + }, + "@firebase/functions": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.10.0.tgz", + "integrity": "sha512-2U+fMNxTYhtwSpkkR6WbBcuNMOVaI7MaH3cZ6UAeNfj7AgEwHwMIFLPpC13YNZhno219F0lfxzTAA0N62ndWzA==", + "requires": { + "@firebase/app-check-interop-types": "0.3.0", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/functions-compat": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.5.tgz", + "integrity": "sha512-uD4jwgwVqdWf6uc3NRKF8cSZ0JwGqSlyhPgackyUPe+GAtnERpS4+Vr66g0b3Gge0ezG4iyHo/EXW/Hjx7QhHw==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/functions": "0.10.0", + "@firebase/functions-types": "0.6.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/functions-types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", + "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==" + }, + "@firebase/installations": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", + "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/installations-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.4.tgz", + "integrity": "sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/installations-types": "0.5.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/installations-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", + "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", + "requires": {} + }, + "@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/messaging": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", + "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/messaging-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz", + "integrity": "sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/messaging": "0.12.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/messaging-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", + "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==" + }, + "@firebase/performance": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", + "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/performance-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.4.tgz", + "integrity": "sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/performance": "0.6.4", + "@firebase/performance-types": "0.2.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/performance-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", + "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==" + }, + "@firebase/remote-config": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", + "integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/remote-config-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz", + "integrity": "sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-types": "0.3.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/remote-config-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", + "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==" + }, + "@firebase/storage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", + "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/storage-compat": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.2.tgz", + "integrity": "sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-types": "0.8.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/storage-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", + "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", + "requires": {} + }, + "@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@firebase/webchannel-wrapper": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.3.tgz", + "integrity": "sha512-+ZplYUN3HOpgCfgInqgdDAbkGGVzES1cs32JJpeqoh87SkRobGXElJx+1GZSaDqzFL+bYiX18qEcBK76mYs8uA==" + }, + "@grpc/grpc-js": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.3.tgz", + "integrity": "sha512-b8iWtdrYIeT5fdZdS4Br/6h/kuk0PW5EVBUGk1amSbrpL8DlktJD43CdcCWwRdd6+jgwHhADSbL9CsNnm6EUPA==", + "requires": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + } + }, + "@grpc/proto-loader": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", + "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==", + "requires": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.4", + "yargs": "^17.7.2" + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "requires": {} + }, + "@jest/expect-utils": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.4.1.tgz", + "integrity": "sha512-w6YJMn5DlzmxjO00i9wu2YSozUYRBhIoJ6nQwpMYcBMtiqMGJm1QBzOf6DDgRao8dbtpDoaqLg6iiQTvv0UHhQ==", + "dev": true, + "requires": { + "jest-get-type": "^29.2.0" + } + }, + "@jest/schemas": { + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.0.tgz", + "integrity": "sha512-0E01f/gOZeNTG76i5eWWSupvSHaIINrTie7vCyjiYFKgzNdyEGd12BUv4oNBFHOqlHDbtoJi3HrQ38KCC90NsQ==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@jest/types": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.4.1.tgz", + "integrity": "sha512-zbrAXDUOnpJ+FMST2rV7QZOgec8rskg2zv8g2ajeqitp4tvZiyqTCYXANrKsM+ryj5o+LI+ZN2EgU9drrkiwSA==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@nivo/annotations": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.80.0.tgz", + "integrity": "sha512-bC9z0CLjU07LULTMWsqpjovRtHxP7n8oJjqBQBLmHOGB4IfiLbrryBfu9+aEZH3VN2jXHhdpWUz+HxeZzOzsLg==", + "requires": { + "@nivo/colors": "0.80.0", + "@react-spring/web": "9.4.5", + "lodash": "^4.17.21" + } + }, + "@nivo/arcs": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/arcs/-/arcs-0.80.0.tgz", + "integrity": "sha512-g5m/wM36Ey45J3hrVDBPMw1Z6GOgIRwgb5zTh7TFoPuhRBZEDQLmctk8XYOm0xOMVCzsm6WkU5wlSQUeBY6IHQ==", + "requires": { + "@nivo/colors": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-shape": "^1.3.5" + } + }, + "@nivo/axes": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.80.0.tgz", + "integrity": "sha512-AsUyaSHGwQVSEK8QXpsn8X+poZxvakLMYW7crKY1xTGPNw+SU4SSBohPVumm2jMH3fTSLNxLhAjWo71GBJXfdA==", + "requires": { + "@nivo/scales": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-format": "^1.4.4", + "d3-time-format": "^3.0.0" + } + }, + "@nivo/bar": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/bar/-/bar-0.80.0.tgz", + "integrity": "sha512-woE/S12Sp+RKQeOHtp302WXfy5usj73cV/gjP95PzJxMv+Rn01i1Uwys3BILzc9h4+OxYuWTFqLADAySAmi7qQ==", + "requires": { + "@nivo/annotations": "0.80.0", + "@nivo/axes": "0.80.0", + "@nivo/colors": "0.80.0", + "@nivo/legends": "0.80.0", + "@nivo/scales": "0.80.0", + "@nivo/tooltip": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-scale": "^3.2.3", + "d3-shape": "^1.3.5", + "lodash": "^4.17.21" + } + }, + "@nivo/colors": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.80.0.tgz", + "integrity": "sha512-T695Zr411FU4RPo7WDINOAn8f79DPP10SFJmDdEqELE+cbzYVTpXqLGZ7JMv88ko7EOf9qxLQgcBqY69rp9tHQ==", + "requires": { + "d3-color": "^2.0.0", + "d3-scale": "^3.2.3", + "d3-scale-chromatic": "^2.0.0", + "lodash": "^4.17.21" + } + }, + "@nivo/core": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.80.0.tgz", + "integrity": "sha512-6caih0RavXdWWSfde+rC2pk17WrX9YQlqK26BrxIdXzv3Ydzlh5SkrC7dR2TEvMGBhunzVeLOfiC2DWT1S8CFg==", + "requires": { + "@nivo/recompose": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-color": "^2.0.0", + "d3-format": "^1.4.4", + "d3-interpolate": "^2.0.1", + "d3-scale": "^3.2.3", + "d3-scale-chromatic": "^2.0.0", + "d3-shape": "^1.3.5", + "d3-time-format": "^3.0.0", + "lodash": "^4.17.21" + } + }, + "@nivo/legends": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.80.0.tgz", + "integrity": "sha512-h0IUIPGygpbKIZZZWIxkkxOw4SO0rqPrqDrykjaoQz4CvL4HtLIUS3YRA4akKOVNZfS5agmImjzvIe0s3RvqlQ==", + "requires": {} + }, + "@nivo/line": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.80.0.tgz", + "integrity": "sha512-6UAD/y74qq3DDRnVb+QUPvXYojxMtwXMipGSNvCGk8omv1QZNTaUrbV+eQacvn9yh//a0yZcWipnpq0tGJyJCA==", + "requires": { + "@nivo/annotations": "0.80.0", + "@nivo/axes": "0.80.0", + "@nivo/colors": "0.80.0", + "@nivo/legends": "0.80.0", + "@nivo/scales": "0.80.0", + "@nivo/tooltip": "0.80.0", + "@nivo/voronoi": "0.80.0", + "@react-spring/web": "9.4.5", + "d3-shape": "^1.3.5" + } + }, + "@nivo/pie": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/pie/-/pie-0.80.0.tgz", + "integrity": "sha512-Zj2PtozUg5wizxdI/2o13YzwnBwf8lLrgc8vH7ucsgOu5nj6oLLpGTuNd3CBmRJHFGIGNT39bP63lKnB3P6qOQ==", + "requires": { + "@nivo/arcs": "0.80.0", + "@nivo/colors": "0.80.0", + "@nivo/legends": "0.80.0", + "@nivo/tooltip": "0.80.0", + "d3-shape": "^1.3.5" + } + }, + "@nivo/recompose": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/recompose/-/recompose-0.80.0.tgz", + "integrity": "sha512-iL3g7j3nJGD9+mRDbwNwt/IXDXH6E29mhShY1I7SP91xrfusZV9pSFf4EzyYgruNJk/2iqMuaqn+e+TVFra44A==", + "requires": { + "react-lifecycles-compat": "^3.0.4" + } + }, + "@nivo/scales": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.80.0.tgz", + "integrity": "sha512-4y2pQdCg+f3n4TKXC2tYuq71veZM+xPRQbOTgGYJpuBvMc7pQsXF9T5z7ryeIG9hkpXkrlyjecU6XcAG7tLSNg==", + "requires": { + "d3-scale": "^3.2.3", + "d3-time": "^1.0.11", + "d3-time-format": "^3.0.0", + "lodash": "^4.17.21" + }, + "dependencies": { + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + } + } + }, + "@nivo/tooltip": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.80.0.tgz", + "integrity": "sha512-qGmrreRwnCsYjn/LAuwBtxBn/tvG8y+rwgd4gkANLBAoXd3bzJyvmkSe+QJPhUG64bq57ibDK+lO2pC48a3/fw==", + "requires": { + "@react-spring/web": "9.4.5" + } + }, + "@nivo/voronoi": { + "version": "0.80.0", + "resolved": "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.80.0.tgz", + "integrity": "sha512-zaJV3I3cRu1gHpsXCIEvp6GGlGY8P7D9CwAVCjYDGrz3W/+GKN0kA7qGyHTC97zVxJtfefxSPlP/GtOdxac+qw==", + "requires": { + "d3-delaunay": "^5.3.0", + "d3-scale": "^3.2.3" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@react-spring/animated": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.4.5.tgz", + "integrity": "sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA==", + "requires": { + "@react-spring/shared": "~9.4.5", + "@react-spring/types": "~9.4.5" + } + }, + "@react-spring/core": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.4.5.tgz", + "integrity": "sha512-83u3FzfQmGMJFwZLAJSwF24/ZJctwUkWtyPD7KYtNagrFeQKUH1I05ZuhmCmqW+2w1KDW1SFWQ43RawqfXKiiQ==", + "requires": { + "@react-spring/animated": "~9.4.5", + "@react-spring/rafz": "~9.4.5", + "@react-spring/shared": "~9.4.5", + "@react-spring/types": "~9.4.5" + } + }, + "@react-spring/rafz": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.4.5.tgz", + "integrity": "sha512-swGsutMwvnoyTRxvqhfJBtGM8Ipx6ks0RkIpNX9F/U7XmyPvBMGd3GgX/mqxZUpdlsuI1zr/jiYw+GXZxAlLcQ==" + }, + "@react-spring/shared": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.4.5.tgz", + "integrity": "sha512-JhMh3nFKsqyag0KM5IIM8BQANGscTdd0mMv3BXsUiMZrcjQTskyfnv5qxEeGWbJGGar52qr5kHuBHtCjQOzniA==", + "requires": { + "@react-spring/rafz": "~9.4.5", + "@react-spring/types": "~9.4.5" + } + }, + "@react-spring/types": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.4.5.tgz", + "integrity": "sha512-mpRIamoHwql0ogxEUh9yr4TP0xU5CWyZxVQeccGkHHF8kPMErtDXJlxyo0lj+telRF35XNihtPTWoflqtyARmg==" + }, + "@react-spring/web": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.4.5.tgz", + "integrity": "sha512-NGAkOtKmOzDEctL7MzRlQGv24sRce++0xAY7KlcxmeVkR7LRSGkoXHaIfm9ObzxPMcPHQYQhf3+X9jepIFNHQA==", + "requires": { + "@react-spring/animated": "~9.4.5", + "@react-spring/core": "~9.4.5", + "@react-spring/shared": "~9.4.5", + "@react-spring/types": "~9.4.5" + } + }, + "@remix-run/router": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.1.0.tgz", + "integrity": "sha512-rGl+jH/7x1KBCQScz9p54p0dtPLNeKGb3e0wD2H5/oZj41bwQUnXdzbj2TbUAFhvD7cp9EyEQA4dEgpUFa1O7Q==" + }, + "@rollup/pluginutils": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", + "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@sinclair/typebox": { + "version": "0.25.21", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.21.tgz", + "integrity": "sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==", + "dev": true + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "requires": {} + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "requires": {} + }, + "@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + } + }, + "@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "requires": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "dependencies": { + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "optional": true, + "peer": true + } + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "requires": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + } + }, + "@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "requires": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + } + }, + "@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "requires": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "dependencies": { + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "optional": true, + "peer": true + } + } + }, + "@svgr/rollup": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/rollup/-/rollup-8.1.0.tgz", + "integrity": "sha512-0XR1poYvPQoPpmfDYLEqUGu5ePAQ4pdgN3VFsZBNAeze7qubVpsIY1o1R6PZpKep/DKu33GSm2NhwpCLkMs2Cw==", + "dev": true, + "requires": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@rollup/pluginutils": "^5.0.2", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + } + }, + "@testing-library/dom": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz", + "integrity": "sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", + "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@trivago/prettier-plugin-sort-imports": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.1.1.tgz", + "integrity": "sha512-dQ2r2uzNr1x6pJsuh/8x0IRA3CBUB+pWEW3J/7N98axqt7SQSm+2fy0FLNXvXGg77xEDC7KHxJlHfLYyi7PDcw==", + "dev": true, + "requires": { + "@babel/generator": "7.17.7", + "@babel/parser": "^7.20.5", + "@babel/traverse": "7.17.3", + "@babel/types": "7.17.0", + "javascript-natural-sort": "0.7.1", + "lodash": "^4.17.21" + }, + "dependencies": { + "@babel/generator": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/traverse": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true + }, + "@types/aria-query": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", + "dev": true + }, + "@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, + "@types/cookie": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", + "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" + }, + "@types/estree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", + "dev": true + }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.4.0.tgz", + "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "pretty-format": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", + "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + } + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/node": { + "version": "18.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", + "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==" + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "@types/react": { + "version": "18.0.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz", + "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-color": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.7.tgz", + "integrity": "sha512-IGZA7e8Oia0+Sb3/1KP0qTThGelZ9DRspfeLrFWQWv5vXHiYlJJQMC2kgQr75CtP4uL8/kvT8qBgrOVlxVoNTw==", + "requires": { + "@types/react": "*", + "@types/reactcss": "*" + } + }, + "@types/react-dom": { + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.9.tgz", + "integrity": "sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/reactcss": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.7.tgz", + "integrity": "sha512-MYPuVierMjIo0EDQnNauvBA94IOeB9lfjC619g+26u7ilsTtoFv6X7eQvaw79Fqqpi0yzoSMz0nUazeJlQUZnA==", + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "@types/testing-library__jest-dom": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", + "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "dev": true, + "requires": { + "@types/jest": "*" + } + }, + "@types/yargs": { + "version": "17.0.20", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.20.tgz", + "integrity": "sha512-eknWrTHofQuPk2iuqDm1waA7V6xPlbgBoaaXEgYkClhLOnB0TtbW+srJaOToAgawPxPlHQzwypFA2bhZaUGP5A==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz", + "integrity": "sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.46.1", + "@typescript-eslint/type-utils": "5.46.1", + "@typescript-eslint/utils": "5.46.1", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz", + "integrity": "sha512-RelQ5cGypPh4ySAtfIMBzBGyrNerQcmfA1oJvPj5f+H4jI59rl9xxpn4bonC0tQvUKOEN7eGBFWxFLK3Xepneg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.46.1", + "@typescript-eslint/types": "5.46.1", + "@typescript-eslint/typescript-estree": "5.46.1", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz", + "integrity": "sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.46.1", + "@typescript-eslint/visitor-keys": "5.46.1" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz", + "integrity": "sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.46.1", + "@typescript-eslint/utils": "5.46.1", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz", + "integrity": "sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz", + "integrity": "sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.46.1", + "@typescript-eslint/visitor-keys": "5.46.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz", + "integrity": "sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.46.1", + "@typescript-eslint/types": "5.46.1", + "@typescript-eslint/typescript-estree": "5.46.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "dependencies": { + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz", + "integrity": "sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.46.1", + "eslint-visitor-keys": "^3.3.0" + } + }, + "@vitejs/plugin-react": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.0.0.tgz", + "integrity": "sha512-1mvyPc0xYW5G8CHQvJIJXLoMjl5Ct3q2g5Y2s6Ccfgwm45y48LBvsla7az+GkkAtYikWQ4Lxqcsq5RHLcZgtNQ==", + "dev": true, + "requires": { + "@babel/core": "^7.20.5", + "@babel/plugin-transform-react-jsx-self": "^7.18.6", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "magic-string": "^0.27.0", + "react-refresh": "^0.14.0" + } + }, + "@vitest/expect": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.3.tgz", + "integrity": "sha512-dnxllhfln88DOvpAK1fuI7/xHwRgTgR4wdxHldPaoTaBu6Rh9zK5b//v/cjTkhOfNP/AJ8evbNO8H7c3biwd1g==", + "dev": true, + "requires": { + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "chai": "^4.3.7" + } + }, + "@vitest/runner": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.3.tgz", + "integrity": "sha512-P0qYbATaemy1midOLkw7qf8jraJszCoEvjQOSlseiXZyEDaZTZ50J+lolz2hWiWv6RwDu1iNseL9XLsG0Jm2KQ==", + "dev": true, + "requires": { + "@vitest/utils": "0.28.3", + "p-limit": "^4.0.0", + "pathe": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + } + } + }, + "@vitest/spy": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.3.tgz", + "integrity": "sha512-jULA6suS6CCr9VZfr7/9x97pZ0hC55prnUNHNrg5/q16ARBY38RsjsfhuUXt6QOwvIN3BhSS0QqPzyh5Di8g6w==", + "dev": true, + "requires": { + "tinyspy": "^1.0.2" + } + }, + "@vitest/utils": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.3.tgz", + "integrity": "sha512-YHiQEHQqXyIbhDqETOJUKx9/psybF7SFFVCNfOvap0FvyUqbzTSDCa3S5lL4C0CLXkwVZttz9xknDoyHMguFRQ==", + "dev": true, + "requires": { + "cli-truncate": "^3.1.0", + "diff": "^5.1.0", + "loupe": "^2.3.6", + "picocolors": "^1.0.0", + "pretty-format": "^27.5.1" + } + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true + }, + "acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "requires": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, + "axe-core": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.1.tgz", + "integrity": "sha512-lCZN5XRuOnpG4bpMq8v0khrWtUOn+i8lZSb6wHZH56ZfbIEv6XwJV84AAueh9/zi7qPVJ/E4yz6fmsiyOmXR4w==", + "dev": true + }, + "axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", + "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.32.2" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.3" + } + }, + "babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, + "caniuse-lite": { + "version": "1.0.30001551", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz", + "integrity": "sha512-vtBAez47BoGMMzlbYhfXrMV1kvRF2WP/lqiMuDu1Sb4EE4LKEgjopFDSRtZfdVnslNRpOqV/woE+Xgrwj6VQlg==", + "dev": true + }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true + }, + "ci-info": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", + "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "dev": true + }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "core-js-compat": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.1.tgz", + "integrity": "sha512-6pYKNOgD/j/bkC5xS5IIg6bncid3rfrI42oBH1SQJbsmYPKF7rhzcFzYCcxYMmNQQ0rCEB8WqpW7QHndOggaeQ==", + "dev": true, + "requires": { + "browserslist": "^4.22.1" + } + }, + "core-js-pure": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz", + "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "requires": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "requires": { + "css-tree": "~2.2.0" + }, + "dependencies": { + "css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "requires": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + } + }, + "mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + } + } + }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "requires": { + "internmap": "^1.0.0" + } + }, + "d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "d3-delaunay": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", + "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "requires": { + "delaunator": "4" + } + }, + "d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "requires": { + "d3-color": "1 - 2" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "requires": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "d3-scale-chromatic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz", + "integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==", + "requires": { + "d3-color": "1 - 2", + "d3-interpolate": "1 - 2" + } + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "requires": { + "d3-array": "2" + } + }, + "d3-time-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "requires": { + "d3-time": "1 - 2" + } + }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + } + }, + "date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delaunator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", + "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + }, + "diff-sequences": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", + "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.559", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.559.tgz", + "integrity": "sha512-iS7KhLYCSJbdo3rUSkhDTVuFNCV34RKs2UaB9Ecr7VlqzjjWW//0nfsFF5dtDmyXlZQaDYYtID5fjtC/6lpRug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", + "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "unbox-primitive": "^1.0.2" + } + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "esbuild": { + "version": "0.16.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.9.tgz", + "integrity": "sha512-gkH83yHyijMSZcZFs1IWew342eMdFuWXmQo3zkDPTre25LIPBJsXryg02M3u8OpTwCJdBkdaQwqKkDLnAsAeLQ==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.16.9", + "@esbuild/android-arm64": "0.16.9", + "@esbuild/android-x64": "0.16.9", + "@esbuild/darwin-arm64": "0.16.9", + "@esbuild/darwin-x64": "0.16.9", + "@esbuild/freebsd-arm64": "0.16.9", + "@esbuild/freebsd-x64": "0.16.9", + "@esbuild/linux-arm": "0.16.9", + "@esbuild/linux-arm64": "0.16.9", + "@esbuild/linux-ia32": "0.16.9", + "@esbuild/linux-loong64": "0.16.9", + "@esbuild/linux-mips64el": "0.16.9", + "@esbuild/linux-ppc64": "0.16.9", + "@esbuild/linux-riscv64": "0.16.9", + "@esbuild/linux-s390x": "0.16.9", + "@esbuild/linux-x64": "0.16.9", + "@esbuild/netbsd-x64": "0.16.9", + "@esbuild/openbsd-x64": "0.16.9", + "@esbuild/sunos-x64": "0.16.9", + "@esbuild/win32-arm64": "0.16.9", + "@esbuild/win32-ia32": "0.16.9", + "@esbuild/win32-x64": "0.16.9" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.4.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + } + }, + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-alias": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz", + "integrity": "sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", + "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.18.9", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.3", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.2", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + } + }, + "eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-react": { + "version": "7.31.11", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.11.tgz", + "integrity": "sha512-TTvq5JsT5v56wPa9OYHzsrOlHzKZKjV+aLgS+55NJP/cuzdiQPC7PfYoUjMoxlffKtvijpk7vA/jmuqRb9nohw==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "expect": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.4.1.tgz", + "integrity": "sha512-OKrGESHOaMxK3b6zxIq9SOW8kEXztKff/Dvg88j4xIJxur1hspEbedVkR3GpHe5LO+WB2Qw7OWN0RMTdp6as5A==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.4.1", + "jest-get-type": "^29.2.0", + "jest-matcher-utils": "^29.4.1", + "jest-message-util": "^29.4.1", + "jest-util": "^29.4.1" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "firebase": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.4.0.tgz", + "integrity": "sha512-3Z8WsNwA7kbcKGZ+nrTZ/ES518pk0K440ZJYD8nUNKN5hV6ll+unhUw30t1msedN6yIFjhsC/9OwT4Z0ohwO2w==", + "requires": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-compat": "0.2.6", + "@firebase/app": "0.9.19", + "@firebase/app-check": "0.8.0", + "@firebase/app-check-compat": "0.3.7", + "@firebase/app-compat": "0.2.19", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "1.3.0", + "@firebase/auth-compat": "0.4.6", + "@firebase/database": "1.0.1", + "@firebase/database-compat": "1.0.1", + "@firebase/firestore": "4.2.0", + "@firebase/firestore-compat": "0.3.18", + "@firebase/functions": "0.10.0", + "@firebase/functions-compat": "0.3.5", + "@firebase/installations": "0.6.4", + "@firebase/installations-compat": "0.2.4", + "@firebase/messaging": "0.12.4", + "@firebase/messaging-compat": "0.2.4", + "@firebase/performance": "0.6.4", + "@firebase/performance-compat": "0.2.4", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-compat": "0.2.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-compat": "0.3.2", + "@firebase/util": "1.9.3" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, + "ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true + }, + "jest-diff": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.4.1.tgz", + "integrity": "sha512-uazdl2g331iY56CEyfbNA0Ut7Mn2ulAG5vUaEHXycf1L6IPyuImIxSz4F0VYBKi7LYIuxOwTZzK3wh5jHzASMw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", + "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", + "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", + "dev": true + }, + "jest-matcher-utils": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.4.1.tgz", + "integrity": "sha512-k5h0u8V4nAEy6lSACepxL/rw78FLDkBnXhZVgFneVpnJONhb2DhZj/Gv4eNe+1XqQ5IhgUcqj745UwH0HJmMnA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.4.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", + "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.4.1.tgz", + "integrity": "sha512-H4/I0cXUaLeCw6FM+i4AwCnOwHRgitdaUFOdm49022YD5nfyr8C/DrbXOBEyJaj+w/y0gGJ57klssOaUiLLQGQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.4.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.4.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.1.tgz", + "integrity": "sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.4.1.tgz", + "integrity": "sha512-bQy9FPGxVutgpN4VRc0hk6w7Hx/m6L53QxpDreTZgJd9gfx/AV2MjyPde9tGyZRINAUrSv57p2inGBu2dRLmkQ==", + "dev": true, + "requires": { + "@jest/types": "^29.4.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsdom": { + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.0.tgz", + "integrity": "sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + } + }, + "language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "language-tags": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.7.tgz", + "integrity": "sha512-bSytju1/657hFjgUzPAPqszxH62ouE8nQFoFaVlIQfne4wO/wXC9A4+m8jYve7YBBvi59eq0SUpcshvG8h5Usw==", + "dev": true, + "requires": { + "language-subtag-registry": "^0.3.20" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "dev": true + }, + "magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, + "material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, + "mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "mlly": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.1.0.tgz", + "integrity": "sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==", + "dev": true, + "requires": { + "acorn": "^8.8.1", + "pathe": "^1.0.0", + "pkg-types": "^1.0.1", + "ufo": "^1.0.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pathe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", + "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pkg-types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.1.tgz", + "integrity": "sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==", + "dev": true, + "requires": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.0.0", + "pathe": "^1.0.0" + } + }, + "postcss": { + "version": "8.4.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", + "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", + "dev": true, + "peer": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + } + } + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "protobufjs": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-color": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", + "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "requires": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.15", + "lodash-es": "^4.17.15", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + } + }, + "react-cookie": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz", + "integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==", + "requires": { + "@types/hoist-non-react-statics": "^3.0.1", + "hoist-non-react-statics": "^3.0.0", + "universal-cookie": "^4.0.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "react-ga4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", + "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true + }, + "react-router": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.5.0.tgz", + "integrity": "sha512-fqqUSU0NC0tSX0sZbyuxzuAzvGqbjiZItBQnyicWlOUmzhAU8YuLgRbaCL2hf3sJdtRy4LP/WBrWtARkMvdGPQ==", + "requires": { + "@remix-run/router": "1.1.0" + } + }, + "react-router-dom": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.5.0.tgz", + "integrity": "sha512-/XzRc5fq80gW1ctiIGilyKFZC/j4kfe75uivMsTChFbkvrK4ZrF3P3cGIc1f/SSkQ4JiJozPrf+AwUHHWVehVg==", + "requires": { + "@remix-run/router": "1.1.0", + "react-router": "6.5.0" + } + }, + "reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "requires": { + "lodash": "^4.0.1" + } + }, + "recoil": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.6.tgz", + "integrity": "sha512-hsBEw7jFdpBCY/tu2GweiyaqHKxVj6EqF2/SfrglbKvJHhpN57SANWvPW+gE90i3Awi+A5gssOd3u+vWlT+g7g==", + "requires": { + "hamt_plus": "1.0.2" + } + }, + "recoil-persist": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/recoil-persist/-/recoil-persist-4.2.0.tgz", + "integrity": "sha512-MHVfML9GxJP3RpkKR4F5rp7DtvzIvjWhowtMao/b7h2k4afMio/4sMAdUtltIrDaeVegH0Iga8Sx5XQ3oD7CzA==", + "requires": {} + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.7.5.tgz", + "integrity": "sha512-z0ZbqHBtS/et2EEUKMrAl2CoSdwN7ZPzL17UMiKN9RjjqHShTlv7F9J6ZJZJNREYjBh3TvBrdfjkFDIXFNeuiQ==", + "devOptional": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "rollup-plugin-visualizer": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz", + "integrity": "sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==", + "requires": { + "open": "^8.4.0", + "picomatch": "^2.3.1", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "dependencies": { + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" + } + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + } + } + }, + "snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "std-env": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz", + "integrity": "sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==", + "dev": true + }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "requires": { + "internal-slot": "^1.0.4" + } + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "strip-literal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz", + "integrity": "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==", + "dev": true, + "requires": { + "acorn": "^8.8.1" + } + }, + "styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "svgo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", + "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "tinybench": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz", + "integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==", + "dev": true + }, + "tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, + "tinypool": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", + "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", + "dev": true + }, + "tinyspy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz", + "integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true + }, + "ufo": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz", + "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true + }, + "universal-cookie": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", + "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", + "requires": { + "@types/cookie": "^0.3.3", + "cookie": "^0.4.0" + } + }, + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "vite": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.1.tgz", + "integrity": "sha512-kZQPzbDau35iWOhy3CpkrRC7It+HIHtulAzBhMqzGHKRf/4+vmh8rPDDdv98SWQrFWo6//3ozwsRmwQIPZsK9g==", + "dev": true, + "requires": { + "esbuild": "^0.16.3", + "fsevents": "~2.3.2", + "postcss": "^8.4.20", + "resolve": "^1.22.1", + "rollup": "^3.7.0" + } + }, + "vite-node": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.3.tgz", + "integrity": "sha512-uJJAOkgVwdfCX8PUQhqLyDOpkBS5+j+FdbsXoPVPDlvVjRkb/W/mLYQPSL6J+t8R0UV8tJSe8c9VyxVQNsDSyg==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.1.0", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "source-map-support": "^0.5.21", + "vite": "^3.0.0 || ^4.0.0" + } + }, + "vite-plugin-svgr": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.1.0.tgz", + "integrity": "sha512-v7Qic+FWmCChgQNGSI4V8X63OEYsdUoLt66iqIcHozq9bfK/Dwmr0V+LBy1NE8CE98Y8HouEBJ+pto4AMfN5xw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.4", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + } + }, + "vitest": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.3.tgz", + "integrity": "sha512-N41VPNf3VGJlWQizGvl1P5MGyv3ZZA2Zvh+2V8L6tYBAAuqqDK4zExunT1Cdb6dGfZ4gr+IMrnG8d4Z6j9ctPw==", + "dev": true, + "requires": { + "@types/chai": "^4.3.4", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.28.3", + "@vitest/runner": "0.28.3", + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "acorn": "^8.8.1", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "std-env": "^3.3.1", + "strip-literal": "^1.0.0", + "tinybench": "^2.3.1", + "tinypool": "^0.3.1", + "tinyspy": "^1.0.2", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.28.3", + "why-is-node-running": "^2.2.2" + } + }, + "w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "ws": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "dev": true, + "requires": {} + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1cfb55dfd..f8a66b315 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,11 +29,9 @@ const SearchPage = lazy(() => import("@/Cabinet/pages/admin/SearchPage")); const AdminClubPage = lazy(() => import("@/Cabinet/pages/admin/AdminClubPage")); const AdminLoginFailurePage = lazy( () => import("@/Cabinet/pages/admin/AdminLoginFailurePage") + ); const AdminHomePage = lazy(() => import("@/Cabinet/pages/admin/AdminHomePage")); -const AdminSlackNotiPage = lazy( - () => import("@/Cabinet/pages/admin/AdminSlackNotiPage") -); function App(): React.ReactElement { return ( diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx index 90dfce091..c0f00fee6 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -118,7 +118,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { navigator("slack-notification"); closeAll(); }; - + const onClickMainClubButton = () => { navigator("clubs"); closeAll(); @@ -152,7 +152,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { resetCurrentSection(); navigator("/login"); }; - + return ( Date: Tue, 16 Apr 2024 17:22:02 +0900 Subject: [PATCH 0581/1029] =?UTF-8?q?[FE]=20FEAT:=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=EC=B9=A8=ED=95=B4=EB=8F=84=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=EC=B9=A8=20=EC=A0=84=20=ED=85=8C=EB=A7=88=20=EC=9C=A0?= =?UTF-8?q?=EC=A7=80#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/TopNav/DarkMode/DarkMode.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx index b8bc60f75..53ab62cab 100644 --- a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx +++ b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled, { createGlobalStyle, css } from "styled-components"; import { darkModeState } from "@/recoil/atoms"; @@ -105,7 +105,12 @@ export const GlobalStyle = createGlobalStyle` `; const DarkMode = () => { - const [darkMode, setDarkMode] = useRecoilState(darkModeState); + const savedTheme = localStorage.getItem("color-theme"); + var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + // const [darkMode, setDarkMode] = useRecoilState(darkModeState); + const [darkMode, setDarkMode] = useState( + savedTheme ? savedTheme : darkModeQuery.matches ? "dark" : "light" + ); const onClickHandler = () => { setDarkMode((prev) => { return prev === "light" ? "dark" : "light"; @@ -115,13 +120,14 @@ const DarkMode = () => { var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); useEffect(() => { - setDarkMode(() => { - return darkModeQuery.matches ? "dark" : "light"; - }); + darkModeQuery.addEventListener("change", (event) => + setDarkMode(event.matches ? "dark" : "light") + ); }, []); useEffect(() => { document.body.setAttribute("color-theme", darkMode); + localStorage.setItem("color-theme", darkMode); }, [darkMode]); return ( From 6fc6af84b675a8d11df1917e44f7bdae28f69bf0 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 16 Apr 2024 17:45:49 +0900 Subject: [PATCH 0582/1029] dev empty commit From 63519c6e0b3845eb1e82cc56788a3942cf20ff0b Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 16 Apr 2024 17:58:46 +0900 Subject: [PATCH 0583/1029] =?UTF-8?q?[FE]=20REFACT:=20ColorTheme.ts?= =?UTF-8?q?=EC=97=90=20=ED=85=8C=EB=A7=88=20=EB=B3=84=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=20=EB=B0=8F=20GlobalStyle=20=EC=A0=80=EC=9E=A5#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/ColorTheme.ts | 102 ++++++++++++++++ .../components/TopNav/DarkMode/DarkMode.tsx | 112 +----------------- frontend/src/main.tsx | 2 +- 3 files changed, 109 insertions(+), 107 deletions(-) create mode 100644 frontend/src/assets/data/ColorTheme.ts diff --git a/frontend/src/assets/data/ColorTheme.ts b/frontend/src/assets/data/ColorTheme.ts new file mode 100644 index 000000000..a5aa8f2f8 --- /dev/null +++ b/frontend/src/assets/data/ColorTheme.ts @@ -0,0 +1,102 @@ +import { createGlobalStyle, css } from "styled-components"; + +const lightValues = css` + --bg-color: var(--white); + --line-color: var(--shared-gray-color-400); + --normal-text-color: var(--black); + --text-with-bg-color: var(--white); + --card-content-bg-color: var(--white); + --button-line-color: var(--default-main-color); + + /* main color variable */ + --main-color: var(--purple-500); + --sub-color: var(--purple-300); + + --default-main-color: var(--purple-500); + --default-sub-color: var(--purple-300); + --default-mine-color: var(--green-100); + + /* cabinet color variable */ + --mine-color: var(--green-100); + --available-color: var(--main-color); + --pending-color: var(--main-color); + --full-color: var(--gray-200); + --expired-color: var(--red-100); + --banned-color: var(--gray-600); + + --bg-shadow-color-100: var(--black-shadow-100); + --bg-shadow-color-200: var(--black-shadow-200); + --bg-shadow-color-300: var(--black-shadow-300); + --bg-shadow-color-400: var(--black-shadow-400); + --border-shadow-color-100: var(--black-shadow-100); + --border-shadow-color-200: var(--black-shadow-200); + --border-shadow-color-300: var(--black-shadow-300); + + --shared-gray-color-100: var(--gray-100); + --shared-gray-color-200: var(--gray-200); + --shared-gray-color-300: var(--gray-300); + --shared-gray-color-400: var(--gray-400); + --shared-gray-color-500: var(--gray-500); + --shared-gray-color-600: var(--gray-600); + --shared-gray-color-700: var(--gray-800); + + --shared-purple-color-100: var(--purple-100); + + color: var(--normal-text-color); + background-color: var(--bg-color); +`; + +// set up dark theme CSS variables +const darkValues = css` + --bg-color: var(--gray-900); + --line-color: var(--shared-gray-color-300); + --normal-text-color: var(--gray-100); + --text-with-bg-color: var(--gray-100); + --card-content-bg-color: var(--gray-700); + --button-line-color: var(--default-sub-color); + + --main-color: var(--purple-600); + --sub-color: var(--purple-300); + + --default-main-color: var(--purple-600); + --default-sub-color: var(--purple-300); + --default-mine-color: var(--green-200); + + --mine-color: var(--green-200); + --available-color: var(--main-color); + --pending-color: var(--main-color); + --full-color: var(--gray-200); + --expired-color: var(--red-200); + --banned-color: var(--gray-600); + + --bg-shadow-color-100: var(--black-shadow-200); + --bg-shadow-color-200: var(--black-shadow-300); + --bg-shadow-color-300: var(--black-shadow-400); + --bg-shadow-color-400: var(--black-shadow-400); + --border-shadow-color-100: var(--black-shadow-200); + --border-shadow-color-200: var(--black-shadow-300); + --border-shadow-color-300: var(--black-shadow-400); + + --shared-gray-color-100: var(--gray-800); + --shared-gray-color-200: var(--gray-700); + --shared-gray-color-300: var(--gray-600); + --shared-gray-color-400: var(--gray-500); + --shared-gray-color-500: var(--gray-400); + --shared-gray-color-600: var(--gray-300); + --shared-gray-color-700: var(--gray-200); + + --shared-purple-color-100: var(--purple-700); + + color: var(--normal-text-color); + background-color: var(--bg-color); +`; + +export const GlobalStyle = createGlobalStyle` + :root { + ${lightValues} + + [color-theme="dark"] { + ${darkValues} + } + } +`; diff --git a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx index 53ab62cab..1fb083643 100644 --- a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx +++ b/frontend/src/components/TopNav/DarkMode/DarkMode.tsx @@ -1,124 +1,24 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; -import styled, { createGlobalStyle, css } from "styled-components"; +import styled from "styled-components"; import { darkModeState } from "@/recoil/atoms"; -const lightValues = css` - --bg-color: var(--white); - --line-color: var(--shared-gray-color-400); - --normal-text-color: var(--black); - --text-with-bg-color: var(--white); - --card-content-bg-color: var(--white); - --button-line-color: var(--default-main-color); - - /* main color variable */ - --main-color: var(--purple-500); - --sub-color: var(--purple-300); - - --default-main-color: var(--purple-500); - --default-sub-color: var(--purple-300); - --default-mine-color: var(--green-100); - - /* cabinet color variable */ - --mine-color: var(--green-100); - --available-color: var(--main-color); - --pending-color: var(--main-color); - --full-color: var(--gray-200); - --expired-color: var(--red-100); - --banned-color: var(--gray-600); - - --bg-shadow-color-100: var(--black-shadow-100); - --bg-shadow-color-200: var(--black-shadow-200); - --bg-shadow-color-300: var(--black-shadow-300); - --bg-shadow-color-400: var(--black-shadow-400); - --border-shadow-color-100: var(--black-shadow-100); - --border-shadow-color-200: var(--black-shadow-200); - --border-shadow-color-300: var(--black-shadow-300); - - --shared-gray-color-100: var(--gray-100); - --shared-gray-color-200: var(--gray-200); - --shared-gray-color-300: var(--gray-300); - --shared-gray-color-400: var(--gray-400); - --shared-gray-color-500: var(--gray-500); - --shared-gray-color-600: var(--gray-600); - --shared-gray-color-700: var(--gray-800); - - --shared-purple-color-100: var(--purple-100); - - color: var(--normal-text-color); - background-color: var(--bg-color); -`; - -// set up dark theme CSS variables -const darkValues = css` - --bg-color: var(--gray-900); - --line-color: var(--shared-gray-color-300); - --normal-text-color: var(--gray-100); - --text-with-bg-color: var(--gray-100); - --card-content-bg-color: var(--gray-700); - --button-line-color: var(--default-sub-color); - - --main-color: var(--purple-600); - --sub-color: var(--purple-300); - - --default-main-color: var(--purple-600); - --default-sub-color: var(--purple-300); - --default-mine-color: var(--green-200); - - --mine-color: var(--green-200); - --available-color: var(--main-color); - --pending-color: var(--main-color); - --full-color: var(--gray-200); - --expired-color: var(--red-200); - --banned-color: var(--gray-600); - - --bg-shadow-color-100: var(--black-shadow-200); - --bg-shadow-color-200: var(--black-shadow-300); - --bg-shadow-color-300: var(--black-shadow-400); - --bg-shadow-color-400: var(--black-shadow-400); - --border-shadow-color-100: var(--black-shadow-200); - --border-shadow-color-200: var(--black-shadow-300); - --border-shadow-color-300: var(--black-shadow-400); - - --shared-gray-color-100: var(--gray-800); - --shared-gray-color-200: var(--gray-700); - --shared-gray-color-300: var(--gray-600); - --shared-gray-color-400: var(--gray-500); - --shared-gray-color-500: var(--gray-400); - --shared-gray-color-600: var(--gray-300); - --shared-gray-color-700: var(--gray-200); - - --shared-purple-color-100: var(--purple-700); - - color: var(--normal-text-color); - background-color: var(--bg-color); -`; - -export const GlobalStyle = createGlobalStyle` - :root { - ${lightValues} - - [color-theme="dark"] { - ${darkValues} - } - } -`; +// TODO : DarkMode 파일 폴더명 ColorTheme으로 변경 const DarkMode = () => { - const savedTheme = localStorage.getItem("color-theme"); + const savedColorTheme = localStorage.getItem("color-theme"); var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); // const [darkMode, setDarkMode] = useRecoilState(darkModeState); const [darkMode, setDarkMode] = useState( - savedTheme ? savedTheme : darkModeQuery.matches ? "dark" : "light" + savedColorTheme ? savedColorTheme : darkModeQuery.matches ? "dark" : "light" ); + const onClickHandler = () => { setDarkMode((prev) => { return prev === "light" ? "dark" : "light"; }); }; - var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); - useEffect(() => { darkModeQuery.addEventListener("change", (event) => setDarkMode(event.matches ? "dark" : "light") @@ -138,7 +38,7 @@ const DarkMode = () => { }; const ButtonStyled = styled.button` - background-color: var(--test); + background-color: var(--normal-text-color); `; export default DarkMode; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index b45567932..c07540934 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,7 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { RecoilRoot } from "recoil"; -import { GlobalStyle } from "@/components/TopNav/DarkMode/DarkMode"; +import { GlobalStyle } from "@/assets/data/ColorTheme"; import App from "./App"; import "./assets/css/media.css"; import "./assets/css/reset.css"; From d241b7389bd167d32a4a8e10d9ecc28cbdb3e120 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 16 Apr 2024 18:00:46 +0900 Subject: [PATCH 0584/1029] =?UTF-8?q?[FE]=20REFACT:=20DarkMode=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=8F=99#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{TopNav => Card/ThemeColorCard}/DarkMode/DarkMode.tsx | 0 frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx | 2 +- frontend/src/components/TopNav/TopNav.tsx | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename frontend/src/components/{TopNav => Card/ThemeColorCard}/DarkMode/DarkMode.tsx (100%) diff --git a/frontend/src/components/TopNav/DarkMode/DarkMode.tsx b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx similarity index 100% rename from frontend/src/components/TopNav/DarkMode/DarkMode.tsx rename to frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx index 22f344c14..78d73bb61 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -6,11 +6,11 @@ import { ContentInfoStyled, } from "@/components/Card/CardStyles"; import ColorPicker from "@/components/Card/ThemeColorCard/ColorPicker"; +import DarkMode from "@/components/Card/ThemeColorCard/DarkMode/DarkMode"; import { customColors, themeColorData, } from "@/components/Card/ThemeColorCard/colorInfo"; -import DarkMode from "@/components/TopNav/DarkMode/DarkMode"; interface ThemeColorProps { showColorPicker: boolean; diff --git a/frontend/src/components/TopNav/TopNav.tsx b/frontend/src/components/TopNav/TopNav.tsx index e3940d559..686ae9f79 100644 --- a/frontend/src/components/TopNav/TopNav.tsx +++ b/frontend/src/components/TopNav/TopNav.tsx @@ -1,7 +1,7 @@ import React from "react"; import { SetterOrUpdater } from "recoil"; import styled from "styled-components"; -import DarkMode from "@/components/TopNav/DarkMode/DarkMode"; +import DarkMode from "@/components/Card/ThemeColorCard/DarkMode/DarkMode"; import SearchBar from "@/components/TopNav/SearchBar/SearchBar"; import TopNavButtonGroup from "@/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; From 814239c0b4501dac4e1c70585b9c37bb982c0a6e Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 16 Apr 2024 18:01:38 +0900 Subject: [PATCH 0585/1029] =?UTF-8?q?[FE]=20=EC=98=A4=ED=83=80=20=EB=B0=8F?= =?UTF-8?q?=20=EB=9D=84=EC=96=B4=EC=93=B0=EA=B8=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Cabinet/assets/data/SlackAlarm.ts | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/frontend/src/Cabinet/assets/data/SlackAlarm.ts b/frontend/src/Cabinet/assets/data/SlackAlarm.ts index b3e586e40..123470f87 100644 --- a/frontend/src/Cabinet/assets/data/SlackAlarm.ts +++ b/frontend/src/Cabinet/assets/data/SlackAlarm.ts @@ -38,9 +38,9 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ content: `:dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby::dancing_kirby: 안녕하세요. Cabi 팀입니다! :happy_ccabi: 현시간부로 서비스 이용이 정상화되었습니다. - :portal_blue_parrot: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :portal_orange_parrot: - :파일_수납장: 서비스 개선과 관련한 사항은 Cabi 채널 문의주세요! :파일_수납장: - :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:기다려주셔서 감사합니다! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, + :portal_blue_parrot: 서비스는 cabi.42seoul.io 를 이용해주시면 됩니다. :portal_orange_parrot: + :file_cabinet: 서비스 개선과 관련한 사항은 Cabi 채널 문의주세요! :file_cabinet: + :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:기다려주셔서 감사합니다! :party-dinosaur::party-dinosaur::party-dinosaur::party-dinosaur:`, }, { title: "업데이트", @@ -53,23 +53,23 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ }, { title: "이용 안내서", - content: `:파일_수납장: Cabi 이용 안내서 :파일_수납장: + content: `:file_cabinet: Cabi 이용 안내서 :file_cabinet: :embarrassed_cabi: 42seoul의 사물함 대여 서비스를 운영중인 Cabi 팀입니다.:embarrassed_cabi: 자세한 이용 방법은 Cabi 가입 후 홈페이지의 이용 안내서를 참고해 주세요! - :오른쪽을_가리키는_손_모양: https://cabi.42seoul.io/home + :point_right: https://cabi.42seoul.io/home :alert: Cabi FAQ :alert: - :압정: 사물함의 물리적인 문제가 있습니다 (고장 났거나 잠겨있는 경우) + :pushpin: 사물함의 물리적인 문제가 있습니다 (고장 났거나 잠겨있는 경우) :happy_ccabi: 사물함의 물리적인 문제는 데스크에 문의 부탁드립니다! - :압정: 사물함 비밀번호를 모릅니다 (잊어버렸습니다). - :happy_ccabi: 저희 서비스에서 대여한 화면과 슬랙 화면을 준비해서 데스크에 문의해주시기 바랍니다! - :압정: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. - :happy_ccabi: 사물함 안이 꽉 차거나 제대로 닫히지 않은 경우에 발생하는데, 문을 누른 상태로 비밀번호를 입력해 보시고, 그래도 되지 않는다면 데스크에 문의 부탁드립니다! - :압정: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. - :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! - :압정: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? - :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! - :압정: 사물함을 연체 했는데 패널티는 무엇인가요? - :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:울음을_참는_얼굴:`, + :pushpin: 사물함 비밀번호를 모릅니다 (잊어버렸습니다). + :happy_ccabi: 저희 서비스에서 대여한 화면과 슬랙 화면을 준비해서 데스크에 문의해주시기 바랍니다! + :pushpin: 사물함을 닫으려는데 빨간 열쇠 표시가 뜨면서 경고음이 나고 잠기지 않습니다. + :happy_ccabi: 사물함 안이 꽉 차거나 제대로 닫히지 않은 경우에 발생하는데, 문을 누른 상태로 비밀번호를 입력해 보시고, 그래도 되지 않는다면 데스크에 문의 부탁드립니다! + :pushpin: 사물함 대여 후 사용하려고 했더니 안에 짐이 가득 차 있습니다. + :happy_ccabi: 이전 사용자의 짐과 관련한 문의는 데스크에 문의 부탁드립니다! + :pushpin: 공유 사물함을 대여했는데 비밀번호는 어디서 알 수 있을까요? + :happy_ccabi: 같이 사용하는 사람이 있다면 대여 내역에서 공유 메모에 적혀 있을 수 있습니다. 또는 함께 사용하는 분에게 여쭤보세요! + :pushpin: 사물함을 연체 했는데 패널티는 무엇인가요? + :happy_ccabi: 연체일만큼 누적 연체일이 증가하고, 누적일 만큼 대여가 불가능합니다:face_holding_back_tears:`, }, { title: "모집 공고", @@ -88,9 +88,8 @@ export const SlackAlarmTemplates: ISlackAlarmTemplate[] = [ :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: :four_leaf_clover::arrow_right:지금 바로 지원하기:arrow_left::four_leaf_clover: :four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover::four_leaf_clover: - :절하는_남성: 상세한 정보는 구글 폼을 참고해주시고, 이외에 모집과 관련한 문의는 - @sanan - 에게 DM 부탁드립니다! :man-bowing:`, + :man-bowing: 상세한 정보는 구글 폼을 참고해주시고, 이외에 모집과 관련한 문의는 + @jpark2 에게 DM 부탁드립니다! :man-bowing:`, }, { title: "동아리 사물함", From c09884912b034129f02dc68be8b7879c3516baef Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 16 Apr 2024 19:15:49 +0900 Subject: [PATCH 0586/1029] =?UTF-8?q?[FE]=20FEAT:=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=B9=B4=EB=93=9C=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=99=84=EC=84=B1#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/ThemeColorCard/DarkMode/DarkMode.tsx | 26 ++++++++++++++++++- .../Card/ThemeColorCard/ThemeColorCard.tsx | 10 ++++--- .../components/Common/MultiToggleSwitch.tsx | 1 - .../Common/MultiToggleSwitchSeparated.tsx | 23 ++++++++++++---- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx index 1fb083643..61b1d7ade 100644 --- a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx +++ b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx @@ -2,6 +2,8 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { darkModeState } from "@/recoil/atoms"; +import MultiToggleSwitch from "@/components/Common/MultiToggleSwitch"; +import MultiToggleSwitchSeparated from "@/components/Common/MultiToggleSwitchSeparated"; // TODO : DarkMode 파일 폴더명 ColorTheme으로 변경 @@ -29,14 +31,36 @@ const DarkMode = () => { document.body.setAttribute("color-theme", darkMode); localStorage.setItem("color-theme", darkMode); }, [darkMode]); + const [toggleType, setToggleType] = useState("ALL"); + // "ALL" + // setToggleType + // [{name: '전체', key: 'ALL'}, {name: '전체', key: 'ALL'}] return ( <> - mode change + + + ); }; +const ButtonsWrapperStyled = styled.div` + display: flex; + justify-content: center; + padding: 0 16px; +`; + const ButtonStyled = styled.button` background-color: var(--normal-text-color); `; diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx index 78d73bb61..57344548b 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx @@ -1,4 +1,4 @@ -import styled, { css } from "styled-components"; +import styled from "styled-components"; import Card from "@/components/Card/Card"; import { CardContentStyled, @@ -41,12 +41,11 @@ const ThemeColorCard = ({ <> {showColorPicker && } - <> + + + {themeColorData.map(({ title, type, getColor }) => ( diff --git a/frontend/src/components/Common/MultiToggleSwitch.tsx b/frontend/src/components/Common/MultiToggleSwitch.tsx index 20dfd0f04..9201368e7 100644 --- a/frontend/src/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/components/Common/MultiToggleSwitch.tsx @@ -18,7 +18,6 @@ const MultiToggleSwitch = ({ toggleList, }: MultiToggleSwitchProps) => { const wrapperRef = useRef(null); - useEffect(() => { const buttons = wrapperRef.current?.querySelectorAll("button"); diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx index 08e612881..868e724b6 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx @@ -10,12 +10,16 @@ interface MultiToggleSwitchProps { initialState: T; setState: React.Dispatch>; toggleList: toggleItem[]; + buttonHeight?: string; + buttonWidth?: string; } const MultiToggleSwitchSeparated = ({ initialState, setState, toggleList, + buttonHeight, + buttonWidth, }: MultiToggleSwitchProps) => { const wrapperRef = useRef(null); @@ -50,7 +54,12 @@ const MultiToggleSwitchSeparated = ({ } return ( - + {toggleList.map((item) => ( ))} @@ -87,7 +89,7 @@ const WrapperStyled = styled.div<{ props.buttonWidth ? props.buttonWidth : "fit-content"}; min-width: 50px; border-radius: 10px; - font-size: 0.9rem; + font-size: 1rem; height: ${(props) => (props.buttonHeight ? props.buttonHeight : "30px")}; font-weight: 500; background-color: var(--shared-gray-color-100); From cd0aadd32a8b9964972c7a8576b45510878fe4c7 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Tue, 16 Apr 2024 23:26:48 +0900 Subject: [PATCH 0588/1029] =?UTF-8?q?[BE]=20FEAT:=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EB=94=94=EC=8A=A4=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20#1593?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/discord/DiscordWebAlarmMessage.java | 104 ++++++++++++++++++ .../discord/DiscordWebHookMessenger.java | 13 +++ 2 files changed, 117 insertions(+) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebAlarmMessage.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebAlarmMessage.java b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebAlarmMessage.java new file mode 100644 index 000000000..624c2d9cd --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebAlarmMessage.java @@ -0,0 +1,104 @@ +package org.ftclub.cabinet.alarm.discord; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import lombok.Builder; +import lombok.Getter; +import org.springframework.web.util.ContentCachingRequestWrapper; + +@Builder +@Getter +public class DiscordWebAlarmMessage { + + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern( + "yyyy-MM-dd HH:mm:ss"); + private final String subject; + private final String requestURI; + private final String httpMethod; + private final String headers; + private final String body; + private final String parameters; + private final String responseBody; + + public static DiscordWebAlarmMessage fromHttpServletRequest(HttpServletRequest request, + String subject, + String responseBody) { + String requestBody = ""; + if (request instanceof ContentCachingRequestWrapper) { + byte[] buf = ((ContentCachingRequestWrapper) request).getContentAsByteArray(); + requestBody = new String(buf, 0, buf.length, StandardCharsets.UTF_8).trim(); + } + + String headers = Collections.list(request.getHeaderNames()) + .stream() + .collect(Collectors.toMap(h -> h, request::getHeader)) + .entrySet() + .stream() + .map(entry -> "\"" + entry.getKey() + "\":\"" + entry.getValue() + "\"") + .collect(Collectors.joining(", ", "{", "}")); + + String params = request.getParameterMap().entrySet() + .stream() + .map(entry -> "\"" + entry.getKey() + "\":\"" + Arrays.toString(entry.getValue()) + + "\"") + .collect(Collectors.joining(", ", "{", "}")); + + return DiscordWebAlarmMessage.builder() + .subject(subject) + .requestURI(request.getRequestURI()) + .httpMethod(request.getMethod()) + .headers(headers) + .body(requestBody) + .parameters(params) + .responseBody(responseBody) + .build(); + } + + public static DiscordWebAlarmMessage fromWebRequest(WebRequest request, String subject, + String responseBody) { + if (request instanceof ServletWebRequest) { + HttpServletRequest servletRequest = ((ServletWebRequest) request).getRequest(); + return fromHttpServletRequest(servletRequest, subject, responseBody); + } else { + String params = request.getParameterMap().entrySet().stream() + .map(entry -> entry.getKey() + "=" + String.join(", ", entry.getValue())) + .reduce((p1, p2) -> p1 + "; " + p2) + .orElse("No parameters"); + + String method = request.getHeader("X-HTTP-Method-Override"); // 클라이언트가 메서드를 오버라이드 했을 경우 + if (method == null) { + method = request.getHeader("Method"); + } + + return DiscordWebAlarmMessage.builder() + .subject(subject) + .requestURI(request.getContextPath()) + .httpMethod(method) + .parameters(params) + .responseBody(responseBody) + .build(); + } + } + + @Override + public String toString() { + return "```java\n" + + "Subject: \"" + subject + "\"\n" + + "Issued at: \"" + LocalDateTime.now().format(formatter) + "\"\n" + + "Request URI: \"" + requestURI + "\"\n" + + "HTTP Method: \"" + httpMethod + "\"\n" + + "Request Headers: \"" + headers + "\"\n" + + "Request Body: \"" + body + "\"\n" + + "Request Parameters: \"" + parameters + "\"\n" + + "Response Body: \"" + responseBody + "\"\n" + + "```"; + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java index 575a9b358..29f50db60 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java @@ -9,6 +9,7 @@ @Component public class DiscordWebHookMessenger { + private final static String DISCORD_WEBHOOK_MESSAGE_KEY = "content"; private final String discordWebHookUrl; @@ -27,4 +28,16 @@ public void sendMessage(DiscordAlarmMessage message) { .bodyToMono(String.class) .block(); } + + public void sendMessage(DiscordWebAlarmMessage message) { + Map body = new HashMap<>(); + body.put(DISCORD_WEBHOOK_MESSAGE_KEY, message.toString()); + + WebClient.create().post() + .uri(discordWebHookUrl) + .bodyValue(body) + .retrieve() + .bodyToMono(String.class) + .block(); + } } From 796cf14dc07bffca190cc6a12561a04c1127cf8e Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Tue, 16 Apr 2024 23:28:18 +0900 Subject: [PATCH 0589/1029] =?UTF-8?q?[BE]=20FEAT:=20ExceptionController?= =?UTF-8?q?=EC=97=90=20UncheckedException,=20SpringMVCException=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=B0=8F=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#1593?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/filter/CachingRequestBodyFilter.java | 22 +++++ .../auth/service/AdminOauthService.java | 17 ++-- .../exception/ExceptionController.java | 91 ++++++++++++++++++- 3 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/auth/filter/CachingRequestBodyFilter.java diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/filter/CachingRequestBodyFilter.java b/backend/src/main/java/org/ftclub/cabinet/auth/filter/CachingRequestBodyFilter.java new file mode 100644 index 000000000..a7d9bf941 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/auth/filter/CachingRequestBodyFilter.java @@ -0,0 +1,22 @@ +package org.ftclub.cabinet.auth.filter; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import javax.servlet.http.HttpServletRequest; +import org.springframework.web.util.ContentCachingRequestWrapper; + +@Component +public class CachingRequestBodyFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + HttpServletRequest wrappedRequest = new ContentCachingRequestWrapper(request); + filterChain.doFilter(wrappedRequest, response); + } +} diff --git a/backend/src/main/java/org/ftclub/cabinet/auth/service/AdminOauthService.java b/backend/src/main/java/org/ftclub/cabinet/auth/service/AdminOauthService.java index 96ffc1c49..645accdaf 100644 --- a/backend/src/main/java/org/ftclub/cabinet/auth/service/AdminOauthService.java +++ b/backend/src/main/java/org/ftclub/cabinet/auth/service/AdminOauthService.java @@ -23,6 +23,7 @@ @Log4j2 @RequiredArgsConstructor public class AdminOauthService { + @Qualifier(OauthConfig.GOOGLE_OAUTH_20_SERVICE) private final OAuth20Service googleOAuth20Service; private final ObjectMapper objectMapper; @@ -46,21 +47,24 @@ public void requestLogin(HttpServletResponse res) throws IOException { * @throws ExecutionException 비동기 처리시 스레드에서 발생한 오류 처리 예외 * @throws InterruptedException 비동기 처리시 스레드 종료를 위한 예외 */ - public GoogleProfile getProfileByCode(String code) throws IOException, ExecutionException, InterruptedException { + public GoogleProfile getProfileByCode(String code) + throws IOException, ExecutionException, InterruptedException { OAuth2AccessToken accessToken = googleOAuth20Service.getAccessToken(code); - OAuthRequest oAuthRequest = new OAuthRequest(Verb.GET, "https://www.googleapis.com/oauth2/v2/userinfo"); + OAuthRequest oAuthRequest = new OAuthRequest(Verb.GET, + "https://www.googleapis.com/oauth2/v2/userinfo"); googleOAuth20Service.signRequest(accessToken, oAuthRequest); try { Response response = googleOAuth20Service.execute(oAuthRequest); return convertJsonStringToProfile(response.getBody()); } catch (Exception e) { - if (e instanceof IOException) + if (e instanceof IOException) { log.error("42 API 서버에서 프로필 정보를 가져오는데 실패했습니다." + "code: {}, message: {}", code, e.getMessage()); - if (e instanceof ExecutionException || e instanceof InterruptedException) + } + if (e instanceof ExecutionException || e instanceof InterruptedException) { log.error("42 API 서버에서 프로필 정보를 비동기적으로 가져오는데 실패했습니다." + "code: {}, message: {}", code, e.getMessage()); - e.printStackTrace(); + } throw ExceptionStatus.INTERNAL_SERVER_ERROR.asServiceException(); } } @@ -71,7 +75,8 @@ public GoogleProfile getProfileByCode(String code) throws IOException, Execution * @param jsonString * @return * @throws IOException - * @see 참고 + * @see 참고 */ private GoogleProfile convertJsonStringToProfile(String jsonString) throws IOException { JsonNode jsonNode = objectMapper.readTree(jsonString); diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java index 14f146483..07e520e16 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java @@ -1,18 +1,37 @@ package org.ftclub.cabinet.exception; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.discord.DiscordWebAlarmMessage; +import org.ftclub.cabinet.alarm.discord.DiscordWebHookMessenger; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @Log4j2 @RestControllerAdvice -public class ExceptionController { +@RequiredArgsConstructor +public class ExceptionController extends ResponseEntityExceptionHandler { + + private final DiscordWebHookMessenger discordWebHookMessenger; + private static final String DEFAULT_ERROR_MESSAGE_VALUE = "까비 서버에서 예기치 않은 오류가 발생했어요.🥲"; + private static final String SPRING_MVC_ERROR_MESSAGE_VALUE = "Spring MVC 에서 예기치 않은 오류가 발생했어요.🥲"; @ExceptionHandler(ControllerException.class) public ResponseEntity controllerExceptionHandler(ControllerException e) { - log.info("[ControllerException] {} : {}", e.status.getError(), e.status.getMessage()); - e.printStackTrace(); + StackTraceElement stackTraceElement = e.getStackTrace()[0]; + log.info("[ControllerException] {} : {} at {}", + e.status.getError(), e.status.getMessage(), + stackTraceElement); + if (log.isDebugEnabled()) { + log.debug(e); + } return ResponseEntity .status(e.status.getStatusCode()) .body(e.status); @@ -21,7 +40,9 @@ public ResponseEntity controllerExceptionHandler(ControllerException e) { @ExceptionHandler(ServiceException.class) public ResponseEntity serviceExceptionHandler(ServiceException e) { log.info("[ServiceException] {} : {}", e.status.getError(), e.status.getMessage()); - e.printStackTrace(); + if (log.isDebugEnabled()) { + log.debug(e); + } return ResponseEntity .status(e.status.getStatusCode()) .body(e.status); @@ -30,6 +51,9 @@ public ResponseEntity serviceExceptionHandler(ServiceException e) { @ExceptionHandler(CustomServiceException.class) public ResponseEntity customServiceExceptionHandler(CustomServiceException e) { log.info("[CustomServiceException] {} : {}", e.status.getError(), e.status.getMessage()); + if (log.isDebugEnabled()) { + log.debug(e); + } return ResponseEntity .status(e.status.getStatusCode()) .body(e.status); @@ -38,6 +62,9 @@ public ResponseEntity customServiceExceptionHandler(CustomServiceException e) @ExceptionHandler(DomainException.class) public ResponseEntity domainExceptionHandler(DomainException e) { log.warn("[DomainException] {} : {}", e.status.getError(), e.status.getMessage()); + if (log.isDebugEnabled()) { + log.debug(e); + } return ResponseEntity .status(e.status.getStatusCode()) .body(e.status); @@ -46,9 +73,65 @@ public ResponseEntity domainExceptionHandler(DomainException e) { @ExceptionHandler(UtilException.class) public ResponseEntity utilExceptionHandler(UtilException e) { log.warn("[UtilException] {} : {}", e.status.getError(), e.status.getMessage()); + if (log.isDebugEnabled()) { + log.debug(e); + } return ResponseEntity .status(e.status.getStatusCode()) .body(e.status); } + @NotNull + @Override + protected ResponseEntity handleExceptionInternal( + @NotNull Exception e, Object body, + @NotNull org.springframework.http.HttpHeaders headers, + @NotNull org.springframework.http.HttpStatus status, + @NotNull org.springframework.web.context.request.WebRequest request) { + Map responseBody = new LinkedHashMap<>(); + String requestUri = request.getContextPath(); // requestURI 정보 설정 + responseBody.put("statusCode", status.value()); + responseBody.put("message", e.getMessage()); + responseBody.put("error", status.getReasonPhrase()); + + if (status.is5xxServerError()) { + responseBody.put("message", DEFAULT_ERROR_MESSAGE_VALUE); + log.error("[SpringMVCException] {} : {} at {}", + status.getReasonPhrase(), e.getMessage(), requestUri); + log.error("Exception stack trace: ", e); + discordWebHookMessenger.sendMessage( + DiscordWebAlarmMessage.fromWebRequest( + request, + SPRING_MVC_ERROR_MESSAGE_VALUE, + responseBody.toString() + ) + ); + } else { + log.warn("[SpringMVCException] {} : {} at {}", + status.getReasonPhrase(), e.getMessage(), requestUri); + } + return ResponseEntity.status(status).headers(headers).body(responseBody); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleInternalServerErrorException(Exception e, + HttpServletRequest request) { + log.error("[UncheckedException] {} for request URL: {}", e.getMessage(), + request.getRequestURL()); + log.error("Exception stack trace: ", e); + + Map responseBody = new LinkedHashMap<>(); + responseBody.put("statusCode", HttpStatus.INTERNAL_SERVER_ERROR.value()); + responseBody.put("message", DEFAULT_ERROR_MESSAGE_VALUE); + responseBody.put("error", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()); + + discordWebHookMessenger.sendMessage( + DiscordWebAlarmMessage.fromHttpServletRequest( + request, + DEFAULT_ERROR_MESSAGE_VALUE, + responseBody.toString() + ) + ); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(responseBody); + } } From 53e573f94508e6f92b7eb3815d8bbd13bda4560f Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Tue, 16 Apr 2024 23:29:32 +0900 Subject: [PATCH 0590/1029] =?UTF-8?q?[BE]=20FIX:=20=EB=AA=A8=EB=93=A0=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=A1=9C=EA=B7=B8=EC=97=90=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EB=B3=B4=EB=82=B4=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20#1593?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/log4j2-prod.xml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/backend/src/main/resources/log4j2-prod.xml b/backend/src/main/resources/log4j2-prod.xml index 614eab665..8c62a6ff3 100644 --- a/backend/src/main/resources/log4j2-prod.xml +++ b/backend/src/main/resources/log4j2-prod.xml @@ -1,6 +1,7 @@ - + @@ -8,7 +9,8 @@ - + @@ -20,16 +22,6 @@ - - - application/json - - - {"content": "Profile: Main | %encode{%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level: %m%n}{JSON}"} - - - - From c0af619d19520648b8090425a5d327a8adf93280 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Wed, 17 Apr 2024 14:25:28 +0900 Subject: [PATCH 0591/1029] =?UTF-8?q?[BE]=20FIX:=20INFO=20=EB=8C=80?= =?UTF-8?q?=EC=8B=A0=20DEBUG=EC=97=90=EC=84=9C=EB=A7=8C=20=EC=8A=A4?= =?UTF-8?q?=ED=83=9D=20=ED=8A=B8=EB=A0=88=EC=9D=B4=EC=8A=A4=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EB=82=A8=EA=B8=B0=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#1593?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/exception/ExceptionController.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java index 07e520e16..92521f871 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionController.java @@ -25,12 +25,9 @@ public class ExceptionController extends ResponseEntityExceptionHandler { @ExceptionHandler(ControllerException.class) public ResponseEntity controllerExceptionHandler(ControllerException e) { - StackTraceElement stackTraceElement = e.getStackTrace()[0]; - log.info("[ControllerException] {} : {} at {}", - e.status.getError(), e.status.getMessage(), - stackTraceElement); + log.info("[ControllerException] {} : {}", e.status.getError(), e.status.getMessage()); if (log.isDebugEnabled()) { - log.debug(e); + log.debug("Exception stack trace: ", e); } return ResponseEntity .status(e.status.getStatusCode()) @@ -41,7 +38,7 @@ public ResponseEntity controllerExceptionHandler(ControllerException e) { public ResponseEntity serviceExceptionHandler(ServiceException e) { log.info("[ServiceException] {} : {}", e.status.getError(), e.status.getMessage()); if (log.isDebugEnabled()) { - log.debug(e); + log.debug("Exception stack trace: ", e); } return ResponseEntity .status(e.status.getStatusCode()) @@ -52,7 +49,7 @@ public ResponseEntity serviceExceptionHandler(ServiceException e) { public ResponseEntity customServiceExceptionHandler(CustomServiceException e) { log.info("[CustomServiceException] {} : {}", e.status.getError(), e.status.getMessage()); if (log.isDebugEnabled()) { - log.debug(e); + log.debug("Exception stack trace: ", e); } return ResponseEntity .status(e.status.getStatusCode()) @@ -63,7 +60,7 @@ public ResponseEntity customServiceExceptionHandler(CustomServiceException e) public ResponseEntity domainExceptionHandler(DomainException e) { log.warn("[DomainException] {} : {}", e.status.getError(), e.status.getMessage()); if (log.isDebugEnabled()) { - log.debug(e); + log.debug("Exception stack trace: ", e); } return ResponseEntity .status(e.status.getStatusCode()) @@ -74,7 +71,7 @@ public ResponseEntity domainExceptionHandler(DomainException e) { public ResponseEntity utilExceptionHandler(UtilException e) { log.warn("[UtilException] {} : {}", e.status.getError(), e.status.getMessage()); if (log.isDebugEnabled()) { - log.debug(e); + log.debug("Exception stack trace: ", e); } return ResponseEntity .status(e.status.getStatusCode()) From dbe9ee1ee6cc56eb3af548ca883ff7776ee619ac Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Wed, 17 Apr 2024 15:05:18 +0900 Subject: [PATCH 0592/1029] [BE] FIX: log4j2-prod.xml format --- backend/src/main/resources/log4j2-prod.xml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/backend/src/main/resources/log4j2-prod.xml b/backend/src/main/resources/log4j2-prod.xml index 8c62a6ff3..5399c30d6 100644 --- a/backend/src/main/resources/log4j2-prod.xml +++ b/backend/src/main/resources/log4j2-prod.xml @@ -1,7 +1,6 @@ - + @@ -9,8 +8,7 @@ - + @@ -30,7 +28,6 @@ - From 64b42a2b1c517726fdef6306d6918778e58463d1 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 17 Apr 2024 18:55:56 +0900 Subject: [PATCH 0593/1029] =?UTF-8?q?[FE]=20FEAT:=20=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EB=B3=84=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80#15?= =?UTF-8?q?51?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/maps.ts | 21 ++++++++++++------- .../Card/ThemeColorCard/DarkMode/DarkMode.tsx | 21 +++++++++++++++---- .../Common/MultiToggleSwitchSeparated.tsx | 18 +++++++++------- .../src/types/enum/colorTheme.type.enum.ts | 7 +++++++ 4 files changed, 48 insertions(+), 19 deletions(-) create mode 100644 frontend/src/types/enum/colorTheme.type.enum.ts diff --git a/frontend/src/assets/data/maps.ts b/frontend/src/assets/data/maps.ts index 91f18ee62..f1b6d251a 100644 --- a/frontend/src/assets/data/maps.ts +++ b/frontend/src/assets/data/maps.ts @@ -1,11 +1,12 @@ import { ReactComponent as ClubIcon } from "@/assets/images/clubIcon.svg"; +import { ReactComponent as MonitorMobileIcon } from "@/assets/images/monitorMobile.svg"; +import { ReactComponent as MoonIcon } from "@/assets/images/moon.svg"; import { ReactComponent as PrivateIcon } from "@/assets/images/privateIcon.svg"; import { ReactComponent as ShareIcon } from "@/assets/images/shareIcon.svg"; +import { ReactComponent as SunIcon } from "@/assets/images/sun.svg"; import CabinetStatus from "@/types/enum/cabinet.status.enum"; import CabinetType from "@/types/enum/cabinet.type.enum"; -import { ReactComponent as SunIcon } from "@/assets/images/sun.svg"; -import { ReactComponent as MoonIcon } from "@/assets/images/moon.svg"; -import { ReactComponent as MonitorMobileIcon } from "@/assets/images/monitor-mobbile.svg"; +import ColorThemeType from "@/types/enum/colorTheme.type.enum"; export enum additionalModalType { MODAL_RETURN = "MODAL_RETURN", @@ -228,8 +229,12 @@ export const cabinetTypeLabelMap = { [CabinetType.SHARE]: "공유 사물함", }; -export const colorThemeIconComponentMap: CabinetIconComponentMap = { - [CabinetType.PRIVATE]: SunIcon, - [CabinetType.SHARE]: MoonIcon, - [CabinetType.CLUB]: MonitorMobileIcon, -}; \ No newline at end of file +type colorThemeIconComponentMap = { + [key in ColorThemeType]: React.ComponentType>; +}; + +export const colorThemeIconComponentMap: colorThemeIconComponentMap = { + [ColorThemeType.LIGHT]: SunIcon, + [ColorThemeType.DARK]: MoonIcon, + [ColorThemeType.DEVICE]: MonitorMobileIcon, +}; diff --git a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx index 6d0526da8..92d7381c3 100644 --- a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx +++ b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx @@ -2,8 +2,9 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { darkModeState } from "@/recoil/atoms"; -import MultiToggleSwitch from "@/components/Common/MultiToggleSwitch"; import MultiToggleSwitchSeparated from "@/components/Common/MultiToggleSwitchSeparated"; +import { colorThemeIconComponentMap } from "@/assets/data/maps"; +import ColorThemeType from "@/types/enum/colorTheme.type.enum"; // TODO : DarkMode 파일 폴더명 ColorTheme으로 변경 @@ -43,9 +44,21 @@ const DarkMode = () => { initialState={toggleType} setState={setToggleType} toggleList={[ - { name: "라이트", key: 0 }, - { name: "다크", key: 1 }, - { name: "기기설정", key: 2 }, + { + name: "라이트", + key: 0, + icon: colorThemeIconComponentMap[ColorThemeType.LIGHT], + }, + { + name: "다크", + key: 1, + icon: colorThemeIconComponentMap[ColorThemeType.DARK], + }, + { + name: "기기설정", + key: 2, + icon: colorThemeIconComponentMap[ColorThemeType.DEVICE], + }, ]} buttonHeight={"90px"} buttonWidth={"90px"} diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx index 0f64fcb05..a4caa0146 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useRef } from "react"; import styled from "styled-components"; -import { ReactComponent as SunIcon } from "@/assets/images/sun.svg"; export interface toggleItem { name: string; key: number; + icon?: React.ComponentType>; } interface MultiToggleSwitchProps { @@ -61,12 +61,16 @@ const MultiToggleSwitchSeparated = ({ buttonHeight={buttonHeight} buttonWidth={buttonWidth} > - {toggleList.map((item) => ( - - ))} + {toggleList.map((item) => { + const ColorThemeIcon = item.icon; + + return ( + + ); + })} ); }; diff --git a/frontend/src/types/enum/colorTheme.type.enum.ts b/frontend/src/types/enum/colorTheme.type.enum.ts new file mode 100644 index 000000000..6442b164b --- /dev/null +++ b/frontend/src/types/enum/colorTheme.type.enum.ts @@ -0,0 +1,7 @@ +enum ColorThemeType { + LIGHT = "LIGHT", + DARK = "DARK", + DEVICE = "DEVICE", +} + +export default ColorThemeType; From 7ed3a0c1964ccd5e22116c8234362ae3714ee078 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 17 Apr 2024 19:09:49 +0900 Subject: [PATCH 0594/1029] =?UTF-8?q?[FE]=20REFACT:=20ButtonStyled,=20Colo?= =?UTF-8?q?rThemeIconStyled=20=EB=8F=84=EC=9E=85#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/MultiToggleSwitchSeparated.tsx | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx index a4caa0146..98f1613b1 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx @@ -55,51 +55,59 @@ const MultiToggleSwitchSeparated = ({ } return ( - + {toggleList.map((item) => { const ColorThemeIcon = item.icon; - return ( - + ); })} ); }; -const WrapperStyled = styled.div<{ - buttonHeight?: string; - buttonWidth?: string; -}>` +const WrapperStyled = styled.div` width: 100%; display: flex; align-items: center; border-radius: 10px; justify-content: space-between; +`; - button { - display: flex; - justify-content: center; - align-items: center; - width: ${(props) => - props.buttonWidth ? props.buttonWidth : "fit-content"}; - min-width: 50px; - border-radius: 10px; - font-size: 1rem; - height: ${(props) => (props.buttonHeight ? props.buttonHeight : "30px")}; - font-weight: 500; - background-color: var(--shared-gray-color-100); - color: var(--normal-text-color); - padding: 4px 12px; - } +const ButtonStyled = styled.div<{ + buttonHeight?: string; + buttonWidth?: string; + icon?: React.ComponentType>; +}>` + display: flex; + justify-content: ${(props) => (props.icon ? "space-between" : "center")}; + align-items: center; + flex-direction: ${(props) => (props.icon ? "column" : "row")}; + min-width: 50px; + width: ${(props) => (props.buttonWidth ? props.buttonWidth : "fit-content")}; + min-width: 50px; + border-radius: 10px; + font-size: 1rem; + height: ${(props) => (props.buttonHeight ? props.buttonHeight : "30px")}; + font-weight: 500; + background-color: var(--shared-gray-color-100); + color: var(--normal-text-color); + padding: ${(props) => (props.icon ? "12px 0 16px 0" : "4px 12px")}; ; `; +const ColorThemeIconStyled = styled.div``; + export default MultiToggleSwitchSeparated; From e8a6dba5dd4a21d833d2b473ea1f6c6edf9ebc4d Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Wed, 17 Apr 2024 19:14:19 +0900 Subject: [PATCH 0595/1029] =?UTF-8?q?[FE]=20FIX:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=B9=B4=EB=93=9C=EC=99=80=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=9E=90=EB=A6=AC=20=EB=B3=80=EA=B2=BD#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/images/monitorMobile.svg | 12 ++++++------ frontend/src/pages/ProfilePage.tsx | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/assets/images/monitorMobile.svg b/frontend/src/assets/images/monitorMobile.svg index 6bf282c06..ea366142b 100644 --- a/frontend/src/assets/images/monitorMobile.svg +++ b/frontend/src/assets/images/monitorMobile.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index fb554cc5c..2c1bc2e91 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -75,7 +75,7 @@ const CardGridWrapper = styled.div` grid-template-rows: 163px 183px 230px; grid-template-areas: "profile lentInfo" // h: 163px h: 366px "extension lentInfo" // h: 183px - "theme notification"; // h: 230px h: 230px; + "notification theme"; // h: 230px h: 230px; @media (max-width: 768px) { grid-template-columns: 350px; @@ -84,8 +84,8 @@ const CardGridWrapper = styled.div` "profile" "lentInfo" "extension" - "theme" - "notification"; + "notification" + "theme"; } `; From 22e6ba3dee3d03470acc118911c9cc3ee91cec08 Mon Sep 17 00:00:00 2001 From: SpaceChae <13278955+enaenen@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:47:06 +0900 Subject: [PATCH 0596/1029] MTD (#1598) Co-authored-by: Jiwon Park <82518170+Z1Park@users.noreply.github.com> Co-authored-by: Siwon Choi <83565255+sichoi42@users.noreply.github.com> From 81a269db7316fe89b39793dd9a541c11674f91d1 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 18 Apr 2024 18:27:49 +0900 Subject: [PATCH 0597/1029] =?UTF-8?q?[FE]=20FIX:=20MultiToggleSwitchSepara?= =?UTF-8?q?ted=EC=9D=98=20div=20>=20button,=20className=20>=20id=20&=20tog?= =?UTF-8?q?gleItem=20=EC=9D=B4=EB=A6=84=EC=97=90=20Separated=EB=B6=99?= =?UTF-8?q?=EC=97=AC=EC=84=9C=20MultiToggleSwitch=EC=99=80=20=EA=B5=AC?= =?UTF-8?q?=EB=B6=84#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/MultiToggleSwitchSeparated.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx index 98f1613b1..032dba092 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx @@ -1,16 +1,16 @@ import React, { useEffect, useRef } from "react"; import styled from "styled-components"; -export interface toggleItem { +export interface toggleItemSeparated { name: string; - key: number; + key: string; icon?: React.ComponentType>; } interface MultiToggleSwitchProps { initialState: T; setState: React.Dispatch>; - toggleList: toggleItem[]; + toggleList: toggleItemSeparated[]; buttonHeight?: string; buttonWidth?: string; } @@ -28,7 +28,7 @@ const MultiToggleSwitchSeparated = ({ const buttons = wrapperRef.current?.querySelectorAll("button"); buttons?.forEach((button) => { - if (button.className === `${initialState}`) { + if (button.id === `${initialState}`) { button.style.color = "var(--bg-color)"; button.style.backgroundColor = "var(--main-color)"; } @@ -51,7 +51,7 @@ const MultiToggleSwitchSeparated = ({ target.style.color = "var(--bg-color)"; target.style.backgroundColor = "var(--main-color)"; - setState(target.className as React.SetStateAction); + setState(target.id as React.SetStateAction); } return ( @@ -61,7 +61,7 @@ const MultiToggleSwitchSeparated = ({ return ( >; From cc018f76db71470fcd6e5263285f9eb74c3d2c5b Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 18 Apr 2024 18:29:16 +0900 Subject: [PATCH 0598/1029] =?UTF-8?q?[FE]=20FIX:=20colorThemeIconComponent?= =?UTF-8?q?Map=EC=97=90=20Toggle=EC=9D=84=20=EB=B6=99=EC=97=AC=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=AC=20=ED=85=8C=EB=A7=88=20=ED=86=A0=EA=B8=80=EA=B3=BC=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=AC=20=EB=AA=A8=EB=93=9C=EB=A5=BC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/maps.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/assets/data/maps.ts b/frontend/src/assets/data/maps.ts index f1b6d251a..d9f89a892 100644 --- a/frontend/src/assets/data/maps.ts +++ b/frontend/src/assets/data/maps.ts @@ -6,7 +6,7 @@ import { ReactComponent as ShareIcon } from "@/assets/images/shareIcon.svg"; import { ReactComponent as SunIcon } from "@/assets/images/sun.svg"; import CabinetStatus from "@/types/enum/cabinet.status.enum"; import CabinetType from "@/types/enum/cabinet.type.enum"; -import ColorThemeType from "@/types/enum/colorTheme.type.enum"; +import { ColorThemeToggleType } from "@/types/enum/colorTheme.type.enum"; export enum additionalModalType { MODAL_RETURN = "MODAL_RETURN", @@ -229,12 +229,15 @@ export const cabinetTypeLabelMap = { [CabinetType.SHARE]: "공유 사물함", }; -type colorThemeIconComponentMap = { - [key in ColorThemeType]: React.ComponentType>; +type colorThemeToggleIconComponentMap = { + [key in ColorThemeToggleType]: React.ComponentType< + React.SVGProps + >; }; -export const colorThemeIconComponentMap: colorThemeIconComponentMap = { - [ColorThemeType.LIGHT]: SunIcon, - [ColorThemeType.DARK]: MoonIcon, - [ColorThemeType.DEVICE]: MonitorMobileIcon, -}; +export const colorThemeToggleIconComponentMap: colorThemeToggleIconComponentMap = + { + [ColorThemeToggleType.LIGHT]: SunIcon, + [ColorThemeToggleType.DARK]: MoonIcon, + [ColorThemeToggleType.DEVICE]: MonitorMobileIcon, + }; From adda16d06fa34c6f6eb80bddfefd5da3272f96f0 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 18 Apr 2024 18:30:37 +0900 Subject: [PATCH 0599/1029] =?UTF-8?q?[FE]=20FIX:=20colorThemeIconComponent?= =?UTF-8?q?Map=EC=97=90=20Toggle=EC=9D=84=20=EB=B6=99=EC=97=AC=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=AC=20=ED=85=8C=EB=A7=88=20=ED=86=A0=EA=B8=80=EA=B3=BC=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=AC=20=EB=AA=A8=EB=93=9C=EB=A5=BC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/types/enum/colorTheme.type.enum.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/types/enum/colorTheme.type.enum.ts b/frontend/src/types/enum/colorTheme.type.enum.ts index 6442b164b..79cb633ab 100644 --- a/frontend/src/types/enum/colorTheme.type.enum.ts +++ b/frontend/src/types/enum/colorTheme.type.enum.ts @@ -1,7 +1,12 @@ -enum ColorThemeType { +// NOTE : 라이트, 다크, 디바이스 토글 +export enum ColorThemeToggleType { LIGHT = "LIGHT", DARK = "DARK", DEVICE = "DEVICE", } -export default ColorThemeType; +// NOTE : 라이트 모드 / 다크 모드 +export enum ColorThemeType { + LIGHT = "LIGHT", + DARK = "DARK", +} From d8f8c190f6c74629ebde208b0d317c6dee72849d Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 18 Apr 2024 18:32:13 +0900 Subject: [PATCH 0600/1029] =?UTF-8?q?[FE]=20FEAT:=20=ED=85=8C=EB=A7=88=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=AC=20=ED=86=A0=EA=B8=80=EB=A1=9C=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EC=84=A0=ED=83=9D=20=ED=9B=84=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=EC=B9=A8=ED=95=B4=EB=8F=84=20=EC=A0=84=EC=97=90=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=ED=96=88=EB=8D=98=20=ED=86=A0=EA=B8=80?= =?UTF-8?q?=EC=9D=B4=20=EB=88=8C=EB=A0=A4=EC=9E=88=EA=B2=8C=ED=95=A8#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/ThemeColorCard/DarkMode/DarkMode.tsx | 87 +++++++++++-------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx index 92d7381c3..779a709f2 100644 --- a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx +++ b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx @@ -2,29 +2,61 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { darkModeState } from "@/recoil/atoms"; -import MultiToggleSwitchSeparated from "@/components/Common/MultiToggleSwitchSeparated"; -import { colorThemeIconComponentMap } from "@/assets/data/maps"; -import ColorThemeType from "@/types/enum/colorTheme.type.enum"; +import MultiToggleSwitchSeparated, { + toggleItemSeparated, +} from "@/components/Common/MultiToggleSwitchSeparated"; +import { colorThemeToggleIconComponentMap } from "@/assets/data/maps"; +import { + ColorThemeToggleType, + ColorThemeType, +} from "@/types/enum/colorTheme.type.enum"; // TODO : DarkMode 파일 폴더명 ColorTheme으로 변경 +const toggleList: toggleItemSeparated[] = [ + { + name: "라이트", + key: ColorThemeToggleType.LIGHT, + icon: colorThemeToggleIconComponentMap[ColorThemeToggleType.LIGHT], + }, + { + name: "다크", + key: ColorThemeToggleType.DARK, + icon: colorThemeToggleIconComponentMap[ColorThemeToggleType.DARK], + }, + { + name: "기기설정", + key: ColorThemeToggleType.DEVICE, + icon: colorThemeToggleIconComponentMap[ColorThemeToggleType.DEVICE], + }, +]; + const DarkMode = () => { const savedColorTheme = localStorage.getItem("color-theme"); + const savedColorThemeToggle = localStorage.getItem("color-theme-toggle"); var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); - // const [darkMode, setDarkMode] = useRecoilState(darkModeState); - const [darkMode, setDarkMode] = useState( - savedColorTheme ? savedColorTheme : darkModeQuery.matches ? "dark" : "light" + const [darkMode, setDarkMode] = useState( + savedColorTheme + ? (savedColorTheme as ColorThemeType) + : darkModeQuery.matches + ? ColorThemeType.DARK + : ColorThemeType.LIGHT + ); + const [toggleType, setToggleType] = useState( + savedColorThemeToggle + ? (savedColorThemeToggle as ColorThemeToggleType) + : ColorThemeToggleType.DEVICE ); - const onClickHandler = () => { - setDarkMode((prev) => { - return prev === "light" ? "dark" : "light"; - }); - }; + // const onClickHandler = () => { + // setDarkMode((prev) => { + // return prev === "light" ? "dark" : "light"; + // }); + // }; useEffect(() => { darkModeQuery.addEventListener("change", (event) => - setDarkMode(event.matches ? "dark" : "light") + setDarkMode(event.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT) ); }, []); @@ -32,10 +64,11 @@ const DarkMode = () => { document.body.setAttribute("color-theme", darkMode); localStorage.setItem("color-theme", darkMode); }, [darkMode]); - const [toggleType, setToggleType] = useState("기기설정"); - // "ALL" - // setToggleType - // [{name: '전체', key: 'ALL'}, {name: '전체', key: 'ALL'}] + + useEffect(() => { + document.body.setAttribute("color-theme", darkMode); + localStorage.setItem("color-theme-toggle", toggleType); + }, [toggleType]); return ( <> @@ -43,23 +76,7 @@ const DarkMode = () => { @@ -74,8 +91,4 @@ const ButtonsWrapperStyled = styled.div` padding: 0 16px; `; -const ButtonStyled = styled.button` - background-color: var(--normal-text-color); -`; - export default DarkMode; From e148c9da6e3822dad7e1c6823836272d10b64604 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Fri, 19 Apr 2024 16:53:40 +0900 Subject: [PATCH 0601/1029] =?UTF-8?q?[BE]=20FIX:=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=EB=9F=AC=20=EA=B0=81=20=ED=83=9C=EC=8A=A4=ED=81=AC=20?= =?UTF-8?q?=EB=A7=88=EB=8B=A4=20try-catch=20=EC=A0=81=EC=9A=A9=20&=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=EC=8B=9C=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=B6=94=EA=B0=80=20#1597?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../discord/DiscordScheduleAlarmMessage.java | 30 +++++++++ .../discord/DiscordWebHookMessenger.java | 25 +++++++ .../utils/scheduler/SystemScheduler.java | 65 ++++++++++++++++--- 3 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordScheduleAlarmMessage.java diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordScheduleAlarmMessage.java b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordScheduleAlarmMessage.java new file mode 100644 index 000000000..ef54801e1 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordScheduleAlarmMessage.java @@ -0,0 +1,30 @@ +package org.ftclub.cabinet.alarm.discord; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class DiscordScheduleAlarmMessage { + + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern( + "yyyy-MM-dd HH:mm:ss"); + private final String subject; + private final String taskName; + private final String taskMethodName; + private final String taskParameters; + + @Override + public String toString() { + return "```java\n" + + "Subject: \"" + subject + "\"\n" + + "Task: \"" + taskName + "\"\n" + + "Issued at: \"" + LocalDateTime.now().format(formatter) + "\"\n" + + "Method: \"" + taskMethodName + "\"\n" + + "Parameters: \"" + taskParameters + "\"\n" + + "```"; + } +} + diff --git a/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java index 29f50db60..ed6cbaf25 100644 --- a/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java +++ b/backend/src/main/java/org/ftclub/cabinet/alarm/discord/DiscordWebHookMessenger.java @@ -40,4 +40,29 @@ public void sendMessage(DiscordWebAlarmMessage message) { .bodyToMono(String.class) .block(); } + + public void sendMessage(DiscordScheduleAlarmMessage message) { + Map body = new HashMap<>(); + body.put(DISCORD_WEBHOOK_MESSAGE_KEY, message.toString()); + + WebClient.create().post() + .uri(discordWebHookUrl) + .bodyValue(body) + .retrieve() + .bodyToMono(String.class) + .block(); + } + + public void sendMessage(String title, String message) { + Map body = new HashMap<>(); + body.put(DISCORD_WEBHOOK_MESSAGE_KEY, + "```title: " + title + "\nmessage: " + message + "```"); + + WebClient.create().post() + .uri(discordWebHookUrl) + .bodyValue(body) + .retrieve() + .bodyToMono(String.class) + .block(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index c512f9335..2e4384390 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -5,6 +5,8 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.ftclub.cabinet.alarm.discord.DiscordScheduleAlarmMessage; +import org.ftclub.cabinet.alarm.discord.DiscordWebHookMessenger; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.UserBlackHoleEvent; import org.ftclub.cabinet.lent.service.LentFacadeService; @@ -27,12 +29,19 @@ public class SystemScheduler { private static final long DELAY_TIME = 2000; + private static final String DEFAULT_ERROR_TITLE = "까비 스케줄러 작업 중 예기치 않은 오류가 발생했어요.🥲"; private final UserQueryService userQueryService; private final LentExtensionManager lentExtensionManager; private final OverdueManager overdueManager; private final LentFacadeService lentFacadeService; private final BlackholeManager blackholeManager; private final ReleaseManager releaseManager; + private final DiscordWebHookMessenger discordWebHookMessenger; + + private void errorHandle(Exception e, DiscordScheduleAlarmMessage message) { + log.error("Error message: {}, Error occurred in scheduled task: ", message, e); + discordWebHookMessenger.sendMessage(message); + } /** * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거 @@ -42,7 +51,16 @@ public void checkAllLents() { log.info("called checkAllLents"); List activeLents = lentFacadeService.getAllActiveLentHistories(); for (ActiveLentHistoryDto activeLent : activeLents) { - overdueManager.handleOverdue(activeLent); + try { + overdueManager.handleOverdue(activeLent); + } catch (Exception e) { + errorHandle(e, DiscordScheduleAlarmMessage.builder() + .subject(DEFAULT_ERROR_TITLE) + .taskName("연체 처리 작업") + .taskMethodName("checkAllLents") + .taskParameters(activeLent.toString()) + .build()); + } /* leaveAbsenceManager.handleLeaveAbsence(activeLent.getUserId(), activeLent.getName()); try { @@ -60,18 +78,24 @@ public void checkAllLents() { @Scheduled(cron = "${cabinet.schedule.cron.risk-of-blackhole}") public void checkRiskOfBlackhole() { log.info("called checkRiskOfBlackhole"); - List closeWithBlackholeUsers = userQueryService.findUsersAtRiskOfBlackhole() .stream() .map(user -> UserBlackHoleEvent.of(user.getId(), user.getName(), user.getEmail(), user.getBlackholedAt())) .collect(Collectors.toList()); for (UserBlackHoleEvent blackholeInfo : closeWithBlackholeUsers) { - blackholeManager.handleBlackHole(blackholeInfo); try { + blackholeManager.handleBlackHole(blackholeInfo); Thread.sleep(DELAY_TIME); } catch (InterruptedException e) { - log.error(e.getMessage()); + log.warn(e.getMessage()); + } catch (Exception e) { + errorHandle(e, DiscordScheduleAlarmMessage.builder() + .subject(DEFAULT_ERROR_TITLE) + .taskName("블랙홀 처리 작업 (예정자)") + .taskMethodName("checkRiskOfBlackhole") + .taskParameters(blackholeInfo.toString()) + .build()); } } } @@ -90,11 +114,18 @@ public void checkNoRiskOfBlackhole() { .collect(Collectors.toList()); for (UserBlackHoleEvent blackholeUserInfo : safeFromBlackholeUsers) { - blackholeManager.handleBlackHole(blackholeUserInfo); try { + blackholeManager.handleBlackHole(blackholeUserInfo); Thread.sleep(DELAY_TIME); } catch (InterruptedException e) { - log.error(e.getMessage()); + log.warn(e.getMessage()); + } catch (Exception e) { + errorHandle(e, DiscordScheduleAlarmMessage.builder() + .subject(DEFAULT_ERROR_TITLE) + .taskName("블랙홀 처리 작업 (전체)") + .taskMethodName("checkNoRiskOfBlackhole") + .taskParameters(blackholeUserInfo.toString()) + .build()); } } } @@ -106,13 +137,31 @@ public void checkNoRiskOfBlackhole() { @Scheduled(cron = "${cabinet.schedule.cron.cabinet-release-time}") public void releasePendingCabinet() { log.info("releasePendingCabinet {}", LocalDateTime.now()); - releaseManager.releasingCabinets(); + try { + releaseManager.releasingCabinets(); + } catch (Exception e) { + errorHandle(e, DiscordScheduleAlarmMessage.builder() + .subject(DEFAULT_ERROR_TITLE) + .taskName("Pending 사물함 Release 작업") + .taskMethodName("releasePendingCabinet") + .taskParameters("") + .build()); + } } @Scheduled(cron = "${cabinet.schedule.cron.extension-issue-time}") public void lentExtensionIssue() { log.info("called lentExtensionIssue"); - lentExtensionManager.issueLentExtension(); + try { + lentExtensionManager.issueLentExtension(); + } catch (Exception e) { + errorHandle(e, DiscordScheduleAlarmMessage.builder() + .subject(DEFAULT_ERROR_TITLE) + .taskName("연장권 발급 작업") + .taskMethodName("lentExtensionIssue") + .taskParameters("") + .build()); + } } // @Scheduled(cron = "${cabinet.schedule.cron.extensible-user-check}") From 37c527232a8925e10f3076df549c4e1875e449ca Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Fri, 19 Apr 2024 17:09:58 +0900 Subject: [PATCH 0602/1029] =?UTF-8?q?[BE]=20FIX:=20=EC=97=B0=EC=B2=B4=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20NPE=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#1597?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftclub/cabinet/dto/ActiveLentHistoryDto.java | 2 ++ .../ftclub/cabinet/lent/domain/LentHistory.java | 8 +++++--- .../org/ftclub/cabinet/mapper/LentMapper.java | 3 ++- .../utils/overdue/manager/OverdueManager.java | 15 ++++++++------- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java index dcabcec1b..f0cab580a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java @@ -1,5 +1,6 @@ package org.ftclub.cabinet.dto; +import javax.annotation.Nullable; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @@ -14,5 +15,6 @@ public class ActiveLentHistoryDto { private final String email; private final Long cabinetId; private final Boolean isExpired; + @Nullable private final Long daysFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java index ea5242cc8..32b282e85 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java @@ -3,6 +3,7 @@ import static javax.persistence.FetchType.LAZY; import java.time.LocalDateTime; +import javax.annotation.Nullable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -210,11 +211,12 @@ public Boolean isExpired(LocalDateTime now) { } /** - * 만료일까지 남은 일수를 계산합니다. 만료시간이 설정되지 않았으면 null을 반환합니다. + * 만료일까지 남은 일수를 계산합니다. 만료일이 설정되지 않았을 경우 {@code null}을 반환합니다. * - * @return 만료일까지 남은 일수 (만료일 - 현재시간) (일 기준, 올림) + * @param now 현재 시간을 나타내는 {@code LocalDateTime} 객체 + * @return 만료일까지 남은 일수를 일 단위로 반환합니다. 만료일이 설정되지 않았을 경우 {@code null} 반환. */ - public Long getDaysUntilExpiration(LocalDateTime now) { + public @Nullable Long getDaysUntilExpiration(LocalDateTime now) { if (isSetExpiredAt()) { return DateUtil.calculateTwoDateDiffCeil(expiredAt, now); } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java index 3f26df680..977efa46b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java @@ -3,6 +3,7 @@ import static org.mapstruct.NullValueMappingStrategy.RETURN_DEFAULT; import java.util.List; +import javax.annotation.Nullable; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.club.domain.ClubLentHistory; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; @@ -55,6 +56,6 @@ ActiveLentHistoryDto toActiveLentHistoryDto(LentHistory lentHistory, User user, Cabinet cabinet, Boolean isExpired, - Long daysLeftFromExpireDate + @Nullable Long daysLeftFromExpireDate ); } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java index a8bd7237a..3db589f11 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/overdue/manager/OverdueManager.java @@ -1,7 +1,7 @@ package org.ftclub.cabinet.utils.overdue.manager; +import javax.annotation.Nullable; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.alarm.config.AlarmProperties; import org.ftclub.cabinet.alarm.domain.AlarmEvent; import org.ftclub.cabinet.alarm.domain.LentExpirationAlarm; @@ -9,12 +9,13 @@ import org.ftclub.cabinet.cabinet.domain.CabinetStatus; import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; +import org.ftclub.cabinet.log.Logging; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor -@Log4j2 +@Logging /** * 연체 관리자 클래스 * @@ -33,15 +34,16 @@ public class OverdueManager { * 반환한다. * * @param isExpired 연체 기간이 지났는지 여부 (true: 연체 기간이 지남, false: 연체 기간이 지나지 않음) - * @param daysLeftFromExpireDate 만료일까지 남은 일수 + * @param daysLeftFromExpireDate 만료일로부터 남은 일수 (null: 만료일이 없음) * @return 연체 타입 */ - public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate) { - log.info("called getOverdueType with {}, {}", isExpired, daysLeftFromExpireDate); + private OverdueType getOverdueType(Boolean isExpired, @Nullable Long daysLeftFromExpireDate) { if (isExpired) { return OverdueType.OVERDUE; } - + if (daysLeftFromExpireDate == null) { + return OverdueType.NONE; + } if (daysLeftFromExpireDate.equals(alarmProperties.getOverdueTermWeekBefore())) { return OverdueType.SOON_OVERDUE; } @@ -60,7 +62,6 @@ public OverdueType getOverdueType(Boolean isExpired, Long daysLeftFromExpireDate public void handleOverdue(ActiveLentHistoryDto activeLent) { OverdueType overdueType = getOverdueType(activeLent.getIsExpired(), activeLent.getDaysFromExpireDate()); - log.info("called handleOverdue: activeLent={}, overdueType={}", activeLent, overdueType); switch (overdueType) { case NONE: return; From 00865712a97f62a051ab2ac1dfefe79d40902985 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Sat, 20 Apr 2024 00:42:03 +0900 Subject: [PATCH 0603/1029] =?UTF-8?q?[BE]=20FIX:=20=EC=97=B0=EC=B2=B4=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20NPE=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(isSetExpired=20=EC=A0=9C=EA=B1=B0)=20#1597?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/dto/ActiveLentHistoryDto.java | 2 - .../cabinet/lent/domain/LentHistory.java | 59 +++++++++++-------- .../org/ftclub/cabinet/mapper/LentMapper.java | 2 +- .../cabinet/lent/domain/LentHistoryTest.java | 14 ++--- 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java index f0cab580a..dcabcec1b 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ActiveLentHistoryDto.java @@ -1,6 +1,5 @@ package org.ftclub.cabinet.dto; -import javax.annotation.Nullable; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @@ -15,6 +14,5 @@ public class ActiveLentHistoryDto { private final String email; private final Long cabinetId; private final Boolean isExpired; - @Nullable private final Long daysFromExpireDate; } diff --git a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java index 32b282e85..b85f0008d 100644 --- a/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java +++ b/backend/src/main/java/org/ftclub/cabinet/lent/domain/LentHistory.java @@ -160,18 +160,19 @@ public void setExpiredAt(LocalDateTime expiredAt) { new DomainException(ExceptionStatus.INVALID_STATUS)); } - /** - * 만료일이 설정 되어있는 지 확인합니다. 만료일이 {@link DateUtil}의 infinityDate와 같으면 만료일이 설정되어 있지 않다고 판단합니다. - * - * @return 설정이 되어있으면 true 아니면 false - */ - public boolean isSetExpiredAt() { - LocalDateTime expiredAt = getExpiredAt(); - if (expiredAt == null) { - throw ExceptionStatus.INTERNAL_SERVER_ERROR.asDomainException(); - } - return !expiredAt.isEqual(DateUtil.getInfinityDate()); - } +// NOTE: 2024-04-20 기준 정책상 만료일이 설정되어 있지 않은 경우는 없으므로 삭제 +// /** +// * 만료일이 설정 되어있는 지 확인합니다. 만료일이 {@link DateUtil}의 infinityDate와 같으면 만료일이 설정되어 있지 않다고 판단합니다. +// * +// * @return 설정이 되어있으면 true 아니면 false +// */ +// public boolean isSetExpiredAt() { +// LocalDateTime expiredAt = getExpiredAt(); +// if (expiredAt == null) { +// throw ExceptionStatus.INTERNAL_SERVER_ERROR.asDomainException(); +// } +// return !expiredAt.isEqual(DateUtil.getInfinityDate()); +// } /** * 반납일이 설정 되어있는 지 확인합니다. 반납일이 {@link DateUtil}의 infinityDate와 같으면 만료일이 설정되어 있지 않다고 판단합니다. @@ -192,10 +193,14 @@ public boolean isSetEndedAt() { * @return endedAt - expiredAt의 값을(일 기준) */ public Long getDaysDiffEndedAndExpired() { - if (isSetExpiredAt() && isSetEndedAt()) { +// if (isSetExpiredAt() && isSetEndedAt()) { +// return DateUtil.calculateTwoDateDiff(endedAt, expiredAt) + 1; +// } +// return null; + if (isSetEndedAt()) { return DateUtil.calculateTwoDateDiff(endedAt, expiredAt) + 1; } - return null; + throw ExceptionStatus.INTERNAL_SERVER_ERROR.asDomainException(); } /** @@ -204,23 +209,25 @@ public Long getDaysDiffEndedAndExpired() { * @return 만료일이 지났으면 true 아니면 false, 만료일이 설정되어 있지 않으면 false */ public Boolean isExpired(LocalDateTime now) { - if (isSetExpiredAt()) { - return DateUtil.calculateTwoDateDiffCeil(expiredAt, now) > 0; - } - return false; +// if (isSetExpiredAt()) { +// return DateUtil.calculateTwoDateDiffCeil(expiredAt, now) > 0; +// } +// return false; + return DateUtil.calculateTwoDateDiffCeil(expiredAt, now) > 0; } /** - * 만료일까지 남은 일수를 계산합니다. 만료일이 설정되지 않았을 경우 {@code null}을 반환합니다. + * 만료일까지 남은 일수를 계산합니다. * * @param now 현재 시간을 나타내는 {@code LocalDateTime} 객체 - * @return 만료일까지 남은 일수를 일 단위로 반환합니다. 만료일이 설정되지 않았을 경우 {@code null} 반환. - */ - public @Nullable Long getDaysUntilExpiration(LocalDateTime now) { - if (isSetExpiredAt()) { - return DateUtil.calculateTwoDateDiffCeil(expiredAt, now); - } - return null; + * @return 만료일까지 남은 일수를 일 단위로 반환합니다. + */ + public Long getDaysUntilExpiration(LocalDateTime now) { +// if (isSetExpiredAt()) { +// return DateUtil.calculateTwoDateDiffCeil(expiredAt, now); +// } +// return null; + return DateUtil.calculateTwoDateDiffCeil(expiredAt, now); } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java index 977efa46b..8f8f18607 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/LentMapper.java @@ -56,6 +56,6 @@ ActiveLentHistoryDto toActiveLentHistoryDto(LentHistory lentHistory, User user, Cabinet cabinet, Boolean isExpired, - @Nullable Long daysLeftFromExpireDate + Long daysFromExpireDate ); } diff --git a/backend/src/test/java/org/ftclub/cabinet/lent/domain/LentHistoryTest.java b/backend/src/test/java/org/ftclub/cabinet/lent/domain/LentHistoryTest.java index 92a8fdd31..8d79e6540 100644 --- a/backend/src/test/java/org/ftclub/cabinet/lent/domain/LentHistoryTest.java +++ b/backend/src/test/java/org/ftclub/cabinet/lent/domain/LentHistoryTest.java @@ -33,13 +33,13 @@ void isCabinetIdEqual() { assertFalse(lentHistory.isCabinetIdEqual(2L)); } - @Test - void isSetExpiredAt() { - LocalDateTime now = LocalDateTime.now(); - LentHistory lentHistory = LentHistory.of(now, now.plusDays(3), 1L, 1L); - assertTrue(lentHistory.isSetExpiredAt()); - assertThrows(DomainException.class, () -> LentHistory.of(now, null, 1L, 1L)); - } +// @Test +// void isSetExpiredAt() { +// LocalDateTime now = LocalDateTime.now(); +// LentHistory lentHistory = LentHistory.of(now, now.plusDays(3), 1L, 1L); +// assertTrue(lentHistory.isSetExpiredAt()); +// assertThrows(DomainException.class, () -> LentHistory.of(now, null, 1L, 1L)); +// } @Test void isSetEndedAt() { From 425f1f55cc2061bd31eb51c07e4c0142c8a84f5d Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 16:15:12 +0900 Subject: [PATCH 0604/1029] =?UTF-8?q?[FE]=20FIX:=20=EA=B8=B0=EC=A1=B4=20Mu?= =?UTF-8?q?ltiToggleSwitchSeparated=EC=9D=98=20=EC=9E=90=EB=B0=94=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A1=9C=20css?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EB=8C=80=EC=8B=A0=20styled=20component?= =?UTF-8?q?=EB=A1=9C=20css=20=EB=B3=80=EA=B2=BD=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B0=94=EA=BF=88#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/ThemeColorCard/DarkMode/DarkMode.tsx | 2 +- .../Common/MultiToggleSwitchSeparated.tsx | 70 +++++++------------ 2 files changed, 28 insertions(+), 44 deletions(-) diff --git a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx index 779a709f2..c0375c86c 100644 --- a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx +++ b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx @@ -74,7 +74,7 @@ const DarkMode = () => { <> { - initialState: T; + state: T; setState: React.Dispatch>; toggleList: toggleItemSeparated[]; buttonHeight?: string; @@ -16,46 +16,19 @@ interface MultiToggleSwitchProps { } const MultiToggleSwitchSeparated = ({ - initialState, setState, toggleList, buttonHeight, buttonWidth, + state, }: MultiToggleSwitchProps) => { - const wrapperRef = useRef(null); - - useEffect(() => { - const buttons = wrapperRef.current?.querySelectorAll("button"); - - buttons?.forEach((button) => { - if (button.id === `${initialState}`) { - button.style.color = "var(--bg-color)"; - button.style.backgroundColor = "var(--main-color)"; - } - }); - }, [initialState]); - - function switchToggle(e: any) { - const target = e.target as HTMLButtonElement; - - if (target === e.currentTarget) return; - - // setPage(0); - const buttons = wrapperRef.current?.querySelectorAll("button"); - - buttons?.forEach((button) => { - button.style.color = "var(--normal-text-color)"; - button.style.backgroundColor = "var(--shared-gray-color-100)"; - }); - - target.style.color = "var(--bg-color)"; - target.style.backgroundColor = "var(--main-color)"; - - setState(target.id as React.SetStateAction); - } + const switchToggle = (itemKey: string) => { + if (state === itemKey) return; + setState(itemKey as React.SetStateAction); + }; return ( - + {toggleList.map((item) => { const ColorThemeIcon = item.icon; return ( @@ -65,12 +38,10 @@ const MultiToggleSwitchSeparated = ({ buttonHeight={buttonHeight} buttonWidth={buttonWidth} icon={ColorThemeIcon} + isClicked={state === item.key} + onClick={() => switchToggle(item.key)} > - {ColorThemeIcon && ( - - - - )} + {ColorThemeIcon && } {item.name} ); @@ -91,6 +62,7 @@ const ButtonStyled = styled.button<{ buttonHeight?: string; buttonWidth?: string; icon?: React.ComponentType>; + isClicked: boolean; }>` display: flex; justify-content: ${(props) => (props.icon ? "space-between" : "center")}; @@ -104,10 +76,22 @@ const ButtonStyled = styled.button<{ height: ${(props) => (props.buttonHeight ? props.buttonHeight : "30px")}; font-weight: 500; background-color: var(--shared-gray-color-100); + background-color: ${(props) => + props.isClicked ? "var(--main-color)" : "var(--shared-gray-color-100)"}; color: var(--normal-text-color); - padding: ${(props) => (props.icon ? "12px 0 16px 0" : "4px 12px")}; ; -`; + color: ${(props) => + props.isClicked ? "var(--bg-color)" : "var(--normal-text-color)"}; + padding: ${(props) => (props.icon ? "12px 0 16px 0" : "4px 12px")}; -const ColorThemeIconStyled = styled.div``; + & > svg { + width: 30px; + height: 30px; + } + + & > svg > path { + stroke: ${(props) => + props.isClicked ? "var(--bg-color)" : "var(--normal-text-color)"}; + } +`; export default MultiToggleSwitchSeparated; From 78445e585f03fbe612bb1b421b09eef4f13e52b6 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 17:11:37 +0900 Subject: [PATCH 0605/1029] =?UTF-8?q?[FE]=20FIX:=20ColorThemeType=EC=97=90?= =?UTF-8?q?=20=EB=A7=9E=EA=B2=8C=20=EB=8C=80=EB=AC=B8=EC=9E=90=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/data/ColorTheme.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/assets/data/ColorTheme.ts b/frontend/src/assets/data/ColorTheme.ts index a5aa8f2f8..fa61f6cc2 100644 --- a/frontend/src/assets/data/ColorTheme.ts +++ b/frontend/src/assets/data/ColorTheme.ts @@ -94,8 +94,7 @@ const darkValues = css` export const GlobalStyle = createGlobalStyle` :root { ${lightValues} - - [color-theme="dark"] { + [color-theme="DARK"] { ${darkValues} } } From e59d47b329667b21c8aa08e6426f32cfa7c50fb8 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 17:12:50 +0900 Subject: [PATCH 0606/1029] =?UTF-8?q?[FE]=20FEAT:=20=ED=86=A0=EA=B8=80=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EB=88=84=EB=A5=BC=EB=95=8C=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=20=ED=95=B4=EB=8B=B9=20=ED=86=A0=EA=B8=80=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=EC=9D=91=ED=95=98=EB=8A=94=20=ED=85=8C=EB=A7=88?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/ThemeColorCard/DarkMode/DarkMode.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx index c0375c86c..48980ca97 100644 --- a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx +++ b/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx @@ -48,12 +48,6 @@ const DarkMode = () => { : ColorThemeToggleType.DEVICE ); - // const onClickHandler = () => { - // setDarkMode((prev) => { - // return prev === "light" ? "dark" : "light"; - // }); - // }; - useEffect(() => { darkModeQuery.addEventListener("change", (event) => setDarkMode(event.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT) @@ -66,8 +60,17 @@ const DarkMode = () => { }, [darkMode]); useEffect(() => { - document.body.setAttribute("color-theme", darkMode); localStorage.setItem("color-theme-toggle", toggleType); + + if (toggleType === ColorThemeToggleType.LIGHT) { + setDarkMode(ColorThemeType.LIGHT); + } else if (toggleType === ColorThemeToggleType.DARK) { + setDarkMode(ColorThemeType.DARK); + } else { + setDarkMode( + darkModeQuery.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT + ); + } }, [toggleType]); return ( From d780e5446671324cc5cd5e66f0a04e5f583b0f7e Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 17:13:42 +0900 Subject: [PATCH 0607/1029] =?UTF-8?q?[FE]=20FIX:=20=EB=B0=B0=EA=B2=BD=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=ED=85=8D=EC=8A=A4=ED=8A=B8=EB=8A=94=20--t?= =?UTF-8?q?ext-with-bg-color=20=EC=82=AC=EC=9A=A9#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Common/MultiToggleSwitchSeparated.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx index f61b987f5..a4abeb531 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx @@ -75,12 +75,10 @@ const ButtonStyled = styled.button<{ font-size: 1rem; height: ${(props) => (props.buttonHeight ? props.buttonHeight : "30px")}; font-weight: 500; - background-color: var(--shared-gray-color-100); background-color: ${(props) => props.isClicked ? "var(--main-color)" : "var(--shared-gray-color-100)"}; - color: var(--normal-text-color); color: ${(props) => - props.isClicked ? "var(--bg-color)" : "var(--normal-text-color)"}; + props.isClicked ? "var(--text-with-bg-color)" : "var(--normal-text-color)"}; padding: ${(props) => (props.icon ? "12px 0 16px 0" : "4px 12px")}; & > svg { @@ -90,7 +88,9 @@ const ButtonStyled = styled.button<{ & > svg > path { stroke: ${(props) => - props.isClicked ? "var(--bg-color)" : "var(--normal-text-color)"}; + props.isClicked + ? "var(--text-with-bg-color)" + : "var(--normal-text-color)"}; } `; From e61be2c674277da68ad8b81c2ec1f0ad92612f16 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 17:19:40 +0900 Subject: [PATCH 0608/1029] =?UTF-8?q?[FE]=20FIX:=20=EB=B0=B0=EA=B2=BD=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=ED=85=8D=EC=8A=A4=ED=8A=B8=EB=8A=94=20--t?= =?UTF-8?q?ext-with-bg-color=20=EC=82=AC=EC=9A=A9#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/AvailablePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/AvailablePage.tsx b/frontend/src/pages/AvailablePage.tsx index caec21bdf..69187bc1d 100644 --- a/frontend/src/pages/AvailablePage.tsx +++ b/frontend/src/pages/AvailablePage.tsx @@ -226,7 +226,7 @@ const RefreshButtonStyled = styled.button` } & > svg > path { - stroke: var(--normal-text-color); + stroke: var(--text-with-bg-color); } &:hover { From 07dbd1de7e57124d00f17e88929594e20c1a5f5f Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 17:43:01 +0900 Subject: [PATCH 0609/1029] =?UTF-8?q?[FE]=20FEAT:=20=ED=85=8C=EB=A7=88=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=AC=EC=97=90=EC=84=9C=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=94=94=EB=A0=89=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=EB=AA=85=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=83=9D=EC=84=B1=20#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ThemeColorCard => DisplayStyleCard}/ColorPicker.tsx | 0 .../DarkMode/DarkMode.tsx | 0 .../Card/DisplayStyleCard/DisplayStyleCard.container.tsx | 0 .../components/Card/DisplayStyleCard/DisplayStyleCard.tsx | 0 .../ThemeColorCard.container.tsx | 3 ++- .../{ThemeColorCard => DisplayStyleCard}/ThemeColorCard.tsx | 6 +++--- .../Card/{ThemeColorCard => DisplayStyleCard}/colorInfo.ts | 0 frontend/src/components/TopNav/TopNav.tsx | 2 +- frontend/src/pages/ProfilePage.tsx | 2 +- 9 files changed, 7 insertions(+), 6 deletions(-) rename frontend/src/components/Card/{ThemeColorCard => DisplayStyleCard}/ColorPicker.tsx (100%) rename frontend/src/components/Card/{ThemeColorCard => DisplayStyleCard}/DarkMode/DarkMode.tsx (100%) create mode 100644 frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx create mode 100644 frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx rename frontend/src/components/Card/{ThemeColorCard => DisplayStyleCard}/ThemeColorCard.container.tsx (96%) rename frontend/src/components/Card/{ThemeColorCard => DisplayStyleCard}/ThemeColorCard.tsx (94%) rename frontend/src/components/Card/{ThemeColorCard => DisplayStyleCard}/colorInfo.ts (100%) diff --git a/frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx b/frontend/src/components/Card/DisplayStyleCard/ColorPicker.tsx similarity index 100% rename from frontend/src/components/Card/ThemeColorCard/ColorPicker.tsx rename to frontend/src/components/Card/DisplayStyleCard/ColorPicker.tsx diff --git a/frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx b/frontend/src/components/Card/DisplayStyleCard/DarkMode/DarkMode.tsx similarity index 100% rename from frontend/src/components/Card/ThemeColorCard/DarkMode/DarkMode.tsx rename to frontend/src/components/Card/DisplayStyleCard/DarkMode/DarkMode.tsx diff --git a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx b/frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.container.tsx similarity index 96% rename from frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx rename to frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.container.tsx index 74838244c..a2d24bf01 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.container.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.container.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import ThemeColorCard from "@/components/Card/ThemeColorCard/ThemeColorCard"; +import ThemeColorCard from "@/components/Card/DisplayStyleCard/ThemeColorCard"; import ColorType from "@/types/enum/color.type.enum"; const ThemeColorCardContainer = () => { @@ -50,6 +50,7 @@ const ThemeColorCardContainer = () => { "var(--default-sub-color)", "var(--default-mine-color)" ); + // TODO : 컬러 테마 디바이스로 }; const handleSave = () => { diff --git a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx b/frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.tsx similarity index 94% rename from frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx rename to frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.tsx index 70b6be1ae..2010dc196 100644 --- a/frontend/src/components/Card/ThemeColorCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.tsx @@ -5,12 +5,12 @@ import { CardContentWrapper, ContentInfoStyled, } from "@/components/Card/CardStyles"; -import ColorPicker from "@/components/Card/ThemeColorCard/ColorPicker"; -import DarkMode from "@/components/Card/ThemeColorCard/DarkMode/DarkMode"; +import ColorPicker from "@/components/Card/DisplayStyleCard/ColorPicker"; +import DarkMode from "@/components/Card/DisplayStyleCard/DarkMode/DarkMode"; import { customColors, themeColorData, -} from "@/components/Card/ThemeColorCard/colorInfo"; +} from "@/components/Card/DisplayStyleCard/colorInfo"; interface ThemeColorProps { showColorPicker: boolean; diff --git a/frontend/src/components/Card/ThemeColorCard/colorInfo.ts b/frontend/src/components/Card/DisplayStyleCard/colorInfo.ts similarity index 100% rename from frontend/src/components/Card/ThemeColorCard/colorInfo.ts rename to frontend/src/components/Card/DisplayStyleCard/colorInfo.ts diff --git a/frontend/src/components/TopNav/TopNav.tsx b/frontend/src/components/TopNav/TopNav.tsx index 686ae9f79..dd41c9da1 100644 --- a/frontend/src/components/TopNav/TopNav.tsx +++ b/frontend/src/components/TopNav/TopNav.tsx @@ -1,7 +1,7 @@ import React from "react"; import { SetterOrUpdater } from "recoil"; import styled from "styled-components"; -import DarkMode from "@/components/Card/ThemeColorCard/DarkMode/DarkMode"; +import DarkMode from "@/components/Card/DisplayStyleCard/DarkMode/DarkMode"; import SearchBar from "@/components/TopNav/SearchBar/SearchBar"; import TopNavButtonGroup from "@/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index 2c1bc2e91..462634c40 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -6,11 +6,11 @@ import { useEffect, useState } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; import styled from "styled-components"; import { userState } from "@/recoil/atoms"; +import ThemeColorCardContainer from "@/components/Card/DisplayStyleCard/ThemeColorCard.container"; import ExtensionCardContainer from "@/components/Card/ExtensionCard/ExtensionCard.container"; import LentInfoCardContainer from "@/components/Card/LentInfoCard/LentInfoCard.container"; import NotificationCardContainer from "@/components/Card/NotificationCard/NotificationCard.container"; import ProfileCardContainer from "@/components/Card/ProfileCard/ProfileCard.container"; -import ThemeColorCardContainer from "@/components/Card/ThemeColorCard/ThemeColorCard.container"; import LoadingAnimation from "@/components/Common/LoadingAnimation"; import { axiosMyInfo, axiosUpdateDeviceToken } from "@/api/axios/axios.custom"; import { deleteRecoilPersistFloorSection } from "@/utils/recoilPersistUtils"; From cd4dfea95b01704dd895cb69efe0881d385996e3 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 19:00:49 +0900 Subject: [PATCH 0610/1029] =?UTF-8?q?[FE]=20FIX:=20customColors=EC=9D=98?= =?UTF-8?q?=20=EC=8B=A4=EC=A0=9C=20=EA=B0=92=20=EA=B5=AC=ED=95=98=EB=8A=94?= =?UTF-8?q?=20GetCustomColorsValues=20=ED=95=A8=EC=88=98=20=EB=A7=8C?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EC=84=9C=20customColors=20=EB=8C=80=EC=8B=A0?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/DisplayStyleCard/ColorPicker.tsx | 6 +++--- .../Card/DisplayStyleCard/ThemeColorCard.tsx | 6 +----- .../components/Card/DisplayStyleCard/colorInfo.ts | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Card/DisplayStyleCard/ColorPicker.tsx b/frontend/src/components/Card/DisplayStyleCard/ColorPicker.tsx index 58ed6a4eb..ddd556e68 100644 --- a/frontend/src/components/Card/DisplayStyleCard/ColorPicker.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/ColorPicker.tsx @@ -1,20 +1,20 @@ import { TwitterPicker } from "react-color"; import styled from "styled-components"; +import { GetCustomColorsValues } from "@/components/Card/DisplayStyleCard/colorInfo"; interface ColorPickerProps { color: string; onChange: (color: { hex: string }) => void; - customColors: string[]; } -const ColorPicker = ({ color, onChange, customColors }: ColorPickerProps) => { +const ColorPicker = ({ color, onChange }: ColorPickerProps) => { return ( handleChange(color, selectedColorType)} - customColors={customColors} /> )} diff --git a/frontend/src/components/Card/DisplayStyleCard/colorInfo.ts b/frontend/src/components/Card/DisplayStyleCard/colorInfo.ts index d71602447..b6e2ffa93 100644 --- a/frontend/src/components/Card/DisplayStyleCard/colorInfo.ts +++ b/frontend/src/components/Card/DisplayStyleCard/colorInfo.ts @@ -40,3 +40,17 @@ export const customColors = [ "var(--custom-purple-100)", "var(--custom-purple-200)", ]; + +export const GetCustomColorsValues = () => { + return customColors.map((color) => { + // NOTE : "var(--custom-pink)"에서 "--custom-pink" 추출 + const startIndex = color.indexOf("(") + 1; + const endIndex = color.lastIndexOf(")"); + const extractedValue = color.substring(startIndex, endIndex); + + // NOTE : var(--custom-pink)의 값 구하기 + return getComputedStyle(document.documentElement).getPropertyValue( + extractedValue + ); + }); +}; From 293b182237beb17dea050d37bb4f5edbd84e3294 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 19:08:51 +0900 Subject: [PATCH 0611/1029] =?UTF-8?q?[FE]=20FIX:=20theme=20color=EC=97=90?= =?UTF-8?q?=EC=84=9C=20point=20color=EB=A1=9C=20=EB=B3=80=EA=B2=BD#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => PointColor}/ColorPicker.tsx | 0 .../PointColor.container.tsx} | 8 ++++---- .../PointColor.tsx} | 14 +++++++------- .../components/Card/DisplayStyleCard/colorInfo.ts | 2 +- frontend/src/pages/ProfilePage.tsx | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) rename frontend/src/components/Card/DisplayStyleCard/{ => PointColor}/ColorPicker.tsx (100%) rename frontend/src/components/Card/DisplayStyleCard/{ThemeColorCard.container.tsx => PointColor/PointColor.container.tsx} (95%) rename frontend/src/components/Card/DisplayStyleCard/{ThemeColorCard.tsx => PointColor/PointColor.tsx} (91%) diff --git a/frontend/src/components/Card/DisplayStyleCard/ColorPicker.tsx b/frontend/src/components/Card/DisplayStyleCard/PointColor/ColorPicker.tsx similarity index 100% rename from frontend/src/components/Card/DisplayStyleCard/ColorPicker.tsx rename to frontend/src/components/Card/DisplayStyleCard/PointColor/ColorPicker.tsx diff --git a/frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.container.tsx b/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.container.tsx similarity index 95% rename from frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.container.tsx rename to frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.container.tsx index a2d24bf01..569ba022c 100644 --- a/frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.container.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.container.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -import ThemeColorCard from "@/components/Card/DisplayStyleCard/ThemeColorCard"; +import PointColor from "@/components/Card/DisplayStyleCard/PointColor/PointColor"; import ColorType from "@/types/enum/color.type.enum"; -const ThemeColorCardContainer = () => { +const PointColorContainer = () => { const savedMainColor = localStorage.getItem("main-color") || "var(--default-main-color)"; const savedSubColor = @@ -103,7 +103,7 @@ const ThemeColorCardContainer = () => { ]); return ( - { ); }; -export default ThemeColorCardContainer; +export default PointColorContainer; diff --git a/frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.tsx b/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx similarity index 91% rename from frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.tsx rename to frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx index 6929291c1..bf0c9d2de 100644 --- a/frontend/src/components/Card/DisplayStyleCard/ThemeColorCard.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx @@ -5,11 +5,11 @@ import { CardContentWrapper, ContentInfoStyled, } from "@/components/Card/CardStyles"; -import ColorPicker from "@/components/Card/DisplayStyleCard/ColorPicker"; import DarkMode from "@/components/Card/DisplayStyleCard/DarkMode/DarkMode"; -import { themeColorData } from "@/components/Card/DisplayStyleCard/colorInfo"; +import ColorPicker from "@/components/Card/DisplayStyleCard/PointColor/ColorPicker"; +import { pointColorData } from "@/components/Card/DisplayStyleCard/colorInfo"; -interface ThemeColorProps { +interface PointColorProps { showColorPicker: boolean; handleChange: (mainColor: { hex: string }, type: string) => void; handleReset: () => void; @@ -22,7 +22,7 @@ interface ThemeColorProps { selectedColorType: string; } -const ThemeColorCard = ({ +const PointColor = ({ showColorPicker, handleChange, handleReset, @@ -33,7 +33,7 @@ const ThemeColorCard = ({ mineColor, handleColorButtonClick, selectedColorType, -}: ThemeColorProps) => { +}: PointColorProps) => { return ( <> {showColorPicker && } @@ -73,7 +73,7 @@ const ThemeColorCard = ({ - {themeColorData.map(({ title, type, getColor }) => ( + {pointColorData.map(({ title, type, getColor }) => ( string; } -export const themeColorData: ColorData[] = [ +export const pointColorData: ColorData[] = [ { title: "메인 컬러", type: ColorType.MAIN, diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index 462634c40..fd81682f6 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -3,10 +3,10 @@ import { requestFcmAndGetDeviceToken, } from "@/firebase/firebase-messaging-sw"; import { useEffect, useState } from "react"; -import { useRecoilState, useRecoilValue } from "recoil"; +import { useRecoilState } from "recoil"; import styled from "styled-components"; import { userState } from "@/recoil/atoms"; -import ThemeColorCardContainer from "@/components/Card/DisplayStyleCard/ThemeColorCard.container"; +import PointColorContainer from "@/components/Card/DisplayStyleCard/PointColor/PointColor.container"; import ExtensionCardContainer from "@/components/Card/ExtensionCard/ExtensionCard.container"; import LentInfoCardContainer from "@/components/Card/LentInfoCard/LentInfoCard.container"; import NotificationCardContainer from "@/components/Card/NotificationCard/NotificationCard.container"; @@ -56,7 +56,7 @@ const ProfilePage = () => { name={myInfo.name} unbannedAt={myInfo.unbannedAt} /> - + )} From ba3ae2374e40511e07c53e1f2adb7d42f1dbd6ce Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 19:54:26 +0900 Subject: [PATCH 0612/1029] =?UTF-8?q?[FE]=20FIX:=20DisplayStyleCard=20?= =?UTF-8?q?=EC=95=84=EB=9E=98=EC=97=90=20DarkMode=EC=99=80=20PointColor=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=EC=8B=9C=ED=82=B4#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DisplayStyleCard.container.tsx | 121 ++++++++++++++++++ .../DisplayStyleCard/DisplayStyleCard.tsx | 102 +++++++++++++++ .../PointColor/PointColor.container.tsx | 121 ------------------ .../PointColor/PointColor.tsx | 111 ++++------------ frontend/src/pages/ProfilePage.tsx | 4 +- 5 files changed, 248 insertions(+), 211 deletions(-) delete mode 100644 frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.container.tsx diff --git a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx index e69de29bb..815275032 100644 --- a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx @@ -0,0 +1,121 @@ +import { useEffect, useState } from "react"; +import DisplayStyleCard from "@/components/Card/DisplayStyleCard/DisplayStyleCard"; +import ColorType from "@/types/enum/color.type.enum"; + +const DisplayStyleCardContainer = () => { + const savedMainColor = + localStorage.getItem("main-color") || "var(--default-main-color)"; + const savedSubColor = + localStorage.getItem("sub-color") || "var(--default-sub-color)"; + const savedMineColor = + localStorage.getItem("mine-color") || "var(--default-mine-color)"; + + const [mainColor, setMainColor] = useState(savedMainColor); + const [subColor, setSubColor] = useState(savedSubColor); + const [mineColor, setMineColor] = useState(savedMineColor); + + const [showColorPicker, setShowColorPicker] = useState(false); + const root: HTMLElement = document.documentElement; + + const handleChange = (mainColor: { hex: string }, colorType: string) => { + const selectedColor: string = mainColor.hex; + if (colorType === ColorType.MAIN) { + setMainColor(selectedColor); + } else if (colorType === ColorType.SUB) { + setSubColor(selectedColor); + } else if (colorType === ColorType.MINE) { + setMineColor(selectedColor); + } + }; + + const setColorsAndLocalStorage = ( + main: string, + sub: string, + mine: string + ) => { + setMainColor(main); + setSubColor(sub); + setMineColor(mine); + root.style.setProperty("--main-color", main); + root.style.setProperty("--sub-color", sub); + root.style.setProperty("--mine-color", mine); + localStorage.setItem("main-color", main); + localStorage.setItem("sub-color", sub); + localStorage.setItem("mine-color", mine); + }; + + const handleReset = () => { + setColorsAndLocalStorage( + "var(--default-main-color)", + "var(--default-sub-color)", + "var(--default-mine-color)" + ); + // TODO : 컬러 테마 디바이스로 + }; + + const handleSave = () => { + setColorsAndLocalStorage(mainColor, subColor, mineColor); + toggleColorPicker(true); + }; + + const handleCancel = () => { + setColorsAndLocalStorage(savedMainColor, savedSubColor, savedMineColor); + toggleColorPicker(true); + }; + + const toggleColorPicker = (isChange: boolean) => { + if (isChange) setShowColorPicker(!showColorPicker); + }; + + const [selectedColorType, setSelectedColorType] = useState( + ColorType.MAIN + ); + + const handleColorButtonClick = (colorType: string) => { + setSelectedColorType(colorType); + setShowColorPicker(true); + }; + + useEffect(() => { + root.style.setProperty("--main-color", mainColor); + root.style.setProperty("--mine-color", mineColor); + const confirmBeforeUnload = (e: BeforeUnloadEvent) => { + if ( + mainColor !== savedMainColor || + subColor !== savedSubColor || + mineColor !== savedMineColor + ) { + e.returnValue = + "변경된 색상이 저장되지 않을 수 있습니다. 페이지를 나가시겠습니까?"; + } + }; + window.addEventListener("beforeunload", confirmBeforeUnload); + return () => { + window.removeEventListener("beforeunload", confirmBeforeUnload); + }; + }, [ + mainColor, + mineColor, + savedMainColor, + savedMineColor, + subColor, + savedSubColor, + ]); + + return ( + + ); +}; + +export default DisplayStyleCardContainer; diff --git a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx index e69de29bb..0628ae4f1 100644 --- a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx @@ -0,0 +1,102 @@ +import styled from "styled-components"; +import Card from "@/components/Card/Card"; +import { CardContentWrapper } from "@/components/Card/CardStyles"; +import DarkMode from "@/components/Card/DisplayStyleCard/DarkMode/DarkMode"; +import PointColor from "./PointColor/PointColor"; + +interface PointColorProps { + showColorPicker: boolean; + handleChange: (mainColor: { hex: string }, type: string) => void; + handleReset: () => void; + handleSave: () => void; + handleCancel: () => void; + mainColor: string; + subColor: string; + mineColor: string; + handleColorButtonClick: (colorType: string) => void; + selectedColorType: string; +} + +const DisplayStyleCard = ({ + showColorPicker, + handleChange, + handleReset, + handleSave, + handleCancel, + mainColor, + subColor, + mineColor, + handleColorButtonClick, + selectedColorType, +}: PointColorProps) => { + return ( + <> + {showColorPicker && } + + + <> + + + + + + + + + + + ); +}; + +const BackgroundOverlayStyled = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--bg-shadow-color-100); +`; + +const ThemeColorCardWrapper = styled.div` + z-index: 1; + align-self: start; +`; + +export default DisplayStyleCard; diff --git a/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.container.tsx b/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.container.tsx deleted file mode 100644 index 569ba022c..000000000 --- a/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.container.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useEffect, useState } from "react"; -import PointColor from "@/components/Card/DisplayStyleCard/PointColor/PointColor"; -import ColorType from "@/types/enum/color.type.enum"; - -const PointColorContainer = () => { - const savedMainColor = - localStorage.getItem("main-color") || "var(--default-main-color)"; - const savedSubColor = - localStorage.getItem("sub-color") || "var(--default-sub-color)"; - const savedMineColor = - localStorage.getItem("mine-color") || "var(--default-mine-color)"; - - const [mainColor, setMainColor] = useState(savedMainColor); - const [subColor, setSubColor] = useState(savedSubColor); - const [mineColor, setMineColor] = useState(savedMineColor); - - const [showColorPicker, setShowColorPicker] = useState(false); - const root: HTMLElement = document.documentElement; - - const handleChange = (mainColor: { hex: string }, colorType: string) => { - const selectedColor: string = mainColor.hex; - if (colorType === ColorType.MAIN) { - setMainColor(selectedColor); - } else if (colorType === ColorType.SUB) { - setSubColor(selectedColor); - } else if (colorType === ColorType.MINE) { - setMineColor(selectedColor); - } - }; - - const setColorsAndLocalStorage = ( - main: string, - sub: string, - mine: string - ) => { - setMainColor(main); - setSubColor(sub); - setMineColor(mine); - root.style.setProperty("--main-color", main); - root.style.setProperty("--sub-color", sub); - root.style.setProperty("--mine-color", mine); - localStorage.setItem("main-color", main); - localStorage.setItem("sub-color", sub); - localStorage.setItem("mine-color", mine); - }; - - const handleReset = () => { - setColorsAndLocalStorage( - "var(--default-main-color)", - "var(--default-sub-color)", - "var(--default-mine-color)" - ); - // TODO : 컬러 테마 디바이스로 - }; - - const handleSave = () => { - setColorsAndLocalStorage(mainColor, subColor, mineColor); - toggleColorPicker(true); - }; - - const handleCancel = () => { - setColorsAndLocalStorage(savedMainColor, savedSubColor, savedMineColor); - toggleColorPicker(true); - }; - - const toggleColorPicker = (isChange: boolean) => { - if (isChange) setShowColorPicker(!showColorPicker); - }; - - const [selectedColorType, setSelectedColorType] = useState( - ColorType.MAIN - ); - - const handleColorButtonClick = (colorType: string) => { - setSelectedColorType(colorType); - setShowColorPicker(true); - }; - - useEffect(() => { - root.style.setProperty("--main-color", mainColor); - root.style.setProperty("--mine-color", mineColor); - const confirmBeforeUnload = (e: BeforeUnloadEvent) => { - if ( - mainColor !== savedMainColor || - subColor !== savedSubColor || - mineColor !== savedMineColor - ) { - e.returnValue = - "변경된 색상이 저장되지 않을 수 있습니다. 페이지를 나가시겠습니까?"; - } - }; - window.addEventListener("beforeunload", confirmBeforeUnload); - return () => { - window.removeEventListener("beforeunload", confirmBeforeUnload); - }; - }, [ - mainColor, - mineColor, - savedMainColor, - savedMineColor, - subColor, - savedSubColor, - ]); - - return ( - - ); -}; - -export default PointColorContainer; diff --git a/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx b/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx index bf0c9d2de..547df74d2 100644 --- a/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx @@ -1,20 +1,14 @@ import styled from "styled-components"; -import Card from "@/components/Card/Card"; import { CardContentStyled, - CardContentWrapper, ContentInfoStyled, } from "@/components/Card/CardStyles"; -import DarkMode from "@/components/Card/DisplayStyleCard/DarkMode/DarkMode"; import ColorPicker from "@/components/Card/DisplayStyleCard/PointColor/ColorPicker"; import { pointColorData } from "@/components/Card/DisplayStyleCard/colorInfo"; interface PointColorProps { showColorPicker: boolean; handleChange: (mainColor: { hex: string }, type: string) => void; - handleReset: () => void; - handleSave: () => void; - handleCancel: () => void; mainColor: string; subColor: string; mineColor: string; @@ -25,9 +19,6 @@ interface PointColorProps { const PointColor = ({ showColorPicker, handleChange, - handleReset, - handleSave, - handleCancel, mainColor, subColor, mineColor, @@ -36,89 +27,33 @@ const PointColor = ({ }: PointColorProps) => { return ( <> - {showColorPicker && } - - - <> - - - - - {pointColorData.map(({ title, type, getColor }) => ( - - handleColorButtonClick(type)} - > - {title} - - handleColorButtonClick(type)} - color={getColor({ mainColor, subColor, mineColor })} - isSelected={type === selectedColorType && showColorPicker} - showColorPicker={showColorPicker} - /> - - ))} - {showColorPicker && ( - handleChange(color, selectedColorType)} - /> - )} - - - - + {pointColorData.map(({ title, type, getColor }) => ( + + handleColorButtonClick(type)} + > + {title} + + handleColorButtonClick(type)} + color={getColor({ mainColor, subColor, mineColor })} + isSelected={type === selectedColorType && showColorPicker} + showColorPicker={showColorPicker} + /> + + ))} + {showColorPicker && ( + handleChange(color, selectedColorType)} + /> + )} ); }; -const BackgroundOverlayStyled = styled.div` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--bg-shadow-color-100); - z-index: 1; -`; - -const ThemeColorCardWrapper = styled.div` - z-index: 1; - align-self: start; -`; - const ColorButtonStyled = styled.button<{ color: string; isSelected: boolean; diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index fd81682f6..4c3a64ed4 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -6,7 +6,7 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { userState } from "@/recoil/atoms"; -import PointColorContainer from "@/components/Card/DisplayStyleCard/PointColor/PointColor.container"; +import DisplayStyleCardContainer from "@/components/Card/DisplayStyleCard/DisplayStyleCard.container"; import ExtensionCardContainer from "@/components/Card/ExtensionCard/ExtensionCard.container"; import LentInfoCardContainer from "@/components/Card/LentInfoCard/LentInfoCard.container"; import NotificationCardContainer from "@/components/Card/NotificationCard/NotificationCard.container"; @@ -56,8 +56,8 @@ const ProfilePage = () => { name={myInfo.name} unbannedAt={myInfo.unbannedAt} /> - + )} From 1893318fb937336dcc70beb3549f2af4619f5fc0 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 20:46:44 +0900 Subject: [PATCH 0613/1029] =?UTF-8?q?[FE]=20FEAT:=20handleColorThemeButton?= =?UTF-8?q?Click=20=EC=9D=B4=EC=9A=A9=ED=95=B4=EC=84=9C=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=AC=20=ED=85=8C=EB=A7=88=20=EB=B2=84=ED=8A=BC=20=EB=88=84?= =?UTF-8?q?=EB=A5=B4=EB=A9=B4=20=ED=99=94=EB=A9=B4=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EC=84=A4=EC=A0=95=20=EB=AA=A8=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=84=ED=99=98#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DisplayStyleCard/DarkMode/DarkMode.tsx | 128 +++++++------ .../DisplayStyleCard.container.tsx | 76 +++++++- .../DisplayStyleCard/DisplayStyleCard.tsx | 26 ++- .../PointColor/PointColor.tsx | 8 +- .../Common/MultiToggleSwitchSeparated.tsx | 176 +++++++++--------- frontend/src/pages/ProfilePage.tsx | 4 +- 6 files changed, 252 insertions(+), 166 deletions(-) diff --git a/frontend/src/components/Card/DisplayStyleCard/DarkMode/DarkMode.tsx b/frontend/src/components/Card/DisplayStyleCard/DarkMode/DarkMode.tsx index 48980ca97..8e724b76c 100644 --- a/frontend/src/components/Card/DisplayStyleCard/DarkMode/DarkMode.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/DarkMode/DarkMode.tsx @@ -2,14 +2,8 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { darkModeState } from "@/recoil/atoms"; -import MultiToggleSwitchSeparated, { - toggleItemSeparated, -} from "@/components/Common/MultiToggleSwitchSeparated"; import { colorThemeToggleIconComponentMap } from "@/assets/data/maps"; -import { - ColorThemeToggleType, - ColorThemeType, -} from "@/types/enum/colorTheme.type.enum"; +import { ColorThemeToggleType } from "@/types/enum/colorTheme.type.enum"; // TODO : DarkMode 파일 폴더명 ColorTheme으로 변경 @@ -31,58 +25,39 @@ const toggleList: toggleItemSeparated[] = [ }, ]; -const DarkMode = () => { - const savedColorTheme = localStorage.getItem("color-theme"); - const savedColorThemeToggle = localStorage.getItem("color-theme-toggle"); - var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); - const [darkMode, setDarkMode] = useState( - savedColorTheme - ? (savedColorTheme as ColorThemeType) - : darkModeQuery.matches - ? ColorThemeType.DARK - : ColorThemeType.LIGHT - ); - const [toggleType, setToggleType] = useState( - savedColorThemeToggle - ? (savedColorThemeToggle as ColorThemeToggleType) - : ColorThemeToggleType.DEVICE - ); - - useEffect(() => { - darkModeQuery.addEventListener("change", (event) => - setDarkMode(event.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT) - ); - }, []); - - useEffect(() => { - document.body.setAttribute("color-theme", darkMode); - localStorage.setItem("color-theme", darkMode); - }, [darkMode]); - - useEffect(() => { - localStorage.setItem("color-theme-toggle", toggleType); - - if (toggleType === ColorThemeToggleType.LIGHT) { - setDarkMode(ColorThemeType.LIGHT); - } else if (toggleType === ColorThemeToggleType.DARK) { - setDarkMode(ColorThemeType.DARK); - } else { - setDarkMode( - darkModeQuery.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT - ); - } - }, [toggleType]); +export interface toggleItemSeparated { + name: string; + key: string; + icon?: React.ComponentType>; +} +const DarkMode = ({ + colorThemeToggle, + handleColorThemeButtonClick, +}: { + colorThemeToggle: ColorThemeToggleType; + handleColorThemeButtonClick: (colorThemeToggleType: string) => void; +}) => { return ( <> - + + {toggleList.map((item) => { + const ColorThemeIcon = item.icon; + return ( + handleColorThemeButtonClick(item.key)} + > + {ColorThemeIcon && } + {item.name} + + ); + })} + ); @@ -94,4 +69,47 @@ const ButtonsWrapperStyled = styled.div` padding: 0 16px; `; +const WrapperStyled = styled.div` + width: 100%; + display: flex; + align-items: center; + border-radius: 10px; + justify-content: space-between; +`; + +const ButtonStyled = styled.button<{ + buttonWidth?: string; + icon?: React.ComponentType>; + isClicked: boolean; +}>` + display: flex; + justify-content: ${(props) => (props.icon ? "space-between" : "center")}; + align-items: center; + flex-direction: ${(props) => (props.icon ? "column" : "row")}; + min-width: 50px; + width: 90px; + min-width: 50px; + border-radius: 10px; + font-size: 1rem; + height: 90px; + font-weight: 500; + background-color: ${(props) => + props.isClicked ? "var(--main-color)" : "var(--shared-gray-color-100)"}; + color: ${(props) => + props.isClicked ? "var(--text-with-bg-color)" : "var(--normal-text-color)"}; + padding: ${(props) => (props.icon ? "12px 0 16px 0" : "4px 12px")}; + + & > svg { + width: 30px; + height: 30px; + } + + & > svg > path { + stroke: ${(props) => + props.isClicked + ? "var(--text-with-bg-color)" + : "var(--normal-text-color)"}; + } +`; + export default DarkMode; diff --git a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx index 815275032..b70eb0c98 100644 --- a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx @@ -1,7 +1,12 @@ import { useEffect, useState } from "react"; import DisplayStyleCard from "@/components/Card/DisplayStyleCard/DisplayStyleCard"; import ColorType from "@/types/enum/color.type.enum"; +import { + ColorThemeToggleType, + ColorThemeType, +} from "@/types/enum/colorTheme.type.enum"; +// TODO: 포인트랑 테마 구분지을 수 있게 명명 const DisplayStyleCardContainer = () => { const savedMainColor = localStorage.getItem("main-color") || "var(--default-main-color)"; @@ -17,7 +22,14 @@ const DisplayStyleCardContainer = () => { const [showColorPicker, setShowColorPicker] = useState(false); const root: HTMLElement = document.documentElement; - const handleChange = (mainColor: { hex: string }, colorType: string) => { + const [selectedColorType, setSelectedColorType] = useState( + ColorType.MAIN + ); + + const handlePointColorChange = ( + mainColor: { hex: string }, + colorType: string + ) => { const selectedColor: string = mainColor.hex; if (colorType === ColorType.MAIN) { setMainColor(selectedColor); @@ -67,12 +79,16 @@ const DisplayStyleCardContainer = () => { if (isChange) setShowColorPicker(!showColorPicker); }; - const [selectedColorType, setSelectedColorType] = useState( - ColorType.MAIN - ); + const handlePointColorButtonClick = (pointColorType: string) => { + setSelectedColorType(pointColorType); + setShowColorPicker(true); + }; - const handleColorButtonClick = (colorType: string) => { - setSelectedColorType(colorType); + const handleColorThemeButtonClick = (colorThemeToggleType: string) => { + if (toggleType === colorThemeToggleType) return; + setToggleType( + colorThemeToggleType as React.SetStateAction + ); setShowColorPicker(true); }; @@ -102,18 +118,62 @@ const DisplayStyleCardContainer = () => { savedSubColor, ]); + const savedColorTheme = localStorage.getItem("color-theme"); + const savedColorThemeToggle = localStorage.getItem("color-theme-toggle"); + var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const [darkMode, setDarkMode] = useState( + savedColorTheme + ? (savedColorTheme as ColorThemeType) + : darkModeQuery.matches + ? ColorThemeType.DARK + : ColorThemeType.LIGHT + ); + const [toggleType, setToggleType] = useState( + savedColorThemeToggle + ? (savedColorThemeToggle as ColorThemeToggleType) + : ColorThemeToggleType.DEVICE + ); + + useEffect(() => { + darkModeQuery.addEventListener("change", (event) => + setDarkMode(event.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT) + ); + }, []); + + useEffect(() => { + document.body.setAttribute("color-theme", darkMode); + localStorage.setItem("color-theme", darkMode); + }, [darkMode]); + + useEffect(() => { + if (!showColorPicker) setShowColorPicker(true); + localStorage.setItem("color-theme-toggle", toggleType); + + if (toggleType === ColorThemeToggleType.LIGHT) { + setDarkMode(ColorThemeType.LIGHT); + } else if (toggleType === ColorThemeToggleType.DARK) { + setDarkMode(ColorThemeType.DARK); + } else { + setDarkMode( + darkModeQuery.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT + ); + } + }, [toggleType]); + return ( ); }; diff --git a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx index 0628ae4f1..259ca4798 100644 --- a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx @@ -2,33 +2,38 @@ import styled from "styled-components"; import Card from "@/components/Card/Card"; import { CardContentWrapper } from "@/components/Card/CardStyles"; import DarkMode from "@/components/Card/DisplayStyleCard/DarkMode/DarkMode"; +import { ColorThemeToggleType } from "@/types/enum/colorTheme.type.enum"; import PointColor from "./PointColor/PointColor"; -interface PointColorProps { +interface DisplayStyleProps { showColorPicker: boolean; - handleChange: (mainColor: { hex: string }, type: string) => void; + handlePointColorChange: (mainColor: { hex: string }, type: string) => void; handleReset: () => void; handleSave: () => void; handleCancel: () => void; mainColor: string; subColor: string; mineColor: string; - handleColorButtonClick: (colorType: string) => void; + handlePointColorButtonClick: (colorType: string) => void; selectedColorType: string; + colorThemeToggle: ColorThemeToggleType; + handleColorThemeButtonClick: (colorThemeToggleType: string) => void; } const DisplayStyleCard = ({ showColorPicker, - handleChange, + handlePointColorChange, handleReset, handleSave, handleCancel, mainColor, subColor, mineColor, - handleColorButtonClick, + handlePointColorButtonClick, selectedColorType, -}: PointColorProps) => { + colorThemeToggle, + handleColorThemeButtonClick, +}: DisplayStyleProps) => { return ( <> {showColorPicker && } @@ -65,16 +70,19 @@ const DisplayStyleCard = ({ > <> - + diff --git a/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx b/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx index 547df74d2..75d4277a6 100644 --- a/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/PointColor/PointColor.tsx @@ -12,7 +12,7 @@ interface PointColorProps { mainColor: string; subColor: string; mineColor: string; - handleColorButtonClick: (colorType: string) => void; + handlePointColorButtonClick: (colorType: string) => void; selectedColorType: string; } @@ -22,7 +22,7 @@ const PointColor = ({ mainColor, subColor, mineColor, - handleColorButtonClick, + handlePointColorButtonClick, selectedColorType, }: PointColorProps) => { return ( @@ -32,12 +32,12 @@ const PointColor = ({ handleColorButtonClick(type)} + onClick={() => handlePointColorButtonClick(type)} > {title} handleColorButtonClick(type)} + onClick={() => handlePointColorButtonClick(type)} color={getColor({ mainColor, subColor, mineColor })} isSelected={type === selectedColorType && showColorPicker} showColorPicker={showColorPicker} diff --git a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx index a4abeb531..c56814041 100644 --- a/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/components/Common/MultiToggleSwitchSeparated.tsx @@ -1,97 +1,97 @@ -import React from "react"; -import styled from "styled-components"; +// import React from "react"; +// import styled from "styled-components"; -export interface toggleItemSeparated { - name: string; - key: string; - icon?: React.ComponentType>; -} +// export interface toggleItemSeparated { +// name: string; +// key: string; +// icon?: React.ComponentType>; +// } -interface MultiToggleSwitchProps { - state: T; - setState: React.Dispatch>; - toggleList: toggleItemSeparated[]; - buttonHeight?: string; - buttonWidth?: string; -} +// interface MultiToggleSwitchProps { +// state: T; +// setState: React.Dispatch>; +// toggleList: toggleItemSeparated[]; +// buttonHeight?: string; +// buttonWidth?: string; +// } -const MultiToggleSwitchSeparated = ({ - setState, - toggleList, - buttonHeight, - buttonWidth, - state, -}: MultiToggleSwitchProps) => { - const switchToggle = (itemKey: string) => { - if (state === itemKey) return; - setState(itemKey as React.SetStateAction); - }; +// const MultiToggleSwitchSeparated = ({ +// state, +// setState, +// toggleList, +// buttonHeight, +// buttonWidth, +// }: MultiToggleSwitchProps) => { +// const switchToggle = (itemKey: string) => { +// if (state === itemKey) return; +// setState(itemKey as React.SetStateAction); +// }; - return ( - - {toggleList.map((item) => { - const ColorThemeIcon = item.icon; - return ( - switchToggle(item.key)} - > - {ColorThemeIcon && } - {item.name} - - ); - })} - - ); -}; +// return ( +// +// {toggleList.map((item) => { +// const ColorThemeIcon = item.icon; +// return ( +// switchToggle(item.key)} +// > +// {ColorThemeIcon && } +// {item.name} +// +// ); +// })} +// +// ); +// }; -const WrapperStyled = styled.div` - width: 100%; - display: flex; - align-items: center; - border-radius: 10px; - justify-content: space-between; -`; +// const WrapperStyled = styled.div` +// width: 100%; +// display: flex; +// align-items: center; +// border-radius: 10px; +// justify-content: space-between; +// `; -const ButtonStyled = styled.button<{ - buttonHeight?: string; - buttonWidth?: string; - icon?: React.ComponentType>; - isClicked: boolean; -}>` - display: flex; - justify-content: ${(props) => (props.icon ? "space-between" : "center")}; - align-items: center; - flex-direction: ${(props) => (props.icon ? "column" : "row")}; - min-width: 50px; - width: ${(props) => (props.buttonWidth ? props.buttonWidth : "fit-content")}; - min-width: 50px; - border-radius: 10px; - font-size: 1rem; - height: ${(props) => (props.buttonHeight ? props.buttonHeight : "30px")}; - font-weight: 500; - background-color: ${(props) => - props.isClicked ? "var(--main-color)" : "var(--shared-gray-color-100)"}; - color: ${(props) => - props.isClicked ? "var(--text-with-bg-color)" : "var(--normal-text-color)"}; - padding: ${(props) => (props.icon ? "12px 0 16px 0" : "4px 12px")}; +// const ButtonStyled = styled.button<{ +// buttonHeight?: string; +// buttonWidth?: string; +// icon?: React.ComponentType>; +// isClicked: boolean; +// }>` +// display: flex; +// justify-content: ${(props) => (props.icon ? "space-between" : "center")}; +// align-items: center; +// flex-direction: ${(props) => (props.icon ? "column" : "row")}; +// min-width: 50px; +// width: ${(props) => (props.buttonWidth ? props.buttonWidth : "fit-content")}; +// min-width: 50px; +// border-radius: 10px; +// font-size: 1rem; +// height: ${(props) => (props.buttonHeight ? props.buttonHeight : "30px")}; +// font-weight: 500; +// background-color: ${(props) => +// props.isClicked ? "var(--main-color)" : "var(--shared-gray-color-100)"}; +// color: ${(props) => +// props.isClicked ? "var(--text-with-bg-color)" : "var(--normal-text-color)"}; +// padding: ${(props) => (props.icon ? "12px 0 16px 0" : "4px 12px")}; - & > svg { - width: 30px; - height: 30px; - } +// & > svg { +// width: 30px; +// height: 30px; +// } - & > svg > path { - stroke: ${(props) => - props.isClicked - ? "var(--text-with-bg-color)" - : "var(--normal-text-color)"}; - } -`; +// & > svg > path { +// stroke: ${(props) => +// props.isClicked +// ? "var(--text-with-bg-color)" +// : "var(--normal-text-color)"}; +// } +// `; -export default MultiToggleSwitchSeparated; +// export default MultiToggleSwitchSeparated; diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index 4c3a64ed4..e59f92755 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -68,11 +68,11 @@ const CardGridWrapper = styled.div` display: grid; padding: 60px 0; justify-content: center; - align-items: center; + align-items: start; width: 100%; grid-gap: 20px; grid-template-columns: 350px 350px; - grid-template-rows: 163px 183px 230px; + grid-template-rows: 163px 183px 348px; grid-template-areas: "profile lentInfo" // h: 163px h: 366px "extension lentInfo" // h: 183px "notification theme"; // h: 230px h: 230px; From 4ecaae3286242e1f3275fd59876e0ccca72cd355 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 20:55:18 +0900 Subject: [PATCH 0614/1029] =?UTF-8?q?[FE]=20FIX:=20ColorTheme=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ColorTheme.tsx} | 64 ++++++++----------- .../DisplayStyleCard/DisplayStyleCard.tsx | 4 +- frontend/src/components/TopNav/TopNav.tsx | 4 +- 3 files changed, 30 insertions(+), 42 deletions(-) rename frontend/src/components/Card/DisplayStyleCard/{DarkMode/DarkMode.tsx => ColorTheme/ColorTheme.tsx} (61%) diff --git a/frontend/src/components/Card/DisplayStyleCard/DarkMode/DarkMode.tsx b/frontend/src/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx similarity index 61% rename from frontend/src/components/Card/DisplayStyleCard/DarkMode/DarkMode.tsx rename to frontend/src/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx index 8e724b76c..015065c29 100644 --- a/frontend/src/components/Card/DisplayStyleCard/DarkMode/DarkMode.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx @@ -1,11 +1,15 @@ -import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { darkModeState } from "@/recoil/atoms"; import { colorThemeToggleIconComponentMap } from "@/assets/data/maps"; import { ColorThemeToggleType } from "@/types/enum/colorTheme.type.enum"; -// TODO : DarkMode 파일 폴더명 ColorTheme으로 변경 +// TODO : ColorTheme 파일 폴더명 ColorTheme으로 변경 +interface toggleItemSeparated { + name: string; + key: string; + icon: React.ComponentType>; +} const toggleList: toggleItemSeparated[] = [ { @@ -25,13 +29,7 @@ const toggleList: toggleItemSeparated[] = [ }, ]; -export interface toggleItemSeparated { - name: string; - key: string; - icon?: React.ComponentType>; -} - -const DarkMode = ({ +const ColorTheme = ({ colorThemeToggle, handleColorThemeButtonClick, }: { @@ -41,23 +39,20 @@ const DarkMode = ({ return ( <> - - {toggleList.map((item) => { - const ColorThemeIcon = item.icon; - return ( - handleColorThemeButtonClick(item.key)} - > - {ColorThemeIcon && } - {item.name} - - ); - })} - + {toggleList.map((item) => { + const ColorThemeIcon = item.icon; + return ( + handleColorThemeButtonClick(item.key)} + > + {ColorThemeIcon && } + {item.name} + + ); + })} ); @@ -66,26 +61,19 @@ const DarkMode = ({ const ButtonsWrapperStyled = styled.div` display: flex; justify-content: center; - padding: 0 16px; -`; - -const WrapperStyled = styled.div` - width: 100%; - display: flex; align-items: center; border-radius: 10px; justify-content: space-between; + padding: 0 16px; `; const ButtonStyled = styled.button<{ - buttonWidth?: string; - icon?: React.ComponentType>; isClicked: boolean; }>` display: flex; - justify-content: ${(props) => (props.icon ? "space-between" : "center")}; + justify-content: space-between; align-items: center; - flex-direction: ${(props) => (props.icon ? "column" : "row")}; + flex-direction: column; min-width: 50px; width: 90px; min-width: 50px; @@ -97,7 +85,7 @@ const ButtonStyled = styled.button<{ props.isClicked ? "var(--main-color)" : "var(--shared-gray-color-100)"}; color: ${(props) => props.isClicked ? "var(--text-with-bg-color)" : "var(--normal-text-color)"}; - padding: ${(props) => (props.icon ? "12px 0 16px 0" : "4px 12px")}; + padding: 12px 0 16px 0; & > svg { width: 30px; @@ -112,4 +100,4 @@ const ButtonStyled = styled.button<{ } `; -export default DarkMode; +export default ColorTheme; diff --git a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx index 259ca4798..96a5482db 100644 --- a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; import Card from "@/components/Card/Card"; import { CardContentWrapper } from "@/components/Card/CardStyles"; -import DarkMode from "@/components/Card/DisplayStyleCard/DarkMode/DarkMode"; +import ColorTheme from "@/components/Card/DisplayStyleCard/ColorTheme/ColorTheme"; import { ColorThemeToggleType } from "@/types/enum/colorTheme.type.enum"; import PointColor from "./PointColor/PointColor"; @@ -70,7 +70,7 @@ const DisplayStyleCard = ({ > <> - diff --git a/frontend/src/components/TopNav/TopNav.tsx b/frontend/src/components/TopNav/TopNav.tsx index dd41c9da1..57111676b 100644 --- a/frontend/src/components/TopNav/TopNav.tsx +++ b/frontend/src/components/TopNav/TopNav.tsx @@ -1,7 +1,7 @@ import React from "react"; import { SetterOrUpdater } from "recoil"; import styled from "styled-components"; -import DarkMode from "@/components/Card/DisplayStyleCard/DarkMode/DarkMode"; +import ColorTheme from "@/components/Card/DisplayStyleCard/ColorTheme/ColorTheme"; import SearchBar from "@/components/TopNav/SearchBar/SearchBar"; import TopNavButtonGroup from "@/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; import { ReactComponent as LogoImg } from "@/assets/images/logo.svg"; @@ -85,7 +85,7 @@ const TopNav: React.FC<{ - {/* */} + {/* */} {/* TODO : 임시 위치 */} {isAdmin && } From 8935f4261a71b03574bc22823ef2bae02fe7ea47 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Sat, 20 Apr 2024 21:45:00 +0900 Subject: [PATCH 0615/1029] =?UTF-8?q?[FE]=20FEAT:=20handleReset,=20handleS?= =?UTF-8?q?ave,=20handleCancel=EC=97=90=20=EC=BB=AC=EB=9F=AC=20=ED=85=8C?= =?UTF-8?q?=EB=A7=88=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ColorTheme/ColorTheme.tsx | 1 - .../DisplayStyleCard.container.tsx | 45 +++++++++---------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx b/frontend/src/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx index 015065c29..87faf8adb 100644 --- a/frontend/src/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx @@ -4,7 +4,6 @@ import { darkModeState } from "@/recoil/atoms"; import { colorThemeToggleIconComponentMap } from "@/assets/data/maps"; import { ColorThemeToggleType } from "@/types/enum/colorTheme.type.enum"; -// TODO : ColorTheme 파일 폴더명 ColorTheme으로 변경 interface toggleItemSeparated { name: string; key: string; diff --git a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx index b70eb0c98..f67c8614a 100644 --- a/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx +++ b/frontend/src/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx @@ -43,7 +43,8 @@ const DisplayStyleCardContainer = () => { const setColorsAndLocalStorage = ( main: string, sub: string, - mine: string + mine: string, + toggleType: ColorThemeToggleType ) => { setMainColor(main); setSubColor(sub); @@ -54,29 +55,33 @@ const DisplayStyleCardContainer = () => { localStorage.setItem("main-color", main); localStorage.setItem("sub-color", sub); localStorage.setItem("mine-color", mine); + + setToggleType(toggleType); + localStorage.setItem("color-theme-toggle", toggleType); }; const handleReset = () => { setColorsAndLocalStorage( "var(--default-main-color)", "var(--default-sub-color)", - "var(--default-mine-color)" + "var(--default-mine-color)", + ColorThemeToggleType.DEVICE ); - // TODO : 컬러 테마 디바이스로 }; const handleSave = () => { - setColorsAndLocalStorage(mainColor, subColor, mineColor); - toggleColorPicker(true); + setColorsAndLocalStorage(mainColor, subColor, mineColor, toggleType); + setShowColorPicker(!showColorPicker); }; const handleCancel = () => { - setColorsAndLocalStorage(savedMainColor, savedSubColor, savedMineColor); - toggleColorPicker(true); - }; - - const toggleColorPicker = (isChange: boolean) => { - if (isChange) setShowColorPicker(!showColorPicker); + setColorsAndLocalStorage( + savedMainColor, + savedSubColor, + savedMineColor, + savedColorThemeToggle + ); + setShowColorPicker(!showColorPicker); }; const handlePointColorButtonClick = (pointColorType: string) => { @@ -118,20 +123,16 @@ const DisplayStyleCardContainer = () => { savedSubColor, ]); - const savedColorTheme = localStorage.getItem("color-theme"); - const savedColorThemeToggle = localStorage.getItem("color-theme-toggle"); + const savedColorThemeToggle = + (localStorage.getItem("color-theme-toggle") as ColorThemeToggleType) || + ColorThemeToggleType.DEVICE; var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); const [darkMode, setDarkMode] = useState( - savedColorTheme - ? (savedColorTheme as ColorThemeType) - : darkModeQuery.matches - ? ColorThemeType.DARK - : ColorThemeType.LIGHT + ColorThemeType.LIGHT ); + const [toggleType, setToggleType] = useState( savedColorThemeToggle - ? (savedColorThemeToggle as ColorThemeToggleType) - : ColorThemeToggleType.DEVICE ); useEffect(() => { @@ -142,13 +143,9 @@ const DisplayStyleCardContainer = () => { useEffect(() => { document.body.setAttribute("color-theme", darkMode); - localStorage.setItem("color-theme", darkMode); }, [darkMode]); useEffect(() => { - if (!showColorPicker) setShowColorPicker(true); - localStorage.setItem("color-theme-toggle", toggleType); - if (toggleType === ColorThemeToggleType.LIGHT) { setDarkMode(ColorThemeType.LIGHT); } else if (toggleType === ColorThemeToggleType.DARK) { From 185da2da946a011f2b22aa3dc3ae931fe7e4f4a2 Mon Sep 17 00:00:00 2001 From: jiwon Date: Sun, 21 Apr 2024 21:38:46 +0900 Subject: [PATCH 0616/1029] =?UTF-8?q?[BE]=20HOTFIX:=20Release=20=EB=8F=99?= =?UTF-8?q?=EC=9E=91=20=EC=8B=9C=20=EB=B0=98=EB=82=A9=20=EB=82=A0=EC=A7=9C?= =?UTF-8?q?=EC=99=80=20=EB=AC=B4=EA=B4=80=ED=95=98=EA=B2=8C=20Available?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EB=8A=94=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/repository/CabinetRepository.java | 3 ++ .../cabinet/service/CabinetFacadeService.java | 2 +- .../cabinet/service/CabinetQueryService.java | 8 +--- .../cabinet/utils/release/ReleaseManager.java | 38 +++++++++++++++---- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 817907ad2..8efe292b2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -207,4 +207,7 @@ void updateStatusAndTitleAndMemoByCabinetIdsIn(@Param("cabinetIds") List c @Param("status") CabinetStatus status, @Param("title") String title, @Param("memo") String memo); + + + List findAllByStatus(CabinetStatus cabinetStatus); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index 3757bc685..7f34c4c49 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -367,7 +367,7 @@ public void updateStatus(Long cabinetId, CabinetStatus status) { cabinet.specifyStatus(status); } - @Transactional + public void updateStatus(List cabinetId, CabinetStatus status) { cabinetCommandService.updateStatusBulk(cabinetId, status); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java index bad86ed17..c29bfeab4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java @@ -1,6 +1,5 @@ package org.ftclub.cabinet.cabinet.service; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import javax.transaction.Transactional; @@ -205,12 +204,9 @@ public Page findAllByStatus(CabinetStatus status, Pageable pageable) { * 사물함 상태에 맞는 사물함들 중 from 이전에 대여기간이 만료되는 사물함을 가져옵니다. * * @param cabinetStatus 사물함 상태 - * @param from 조회할 시간 * @return 사물함 */ - public List findAllPendingCabinetsByCabinetStatusAndBeforeEndedAt( - CabinetStatus cabinetStatus, LocalDateTime from) { - return cabinetRepository.findAllCabinetsByCabinetStatusAndBeforeEndedAt(cabinetStatus, - from); + public List findAllPendingCabinets(CabinetStatus cabinetStatus) { + return cabinetRepository.findAllByStatus(cabinetStatus); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java index f20bd45a0..8c8e760a9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java @@ -2,15 +2,21 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; +import org.ftclub.cabinet.cabinet.service.CabinetCommandService; import org.ftclub.cabinet.cabinet.service.CabinetQueryService; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentQueryService; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @Component @RequiredArgsConstructor @@ -18,16 +24,32 @@ public class ReleaseManager { private final CabinetQueryService cabinetQueryService; - private final CabinetFacadeService cabinetFacadeService; + private final CabinetCommandService cabinetCommandService; + private final LentQueryService lentQueryService; - private List getAllPendedYesterdayCabinet() { - return cabinetQueryService.findAllPendingCabinetsByCabinetStatusAndBeforeEndedAt( - CabinetStatus.PENDING, LocalDateTime.from(LocalDate.now().atStartOfDay())); + private Set getAllPendedYesterdayCabinet() { + LocalDateTime from = LocalDateTime.from(LocalDate.now().atStartOfDay()); + List allPendingCabinets = cabinetQueryService.findAllPendingCabinets( + CabinetStatus.PENDING); + List cabinetIds = allPendingCabinets.stream() + .map(Cabinet::getId).collect(Collectors.toList()); + Set todayReturnedSet = lentQueryService.findCabinetLentHistories(cabinetIds) + .stream() + .filter(lh -> lh.getEndedAt().isAfter(from)) + .map(LentHistory::getCabinetId) + .collect(Collectors.toSet()); + + Set cabinetIdSet = new HashSet<>(cabinetIds); + cabinetIdSet.removeAll(todayReturnedSet); + return cabinetIdSet; } + @Transactional public void releasingCabinets() { - List cabinets = getAllPendedYesterdayCabinet(); - List cabinetIds = cabinets.stream().map(Cabinet::getId).collect(Collectors.toList()); - cabinetFacadeService.updateStatus(cabinetIds, CabinetStatus.AVAILABLE); + List cabinetIds = new ArrayList<>(getAllPendedYesterdayCabinet()); + if (cabinetIds.isEmpty()) { + return; + } + cabinetCommandService.updateStatusBulk(cabinetIds, CabinetStatus.AVAILABLE); } } From 21ea3df4791cd33cf3d76c826b5fc3f82f6ae114 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Mon, 22 Apr 2024 13:23:33 +0900 Subject: [PATCH 0617/1029] =?UTF-8?q?[BE]=20FIX:=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EC=97=90=EB=9F=AC=20(=ED=99=95=EC=9D=B8=EB=90=9C?= =?UTF-8?q?=20=EC=98=88=EC=99=B8)=EB=8A=94=20=EC=95=8C=EB=A6=BC=EC=9D=84?= =?UTF-8?q?=20=EB=B0=9C=EC=86=A1=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20#1602?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/ControllerException.java | 24 +++++++------- .../exception/CustomServiceException.java | 31 ++++++++++--------- .../cabinet/exception/DomainException.java | 2 +- .../exception/FtClubCabinetException.java | 9 ++++++ .../cabinet/exception/ServiceException.java | 30 +++++++++--------- .../cabinet/exception/UtilException.java | 5 +-- .../utils/scheduler/SystemScheduler.java | 10 ++++-- 7 files changed, 64 insertions(+), 47 deletions(-) create mode 100644 backend/src/main/java/org/ftclub/cabinet/exception/FtClubCabinetException.java diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ControllerException.java b/backend/src/main/java/org/ftclub/cabinet/exception/ControllerException.java index 40f4d340b..12ca50f37 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ControllerException.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ControllerException.java @@ -4,19 +4,19 @@ import lombok.Getter; @Getter -public class ControllerException extends RuntimeException { +public class ControllerException extends RuntimeException implements FtClubCabinetException { - final ExceptionStatus status; + final ExceptionStatus status; - /** - * @param status exception에 대한 정보에 대한 enum - */ - public ControllerException(ExceptionStatus status) { - this.status = status; - } + /** + * @param status exception에 대한 정보에 대한 enum + */ + public ControllerException(ExceptionStatus status) { + this.status = status; + } - @Override - public String getMessage() { - return status.getMessage(); - } + @Override + public String getMessage() { + return status.getMessage(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/CustomServiceException.java b/backend/src/main/java/org/ftclub/cabinet/exception/CustomServiceException.java index 5e8e6824a..efe9b0365 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/CustomServiceException.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/CustomServiceException.java @@ -2,27 +2,28 @@ /** * Service에서 throw 하는 Exception 중 오류메세지를 커스텀 가능한 Exception + * * @see CustomExceptionStatus */ -public class CustomServiceException extends RuntimeException { +public class CustomServiceException extends RuntimeException implements FtClubCabinetException { - final CustomExceptionStatus status; + final CustomExceptionStatus status; - /** - * @param status exception에 대한 정보에 대한 enum - */ - public CustomServiceException(CustomExceptionStatus status) { - this.status = status; - } + /** + * @param status exception에 대한 정보에 대한 enum + */ + public CustomServiceException(CustomExceptionStatus status) { + this.status = status; + } - public CustomExceptionStatus getStatus() { - return status; - } + public CustomExceptionStatus getStatus() { + return status; + } - @Override - public String getMessage() { - return status.getMessage(); - } + @Override + public String getMessage() { + return status.getMessage(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/DomainException.java b/backend/src/main/java/org/ftclub/cabinet/exception/DomainException.java index 17b38c846..95203996f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/DomainException.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/DomainException.java @@ -3,7 +3,7 @@ import lombok.Getter; @Getter -public class DomainException extends RuntimeException { +public class DomainException extends RuntimeException implements FtClubCabinetException { final ExceptionStatus status; diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/FtClubCabinetException.java b/backend/src/main/java/org/ftclub/cabinet/exception/FtClubCabinetException.java new file mode 100644 index 000000000..422f721ff --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/exception/FtClubCabinetException.java @@ -0,0 +1,9 @@ +package org.ftclub.cabinet.exception; + +/** + * FtClubCabinet 패키지에서 발생하는 예외들의 최상위 인터페이스 이 인터페이스를 구현하는 예외들은 FtClubCabinet 패키지에서 발생한 예외임을 나타냅니다. + * 커스텀 예외를 만들 때 이 인터페이스를 구현하도록 합니다. + */ +public interface FtClubCabinetException { + +} diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ServiceException.java b/backend/src/main/java/org/ftclub/cabinet/exception/ServiceException.java index 9048a85a1..fe894a025 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ServiceException.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ServiceException.java @@ -9,23 +9,23 @@ * * @see org.ftclub.cabinet.exception.ExceptionStatus */ -public class ServiceException extends RuntimeException { +public class ServiceException extends RuntimeException implements FtClubCabinetException { - final ExceptionStatus status; + final ExceptionStatus status; - /** - * @param status exception에 대한 정보에 대한 enum - */ - public ServiceException(ExceptionStatus status) { - this.status = status; - } + /** + * @param status exception에 대한 정보에 대한 enum + */ + public ServiceException(ExceptionStatus status) { + this.status = status; + } - public ExceptionStatus getStatus() { - return status; - } + public ExceptionStatus getStatus() { + return status; + } - @Override - public String getMessage() { - return status.getMessage(); - } + @Override + public String getMessage() { + return status.getMessage(); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/UtilException.java b/backend/src/main/java/org/ftclub/cabinet/exception/UtilException.java index d1bf6a2d4..ba41f2d3f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/UtilException.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/UtilException.java @@ -4,12 +4,13 @@ * Util에서 throw하는 exception들을 위한 exception 사용 예시: *
  *     {@code throw new UtilException(ExceptionStatus.NOT_FOUND_USER);}
- *
+ * * 만약 새로운 exception을 만들 필요가 있다면 {@link ExceptionStatus}에서 새로운 enum값을 추가하면 된다. + * * @see org.ftclub.cabinet.exception.ExceptionStatus */ -public class UtilException extends RuntimeException { +public class UtilException extends RuntimeException implements FtClubCabinetException { final ExceptionStatus status; diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java index 2e4384390..dce80e8d8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/scheduler/SystemScheduler.java @@ -9,6 +9,7 @@ import org.ftclub.cabinet.alarm.discord.DiscordWebHookMessenger; import org.ftclub.cabinet.dto.ActiveLentHistoryDto; import org.ftclub.cabinet.dto.UserBlackHoleEvent; +import org.ftclub.cabinet.exception.FtClubCabinetException; import org.ftclub.cabinet.lent.service.LentFacadeService; import org.ftclub.cabinet.user.service.LentExtensionManager; import org.ftclub.cabinet.user.service.UserQueryService; @@ -39,10 +40,15 @@ public class SystemScheduler { private final DiscordWebHookMessenger discordWebHookMessenger; private void errorHandle(Exception e, DiscordScheduleAlarmMessage message) { - log.error("Error message: {}, Error occurred in scheduled task: ", message, e); - discordWebHookMessenger.sendMessage(message); + if (!(e instanceof FtClubCabinetException)) { + log.error("Error message: {}, Error occurred in scheduled task: ", message, e); + discordWebHookMessenger.sendMessage(message); + } else { + log.warn("Handled FtClubCabinetException, no notification sent: ", e); + } } + /** * 매일 자정마다 대여 기록을 확인하여, 연체 메일 발송 및 휴학생 처리를 트리거 */ From bdeb378ad736d6d3c2944338ad2b9b2e711332ef Mon Sep 17 00:00:00 2001 From: Jiwon Park <82518170+Z1Park@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:58:52 +0900 Subject: [PATCH 0618/1029] DEV TO MAIN (#1601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * MTD (#1598) Co-authored-by: Jiwon Park <82518170+Z1Park@users.noreply.github.com> Co-authored-by: Siwon Choi <83565255+sichoi42@users.noreply.github.com> * [BE] HOTFIX: Release 동작 시 반납 날짜와 무관하게 Available로 변경하는 오류 수정 --------- Co-authored-by: SpaceChae <13278955+enaenen@users.noreply.github.com> Co-authored-by: Siwon Choi <83565255+sichoi42@users.noreply.github.com> Co-authored-by: jiwon --- .../cabinet/repository/CabinetRepository.java | 3 ++ .../cabinet/service/CabinetFacadeService.java | 2 +- .../cabinet/service/CabinetQueryService.java | 8 +--- .../cabinet/utils/release/ReleaseManager.java | 38 +++++++++++++++---- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java index 817907ad2..8efe292b2 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/repository/CabinetRepository.java @@ -207,4 +207,7 @@ void updateStatusAndTitleAndMemoByCabinetIdsIn(@Param("cabinetIds") List c @Param("status") CabinetStatus status, @Param("title") String title, @Param("memo") String memo); + + + List findAllByStatus(CabinetStatus cabinetStatus); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index 3757bc685..7f34c4c49 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -367,7 +367,7 @@ public void updateStatus(Long cabinetId, CabinetStatus status) { cabinet.specifyStatus(status); } - @Transactional + public void updateStatus(List cabinetId, CabinetStatus status) { cabinetCommandService.updateStatusBulk(cabinetId, status); } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java index bad86ed17..c29bfeab4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java @@ -1,6 +1,5 @@ package org.ftclub.cabinet.cabinet.service; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import javax.transaction.Transactional; @@ -205,12 +204,9 @@ public Page findAllByStatus(CabinetStatus status, Pageable pageable) { * 사물함 상태에 맞는 사물함들 중 from 이전에 대여기간이 만료되는 사물함을 가져옵니다. * * @param cabinetStatus 사물함 상태 - * @param from 조회할 시간 * @return 사물함 */ - public List findAllPendingCabinetsByCabinetStatusAndBeforeEndedAt( - CabinetStatus cabinetStatus, LocalDateTime from) { - return cabinetRepository.findAllCabinetsByCabinetStatusAndBeforeEndedAt(cabinetStatus, - from); + public List findAllPendingCabinets(CabinetStatus cabinetStatus) { + return cabinetRepository.findAllByStatus(cabinetStatus); } } diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java index f20bd45a0..8c8e760a9 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/release/ReleaseManager.java @@ -2,15 +2,21 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; -import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; +import org.ftclub.cabinet.cabinet.service.CabinetCommandService; import org.ftclub.cabinet.cabinet.service.CabinetQueryService; +import org.ftclub.cabinet.lent.domain.LentHistory; +import org.ftclub.cabinet.lent.service.LentQueryService; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @Component @RequiredArgsConstructor @@ -18,16 +24,32 @@ public class ReleaseManager { private final CabinetQueryService cabinetQueryService; - private final CabinetFacadeService cabinetFacadeService; + private final CabinetCommandService cabinetCommandService; + private final LentQueryService lentQueryService; - private List getAllPendedYesterdayCabinet() { - return cabinetQueryService.findAllPendingCabinetsByCabinetStatusAndBeforeEndedAt( - CabinetStatus.PENDING, LocalDateTime.from(LocalDate.now().atStartOfDay())); + private Set getAllPendedYesterdayCabinet() { + LocalDateTime from = LocalDateTime.from(LocalDate.now().atStartOfDay()); + List allPendingCabinets = cabinetQueryService.findAllPendingCabinets( + CabinetStatus.PENDING); + List cabinetIds = allPendingCabinets.stream() + .map(Cabinet::getId).collect(Collectors.toList()); + Set todayReturnedSet = lentQueryService.findCabinetLentHistories(cabinetIds) + .stream() + .filter(lh -> lh.getEndedAt().isAfter(from)) + .map(LentHistory::getCabinetId) + .collect(Collectors.toSet()); + + Set cabinetIdSet = new HashSet<>(cabinetIds); + cabinetIdSet.removeAll(todayReturnedSet); + return cabinetIdSet; } + @Transactional public void releasingCabinets() { - List cabinets = getAllPendedYesterdayCabinet(); - List cabinetIds = cabinets.stream().map(Cabinet::getId).collect(Collectors.toList()); - cabinetFacadeService.updateStatus(cabinetIds, CabinetStatus.AVAILABLE); + List cabinetIds = new ArrayList<>(getAllPendedYesterdayCabinet()); + if (cabinetIds.isEmpty()) { + return; + } + cabinetCommandService.updateStatusBulk(cabinetIds, CabinetStatus.AVAILABLE); } } From 79274188804f6481f0869cbab9232c27cdd83836 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Mon, 22 Apr 2024 20:49:40 +0900 Subject: [PATCH 0619/1029] [BE] FEAT: cleanup-stale-branch action (dry-run) --- .github/workflows/branch-cleaner.yaml | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/branch-cleaner.yaml diff --git a/.github/workflows/branch-cleaner.yaml b/.github/workflows/branch-cleaner.yaml new file mode 100644 index 000000000..d1179e514 --- /dev/null +++ b/.github/workflows/branch-cleaner.yaml @@ -0,0 +1,40 @@ +name: Cleaning Up Stale Branches + +permissions: + contents: write + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" # Every day at 00:00 UTC (KST 09:00) + +env: + DAYS_BEFORE_STALE: 30 + DAYS_BEFORE_DELETE: 7 + +jobs: + cleanup-stale-branches: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cleaning up Stale Branches + uses: sichoi42/cleanup-stale-branch@v1 + id: stale + with: + days-before-stale: ${{ env.DAYS_BEFORE_STALE }} + days-before-delete: ${{ env.DAYS_BEFORE_DELETE }} + ignoring-branches: "main,dev,onboard,nestJS" + ignore-branches-pattern: "release/*" + # FIXME: Set dry-run to false when you are ready to delete branches + dry-run: true + use-webhook: true + webhook-url: ${{ secrets.DISCORD_GITHUB_WEBHOOK_URL }} + webhook-type: "discord" + stale-branch-message: > + This branch is considered stale because ${{ env.DAYS_BEFORE_STALE }} days have passed since the last commit. + If you still need this branch, please push a new commit to keep it alive. + If not, this branch will be deleted in ${{ env.DAYS_BEFORE_DELETE }} days. + delete-branch-message: > + This branch was deleted because ${{ env.DAYS_BEFORE_DELETE }} days have passed since the last commit. + - name: Print outputs + run: echo ${{ format('{0},{1}', toJSON(steps.stale.outputs.staled-branches), toJSON(steps.stale.outputs.deleted-branches)) }} From 8c8547b038ac20eb92f58a4b8ef45f3ec97d8358 Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Mon, 22 Apr 2024 23:01:55 +0900 Subject: [PATCH 0620/1029] [BE] FEAT: stale Issue, PR action --- .github/workflows/stale.yaml | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/stale.yaml diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 000000000..3b8e94c11 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,47 @@ +name: Close Stale Issues/PRs + +permissions: + issues: write + pull-requests: write + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" # Every day at 00:00 UTC (KST 09:00) + +env: + OPERATIONS_PER_RUN: 128 + DAYS_BEFORE_ISSUE_STALE: 30 + DAYS_BEFORE_ISSUE_CLOSE: 3 + DAYS_BEFORE_PR_STALE: 30 + DAYS_BEFORE_PR_CLOSE: 3 + +jobs: + close-stale-issues-and-prs: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + operations-per-run: ${{ env.OPERATIONS_PER_RUN }} + days-before-issue-stale: ${{ env.DAYS_BEFORE_ISSUE_STALE }} + days-before-issue-close: ${{ env.DAYS_BEFORE_ISSUE_CLOSE }} + exempt-issue-labels: "no stale" + stale-issue-label: "stale" + stale-issue-message: > + 해당 이슈는 지난 ${{ env.DAYS_BEFORE_ISSUE_STALE }}일 동안 활동이 없어서 "stale" 상태로 표시되었어요. + ${{ env.DAYS_BEFORE_ISSUE_CLOSE }}일 이내에 "no stale"으로 태그가 지정되거나 다른 활동이 발생하지 않으면 자동으로 닫힐 예정입니다. + close-issue-message: > + 해당 이슈는 "stale" 상태로 표시된 후 ${{ env.DAYS_BEFORE_ISSUE_CLOSE }}일 동안 활동이 없어서 자동으로 닫혔어요. + remove-issue-stale-when-updated: true + days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }} + days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }} + exempt-pr-labels: "no stale" + stale-pr-label: "stale" + stale-pr-message: > + 해당 PR은 지난 ${{ env.DAYS_BEFORE_PR_STALE }}일 동안 활동이 없어서 "stale" 상태로 표시되었어요. + ${{ env.DAYS_BEFORE_PR_CLOSE }}일 이내에 "no stale"으로 태그가 지정되거나 다른 활동이 발생하지 않으면 자동으로 닫힐 예정입니다. + 해당 PR이 "stale" 상태로 표시되지 않기를 원하면 이 PR에 코멘트를 남겨주세요. + close-pr-message: > + 해당 PR은 "stale" 상태로 표시된 후 ${{ env.DAYS_BEFORE_PR_CLOSE }}일 동안 활동이 없어서 자동으로 닫혔어요. + remove-pr-stale-when-updated: true + repo-token: ${{ secrets.GITHUB_TOKEN }} From dfceefc76c53cacb0ee8d0309953ed11b957d4ec Mon Sep 17 00:00:00 2001 From: jiwon Date: Tue, 23 Apr 2024 14:00:18 +0900 Subject: [PATCH 0621/1029] =?UTF-8?q?[BE]=20HOTFIX:=20Transactional=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=AF=B8?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EC=BD=94=EB=93=9C,=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cabinet/service/CabinetFacadeService.java | 8 ++----- .../cabinet/service/CabinetQueryService.java | 2 -- .../club/service/ClubFacadeService.java | 3 +++ .../user/service/LentExtensionManager.java | 2 +- .../blackhole/manager/BlackholeManager.java | 3 ++- .../utils/cqrs/DatabaseSynchronizer.java | 21 ------------------- .../cabinet/utils/mail/EmailSender.java | 0 7 files changed, 8 insertions(+), 31 deletions(-) delete mode 100644 backend/src/main/java/org/ftclub/cabinet/utils/cqrs/DatabaseSynchronizer.java delete mode 100644 backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java index 7f34c4c49..0f6e3b461 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetFacadeService.java @@ -194,7 +194,7 @@ private String getCabinetTitle(Cabinet cabinet, List lentHistories) * @param building 건물 이름 * @return 빌딩에 있는 모든 PENDING 상태의 사물함 */ - @Transactional + @Transactional(readOnly = true) public CabinetPendingResponseDto getAvailableCabinets(String building) { final LocalDateTime now = LocalDateTime.now(); final LocalDateTime yesterday = now.minusDays(1).withHour(13).withMinute(0).withSecond(0); @@ -238,6 +238,7 @@ public CabinetPendingResponseDto getAvailableCabinets(String building) { * @param pageable 페이징 정보 * @return 캐비넷 정보 */ + @Transactional(readOnly = true) public CabinetPaginationDto getCabinetPaginationByStatus(CabinetStatus status, Pageable pageable) { Page cabinets = cabinetQueryService.findAllByStatus(status, pageable); @@ -366,9 +367,4 @@ public void updateStatus(Long cabinetId, CabinetStatus status) { Cabinet cabinet = cabinetQueryService.getCabinetForUpdate(cabinetId); cabinet.specifyStatus(status); } - - - public void updateStatus(List cabinetId, CabinetStatus status) { - cabinetCommandService.updateStatusBulk(cabinetId, status); - } } diff --git a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java index c29bfeab4..875b62fb0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java +++ b/backend/src/main/java/org/ftclub/cabinet/cabinet/service/CabinetQueryService.java @@ -2,7 +2,6 @@ import java.util.List; import java.util.Optional; -import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.cabinet.domain.Cabinet; import org.ftclub.cabinet.cabinet.domain.CabinetStatus; @@ -17,7 +16,6 @@ import org.springframework.stereotype.Service; @Service -@Transactional @RequiredArgsConstructor @Logging(level = LogLevel.DEBUG) public class CabinetQueryService { diff --git a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java index bdbef3cc5..9fc807736 100644 --- a/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/club/service/ClubFacadeService.java @@ -117,6 +117,7 @@ public ClubInfoPaginationDto getMyClubs(Long userId) { * @param clubId 동아리 ID * @param name 추가할 사용자 이름 */ + @Transactional public void addClubUser(Long masterId, Long clubId, String name) { User clubMaster = userQueryService.getUser(masterId); ClubRegistration clubMasterRegistration = @@ -142,6 +143,7 @@ public void addClubUser(Long masterId, Long clubId, String name) { * @param clubId 동아리 ID * @param deletedUserId 제거할 사용자 ID */ + @Transactional public void deleteClubUser(Long masterId, Long clubId, Long deletedUserId) { User clubMaster = userQueryService.getUser(masterId); userQueryService.getUser(deletedUserId); @@ -167,6 +169,7 @@ public void deleteClubUser(Long masterId, Long clubId, Long deletedUserId) { * @param clubId 동아리 ID * @param newClubMasterName 새로운 동아리 마스터 이름 */ + @Transactional public void mandateClubUser(Long clubMasterId, Long clubId, String newClubMasterName) { User newClubMaster = userQueryService.getUserByName(newClubMasterName); diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java index 9c176be06..75778b2a1 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java @@ -15,7 +15,6 @@ @Service @RequiredArgsConstructor -@Transactional @Logging(level = LogLevel.DEBUG) public class LentExtensionManager { @@ -27,6 +26,7 @@ public class LentExtensionManager { /** * 연장권을 발급합니다. */ + @Transactional public void issueLentExtension() { UserMonthDataDto[] userLastMonthOccupiedTime = occupiedTimeManager.getUserLastMonthOccupiedTime(); List userMonthDataDtos = occupiedTimeManager.filterToMetUserMonthlyTime( diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java index f8d38afc8..b1a0a4d27 100644 --- a/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/utils/blackhole/manager/BlackholeManager.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import java.time.LocalDateTime; +import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.ftclub.cabinet.auth.domain.FtProfile; @@ -44,7 +45,6 @@ private void terminateInvalidUser(UserBlackHoleEvent userBlackHoleEvent, LocalDa * @param userName 42 intra name * @return 유저 프로필 {@link FtProfile} */ - public FtProfile getUserRecentIntraProfile(String userName) { try { return userOauthService.getProfileByIntraName(tokenManager.getFtAccessToken(), @@ -62,6 +62,7 @@ public FtProfile getUserRecentIntraProfile(String userName) { * * @param dto 유저 정보 {@link UserBlackHoleEvent} */ + @Transactional public void handleBlackHole(UserBlackHoleEvent dto) { LocalDateTime now = LocalDateTime.now(); try { diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/cqrs/DatabaseSynchronizer.java b/backend/src/main/java/org/ftclub/cabinet/utils/cqrs/DatabaseSynchronizer.java deleted file mode 100644 index 92b056fd4..000000000 --- a/backend/src/main/java/org/ftclub/cabinet/utils/cqrs/DatabaseSynchronizer.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.ftclub.cabinet.utils.cqrs; - -import lombok.RequiredArgsConstructor; -import org.ftclub.cabinet.cabinet.service.CabinetFacadeService; -import org.ftclub.cabinet.club.service.ClubFacadeService; -import org.ftclub.cabinet.lent.service.LentFacadeService; -import org.ftclub.cabinet.user.service.UserFacadeService; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -@EnableScheduling -public class DatabaseSynchronizer { - - private final CabinetFacadeService cabinetFacadeService; - private final LentFacadeService lentFacadeService; - private final UserFacadeService userFacadeService; - private final ClubFacadeService clubFacadeService; - -} diff --git a/backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java b/backend/src/main/java/org/ftclub/cabinet/utils/mail/EmailSender.java deleted file mode 100644 index e69de29bb..000000000 From 97fa3f7850bb379f83ead83929ef67f62741209e Mon Sep 17 00:00:00 2001 From: Siwon Choi <83565255+sichoi42@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:39:17 +0900 Subject: [PATCH 0622/1029] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f665b9b6f..9dbba6118 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,6 @@ #### [Common](https://github.com/innovationacademy-kr/42cabi/) -- CI 워크플로우를 구축하여 빌드, 테스트를 자동화하여 개발자들의 생산성을 높였습니다. -- CD 워크플로우를 구축하여 배포과정을 자동화하여 안정적인 서비스를 제공했습니다. - 코드 리뷰를 통해 팀원들의 코드 품질을 향상시키고, 팀원들 간의 지식 공유를 통해 개발자들의 역량을 향상시켰습니다. - 유지/보수와 기능 추가가 용이하도록 코딩 컨벤션을 정하고, 문서화 작업 및 이슈 관리를 체계화했습니다. - Notion, Slack 등의 협업 툴들을 이용하여 팀원 간 정보 시차와 격차를 줄였습니다. @@ -69,6 +67,10 @@ - 블랙홀에 빠진 사용자(퇴학 처리된 사용자)를 적절하게 처리하기 위해 스케줄러를 구성하여 자동으로 해당 사용자의 접근 및 권한을 정리하고 시스템에서 안전하게 제거하도록 했습니다. - 연체/대여/반납 등 중요한 이벤트에 대해 사용자에게 알림을 제공함으로써 사용자가 서비스를 더욱 편리하게 이용할 수 있도록 했습니다. - 로깅, 인증과 같은 횡단 관심사에 대해 AOP를 적용하여 중복되는 코드를 줄이고, 유지보수성을 높였습니다. +- CI 워크플로우를 구축하여 빌드, 테스트를 자동화하여 개발자들의 생산성을 높였습니다. +- CD 워크플로우를 구축하여 배포과정을 자동화하여 안정적인 서비스를 제공했습니다. +- 효율적인 인프라 구조를 설계하여 보다 효율적이고 확장가능성을 가지며, 안정적인 서비스를 제공했습니다. +- Prometheus/Grafana + 핀포인트를 활용한 모니터링 시스템을 구축하여, 문제 발생시, 빠르게 대응하여 문제의 원인을 파악하고 해결할 수 있도록 기반을 마련했습니다.
From 07ce91b0434f7e627c2f2a9347cd3011af4b9254 Mon Sep 17 00:00:00 2001 From: Siwon Choi <83565255+sichoi42@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:44:51 +0900 Subject: [PATCH 0623/1029] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9dbba6118..6075e13fb 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@
+## 🕸️ 인프라 구조도 +![Untitled](https://github.com/innovationacademy-kr/Cabi/assets/83565255/165c1529-6164-4988-9495-6bc2ba3ef0ab) + + ## 🛠 기술 스택
From 28b6a4a23f4e2ffd2d7183946204ed6ba989ba49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EB=AF=BC?= Date: Tue, 23 Apr 2024 15:50:29 +0900 Subject: [PATCH 0624/1029] =?UTF-8?q?[FE]=20FEAT:=20leftNav=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=ED=81=AC=EA=B8=B0=20=ED=86=B5=EC=9D=BC,?= =?UTF-8?q?=20=ED=8F=B0=ED=8A=B8=20=EC=82=AC=EC=9D=B4=EC=A6=88=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=88=20#1605?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- frontend/package-lock.json | 383 ------------------ .../Cabinet/assets/images/clubIconGray.svg | 12 +- .../Cabinet/assets/images/profile-circle.svg | 9 +- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 3 +- .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 2 + .../LeftSectionNav/LeftSectionNavClubs.tsx | 1 + frontend/src/index.css | 2 + 8 files changed, 18 insertions(+), 396 deletions(-) diff --git a/config b/config index 24f8a0ac5..d1eba89e0 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 24f8a0ac525652379356018047bc1c358b344d39 +Subproject commit d1eba89e00f99a59d4a11d503dce83b59ec666e8 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6e01008d6..9e88d9f16 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,6 @@ "@nivo/pie": "^0.80.0", "@types/react-color": "^3.0.7", "axios": "^1.2.1", - "esbuild": "^0.20.2", "firebase": "^10.4.0", "react": "^18.2.0", "react-color": "^2.19.3", @@ -1959,351 +1958,6 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", - "cpu": [ - "loong64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", - "cpu": [ - "mips64el" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint/eslintrc": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", @@ -6060,43 +5714,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" - } - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", diff --git a/frontend/src/Cabinet/assets/images/clubIconGray.svg b/frontend/src/Cabinet/assets/images/clubIconGray.svg index 5b01ae5bc..aafe79d3f 100644 --- a/frontend/src/Cabinet/assets/images/clubIconGray.svg +++ b/frontend/src/Cabinet/assets/images/clubIconGray.svg @@ -1,7 +1,7 @@ - - - - - - + + + + + + diff --git a/frontend/src/Cabinet/assets/images/profile-circle.svg b/frontend/src/Cabinet/assets/images/profile-circle.svg index b92efa90e..dd2a15b36 100644 --- a/frontend/src/Cabinet/assets/images/profile-circle.svg +++ b/frontend/src/Cabinet/assets/images/profile-circle.svg @@ -1,6 +1,5 @@ - - - - + + + + - diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx index b7918c37b..b77b43f98 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -160,7 +160,7 @@ const LeftMainNav = ({ } onClick={onClickProfileButton} > - + Profile @@ -181,6 +181,7 @@ const LeftNavStyled = styled.nav` display: flex; flex-direction: column; justify-content: space-between; + font-size: var(--size-base); `; const TopSectionStyled = styled.section` diff --git a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index f5adb344b..2be36550b 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -101,6 +101,7 @@ const LeftNavOptionStyled = styled.div<{ border-right: 1px solid var(--line-color); font-weight: 300; position: relative; + font-size: var(--size-base); `; const ProfileLeftNavOptionStyled = styled.div<{ @@ -113,6 +114,7 @@ const ProfileLeftNavOptionStyled = styled.div<{ border-right: 1px solid var(--line-color); font-weight: 300; position: relative; + font-size: var(--size-base); & hr { width: 80%; height: 1px; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx index 6c425b127..65b714e47 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx @@ -49,6 +49,7 @@ const ClubLeftNavOptionStyled = styled.div` border-right: 1px solid var(--line-color); font-weight: 300; position: relative; + font-size: var(--size-base); & hr { width: 80%; height: 1px; diff --git a/frontend/src/index.css b/frontend/src/index.css index 64deb9d89..df8df9625 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -28,6 +28,8 @@ --default-sub-color: #b18cff; --default-mine-color: #47ffa7; + --size-base: 14px; + /* font variable */ --main-font: "Noto Sans KR", sans-serif; --building-font: "Do Hyeon", sans-serif; From 30858272be239445b860ac6f96e39e450ea7fa68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EB=AF=BC?= Date: Tue, 23 Apr 2024 17:00:08 +0900 Subject: [PATCH 0625/1029] =?UTF-8?q?[FE]=20FEAT:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=8E=98=EC=9D=B4=EC=A7=80=20select=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=83=89=EC=83=81=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?#1605?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config | 2 +- frontend/src/Cabinet/assets/images/select.svg | 2 +- .../components/Available/FloorContainer.tsx | 14 +++++++------- .../MapInfo/MapFloorSelect/MapFloorSelect.tsx | 12 ++++++++---- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/config b/config index d1eba89e0..24f8a0ac5 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit d1eba89e00f99a59d4a11d503dce83b59ec666e8 +Subproject commit 24f8a0ac525652379356018047bc1c358b344d39 diff --git a/frontend/src/Cabinet/assets/images/select.svg b/frontend/src/Cabinet/assets/images/select.svg index 90368d8c2..4f33830ba 100644 --- a/frontend/src/Cabinet/assets/images/select.svg +++ b/frontend/src/Cabinet/assets/images/select.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/src/Cabinet/components/Available/FloorContainer.tsx b/frontend/src/Cabinet/components/Available/FloorContainer.tsx index c314c6419..d77ccb1c8 100644 --- a/frontend/src/Cabinet/components/Available/FloorContainer.tsx +++ b/frontend/src/Cabinet/components/Available/FloorContainer.tsx @@ -4,6 +4,7 @@ import styled from "styled-components"; import AdminCabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem"; import CabinetListItem from "@/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem"; import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; +import { ReactComponent as SelectImg } from "@/Cabinet/assets/images/select.svg"; // 하나의 층에 대한 타이틀과 캐비넷 리스트를 담고 있는 컴포넌트 const FloorContainer = ({ @@ -25,10 +26,12 @@ const FloorContainer = ({

{floorNumber}층

- + {pendingCabinetsList.length !== 0 ? ( - + {pendingCabinetsList.map((cabinet: CabinetPreviewInfo) => isAdmin ? ( @@ -36,7 +39,7 @@ const FloorContainer = ({ ) )} - + ) : (

해당 층에는 사용 가능한 사물함이 없습니다

@@ -69,15 +72,12 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` cursor: inherit; z-index: 2; height: 30px; - width: 20px; - background: url(/src/Cabinet/assets/images/select.svg) no-repeat center - center; transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; } `; -const FlootCabinetsContainerStyled = styled.div<{ isToggled: boolean }>` +const FloorCabinetsContainerStyled = styled.div<{ isToggled: boolean }>` display: ${(props) => (props.isToggled ? "none" : "flex")}; transition: all 0.3s ease-in-out; flex-wrap: wrap; diff --git a/frontend/src/Cabinet/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx b/frontend/src/Cabinet/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx index 984921352..816a7e996 100644 --- a/frontend/src/Cabinet/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapFloorSelect/MapFloorSelect.tsx @@ -1,5 +1,6 @@ import styled from "styled-components"; import MapFloorSelectOption from "@/Cabinet/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption"; +import { ReactComponent as SelectImg } from "@/Cabinet/assets/images/select.svg"; interface IMapFloorSelect { floor: number; @@ -21,7 +22,8 @@ const MapFloorSelect = ({ floor, setFloor, floorInfo }: IMapFloorSelect) => { return (
- {`${floor}층`} + {`${floor}층`} +
@@ -29,16 +31,18 @@ const MapFloorSelect = ({ floor, setFloor, floorInfo }: IMapFloorSelect) => { }; const MapFloorSelectStyled = styled.div` - background: url("/src/Cabinet/assets/images/select.svg") var(--main-color) - no-repeat 80% 55%; + background: var(--main-color); color: white; cursor: pointer; width: 65px; height: 40px; + padding: 0 12px; line-height: 40px; - text-indent: 12px; border-radius: 10px; margin: 30px 0; + display: flex; + justify-content: space-between; + align-items: center; @media (hover: hover) and (pointer: fine) { &:hover { opacity: 0.9; From 4a5c397b2a21b1493cda9809c112a29fa26a84ed Mon Sep 17 00:00:00 2001 From: sichoi42 <42.4.sichoi@gmail.com> Date: Tue, 23 Apr 2024 17:11:12 +0900 Subject: [PATCH 0626/1029] [BE] FIX: update config --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 24f8a0ac5..aa0fbdedc 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 24f8a0ac525652379356018047bc1c358b344d39 +Subproject commit aa0fbdedc21a0d5007bce160ed0ac7c236ae4687 From 30daf3afbfb22daca39561f25da3d25675990a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EB=AF=BC?= Date: Tue, 23 Apr 2024 17:17:33 +0900 Subject: [PATCH 0627/1029] =?UTF-8?q?[FE]=20FEAT:=20=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=97=B0=EC=9E=A5=EA=B6=8C=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=EB=B0=A9=EB=B2=95=20=EC=95=88=EB=82=B4?= =?UTF-8?q?=EC=84=9C=20=EA=B9=A8=EC=A7=90=20=ED=98=84=EC=83=81=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20=20#1605?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/Cabinet/components/Home/ManualContentBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Cabinet/components/Home/ManualContentBox.tsx b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx index 1d224aef2..aa9775742 100644 --- a/frontend/src/Cabinet/components/Home/ManualContentBox.tsx +++ b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx @@ -111,7 +111,7 @@ const MaunalContentBoxStyled = styled.div<{ `} p { - margin-top: 120px; + margin-top: 90px; ${({ contentStatus }) => (contentStatus === ContentStatus.PENDING || contentStatus === ContentStatus.IN_SESSION) && From 0c5dbb1492b7c1c3bb04b08c1c2fc1626adbe73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EB=AF=BC?= Date: Tue, 23 Apr 2024 19:11:57 +0900 Subject: [PATCH 0628/1029] =?UTF-8?q?[FE]=20FEAT:=20=EB=82=B4=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=B2=84=ED=8A=BC=20=EB=94=94=ED=85=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20topNavDomain=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=ED=81=AC=EA=B8=B0=20=EC=A7=80=EC=A0=95=20=20=20#16?= =?UTF-8?q?05?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/Cabinet/components/Card/Card.tsx | 1 + .../components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/Cabinet/components/Card/Card.tsx b/frontend/src/Cabinet/components/Card/Card.tsx index c1a0272e3..0eef87510 100644 --- a/frontend/src/Cabinet/components/Card/Card.tsx +++ b/frontend/src/Cabinet/components/Card/Card.tsx @@ -112,6 +112,7 @@ const ToolTipIcon = styled.div` export const CardButtonWrapper = styled.div` display: flex; + font-size: var(--size-base); `; export const CardButtonStyled = styled.div<{ diff --git a/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx b/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx index 1cf23e060..dd7096590 100644 --- a/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx @@ -39,7 +39,7 @@ const TopNavDomainGroup = ({ isAdmin = false }: { isAdmin?: boolean }) => { onClick={() => navigator(isAdmin ? domain.adminPath : domain.path)} > - + Date: Tue, 23 Apr 2024 19:28:10 +0900 Subject: [PATCH 0629/1029] =?UTF-8?q?[FE]=20FEAT:=20=EB=8C=80=EC=97=AC?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20margin=20=EC=88=98=EC=A0=95,=20subTitle=20?= =?UTF-8?q?main-color=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=20#1605?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx | 2 +- frontend/src/Cabinet/pages/LogPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx index 502ba75e7..73a18d304 100644 --- a/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx +++ b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx @@ -61,7 +61,7 @@ const LogTableWrapperstyled = styled.div` max-width: 800px; border-radius: 10px; overflow: hidden; - margin: 0 auto; + margin: 60px 0 0 0; box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); `; diff --git a/frontend/src/Cabinet/pages/LogPage.tsx b/frontend/src/Cabinet/pages/LogPage.tsx index 8d84d5d79..07228cae5 100644 --- a/frontend/src/Cabinet/pages/LogPage.tsx +++ b/frontend/src/Cabinet/pages/LogPage.tsx @@ -62,6 +62,6 @@ const SubTitleStyled = styled.div` font-size: 1.2rem; letter-spacing: -0.02rem; margin-bottom: 25px; - color: var(--sub-color); + color: var(--main-color); `; export default LogPage; From fff8147aa2219598a75397505c38173b7ffe0510 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Tue, 23 Apr 2024 20:03:14 +0900 Subject: [PATCH 0630/1029] =?UTF-8?q?[FE]=20FEAT:=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=EC=B9=A8=ED=96=88=EC=9D=84=EB=95=8C=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=EC=A4=91=EC=9D=BC=EB=95=8C=20body=EC=97=90=20color-th?= =?UTF-8?q?eme=20=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80=ED=95=B4=EC=84=9C?= =?UTF-8?q?=20=EC=9B=90=ED=95=98=EB=8A=94=20=EC=BB=AC=EB=9F=AC=ED=85=8C?= =?UTF-8?q?=EB=A7=88=20=EC=A0=81=EC=9A=A9#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 34 +++++++++++++++++++ .../DisplayStyleCard.container.tsx | 1 + frontend/src/pages/Layout.tsx | 10 +++--- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index bd6807355..c115df490 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,6 +1,40 @@ + + { navigator(isAdmin ? domain.adminPath : domain.path)} > - - + + ` display: flex; align-items: center; width: 14px; height: 14px; cursor: pointer; + svg { .logo_svg__currentPath { fill: var(--default-main-color); } + width: 14px; + height: 14px; + } + + & > svg > path { + transform: ${(props) => + props.domainTitle === "수요지식회" ? "scale(1.08)" : "scale(1)"}; } `; @@ -102,7 +115,7 @@ const DomainSeparatorStyled = styled.div` width: 1px; height: 20px; margin: 0 8px; - background-color: #d9d9d9; + background-color: var(--shared-gray-color-300); `; export default TopNavDomainGroup; diff --git a/frontend/src/Cabinet/pages/ProfilePage.tsx b/frontend/src/Cabinet/pages/ProfilePage.tsx index b6794b1d7..f97ca4851 100644 --- a/frontend/src/Cabinet/pages/ProfilePage.tsx +++ b/frontend/src/Cabinet/pages/ProfilePage.tsx @@ -1,3 +1,4 @@ +import { deleteRecoilPersistFloorSection } from "@/Cabinet//utils/recoilPersistUtils"; import { deleteFcmToken, requestFcmAndGetDeviceToken, @@ -6,14 +7,16 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { userState } from "@/Cabinet/recoil/atoms"; +import DisplayStyleCardContainer from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container"; import ExtensionCardContainer from "@/Cabinet/components/Card/ExtensionCard/ExtensionCard.container"; import LentInfoCardContainer from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard.container"; import NotificationCardContainer from "@/Cabinet/components/Card/NotificationCard/NotificationCard.container"; import ProfileCardContainer from "@/Cabinet/components/Card/ProfileCard/ProfileCard.container"; import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; -import { axiosMyInfo, axiosUpdateDeviceToken } from "@/Cabinet/api/axios/axios.custom"; -import { deleteRecoilPersistFloorSection } from "@/Cabinet//utils/recoilPersistUtils"; -import DisplayStyleCardContainer from "@/components/Card/DisplayStyleCard/DisplayStyleCard.container"; +import { + axiosMyInfo, + axiosUpdateDeviceToken, +} from "@/Cabinet/api/axios/axios.custom"; const ProfilePage = () => { const [isLoading, setIsLoading] = useState(true); diff --git a/frontend/src/Presentation/assets/images/logo.svg b/frontend/src/Presentation/assets/images/logo.svg index 1f0bdf423..59dc2b986 100644 --- a/frontend/src/Presentation/assets/images/logo.svg +++ b/frontend/src/Presentation/assets/images/logo.svg @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + diff --git a/frontend/src/Presentation/components/TopNav/TopNav.tsx b/frontend/src/Presentation/components/TopNav/TopNav.tsx index 09dd50676..7b5a5a83e 100644 --- a/frontend/src/Presentation/components/TopNav/TopNav.tsx +++ b/frontend/src/Presentation/components/TopNav/TopNav.tsx @@ -18,7 +18,11 @@ const TopNav = ({
수요지식회
@@ -64,6 +68,12 @@ const LogoDivStyled = styled.div` .logo_svg__currentPath { fill: var(--main-color); } + width: 35px; + height: 35px; + } + + & > svg > path { + transform: scale(1.08); } `; From 3e21ef07a79cd918118bf539a9efffcf0eee2897 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 25 Apr 2024 15:46:41 +0900 Subject: [PATCH 0636/1029] =?UTF-8?q?[FE]=20FIX:=20=EB=8B=A4=ED=81=AC?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=EC=9D=98=20--shared-gray-color-100=20800->70?= =?UTF-8?q?0=20&=20--gray-550=20=EC=B6=94=EA=B0=80=ED=95=98=EA=B3=A0=20?= =?UTF-8?q?=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=EC=9D=98=20--card-content-?= =?UTF-8?q?bg-color=EC=97=90=20=EC=A0=81=EC=9A=A9=20#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/Cabinet/assets/data/ColorTheme.ts | 4 ++-- .../src/Cabinet/components/AdminInfo/Table/AdminTable.tsx | 1 - frontend/src/index.css | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/Cabinet/assets/data/ColorTheme.ts b/frontend/src/Cabinet/assets/data/ColorTheme.ts index fa61f6cc2..72f7093c6 100644 --- a/frontend/src/Cabinet/assets/data/ColorTheme.ts +++ b/frontend/src/Cabinet/assets/data/ColorTheme.ts @@ -52,7 +52,7 @@ const darkValues = css` --line-color: var(--shared-gray-color-300); --normal-text-color: var(--gray-100); --text-with-bg-color: var(--gray-100); - --card-content-bg-color: var(--gray-700); + --card-content-bg-color: var(--gray-550); --button-line-color: var(--default-sub-color); --main-color: var(--purple-600); @@ -77,7 +77,7 @@ const darkValues = css` --border-shadow-color-200: var(--black-shadow-300); --border-shadow-color-300: var(--black-shadow-400); - --shared-gray-color-100: var(--gray-800); + --shared-gray-color-100: var(--gray-700); --shared-gray-color-200: var(--gray-700); --shared-gray-color-300: var(--gray-600); --shared-gray-color-400: var(--gray-500); diff --git a/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx b/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx index 41c770d64..f5ed5dee4 100644 --- a/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx +++ b/frontend/src/Cabinet/components/AdminInfo/Table/AdminTable.tsx @@ -1,5 +1,4 @@ import { useState } from "react"; -import { ITableData } from "src/types/dto/admin.dto"; import styled from "styled-components"; import Pagination from "@/Cabinet/components/AdminInfo/Table/Pagination"; import { ITableData } from "@/Cabinet/types/dto/admin.dto"; diff --git a/frontend/src/index.css b/frontend/src/index.css index 022f732ea..4e0f4f4a4 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -8,6 +8,7 @@ --gray-300: #d9d9d9; --gray-400: #bcbcbc; --gray-500: #7b7b7b; + --gray-550: #3d3f40; --gray-600: #3c3c3c; --gray-700: #313233; --gray-800: #262626; From 7b900cfaecfa2730c8dcf443dfd35945e7823070 Mon Sep 17 00:00:00 2001 From: jnkeniaem Date: Thu, 25 Apr 2024 16:12:42 +0900 Subject: [PATCH 0637/1029] =?UTF-8?q?[FE]=20FIX:=20--text-with-bg-color?= =?UTF-8?q?=EC=97=90=20white=20=EB=B6=99=EC=9E=84=20&=20MultiToggleSwitchS?= =?UTF-8?q?eparated=20=EC=83=89=20=EC=A0=84=EC=97=AD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9#1551?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Cabinet/assets/data/ColorTheme.ts | 4 ++-- .../src/Cabinet/assets/data/ManualContent.ts | 10 +++++----- frontend/src/Cabinet/assets/data/maps.ts | 20 +++++++++---------- .../src/Cabinet/components/Card/CardStyles.ts | 4 ++-- .../ColorTheme/ColorTheme.tsx | 6 ++++-- .../DisplayStyleCard/DisplayStyleCard.tsx | 2 +- .../Card/LentInfoCard/LentInfoCard.tsx | 2 +- .../NotificationCard.container.tsx | 2 +- .../src/Cabinet/components/Club/ClubInfo.tsx | 5 +++-- .../ClubMemberListItem/ClubMemberListItem.tsx | 5 +++-- .../src/Cabinet/components/Common/Button.tsx | 2 +- .../components/Common/MultiToggleSwitch.tsx | 2 +- .../Common/MultiToggleSwitchSeparated.tsx | 17 ++++++++-------- .../components/Common/ToggleSwitch.tsx | 2 +- .../components/Home/ManualContentBox.tsx | 6 +++--- .../LeftNav/LeftMainNav/LeftMainNav.tsx | 2 +- .../LeftNav/LeftSectionNav/LeftSectionNav.tsx | 2 +- .../components/LentLog/LogTable/LogTable.tsx | 2 +- .../MapInfo/MapFloorSelect/MapFloorSelect.tsx | 4 ++-- .../components/MapInfo/MapItem/MapItem.tsx | 2 +- .../Modals/ManualModal/ManualModal.tsx | 10 +++++----- frontend/src/Cabinet/pages/AvailablePage.tsx | 2 +- frontend/src/Cabinet/pages/ClubPage.tsx | 4 ++-- frontend/src/Cabinet/pages/MainPage.tsx | 2 +- .../src/Cabinet/pages/admin/AdminMainPage.tsx | 2 +- frontend/src/index.css | 4 ++-- 26 files changed, 64 insertions(+), 61 deletions(-) diff --git a/frontend/src/Cabinet/assets/data/ColorTheme.ts b/frontend/src/Cabinet/assets/data/ColorTheme.ts index 72f7093c6..e2fcd0734 100644 --- a/frontend/src/Cabinet/assets/data/ColorTheme.ts +++ b/frontend/src/Cabinet/assets/data/ColorTheme.ts @@ -4,7 +4,7 @@ const lightValues = css` --bg-color: var(--white); --line-color: var(--shared-gray-color-400); --normal-text-color: var(--black); - --text-with-bg-color: var(--white); + --white-text-with-bg-color: var(--white); --card-content-bg-color: var(--white); --button-line-color: var(--default-main-color); @@ -51,7 +51,7 @@ const darkValues = css` --bg-color: var(--gray-900); --line-color: var(--shared-gray-color-300); --normal-text-color: var(--gray-100); - --text-with-bg-color: var(--gray-100); + --white-text-with-bg-color: var(--gray-100); --card-content-bg-color: var(--gray-550); --button-line-color: var(--default-sub-color); diff --git a/frontend/src/Cabinet/assets/data/ManualContent.ts b/frontend/src/Cabinet/assets/data/ManualContent.ts index f53727f62..889ec53b0 100644 --- a/frontend/src/Cabinet/assets/data/ManualContent.ts +++ b/frontend/src/Cabinet/assets/data/ManualContent.ts @@ -31,7 +31,7 @@ export const manualContentData: Record = { 연체 페널티는 누적됩니다.
`, - pointColor: "var(--text-with-bg-color)", + pointColor: "var(--white-text-with-bg-color)", }, [ContentStatus.SHARE]: { contentTitle: "공유 사물함", @@ -60,7 +60,7 @@ export const manualContentData: Record = { 연체 페널티는 누적됩니다. `, - pointColor: "var(--text-with-bg-color)", + pointColor: "var(--white-text-with-bg-color)", }, [ContentStatus.CLUB]: { contentTitle: "동아리 사물함", @@ -87,7 +87,7 @@ export const manualContentData: Record = { 상세 페이지가 제공되지 않습니다.
비밀번호는 동아리 내에서 공유하여 이용하세요.`, - pointColor: "var(--text-with-bg-color)", + pointColor: "var(--white-text-with-bg-color)", }, [ContentStatus.PENDING]: { contentTitle: "오픈예정", @@ -103,7 +103,7 @@ export const manualContentData: Record = { title="슬랙 캐비닛 채널 새창으로 열기" > 슬랙 캐비닛 채널에서 확인하세요.`, - pointColor: "var(--text-with-bg-color)", + pointColor: "var(--white-text-with-bg-color)", }, [ContentStatus.IN_SESSION]: { contentTitle: "대기중", @@ -146,4 +146,4 @@ export const manualContentData: Record = { `, pointColor: "var(--normal-text-color)", }, -}; \ No newline at end of file +}; diff --git a/frontend/src/Cabinet/assets/data/maps.ts b/frontend/src/Cabinet/assets/data/maps.ts index cf8681d38..aeaa638c2 100644 --- a/frontend/src/Cabinet/assets/data/maps.ts +++ b/frontend/src/Cabinet/assets/data/maps.ts @@ -41,14 +41,14 @@ export const cabinetIconSrcMap = { }; export const cabinetLabelColorMap = { - [CabinetStatus.AVAILABLE]: "var(--text-with-bg-color)", + [CabinetStatus.AVAILABLE]: "var(--white-text-with-bg-color)", [CabinetStatus.FULL]: "var(--black)", // black - [CabinetStatus.OVERDUE]: "var(--text-with-bg-color)", - [CabinetStatus.BROKEN]: "var(--text-with-bg-color)", - [CabinetStatus.BANNED]: "var(--text-with-bg-color)", + [CabinetStatus.OVERDUE]: "var(--white-text-with-bg-color)", + [CabinetStatus.BROKEN]: "var(--white-text-with-bg-color)", + [CabinetStatus.BANNED]: "var(--white-text-with-bg-color)", [CabinetStatus.IN_SESSION]: "var(--main-color)", - [CabinetStatus.PENDING]: "var(--text-with-bg-color)", + [CabinetStatus.PENDING]: "var(--white-text-with-bg-color)", MINE: "var(--black)", // black }; @@ -203,14 +203,14 @@ export const modalPropsMap = { }; export const cabinetFilterMap = { - [CabinetStatus.AVAILABLE]: "var(--text-with-bg-color)", + [CabinetStatus.AVAILABLE]: "var(--white-text-with-bg-color)", [CabinetStatus.FULL]: "var(--black)", // black - [CabinetStatus.OVERDUE]: "var(--text-with-bg-color)", - [CabinetStatus.BROKEN]: "var(--text-with-bg-color)", - [CabinetStatus.BANNED]: "var(--text-with-bg-color)", + [CabinetStatus.OVERDUE]: "var(--white-text-with-bg-color)", + [CabinetStatus.BROKEN]: "var(--white-text-with-bg-color)", + [CabinetStatus.BANNED]: "var(--white-text-with-bg-color)", [CabinetStatus.IN_SESSION]: "var(--normal-text-color)", - [CabinetStatus.PENDING]: "var(--text-with-bg-color)", + [CabinetStatus.PENDING]: "var(--white-text-with-bg-color)", }; export const cabinetStatusLabelMap = { diff --git a/frontend/src/Cabinet/components/Card/CardStyles.ts b/frontend/src/Cabinet/components/Card/CardStyles.ts index b39a46e81..82a834d37 100644 --- a/frontend/src/Cabinet/components/Card/CardStyles.ts +++ b/frontend/src/Cabinet/components/Card/CardStyles.ts @@ -1,5 +1,5 @@ -import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import styled from "styled-components"; +import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; export const CardContentWrapper = styled.div` background-color: var(--card-content-bg-color); @@ -30,7 +30,7 @@ export const ContentInfoStyled = styled.div<{ props.isSelected && ` background-color: ${props.selectedColor}; - color: var(--text-with-bg-color); + color: var(--white-text-with-bg-color); border-radius: 8px; `} `; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx index 81c493ab4..9ac9f4c31 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx +++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx @@ -83,7 +83,9 @@ const ButtonStyled = styled.button<{ background-color: ${(props) => props.isClicked ? "var(--main-color)" : "var(--shared-gray-color-100)"}; color: ${(props) => - props.isClicked ? "var(--text-with-bg-color)" : "var(--normal-text-color)"}; + props.isClicked + ? "var(--white-text-with-bg-color)" + : "var(--normal-text-color)"}; padding: 12px 0 16px 0; & > svg { @@ -94,7 +96,7 @@ const ButtonStyled = styled.button<{ & > svg > path { stroke: ${(props) => props.isClicked - ? "var(--text-with-bg-color)" + ? "var(--white-text-with-bg-color)" : "var(--normal-text-color)"}; } `; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx index 34f1d85dd..acba67b5f 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx +++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx @@ -49,7 +49,7 @@ const DisplayStyleCard = ({ { label: "저장", onClick: handleSave, - fontColor: "var(--text-with-bg-color)", + fontColor: "var(--white-text-with-bg-color)", backgroundColor: "var(--main-color)", isClickable: true, }, diff --git a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx index 478ed6df9..4f59f4f27 100644 --- a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx @@ -144,7 +144,7 @@ const CabinetRectangleStyled = styled.div<{ : "var(--full-color)"}; color: ${(props) => props.banned - ? "var(--bg-color)" + ? "var(--white-text-with-bg-color)" : props.status === "IN_SESSION" ? "var(--main-color)" : "var(--black)"}; diff --git a/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx index 89d087252..87c9d40a6 100644 --- a/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx @@ -83,7 +83,7 @@ const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { { label: "저장", onClick: handleSave, - fontColor: "var(--text-with-bg-color)", + fontColor: "var(--white-text-with-bg-color)", backgroundColor: "var(--main-color)", isClickable: true, }, diff --git a/frontend/src/Cabinet/components/Club/ClubInfo.tsx b/frontend/src/Cabinet/components/Club/ClubInfo.tsx index f36e7dc1f..1201de1c9 100644 --- a/frontend/src/Cabinet/components/Club/ClubInfo.tsx +++ b/frontend/src/Cabinet/components/Club/ClubInfo.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { useRecoilValue } from "recoil"; +import { useRecoilState } from "recoil"; import styled from "styled-components"; import { userState } from "@/Cabinet/recoil/atoms"; import ClubCabinetInfoCard from "@/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard"; @@ -9,10 +9,11 @@ import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; import { ReactComponent as SadCcabi } from "@/Cabinet/assets/images/sadCcabi.svg"; import { ClubInfoResponseDto } from "@/Cabinet/types/dto/club.dto"; import useClubInfo from "@/Cabinet/hooks/useClubInfo"; +import useMenu from "@/Cabinet/hooks/useMenu"; import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; const ClubInfo = () => { - const myInfo = useRecoilValue(userState); + const [myInfo] = useRecoilState(userState); const { clubState, clubInfo, setPage } = useClubInfo(); const [isMaster, setIsMaster] = useState(false); const { closeAll } = useMenu(); diff --git a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx index a7a767726..629cc858a 100644 --- a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx @@ -84,7 +84,7 @@ const MemberListItemStyled = styled.div<{ isMaster: boolean }>` & > svg > path { stroke: ${(props) => props.isMaster - ? "var(--text-with-bg-color)" + ? "var(--white-text-with-bg-color)" : "var(--normal-text-color)"}; transform: ${(props) => (props.isMaster ? "" : "scale(0.7)")}; } @@ -96,7 +96,8 @@ const MemberNameStyled = styled.p<{ line-height: 28px; height: 28px; font-size: 14px; - color: ${(props) => (props.isMaster ? "var(--text-with-bg-color)" : "")}; + color: ${(props) => + props.isMaster ? "var(--white-text-with-bg-color)" : ""}; `; export default memo(ClubMemberListItem); diff --git a/frontend/src/Cabinet/components/Common/Button.tsx b/frontend/src/Cabinet/components/Common/Button.tsx index b4ae0ccde..82e2d3ea3 100644 --- a/frontend/src/Cabinet/components/Common/Button.tsx +++ b/frontend/src/Cabinet/components/Common/Button.tsx @@ -42,7 +42,7 @@ const ButtonContainerStyled = styled.button` props.theme === "fill" && css` background: var(--main-color); - color: var(--text-with-bg-color); + color: var(--white-text-with-bg-color); `} ${(props) => props.theme === "line" && diff --git a/frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx b/frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx index b3afa2e47..5ff88bb6a 100644 --- a/frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx +++ b/frontend/src/Cabinet/components/Common/MultiToggleSwitch.tsx @@ -23,7 +23,7 @@ const MultiToggleSwitch = ({ buttons?.forEach((button) => { if (button.className === initialState) { - button.style.color = "var(--text-with-bg-color)"; + button.style.color = "var(--white-text-with-bg-color)"; button.style.backgroundColor = "var(--main-color)"; } }); diff --git a/frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx b/frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx index 87bee041d..58d10bd2f 100644 --- a/frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx +++ b/frontend/src/Cabinet/components/Common/MultiToggleSwitchSeparated.tsx @@ -50,7 +50,7 @@ const MultiToggleSwitchSeparated = ({ } return ( - + {toggleList.map((item) => ( + + + {sortedItems.length ? ( + <> + {sortedItems.map((item, idx) => { + const hasTypes = + item.itemDetails !== convertToItemTypeLabel(itemsType); + return ( + + + + + + + {convertToItemTypeLabel(itemsType)} + + {hasTypes && {item.itemDetails}} + + + ); + })} + + ) : ( + !isToggled && ( + + ) + )} + + + + ); +}; + +const ItemCardStyled = styled.div<{ hasTypes: boolean }>` + width: 106px; + height: 106px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 0; + justify-content: ${(props) => (props.hasTypes ? "space-between" : "")}; + border: 1.5px solid var(--capsule-btn-border-color); +`; + +const ItemIconStyled = styled.div<{ itemType: StoreItemType }>` + width: 32px; + height: 32px; + display: flex; + justify-content: center; + align-items: center; + + & > svg > path { + stroke: var(--sys-main-color); + stroke-width: ${(props) => + props.itemType === StoreItemType.EXTENSION ? "2.8px" : "1.5px"}; + } +`; + +const CardTextStyled = styled.div<{ hasTypes: boolean }>` + display: flex; + flex-direction: column; + align-items: center; + + & > #title { + font-size: 14px; + font-weight: bold; + margin: ${(props) => (props.hasTypes ? "0.5rem 0 6px 0" : "16px 0 0 0")}; + } + + & > #type { + font-size: 12px; + color: var(--gray-line-btn-color); + } +`; + +const ItemTitleWrapperStyled = styled.div<{ isToggled: boolean }>` + display: flex; + flex-direction: row; + justify-content: space-between; + font-size: 1.1rem; + color: var(--normal-text-color); + padding-left: 5px; + padding-right: 5px; + border-bottom: 1.5px solid var(--inventory-item-title-border-btm-color); + margin: 50px 0 14px; + cursor: pointer; + + & > button { + all: initial; + cursor: inherit; + z-index: 2; + height: 30px; + transform: ${(props) => + props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; + } + + & > button > svg > path { + stroke: var(--gray-line-btn-color); + } + + & > h2 { + font-weight: bold; + } +`; + +const ItemCardSectionStyled = styled.div<{ isToggled: boolean }>` + display: ${(props) => (props.isToggled ? "none" : "flex")}; + transition: all 0.3s ease-in-out; + flex-wrap: wrap; + gap: 20px; +`; + +const ItemWrapperStyled = styled.div` + width: 70%; + + @media (max-width: 1040px) { + width: 80%; + } +`; + +const CautionWrapperStyled = styled.div` + display: flex; + position: relative; +`; + +const CautionIconStyled = styled.img` + margin-top: 2px; + margin-left: 5px; + width: 16px; + height: 16px; + opacity: 0.6; + :hover { + cursor: pointer; + opacity: 1; + } +`; + +export const ItemTitleStyled = styled.div` + font-weight: bold; + display: flex; + align-items: center; +`; + +const TooltipBoxDateStyled = styled.div` + position: absolute; + top: -60px; + left: 155px; + transform: translateX(-50%); + font-weight: 400; + color: var(--white-text-with-bg-color); + background-color: var(--tooltip-shadow-color); + width: 240px; + padding: 10px; + border-radius: 4px; + font-size: 0.75rem; + text-align: center; + line-height: 1.25rem; + letter-spacing: -0.02rem; + white-space: pre-line; + z-index: 100; + opacity: 0; + transition: opacity 0.5s ease; + + &::after { + content: ""; + position: absolute; + top: 82%; + right: 100%; + margin-top: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent var(--tooltip-shadow-color) transparent + transparent; + } + ${ItemTitleStyled}:hover & { + opacity: 1; + } +`; + +export default InventoryItem; diff --git a/frontend/src/Cabinet/components/Store/ItemUsageLog/ItemLogBlock.tsx b/frontend/src/Cabinet/components/Store/ItemUsageLog/ItemLogBlock.tsx new file mode 100644 index 000000000..127fd6818 --- /dev/null +++ b/frontend/src/Cabinet/components/Store/ItemUsageLog/ItemLogBlock.tsx @@ -0,0 +1,101 @@ +import styled from "styled-components"; +import { IItemUsageLog } from "@/Cabinet/pages/ItemUsageLogPage"; +import { StoreItemType } from "@/Cabinet/types/enum/store.enum"; + +export function mapItemNameToType(itemName: string): StoreItemType { + switch (itemName) { + case "연장권": + return StoreItemType.EXTENSION; + case "이사권": + return StoreItemType.SWAP; + case "알림 등록권": + return StoreItemType.ALARM; + case "페널티 감면권": + return StoreItemType.PENALTY; + default: + return StoreItemType.PENALTY; + } +} + +const extractItemName = (item: string): string => { + // 정규 표현식 패턴을 정의합니다. 숫자와 밑줄을 제외한 모든 부분을 추출합니다. + const pattern = /^[^\d_\-\s]+/; + const match = item.match(pattern); + return match ? match[0] : item; +} + +const formatDate = (date: Date) => { + return new Intl.DateTimeFormat("ko-KR", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + hour12: false, + }).format(date); +}; + +const ItemLogBlock = ({ log }: { log: IItemUsageLog }) => { + return ( + + + + + + {formatDate(log.date)} + {log.title} + + + ); +}; + +const ItemUsageLogStyled = styled.div` + margin-top: 10px; + border-radius: 10px; + height: 90px; + border: 1px solid var(--inventory-item-title-border-btm-color); + display: flex; + align-items: center; +`; + +const IconBlockStyled = styled.div<{ itemType: StoreItemType }>` + display: flex; + width: 60px; + height: 60px; + border-radius: 10px; + background-color: var(--sys-main-color); + justify-content: center; + align-items: center; + margin-left: 30px; + margin-right: 20px; + svg { + width: 40px; + height: 40px; + } + + & > svg > path { + stroke: var(--white-text-with-bg-color); + stroke-width: ${(props) => + props.itemType === StoreItemType.EXTENSION ? "3px" : "1.5px"}; + } +`; + +const ItemUsageInfoStyled = styled.div` + display: flex; + flex-direction: column; + gap: 10px; + align-items: flex-start; +`; + +const ItemDateStyled = styled.div` + font-size: 16px; + word-spacing: -2px; + color: var(--gray-line-btn-color); +`; + +const ItemTitleStyled = styled.div` + font-size: 16px; + font-weight: 800; +`; + +export default ItemLogBlock; diff --git a/frontend/src/Cabinet/components/Store/StoreCoinCheckBox.tsx b/frontend/src/Cabinet/components/Store/StoreCoinCheckBox.tsx new file mode 100644 index 000000000..7fb547b58 --- /dev/null +++ b/frontend/src/Cabinet/components/Store/StoreCoinCheckBox.tsx @@ -0,0 +1,72 @@ +import styled from "styled-components"; +import { ReactComponent as CoinCheckOnFinImg } from "@/Cabinet/assets/images/storeCoinCheckFin.svg"; +import { ReactComponent as CoinCheckOffImg } from "@/Cabinet/assets/images/storeCoinCheckOff.svg"; +import { ReactComponent as CoinCheckOnImg } from "@/Cabinet/assets/images/storeCoinCheckOn.svg"; + +const StoreCoinCheckBox = ({ + monthlyCoinCount, +}: { + monthlyCoinCount: number; +}) => { + return ( + + {Array.from({ length: 20 }, (_, index) => ( + + {index < monthlyCoinCount && index !== 19 ? ( + + + + ) : index === 19 && monthlyCoinCount === 20 ? ( + + ) : ( + + + {index + 1} + + )} + + ))} + + ); +}; + +export default StoreCoinCheckBox; + +const CoinCheckColorContainer = styled.div` + & > svg > circle { + stroke: var(--sys-main-color); + } +`; + +const CoinCheckOffStyled = styled.div` + position: relative; + // cursor: pointer; + //& > svg > circle { + // fill: var(--white-text-with-bg-color); + ///* fill: var(--sys-main-color); */ + //} +`; + +const CoinCheckOffImgStyled = styled.div` + & > svg > circle { + stroke: var(--sys-main-color); + } + position: relative; +`; + +const IndexStyled = styled.span` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + opacity: 0.5; +`; + +const WrapperStyled = styled.div` + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(5, 1fr); + grid-gap: 5px; + margin-top: 15px; + margin-bottom: 30px; +`; diff --git a/frontend/src/Cabinet/components/Store/StoreCoinPick.tsx b/frontend/src/Cabinet/components/Store/StoreCoinPick.tsx new file mode 100644 index 000000000..75795d48c --- /dev/null +++ b/frontend/src/Cabinet/components/Store/StoreCoinPick.tsx @@ -0,0 +1,48 @@ +import styled from "styled-components"; +import Card from "@/Cabinet/components/Card/Card"; +import CoinAnimation from "@/Cabinet/components/Store/CoinAnimation"; +import useMenu from "@/Cabinet/hooks/useMenu"; + +const StoreCoinPick = () => { + const { toggleStore } = useMenu(); + + return ( + + <> + + +

누군가가 매일 흘리는 동점을 주워보세요💰

+

동전은 하루에 한 번씩 획득할 수 있습니다

+
+ toggleStore()}> + 동전 주우러가기 + + +
+ ); +}; + +const CoinSummary = styled.div` + background-color: var(--card-content-bg-color); + font-size: var(--size-base); + word-wrap: normal; + width: 90%; + margin: 5px; + padding: 12px 20px; + line-height: 1.4; + border-radius: 10px; +`; + +const CoinCheckButton = styled.button` + font-weight: 500; + width: 90%; + margin: 8px 0 16px; + font-size: 0.9rem; +`; + +export default StoreCoinPick; diff --git a/frontend/src/Cabinet/components/Store/StoreInfo.tsx b/frontend/src/Cabinet/components/Store/StoreInfo.tsx new file mode 100644 index 000000000..65276fed9 --- /dev/null +++ b/frontend/src/Cabinet/components/Store/StoreInfo.tsx @@ -0,0 +1,163 @@ +import { useEffect, useState } from "react"; +import styled from "styled-components"; +import ButtonContainer from "@/Cabinet/components/Common/Button"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import StoreCoinCheckBox from "@/Cabinet/components/Store/StoreCoinCheckBox"; +import { ReactComponent as CloseIcon } from "@/Cabinet/assets/images/exitButton.svg"; +import { ReactComponent as StoreCoin } from "@/Cabinet/assets/images/storeCoin.svg"; +import { + axiosCoinCheckGet, + axiosCoinCheckPost, +} from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; + +const StoreInfo = () => { + // 처음 날개 열었을 때 get요청 로딩 함수 + const [isLoading, setIsLoading] = useState(false); + const { closeStore } = useMenu(); + const [showResponseModal, setShowResponseModal] = useState(false); + const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false); + const [modalTitle, setModalTitle] = useState(""); + const [modalContent, setModalContent] = useState(""); + const [monthlyCoinCount, setmonthlyCoinCount] = useState(0); + const [todayCoinCollection, setTodayCoinCollection] = + useState(false); + + const tryCoinCheckGet = async () => { + try { + const res = await axiosCoinCheckGet(); + setTodayCoinCollection(res.data.todayCoinCollection); + setmonthlyCoinCount(res.data.monthlyCoinCount); + } catch (error: any) { + throw error; + } + }; + + const tryCoinCheckPost = async () => { + setIsLoading(true); + try { + if (todayCoinCollection === true) { + throw { data: { message: "오늘은 동전을 이미 주웠습니다" } }; + } else { + const res = await axiosCoinCheckPost(); + setModalTitle("동전 줍기 성공"); + setModalContent( + `${res.data.reward}까비를 획득했습니다.` + ); + setmonthlyCoinCount(monthlyCoinCount + 1); + setTodayCoinCollection(true); + } + } catch (error: any) { + if (error.response && error.response.status === 400) { + setModalTitle("동전 줍기 실패"); + } else { + setModalTitle(error.data.message || error.response.data.message); + } + setHasErrorOnResponse(true); + setIsLoading(false); + } finally { + setShowResponseModal(true); + } + }; + + useEffect(() => { + if (monthlyCoinCount === 20 || todayCoinCollection === true) { + setIsLoading(true); + } + tryCoinCheckGet(); + }, [todayCoinCollection, monthlyCoinCount]); + + return ( + + + 동전 줍기 + + + + + + + + + tryCoinCheckPost()} + text="줍기" + theme="fill" + disabled={isLoading} + /> + closeStore()} + text="취소" + theme="line" + /> + + {showResponseModal && + (hasErrorOnResponse ? ( + { + setShowResponseModal(false); + setHasErrorOnResponse(false); + }} + /> + ) : ( + setShowResponseModal(false)} + /> + ))} + + ); +}; + +export default StoreInfo; + +const WrapperStyled = styled.div` + position: fixed; + top: 120px; + right: 0; + min-width: 330px; + width: 330px; + height: calc(100% - 120px); + padding: 40px; + z-index: 9; + transform: translateX(120%); + transition: transform 0.3s ease-in-out; + box-shadow: 0 0 40px 0 var(--left-nav-border-shadow-color); + display: flex; + flex-direction: column; + align-items: center; + background: var(--bg-color); + border-left: 1px solid var(--line-color); + overflow-y: auto; + border-left: 1px solid var(--line-color); ; +`; + +const HeaderStyled = styled.div` + display: flex; + width: 100%; + align-items: center; + color: var(--normal-text-color); + font-weight: bold; + font-size: 1.5rem; + margin-bottom: 30px; +`; + +const ButtonContainerStyled = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + max-height: 140px; +`; + +// 확대시 꺠짐 방지 div +const StoreIconContainer = styled.div``; diff --git a/frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx b/frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx index 157ac8157..59ef017a8 100644 --- a/frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx +++ b/frontend/src/Cabinet/components/TopNav/AdminTopNav.container.tsx @@ -1,5 +1,5 @@ import React, { SetStateAction, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import { buildingsFloorState, @@ -22,6 +22,7 @@ const AdminTopNavContainer: React.FC<{ const { setIsLoading } = props; const { toggleLeftNav } = useMenu(); const navigator = useNavigate(); + const isLocation = useLocation(); const onClickLogo = () => { toggleLeftNav(); @@ -54,7 +55,7 @@ const AdminTopNavContainer: React.FC<{ useEffect(() => { if (currentBuildingName === undefined) return; else if (currentBuildingName === "새롬관") { - navigator("/admin/main"); + navigator(isLocation); } }, [currentBuildingName]); diff --git a/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBar.tsx b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBar.tsx index 43a276b9e..216fc03ab 100644 --- a/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBar.tsx +++ b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchBar.tsx @@ -1,8 +1,12 @@ import { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; +import { useSetRecoilState } from "recoil"; import styled from "styled-components"; +import { selectedTypeOnSearchState } from "@/Cabinet/recoil/atoms"; import SearchBarList from "@/Cabinet/components/TopNav/SearchBar/SearchBarList/SearchBarList"; +import { ReactComponent as SearchIcon } from "@/Cabinet/assets/images/searchWhite.svg"; import { CabinetSimple } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetDetailAreaType from "@/Cabinet/types/enum/cabinetDetailArea.type.enum"; import { axiosSearchByCabinetNumSimple, axiosSearchByIntraId, @@ -22,6 +26,9 @@ const SearchBar = () => { const [searchValue, setSearchValue] = useState(""); const [floor, setFloor] = useState(0); const { debounce } = useDebounce(); + const setSelectedTypeOnSearch = useSetRecoilState( + selectedTypeOnSearchState + ); const resetSearchState = () => { setSearchListById([]); @@ -45,6 +52,10 @@ const SearchBar = () => { resetSearchState(); return alert("두 글자 이상의 검색어를 입력해주세요."); } else { + if (isNaN(Number(searchValue))) + setSelectedTypeOnSearch(CabinetDetailAreaType.USER); + else setSelectedTypeOnSearch(CabinetDetailAreaType.CABINET); + let query = floor ? `?q=${searchInput.current.value}&floor=${floor}` : `?q=${searchInput.current.value}`; @@ -164,7 +175,9 @@ const SearchBar = () => { onChange={() => debounce("topNavSearch", typeSearchInput, 300)} onKeyDown={handleInputKey} > - + + + 취소 {isFocus && searchInput.current?.value && totalLength > 0 && ( @@ -205,16 +218,6 @@ const SearchBarInputStyled = styled.input` } `; -const SearchButtonStyled = styled.button` - background: url("/src/Cabinet/assets/images/searchWhite.svg") no-repeat 50% - 50%; - width: 32px; - height: 32px; - position: absolute; - top: 4px; - right: 14px; -`; - const CancelButtonStyled = styled.button` min-width: 60px; width: 60px; @@ -226,4 +229,12 @@ const CancelButtonStyled = styled.button` } `; +const IconWrapperStyled = styled.div` + width: 24px; + height: 24px; + position: absolute; + top: 8px; + right: 14px; +`; + export default SearchBar; diff --git a/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx index b60f15098..694b6a390 100644 --- a/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx +++ b/frontend/src/Cabinet/components/TopNav/SearchBar/SearchListItem/SearchListItem.tsx @@ -36,7 +36,7 @@ const SearchListItem = (props: { resetSearchState(); }} > - + {isNum ? : } {isNum && {floor}F - } @@ -57,30 +57,30 @@ const LiStyled = styled.li` &.active { background-color: var(--button-line-color); - color: var(--bg-color); + color: var(--white-text-with-bg-color); } &.active strong { - color: var(--bg-color); + color: var(--white-text-with-bg-color); } - &.active div { - filter: invert(100%); + &.active path { + stroke: var(--white-text-with-bg-color); } @media (hover: hover) and (pointer: fine) { &:hover { background-color: var(--button-line-color); - color: var(--bg-color); + color: var(--white-text-with-bg-color); } &:hover strong { - color: var(--bg-color); + color: var(--white-text-with-bg-color); } - &:hover div { - filter: invert(100%); + &:hover path { + stroke: var(--white-text-with-bg-color); } } `; -const ContentIconStyled = styled.div<{ isNum?: boolean }>` +const ContentIconStyled = styled.div` width: 20px; height: 20px; margin-right: 8px; @@ -90,9 +90,12 @@ const ContentIconStyled = styled.div<{ isNum?: boolean }>` height: 20px; } - & path { + & > svg > path { stroke: var(--normal-text-color); - transform: ${(props) => (props.isNum ? "scale(1)" : "scale(0.8)")}; + } + + &:hover & > svg > path { + stroke: var(--white-text-with-bg-color); } `; diff --git a/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx b/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx index 23ee2077e..8ec3690cf 100644 --- a/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx @@ -1,5 +1,5 @@ import React, { SetStateAction, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import { buildingsFloorState, @@ -29,6 +29,7 @@ const TopNavContainer: React.FC<{ const { setIsLoading } = props; const { toggleLeftNav } = useMenu(); const navigator = useNavigate(); + const isLocation = useLocation(); const onClickLogo = () => { toggleLeftNav(); @@ -70,7 +71,7 @@ const TopNavContainer: React.FC<{ useEffect(() => { if (currentBuildingName === undefined) return; else if (currentBuildingName === "새롬관") { - navigator("/home"); + navigator(isLocation); } }, [currentBuildingName]); diff --git a/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx index a6bbaf918..461023b91 100644 --- a/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton.tsx @@ -4,10 +4,10 @@ import { ReactComponent as HappyCcabiImg } from "@/Cabinet/assets/images/happyCc import { ReactComponent as MapImg } from "@/Cabinet/assets/images/map.svg"; import { ReactComponent as MyCabinetIcon } from "@/Cabinet/assets/images/myCabinetIcon.svg"; import { ReactComponent as SearchImg } from "@/Cabinet/assets/images/searchWhite.svg"; +import { ReactComponent as MyCoinImg } from "@/Cabinet/assets/images/storeCoinNav.svg"; interface ITopNavButton { onClick: React.MouseEventHandler; - // imgSrc: string; type: string; disable?: boolean; width?: string; @@ -23,6 +23,7 @@ const TopNavButton = (props: ITopNavButton) => { onClick={props.onClick} disable={props.disable} > + {props.type === "myCoin" && } {props.type === "happyCcabi" && } {props.type === "search" && } {props.type === "myCabinetIcon" && } @@ -42,6 +43,9 @@ const TopNavButtonStyled = styled.div<{ height: 32px; margin-right: 10px; cursor: pointer; + display: flex; + justify-content: center; + align-items: center; /* display: ${({ disable }) => (disable ? "none" : "block")}; */ /* visibility: ${({ disable }) => (disable ? "hidden" : "visible")}; */ @media (hover: hover) and (pointer: fine) { @@ -54,10 +58,13 @@ const TopNavButtonStyled = styled.div<{ fill: var(--bg-color); } + & > svg { + width: ${(props) => (props.id === "searchButton" ? "26px" : "32px")}; + height: ${(props) => (props.id === "searchButton" ? "26px" : "32px")}; + } + & > svg > path { stroke: var(--gray-line-btn-color); - transform: ${(props) => - props.id === "searchButton" ? "scale(0.9)" : "scale(1)"}; } `; diff --git a/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx index fc16f4f97..a1b2d88e3 100644 --- a/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx @@ -24,7 +24,7 @@ export const getDefaultCabinetInfo = () => ({ cabinetId: 0, visibleNum: 0, lentType: CabinetType.PRIVATE, - title: null, + title: "", maxUser: 0, status: CabinetStatus.PENDING, section: "", @@ -33,7 +33,8 @@ export const getDefaultCabinetInfo = () => ({ }); const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => { - const { toggleCabinet, toggleMap, openCabinet, closeAll } = useMenu(); + const { toggleCabinet, toggleMap, toggleStore, openCabinet, closeAll } = + useMenu(); const [currentCabinetId, setCurrentCabinetId] = useRecoilState( currentCabinetIdState ); @@ -105,6 +106,9 @@ const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => { disable={true} /> )} + {!isAdmin && ( + + )} {!isAdmin && !!myInfo.cabinetId && ( { +const UserCabinetInfoAreaContainer = (): JSX.Element => { const targetUserInfo = useRecoilValue(targetUserInfoState); const { closeCabinet, openLent } = useMenu(); - - const userInfoData: ISelectedUserInfo | undefined = targetUserInfo + const userInfoData: ISelectedUserCabinetInfo | undefined = targetUserInfo ? { name: targetUserInfo.name, userId: targetUserInfo.userId, @@ -20,7 +19,7 @@ const UserInfoAreaContainer = (): JSX.Element => { return ( <> - { ); }; -export default UserInfoAreaContainer; +export default UserCabinetInfoAreaContainer; diff --git a/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.tsx b/frontend/src/Cabinet/components/UserCabinetInfoArea/UserCabinetInfoArea.tsx similarity index 82% rename from frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.tsx rename to frontend/src/Cabinet/components/UserCabinetInfoArea/UserCabinetInfoArea.tsx index 2a7dba052..67840970f 100644 --- a/frontend/src/Cabinet/components/UserInfoArea/UserInfoArea.tsx +++ b/frontend/src/Cabinet/components/UserCabinetInfoArea/UserCabinetInfoArea.tsx @@ -1,28 +1,25 @@ import React, { useState } from "react"; import styled from "styled-components"; import ButtonContainer from "@/Cabinet/components/Common/Button"; +import SelectInduction from "@/Cabinet/components/Common/SelectInduction"; import BanModal from "@/Cabinet/components/Modals/BanModal/BanModal"; -import AdminReturnModal from "@/Cabinet/components/Modals/ReturnModal/AdminReturnModal"; -import ChangeToHTML from "@/Cabinet/components/TopNav/SearchBar/SearchListItem/ChangeToHTML"; import { cabinetIconComponentMap, - cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, } from "@/Cabinet/assets/data/maps"; -import cabiLogo from "@/Cabinet/assets/images/logo.svg"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; -export interface ISelectedUserInfo { +export interface ISelectedUserCabinetInfo { name: string; userId: number | null; isBanned: boolean; bannedInfo?: string; } -const UserInfoArea: React.FC<{ - selectedUserInfo?: ISelectedUserInfo; +const UserCabinetInfoArea: React.FC<{ + selectedUserInfo?: ISelectedUserCabinetInfo; closeCabinet: () => void; openLent: React.MouseEventHandler; }> = (props) => { @@ -33,19 +30,17 @@ const UserInfoArea: React.FC<{ const handleOpenBanModal = () => { setShowBanModal(true); }; + const handleCloseBanModal = () => { setShowBanModal(false); }; if (selectedUserInfo === undefined) return ( - - - - 사물함/유저를
- 선택해주세요 -
-
+ ); return ( @@ -92,14 +87,6 @@ const UserInfoArea: React.FC<{ ); }; -const NotSelectedStyled = styled.div` - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -`; - const CabinetDetailAreaStyled = styled.div` height: 100%; display: flex; @@ -108,12 +95,6 @@ const CabinetDetailAreaStyled = styled.div` align-items: center; `; -const CabiLogoStyled = styled.img` - width: 35px; - height: 35px; - margin-bottom: 10px; -`; - const CabinetTypeIconStyled = styled.div` width: 24px; height: 24px; @@ -191,4 +172,4 @@ const CabinetLentDateInfoStyled = styled.div<{ textColor: string }>` text-align: center; `; -export default UserInfoArea; +export default UserCabinetInfoArea; diff --git a/frontend/src/Cabinet/hooks/useMenu.ts b/frontend/src/Cabinet/hooks/useMenu.ts index c3fa83f95..1a47e474a 100644 --- a/frontend/src/Cabinet/hooks/useMenu.ts +++ b/frontend/src/Cabinet/hooks/useMenu.ts @@ -1,3 +1,4 @@ +import { useResetRecoilState } from "recoil"; import { currentCabinetIdState, currentIntraIdState, @@ -5,7 +6,6 @@ import { targetClubUserInfoState, targetUserInfoState, } from "@/Cabinet/recoil/atoms"; -import { useResetRecoilState } from "recoil"; const useMenu = () => { const resetTargetCabinetInfo = useResetRecoilState(targetCabinetInfoState); @@ -16,7 +16,7 @@ const useMenu = () => { const toggleLeftNav = () => { if ( - document.getElementById("leftNavWrap")?.classList.contains("on") == true + document.getElementById("leftNavWrap")?.classList.contains("on") === true ) { closeLeftNav(); } else { @@ -27,6 +27,7 @@ const useMenu = () => { const openLeftNav = () => { closeCabinet(); closeMap(); + closeStore(); closeLent(); closeClubMember(); document.getElementById("menuBg")?.classList.add("on"); @@ -35,7 +36,7 @@ const useMenu = () => { const closeLeftNav = () => { if ( - document.getElementById("leftNavWrap")?.classList.contains("on") == true + document.getElementById("leftNavWrap")?.classList.contains("on") === true ) { document.getElementById("menuBg")?.classList.remove("on"); document.getElementById("leftNavWrap")?.classList.remove("on"); @@ -43,7 +44,9 @@ const useMenu = () => { }; const toggleLent = () => { - if (document.getElementById("lentInfo")?.classList.contains("on") == true) { + if ( + document.getElementById("lentInfo")?.classList.contains("on") === true + ) { closeLent(); } else { openLent(); @@ -53,25 +56,54 @@ const useMenu = () => { const openLent = () => { closeLeftNav(); closeMap(); + closeStore(); document.getElementById("lentInfo")?.classList.add("on"); document.getElementById("menuBg")?.classList.add("on"); }; const closeLent = () => { - if (document.getElementById("lentInfo")?.classList.contains("on") == true) { + if ( + document.getElementById("lentInfo")?.classList.contains("on") === true + ) { document.getElementById("lentInfo")?.classList.remove("on"); } }; const toggleMap = () => { - if (document.getElementById("mapInfo")?.classList.contains("on") == true) { + if (document.getElementById("mapInfo")?.classList.contains("on") === true) { closeMap(); } else { openMap(); } }; + const toggleStore = () => { + if (document.getElementById("storeInfo")?.classList.contains("on") === true) + closeStore(); + else openStore(); + }; + + const openStore = () => { + closeMap(); + closeLeftNav(); + closeCabinet(); + closeLent(); + closeClubMember(); + document.getElementById("storeInfo")?.classList.add("on"); + document.getElementById("menuBg")?.classList.add("on"); + }; + + const closeStore = () => { + if ( + document.getElementById("storeInfo")?.classList.contains("on") == true + ) { + document.getElementById("storeInfo")?.classList.remove("on"); + document.getElementById("menuBg")?.classList.remove("on"); + } + }; + const openMap = () => { + closeStore(); closeLeftNav(); closeCabinet(); closeLent(); @@ -81,7 +113,7 @@ const useMenu = () => { }; const closeMap = () => { - if (document.getElementById("mapInfo")?.classList.contains("on") == true) { + if (document.getElementById("mapInfo")?.classList.contains("on") === true) { document.getElementById("mapInfo")?.classList.remove("on"); document.getElementById("mapFloorOptionBox")?.classList.remove("on"); document.getElementById("menuBg")?.classList.remove("on"); @@ -90,7 +122,7 @@ const useMenu = () => { const toggleCabinet = () => { if ( - document.getElementById("cabinetDetailArea")?.classList.contains("on") == + document.getElementById("cabinetDetailArea")?.classList.contains("on") === true ) { closeCabinet(); @@ -102,6 +134,7 @@ const useMenu = () => { const openCabinet = () => { closeLeftNav(); closeMap(); + closeStore(); closeLent(); closeClubMember(); document.getElementById("cabinetDetailArea")?.classList.add("on"); @@ -110,8 +143,9 @@ const useMenu = () => { const closeCabinet = () => { closeLent(); + closeStore(); if ( - document.getElementById("cabinetDetailArea")?.classList.contains("on") == + document.getElementById("cabinetDetailArea")?.classList.contains("on") === true ) { resetTargetCabinetInfo(); @@ -125,8 +159,9 @@ const useMenu = () => { const toggleClubMember = () => { if ( - document.getElementById("clubMemberInfoArea")?.classList.contains("on") == - true + document + .getElementById("clubMemberInfoArea") + ?.classList.contains("on") === true ) { closeClubMember(); } else { @@ -137,6 +172,7 @@ const useMenu = () => { const openClubMember = () => { closeLeftNav(); closeMap(); + closeStore(); closeLent(); closeCabinet(); document.getElementById("clubMemberInfoArea")?.classList.add("on"); @@ -145,8 +181,9 @@ const useMenu = () => { const closeClubMember = () => { if ( - document.getElementById("clubMemberInfoArea")?.classList.contains("on") == - true + document + .getElementById("clubMemberInfoArea") + ?.classList.contains("on") === true ) { resetTargetClubUserInfo(); document.getElementById("clubMemberInfoArea")?.classList.remove("on"); @@ -154,12 +191,28 @@ const useMenu = () => { } }; + const openUserStore = () => { + closeLeftNav(); + closeMap(); + closeStore(); + document.getElementById("itemInfo")?.classList.add("on"); + document.getElementById("menuBg")?.classList.add("on"); + }; + + const closeUserStore = () => { + if (document.getElementById("itemInfo")?.classList.contains("on") == true) { + document.getElementById("itemInfo")?.classList.remove("on"); + } + }; + const closeAll = () => { closeLeftNav(); closeCabinet(); closeLent(); closeMap(); + closeStore(); closeClubMember(); + closeUserStore(); }; return { @@ -167,6 +220,7 @@ const useMenu = () => { openLeftNav, closeLeftNav, toggleMap, + toggleStore, openMap, closeMap, toggleCabinet, @@ -179,6 +233,10 @@ const useMenu = () => { toggleClubMember, openClubMember, closeClubMember, + openStore, + closeStore, + openUserStore, + closeUserStore, }; }; diff --git a/frontend/src/Cabinet/hooks/useMultiSelect.ts b/frontend/src/Cabinet/hooks/useMultiSelect.ts index 1b5a46a21..eebe1ce43 100644 --- a/frontend/src/Cabinet/hooks/useMultiSelect.ts +++ b/frontend/src/Cabinet/hooks/useMultiSelect.ts @@ -4,11 +4,9 @@ import { selectedTypeOnSearchState, targetCabinetInfoListState, } from "@/Cabinet/recoil/atoms"; -import { - CabinetInfo, - CabinetPreviewInfo, -} from "@/Cabinet/types/dto/cabinet.dto"; +import { CabinetPreviewInfo } from "@/Cabinet/types/dto/cabinet.dto"; import useMenu from "@/Cabinet/hooks/useMenu"; +import CabinetDetailAreaType from "../types/enum/cabinetDetailArea.type.enum"; const useMultiSelect = () => { const [targetCabinetInfoList, setTargetCabinetInfoList] = useRecoilState< @@ -49,7 +47,7 @@ const useMultiSelect = () => { }; const clickCabinetOnMultiSelectMode = (cabinet: CabinetPreviewInfo) => { - setSelectedTypeOnSearch("CABINET"); + setSelectedTypeOnSearch(CabinetDetailAreaType.CABINET); if (!containsCabinet(cabinet.cabinetId)) { setTargetCabinetInfoList([...targetCabinetInfoList, cabinet]); return; diff --git a/frontend/src/Cabinet/pages/CoinLogPage.tsx b/frontend/src/Cabinet/pages/CoinLogPage.tsx new file mode 100644 index 000000000..cab33ed58 --- /dev/null +++ b/frontend/src/Cabinet/pages/CoinLogPage.tsx @@ -0,0 +1,7 @@ +import CoinLog from "@/Cabinet/components/Store/CoinLog/CoinLog"; + +const CoinLogPage = () => { + return ; +}; + +export default CoinLogPage; diff --git a/frontend/src/Cabinet/pages/InventoryPage.tsx b/frontend/src/Cabinet/pages/InventoryPage.tsx new file mode 100644 index 000000000..583f4932c --- /dev/null +++ b/frontend/src/Cabinet/pages/InventoryPage.tsx @@ -0,0 +1,7 @@ +import Inventory from "@/Cabinet/components/Store/Inventory/Inventory"; + +const InventoryPage = () => { + return ; +}; + +export default InventoryPage; diff --git a/frontend/src/Cabinet/pages/ItemUsageLogPage.tsx b/frontend/src/Cabinet/pages/ItemUsageLogPage.tsx new file mode 100644 index 000000000..9e4d288a0 --- /dev/null +++ b/frontend/src/Cabinet/pages/ItemUsageLogPage.tsx @@ -0,0 +1,175 @@ +import { useEffect, useState } from "react"; +import styled from "styled-components"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import UnavailableDataInfo from "@/Cabinet/components/Common/UnavailableDataInfo"; +import { mapItemNameToType } from "@/Cabinet/components/Store/ItemUsageLog/ItemLogBlock"; +import ItemLogBlock from "@/Cabinet/components/Store/ItemUsageLog/ItemLogBlock"; +import { ItemIconMap } from "@/Cabinet/assets/data/maps"; +import { ReactComponent as DropdownChevron } from "@/Cabinet/assets/images/dropdownChevron.svg"; +import { axiosGetItemUsageHistory } from "@/Cabinet/api/axios/axios.custom"; + +export interface IItemUsageLog { + date: Date; + dateStr?: string; + title: string; + logo: React.FunctionComponent>; +} + + + +function createLogEntries(data: { result: any[] }) { + return data.result.map((item) => { + const itemDate = new Date(item.date); + // console.log("itemType : ",item.itemDto.itemName) + // console.log("itemType : ",extractItemName(item.itemDto.itemName)) + // console.log("itemType : ",mapItemNameToType(extractItemName(item.itemDto.itemName))) + return { + date: itemDate, + dateStr: `${itemDate.getFullYear()}년 ${itemDate.getMonth() + 1}월`, + title: + item.itemDto.itemName === item.itemDto.itemDetails + ? item.itemDto.itemName + : `${item.itemDto.itemName} - ${item.itemDto.itemDetails}`, + logo: ItemIconMap[mapItemNameToType(item.itemDto.itemName)], + }; + }); +} + +const ItemUsageLogPage = () => { + const [itemUsageLogs, setItemUsageLogs] = useState([]); + const [page, setPage] = useState(0); + const [hasAdditionalLogs, sethasAdditionalLogs] = useState(true); + const [isLoading, setIsLoading] = useState(true); + const size = 5; + + const getItemUsageLog = async (page: number, size: number) => { + setIsLoading(true); + try { + const data = await axiosGetItemUsageHistory(page, size); + const newLogs = createLogEntries(data); + setItemUsageLogs((prevLogs) => [...prevLogs, ...newLogs]); + sethasAdditionalLogs(data.result.length === size); + } catch (error) { + console.error("Failed to fetch item usage history:", error); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + getItemUsageLog(page, size); + }, [page]); + + const handleMoreClick = () => { + setPage((prev) => prev + 1); + }; + + return itemUsageLogs.length > 0 ? ( + + 아이템 사용내역 + + {itemUsageLogs.map((log, idx, logs) => { + const isNewMonth = idx === 0 || log.dateStr !== logs[idx - 1].dateStr; + return ( + + {isNewMonth && ( + + {log.dateStr} + + )} + + + ); + })} + {hasAdditionalLogs && ( + + + {isLoading ? : "더보기"} + + + + )} + + + ) : ( + + ); +}; + +const WrapperStyled = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 60px 0 100px 0; +`; + +const TitleWrapperStyled = styled.div` + font-size: 32px; + width: 80%; + font-weight: 700; + margin: 20px 0; + display: flex; + justify-content: center; +`; + +const ItemUsageLogWrapperStyled = styled.div` + width: 70%; + + @media (max-width: 1040px) { + width: 80%; + } +`; + +const LogItemStyled = styled.div` + margin-top: 20px; +`; + +const DateSectionStyled = styled.div` + margin-top: 40px; +`; + +const DateTitleStyled = styled.h2` + font-size: 18px; + font-weight: bold; + margin-top: 20px; + margin-bottom: 20px; +`; + +const ButtonContainerStyled = styled.div` + width: 100%; + height: 100%; + margin-top: 20px; + display: flex; + align-items: center; + justify-content: center; +`; + +const MoreButtonStyled = styled.button<{ + isLoading: boolean; +}>` + display: flex; + align-items: center; + justify-content: center; + width: 200px; + height: 50px; + margin: 20px auto; + border: 1px solid var(--sys-main-color); + border-radius: 30px; + background-color: var(--white); + color: var(--sys-main-color); + position: relative; + padding: 0 15px; + + svg { + margin-left: 18px; + margin-bottom: -2px; + width: 13px; + height: 9px; + } +`; + +export default ItemUsageLogPage; diff --git a/frontend/src/Cabinet/pages/Layout.tsx b/frontend/src/Cabinet/pages/Layout.tsx index 6eecfc4ca..c8f029de5 100644 --- a/frontend/src/Cabinet/pages/Layout.tsx +++ b/frontend/src/Cabinet/pages/Layout.tsx @@ -22,15 +22,21 @@ import { ClubResponseDto, } from "@/Cabinet/types/dto/club.dto"; import { UserDto, UserInfo } from "@/Cabinet/types/dto/user.dto"; -import { axiosMyClubList, axiosMyInfo } from "@/Cabinet/api/axios/axios.custom"; +import { + axiosMyClubList, + axiosMyInfo, + axiosMyItems, +} from "@/Cabinet/api/axios/axios.custom"; import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; import useMenu from "@/Cabinet/hooks/useMenu"; +import StoreInfo from "../components/Store/StoreInfo"; const body: HTMLElement = document.body; const root: HTMLElement = document.documentElement; const token = getCookie("access_token"); const Layout = (): JSX.Element => { + const [hasPenaltyItem, setHasPenaltyItem] = useState(true); const [isLoading, setIsLoading] = useState(true); const [isValidToken, setIsValidToken] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); @@ -67,12 +73,16 @@ const Layout = (): JSX.Element => { data: myInfo, data: { date: serverTime }, } = await axiosMyInfo(); + const { data } = await axiosMyItems(); const formattedServerTime = serverTime.split(" KST")[0]; setServerTime(new Date(formattedServerTime)); // 접속 후 최초 서버 시간을 가져옴 setMyInfoData(myInfo); setUser(myInfo); setIsValidToken(true); + if (data.penaltyItems.length == 0) { + setHasPenaltyItem(false); + } if (myInfo.unbannedAt) { openModal(); } @@ -160,11 +170,13 @@ const Layout = (): JSX.Element => { + {isModalOpen && myInfoData && myInfoData.unbannedAt !== undefined && ( )}
diff --git a/frontend/src/Cabinet/pages/MainPage.tsx b/frontend/src/Cabinet/pages/MainPage.tsx index 830e16137..070e57387 100644 --- a/frontend/src/Cabinet/pages/MainPage.tsx +++ b/frontend/src/Cabinet/pages/MainPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useRecoilState, useRecoilValue, useResetRecoilState } from "recoil"; import styled from "styled-components"; @@ -7,12 +7,18 @@ import { currentCabinetIdState, currentFloorNumberState, currentSectionNameState, + isCurrentSectionRenderState, targetCabinetInfoState, } from "@/Cabinet/recoil/atoms"; import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; import CabinetListContainer from "@/Cabinet/components/CabinetList/CabinetList.container"; import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import SectionAlertModal from "@/Cabinet/components/Modals/SectionAlertModal/SectionAlertModal"; import SectionPaginationContainer from "@/Cabinet/components/SectionPagination/SectionPagination.container"; +import { clubSectionsData } from "@/Cabinet/assets/data/mapPositionData"; +import { ReactComponent as FilledHeartIcon } from "@/Cabinet/assets/images/filledHeart.svg"; +import { ReactComponent as LineHeartIcon } from "@/Cabinet/assets/images/lineHeart.svg"; +import { ICurrentSectionInfo } from "@/Cabinet/types/dto/cabinet.dto"; import SectionType from "@/Cabinet/types/enum/map.type.enum"; import useCabinetListRefresh from "@/Cabinet/hooks/useCabinetListRefresh"; import useMenu from "@/Cabinet/hooks/useMenu"; @@ -25,12 +31,14 @@ const MainPage = () => { const navigator = useNavigate(); const resetTargetCabinetInfo = useResetRecoilState(targetCabinetInfoState); const resetCurrentCabinetId = useResetRecoilState(currentCabinetIdState); - const sectionList = useRecoilValue>(currentFloorSectionState); + const sectionList: Array = useRecoilValue< + Array + >(currentFloorSectionState); const [currentSectionName, setCurrentSectionName] = useRecoilState( currentSectionNameState ); const currentSectionIndex = sectionList.findIndex( - (sectionName) => sectionName === currentSectionName + (section) => section.sectionName === currentSectionName ); const currentBuilding = useRecoilValue(currentBuildingNameState); const currentFloor = useRecoilValue(currentFloorNumberState); @@ -38,6 +46,12 @@ const MainPage = () => { currentBuilding, currentFloor ); + const [isClubSection, setIsClubSection] = useState(false); + const [showSectionAlertModal, setShowSectionAlertModal] = + useState(false); + const [isCurrentSectionRender, setIsCurrentSectionRender] = useRecoilState( + isCurrentSectionRenderState + ); useEffect(() => { if (!currentFloor) { @@ -55,6 +69,15 @@ const MainPage = () => { }; }, []); + useEffect(() => { + const clubSection = clubSectionsData.find((section) => { + return section === currentSectionName; + }) + ? true + : false; + setIsClubSection(clubSection); + }, [currentSectionName]); + const swipeSection = (touchEndPosX: number, touchEndPosY: number) => { const touchOffsetX = Math.round(touchEndPosX - touchStartPosX.current); const touchOffsetY = Math.round(touchEndPosY - touchStartPosY.current); @@ -77,20 +100,25 @@ const MainPage = () => { if (direction === "left") { setCurrentSectionName( currentSectionIndex <= 0 - ? sectionList[sectionList.length - 1] - : sectionList[currentSectionIndex - 1] + ? sectionList[sectionList.length - 1].sectionName + : sectionList[currentSectionIndex - 1].sectionName ); } else if (direction === "right") { setCurrentSectionName( currentSectionIndex >= sectionList.length - 1 - ? sectionList[0] - : sectionList[currentSectionIndex + 1] + ? sectionList[0].sectionName + : sectionList[currentSectionIndex + 1].sectionName ); } mainWrapperRef.current?.scrollTo(0, 0); }; + const handleAlertIconBtn = () => { + setShowSectionAlertModal(true); + setIsCurrentSectionRender(false); + }; + return ( <> {isLoading && } @@ -107,6 +135,22 @@ const MainPage = () => { ); }} > + + {!isClubSection && ( + + {sectionList[currentSectionIndex]?.alarmRegistered === true ? ( + + ) : ( + + )} + + )} + @@ -122,6 +166,14 @@ const MainPage = () => { )} + {showSectionAlertModal && ( + + )}
); @@ -160,4 +212,22 @@ const RefreshButtonStyled = styled.button` } `; +const IconWrapperStyled = styled.div<{ disabled: boolean }>` + height: 16px; + width: 16px; + pointer-events: ${(props) => (props.disabled ? "none" : "inherit")}; + + &:hover { + cursor: pointer; + } +`; + +const AlertStyled = styled.div` + height: 30px; + display: flex; + justify-content: end; + align-items: end; + padding-right: 14px; +`; + export default MainPage; diff --git a/frontend/src/Cabinet/pages/ProfilePage.tsx b/frontend/src/Cabinet/pages/ProfilePage.tsx index 90e688408..a91eb6daf 100644 --- a/frontend/src/Cabinet/pages/ProfilePage.tsx +++ b/frontend/src/Cabinet/pages/ProfilePage.tsx @@ -8,9 +8,9 @@ import { useRecoilState } from "recoil"; import styled from "styled-components"; import { userState } from "@/Cabinet/recoil/atoms"; import DisplayStyleCardContainer from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container"; -import ExtensionCardContainer from "@/Cabinet/components/Card/ExtensionCard/ExtensionCard.container"; import LentInfoCardContainer from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard.container"; import NotificationCardContainer from "@/Cabinet/components/Card/NotificationCard/NotificationCard.container"; +import PointColorCardContainer from "@/Cabinet/components/Card/PointColorCard/PointColorCard.container"; import ProfileCardContainer from "@/Cabinet/components/Card/ProfileCard/ProfileCard.container"; import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; import { @@ -52,15 +52,13 @@ const ProfilePage = () => { ) : ( - + )} @@ -75,20 +73,20 @@ const CardGridWrapper = styled.div` width: 100%; grid-gap: 20px; grid-template-columns: 350px 350px; - grid-template-rows: 163px 183px 348px; + grid-template-rows: 163px 183px 230px; grid-template-areas: "profile lentInfo" // h: 163px h: 366px - "extension lentInfo" // h: 183px - "notification theme"; // h: 230px h: 230px; + "displayStyle lentInfo" // h: 183px + "pointColor notification"; // h: 230px h: 230px @media (max-width: 768px) { grid-template-columns: 350px; - grid-template-rows: 163px 366px 183px 230px 348px; + grid-template-rows: 163px 366px 183px 230px 230px; grid-template-areas: "profile" "lentInfo" - "extension" - "notification" - "theme"; + "displayStyle" + "pointColor" + "notification"; } `; diff --git a/frontend/src/Cabinet/pages/StoreMainPage.tsx b/frontend/src/Cabinet/pages/StoreMainPage.tsx new file mode 100644 index 000000000..5559c070d --- /dev/null +++ b/frontend/src/Cabinet/pages/StoreMainPage.tsx @@ -0,0 +1,171 @@ +import { useEffect, useState } from "react"; +import { useRecoilState } from "recoil"; +import styled from "styled-components"; +import { userState } from "@/Cabinet/recoil/atoms"; +import StoreItemCard from "@/Cabinet/components/Card/StoreItemCard/StoreItemCard"; +import StoreBuyItemModal from "@/Cabinet/components/Modals/StoreModal/StoreBuyItemModal"; +import StoreCoinPick from "@/Cabinet/components/Store/StoreCoinPick"; +import { ItemTypeLabelMap } from "@/Cabinet/assets/data/maps"; +import { IItemDetail } from "@/Cabinet/types/dto/store.dto"; +import { axiosItems } from "@/Cabinet/api/axios/axios.custom"; + +export const sortItems = (items: IItemDetail[]) => { + return items.sort((a, b) => { + const order = [ + ItemTypeLabelMap.EXTENSION, + ItemTypeLabelMap.SWAP, + ItemTypeLabelMap.ALARM, + ItemTypeLabelMap.PENALTY, + ]; + const indexA = order.indexOf(a.itemName); + const indexB = order.indexOf(b.itemName); + return indexA - indexB; + }); +}; + +const StoreMainPage = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedItem, setSelectedItem] = useState(null); + const [items, setItem] = useState([] as IItemDetail[]); + const [userInfo] = useRecoilState(userState); + const sortedItems = sortItems(items); + const checkMyCoin = (item: IItemDetail) => { + // if (item.items.length > 3) { + // return ( + // userInfo.coins !== null && userInfo.coins >= item.items[1].itemPrice * -1 + // ); + // } + // else + return ( + userInfo.coins !== null && userInfo.coins >= item.items[0].itemPrice * -1 + ); + }; + + const getItems = async () => { + try { + const response = await axiosItems(); + setItem(response.data.items); + } catch (error) { + throw error; + } + }; + + useEffect(() => { + getItems(); + }, []); + + const buttonClick = (item: IItemDetail) => { + setSelectedItem(item); + setIsModalOpen(true); + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + setSelectedItem(null); + }; + return ( + + + 까비 상점 + + + + {sortedItems.map((item: IItemDetail) => { + return ( + buttonClick(item) : () => {}, + isClickable: checkMyCoin(item), + fontColor: checkMyCoin(item) + ? "var(--sys-main-color)" + : "var(--gray-color)", + }} + /> + ); + })} + + {selectedItem && ( + + )} + + ); +}; + +const StoreTitleStyled = styled.div` + margin-bottom: 20px; + line-height: 1.1; + font-size: 2.5rem; + font-weight: 600; +`; + +const StoreCoinGridWrapper = styled.div` + display: grid; + justify-content: center; + align-items: center; + width: 100%; + grid-gap: 20px; + grid-template-columns: 340px 340px 340px; + grid-template-rows: 150px 150px; + grid-template-areas: + "coinPick EXTENSION SWAP" + "coinPick ALERT PENALTY"; + + padding-bottom: 30px; + + @media (max-width: 1400px) { + grid-template-columns: 340px 340px; + grid-template-rows: 150px 150px 150px; + grid-template-areas: + "coinPick EXTENSION " + "coinPick SWAP" + "ALERT PENALTY"; + } + + // 나중에 고치기 + @media (max-width: 768px) { + grid-template-columns: 340px; + grid-template-rows: 150px 150px 150px 150px 150px 150px; + grid-template-areas: + "coinPick" + "coinPick" + "EXTENSION" + "SWAP" + "ALERT" + "PENALTY"; + } +`; + +const HeaderStyled = styled.div` + // width: 90%; + width: 100%; + max-width: 1060px; + border-bottom: 2px solid var(--service-man-title-border-btm-color); + margin-bottom: 25px; + + @media (max-width: 1400px) { + max-width: 700px; + } + + @media (max-width: 768px) { + max-width: 340px; + } +`; + +const WrapperStyled = styled.div` + padding-top: 80px; + margin-bottom: 30px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + width: 100%; + height: 100%; +`; + +export default StoreMainPage; diff --git a/frontend/src/Cabinet/pages/admin/AdminClubPage.tsx b/frontend/src/Cabinet/pages/admin/AdminClubPage.tsx index 2aaf0f411..7104b00a1 100644 --- a/frontend/src/Cabinet/pages/admin/AdminClubPage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminClubPage.tsx @@ -52,13 +52,13 @@ const AdminClubPage = () => { {pendingCabinetsList.length !== 0 ? ( @@ -41,13 +41,14 @@ const FloorContainer = ({ )} ) : ( - -

해당 층에는 사용 가능한 사물함이 없습니다

- noAvailable -
+ !isToggled && ( + + + + ) )} ); @@ -67,6 +68,7 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` padding-right: 5px; border-bottom: 1.5px solid var(--service-man-title-border-btm-color); cursor: pointer; + button { all: initial; cursor: inherit; @@ -75,6 +77,10 @@ const FloorTitleStyled = styled.div<{ isToggled: boolean }>` transform: ${(props) => props.isToggled ? "rotate(180deg)" : "rotate(0deg)"}; } + + & > button > svg > path { + stroke: var(--gray-line-btn-color); + } `; const FloorCabinetsContainerStyled = styled.div<{ isToggled: boolean }>` @@ -89,21 +95,11 @@ const FloorCabinetsContainerStyled = styled.div<{ isToggled: boolean }>` } `; -const NoAvailableCabinetMessageStyled = styled.div<{ isToggled: boolean }>` - display: ${(props) => (props.isToggled ? "none" : "flex")}; - align-items: center; - margin-top: 20px; - margin-left: 5px; - p { - color: var(--gray-line-btn-color); - line-height: 1.5; - word-break: keep-all; - } - img { - width: 30px; - aspect-ratio: 1 / 1; - margin-left: 8px; - } +const UnavailableCabinetMsgWrapperStyled = styled.div` + width: 100%; + display: flex; + padding-top: 20px; + padding-left: 5px; `; export default FloorContainer; diff --git a/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx index 8caf51754..018fb5d33 100644 --- a/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/AdminCabinetInfoArea.tsx @@ -12,6 +12,7 @@ import { TAdminModalState, } from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; import ButtonContainer from "@/Cabinet/components/Common/Button"; +import SelectInduction from "@/Cabinet/components/Common/SelectInduction"; import ClubLentModal from "@/Cabinet/components/Modals/LentModal/ClubLentModal"; import AdminReturnModal from "@/Cabinet/components/Modals/ReturnModal/AdminReturnModal"; import StatusModalContainer from "@/Cabinet/components/Modals/StatusModal/StatusModal.container"; @@ -66,15 +67,10 @@ const AdminCabinetInfoArea: React.FC<{ (multiSelectTargetInfo && targetCabinetInfoList!.length < 1) ) return ( - - - - - - 사물함/유저를
- 선택해주세요 -
-
+ ); // 다중 선택 모드 진입 후 캐비넷을 하나 이상 선택했을 시 if (multiSelectTargetInfo) { @@ -196,14 +192,6 @@ const AdminCabinetInfoArea: React.FC<{ ); }; -const NotSelectedStyled = styled.div` - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -`; - const CabinetDetailAreaStyled = styled.div` height: 100%; display: flex; @@ -212,17 +200,6 @@ const CabinetDetailAreaStyled = styled.div` align-items: center; `; -const CabiLogoStyled = styled.div` - width: 35px; - height: 35px; - margin-bottom: 10px; - svg { - .logo_svg__currentPath { - fill: var(--sys-main-color); - } - } -`; - const CabinetTypeIconStyled = styled.div` width: 24px; height: 24px; @@ -233,6 +210,11 @@ const CabinetTypeIconStyled = styled.div` & path { stroke: var(--normal-text-color); } + + & > svg { + width: 24px; + height: 24px; + } `; const LinkTextStyled = styled.div` diff --git a/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx index 09be27808..8ec38283a 100644 --- a/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -7,6 +7,7 @@ import { } from "@/Cabinet/components/CabinetInfoArea/CabinetInfoArea.container"; import CountTimeContainer from "@/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container"; import ButtonContainer from "@/Cabinet/components/Common/Button"; +import SelectInduction from "@/Cabinet/components/Common/SelectInduction"; import CancelModal from "@/Cabinet/components/Modals/CancelModal/CancelModal"; import ExtendModal from "@/Cabinet/components/Modals/ExtendModal/ExtendModal"; import InvitationCodeModalContainer from "@/Cabinet/components/Modals/InvitationCodeModal/InvitationCodeModal.container"; @@ -23,7 +24,7 @@ import { cabinetStatusColorMap, } from "@/Cabinet/assets/data/maps"; import alertImg from "@/Cabinet/assets/images/cautionSign.svg"; -import { ReactComponent as ExtensionImg } from "@/Cabinet/assets/images/extensionTicket.svg"; +import { ReactComponent as ExtensionImg } from "@/Cabinet/assets/images/extension.svg"; import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; @@ -50,11 +51,8 @@ const CabinetInfoArea: React.FC<{ closeModal, isSwappable, }) => { - const isExtensionVisible = - isMine && - isExtensible && - selectedCabinetInfo && - selectedCabinetInfo.status !== "IN_SESSION"; + const isExtensionVisible = isMine && selectedCabinetInfo; + // selectedCabinetInfo.status !== "IN_SESSION"; const isHoverBoxVisible = selectedCabinetInfo && selectedCabinetInfo.lentsLength <= 1 && @@ -64,15 +62,10 @@ const CabinetInfoArea: React.FC<{ : null; return selectedCabinetInfo === null ? ( - - - - - - 사물함을
- 선택해주세요 -
-
+ ) : ( @@ -193,13 +186,10 @@ const CabinetInfoArea: React.FC<{ selectedCabinetInfo.lentType === "SHARE" } > - - {"연장권 사용하기"} + + + + 연장권 사용하기 )} {isExtensionVisible && isHoverBoxVisible && ( @@ -272,14 +262,6 @@ const CabinetInfoArea: React.FC<{ ); }; -const NotSelectedStyled = styled.div` - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -`; - const CabinetDetailAreaStyled = styled.div` height: 100%; max-width: 330px; @@ -289,17 +271,6 @@ const CabinetDetailAreaStyled = styled.div` align-items: center; `; -const CabiLogoStyled = styled.div` - width: 35px; - height: 35px; - margin-bottom: 10px; - svg { - .logo_svg__currentPath { - fill: var(--sys-main-color); - } - } -`; - const CabinetTypeIconStyled = styled.div` width: 24px; height: 24px; @@ -310,6 +281,11 @@ const CabinetTypeIconStyled = styled.div` & path { stroke: var(--normal-text-color); } + + & > svg { + width: 24px; + height: 24px; + } `; const TextStyled = styled.p<{ fontSize: string; fontColor: string }>` @@ -460,6 +436,21 @@ const ButtonContainerStyled = styled.button` @media (max-height: 745px) { margin-bottom: 8px; } + + & > span { + height: 20px; + line-height: 18px; + } +`; + +const ExtensionImgStyled = styled.div` + width: 24px; + height: 24px; + margin-right: 10px; + + & > svg > path { + stroke: var(--sys-main-color); + } `; export default CabinetInfoArea; diff --git a/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx index b69de2ce9..55ae40cf3 100644 --- a/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CountTime/CountTime.container.tsx @@ -12,14 +12,13 @@ import { axiosCabinetById, axiosMyLentInfo, } from "@/Cabinet/api/axios/axios.custom"; +import { padTo2Digits } from "@/Cabinet/utils/dateUtils"; const returnCountTime = (countDown: number) => { - const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)) - .toString() - .padStart(2, "0"); - const seconds = Math.floor((countDown % (1000 * 60)) / 1000) - .toString() - .padStart(2, "0"); + const minutes = padTo2Digits( + Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)) + ); + const seconds = padTo2Digits(Math.floor((countDown % (1000 * 60)) / 1000)); return [minutes, seconds]; }; diff --git a/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx index 5fba4ff25..186e0732b 100644 --- a/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/AdminCabinetListItem.tsx @@ -17,6 +17,7 @@ import { } from "@/Cabinet/types/dto/cabinet.dto"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; +import CabinetDetailAreaType from "@/Cabinet/types/enum/cabinetDetailArea.type.enum"; import { axiosCabinetById } from "@/Cabinet/api/axios/axios.custom"; import useMenu from "@/Cabinet/hooks/useMenu"; import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; @@ -28,7 +29,7 @@ const AdminCabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { const setTargetCabinetInfo = useSetRecoilState( targetCabinetInfoState ); - const setSelectedTypeOnSearch = useSetRecoilState( + const setSelectedTypeOnSearch = useSetRecoilState( selectedTypeOnSearchState ); const { openCabinet, closeCabinet } = useMenu(); @@ -64,7 +65,7 @@ const AdminCabinetListItem = (props: CabinetPreviewInfo): JSX.Element => { } setCurrentCabinetId(cabinetId); - setSelectedTypeOnSearch("CABINET"); + setSelectedTypeOnSearch(CabinetDetailAreaType.CABINET); async function getData(cabinetId: number) { try { const { data } = await axiosCabinetById(cabinetId); @@ -253,7 +254,11 @@ const CabinetIconContainerStyled = styled.div<{ & > svg > path { stroke: ${(props) => cabinetFilterMap[props.status]}; - transform: scale(0.7); + } + + & > svg { + width: 16px; + height: 16px; } `; diff --git a/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx index 4aaac3ccd..f65ec3cba 100644 --- a/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetListItem/CabinetListItem.tsx @@ -292,7 +292,11 @@ const CabinetIconContainerStyled = styled.div<{ & > svg > path { stroke: ${(props) => cabinetFilterMap[props.status]}; - transform: scale(0.7); + } + + & > svg { + width: 16px; + height: 16px; } `; diff --git a/frontend/src/Cabinet/components/Card/Card.tsx b/frontend/src/Cabinet/components/Card/Card.tsx index e5357800a..17bb10f24 100644 --- a/frontend/src/Cabinet/components/Card/Card.tsx +++ b/frontend/src/Cabinet/components/Card/Card.tsx @@ -19,6 +19,7 @@ interface CardProps { gridArea: string; width?: string; height?: string; + cardType?: string; } const Card = ({ @@ -29,11 +30,12 @@ const Card = ({ height = "163px", buttons = ([] = []), children, + cardType, }: CardProps) => { return ( {(title || buttons.length > 0) && ( - + {title && {title}} {onClickToolTip && } @@ -77,12 +79,13 @@ export const CardStyled = styled.div<{ grid-area: ${(props) => props.gridArea}; `; -export const CardHeaderStyled = styled.div` +export const CardHeaderStyled = styled.div<{ cardType?: string }>` width: 100%; display: flex; justify-content: space-between; align-items: center; - padding: 20px 20px 10px 30px; + padding: ${(props) => + props.cardType === "store" ? "12px 18px 12px 18px" : "20px 20px 10px 30px"}; `; const CardTitleWrapperStyled = styled.div` diff --git a/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx b/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx index 6a421da3c..51b4f4a3e 100644 --- a/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx +++ b/frontend/src/Cabinet/components/Card/ClubCabinetInfoCard/ClubCabinetInfoCard.tsx @@ -6,7 +6,7 @@ import { ContentDetailStyled, } from "@/Cabinet/components/Card/CardStyles"; import ClubPasswordModalContainer from "@/Cabinet/components/Modals/ClubModal/ClubPasswordModal.container"; -import { ReactComponent as LeaderIcon } from "@/Cabinet/assets/images/leader.svg"; +import { ReactComponent as LeaderIcon } from "@/Cabinet/assets/images/crown.svg"; import { ReactComponent as LockIcon } from "@/Cabinet/assets/images/lock.svg"; import { ClubInfoResponseDto } from "@/Cabinet/types/dto/club.dto"; @@ -165,7 +165,6 @@ const CabinetIconStyled = styled.div` & > svg > path { stroke: var(--normal-text-color); - transform: scale(1.1); } `; diff --git a/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx b/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx index c64f5b266..0d733e3dc 100644 --- a/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx +++ b/frontend/src/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard.tsx @@ -81,7 +81,7 @@ const ClubNoticeTextStyled = styled.div` } ::-webkit-scrollbar-thumb { - background: var(--toggle-switch-off-bg-color); + background: var(--line-color); border-radius: 50px; border: 6px solid transparent; background-clip: padding-box; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx deleted file mode 100644 index bc2284cd3..000000000 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import styled from "styled-components"; -import { ReactComponent as MonitorMobileIcon } from "@/Cabinet/assets/images/monitorMobile.svg"; -import { ReactComponent as MoonIcon } from "@/Cabinet/assets/images/moon.svg"; -import { ReactComponent as SunIcon } from "@/Cabinet/assets/images/sun.svg"; -import { ColorThemeToggleType } from "@/Cabinet/types/enum/colorTheme.type.enum"; - -interface ItoggleItemSeparated { - name: string; - key: string; - icon: React.ComponentType>; -} - -const toggleList: ItoggleItemSeparated[] = [ - { - name: "라이트", - key: ColorThemeToggleType.LIGHT, - icon: SunIcon, - }, - { - name: "다크", - key: ColorThemeToggleType.DARK, - icon: MoonIcon, - }, - { - name: "기기설정", - key: ColorThemeToggleType.DEVICE, - icon: MonitorMobileIcon, - }, -]; - -const ColorTheme = ({ - colorThemeToggle, - handleColorThemeButtonClick, -}: { - colorThemeToggle: ColorThemeToggleType; - handleColorThemeButtonClick: (colorThemeToggleType: string) => void; -}) => { - return ( - <> - - {toggleList.map((item) => { - const ColorThemeIcon = item.icon; - return ( - handleColorThemeButtonClick(item.key)} - > - {ColorThemeIcon && } - {item.name} - - ); - })} - - - ); -}; - -const ButtonsWrapperStyled = styled.div` - display: flex; - justify-content: center; - align-items: center; - border-radius: 10px; - justify-content: space-between; - padding: 0 16px; -`; - -const ButtonStyled = styled.button<{ - isClicked: boolean; -}>` - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: column; - min-width: 50px; - width: 90px; - min-width: 50px; - border-radius: 10px; - font-size: 1rem; - height: 90px; - font-weight: 500; - background-color: ${(props) => - props.isClicked ? "var(--sys-main-color)" : "var(--card-bg-color)"}; - color: ${(props) => - props.isClicked - ? "var(--white-text-with-bg-color)" - : "var(--normal-text-color)"}; - padding: 12px 0 16px 0; - - & > svg { - width: 30px; - height: 30px; - } - - & > svg > path { - stroke: ${(props) => - props.isClicked - ? "var(--white-text-with-bg-color)" - : "var(--normal-text-color)"}; - } -`; - -export default ColorTheme; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx index 256acd97a..472e4251f 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx @@ -1,205 +1,84 @@ import { useEffect, useState } from "react"; import DisplayStyleCard from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard"; -import ColorType from "@/Cabinet/types/enum/color.type.enum"; import { - ColorThemeToggleType, - ColorThemeType, -} from "@/Cabinet/types/enum/colorTheme.type.enum"; + DisplayStyleToggleType, + DisplayStyleType, +} from "@/Cabinet/types/enum/displayStyle.type.enum"; -// 로컬스토리지의 color-theme-toggle 값에 따라 ColorThemeType 반환 -export const getInitialColorTheme = ( - savedColorThemeToggle: ColorThemeToggleType, +// 로컬스토리지의 display-style-toggle 값에 따라 DisplayStyleType 반환 +export const getInitialDisplayStyle = ( + savedDisplayStyleToggle: DisplayStyleToggleType, darkModeQuery: MediaQueryList ) => { // 라이트 / 다크 버튼 - if (savedColorThemeToggle === ColorThemeToggleType.LIGHT) - return ColorThemeType.LIGHT; - else if (savedColorThemeToggle === ColorThemeToggleType.DARK) - return ColorThemeType.DARK; + if (savedDisplayStyleToggle === DisplayStyleToggleType.LIGHT) + return DisplayStyleType.LIGHT; + else if (savedDisplayStyleToggle === DisplayStyleToggleType.DARK) + return DisplayStyleType.DARK; // 디바이스 버튼 if (darkModeQuery.matches) { - return ColorThemeType.DARK; + return DisplayStyleType.DARK; } - return ColorThemeType.LIGHT; + return DisplayStyleType.LIGHT; }; const DisplayStyleCardContainer = () => { - const savedMainColor = - localStorage.getItem("main-color") || "var(--sys-default-main-color)"; - const savedSubColor = - localStorage.getItem("sub-color") || "var(--sys-default-sub-color)"; - const savedMineColor = - localStorage.getItem("mine-color") || "var(--sys-default-mine-color)"; - - const [mainColor, setMainColor] = useState(savedMainColor); - const [subColor, setSubColor] = useState(savedSubColor); - const [mineColor, setMineColor] = useState(savedMineColor); - - const [showColorPicker, setShowColorPicker] = useState(false); - const body: HTMLElement = document.body; - const root: HTMLElement = document.documentElement; - - const [selectedColorType, setSelectedColorType] = useState( - ColorType.MAIN + const savedDisplayStyleToggle = + (localStorage.getItem("display-style-toggle") as DisplayStyleToggleType) || + DisplayStyleToggleType.DEVICE; + var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const initialDisplayStyle = getInitialDisplayStyle( + savedDisplayStyleToggle, + darkModeQuery + ); + const [darkMode, setDarkMode] = useState( + initialDisplayStyle as DisplayStyleType + ); + const [toggleType, setToggleType] = useState( + savedDisplayStyleToggle ); - const handlePointColorChange = ( - mainColor: { hex: string }, - colorType: string - ) => { - const selectedColor: string = mainColor.hex; - if (colorType === ColorType.MAIN) { - setMainColor(selectedColor); - } else if (colorType === ColorType.SUB) { - setSubColor(selectedColor); - } else if (colorType === ColorType.MINE) { - setMineColor(selectedColor); - } - }; - - const setColorsAndLocalStorage = ( - main: string, - sub: string, - mine: string, - toggleType: ColorThemeToggleType - ) => { - setMainColor(main); - setSubColor(sub); - setMineColor(mine); - body.style.setProperty("--sys-main-color", main); - body.style.setProperty("--sys-sub-color", sub); - body.style.setProperty("--mine-color", mine); - root.style.setProperty("--sys-main-color", main); - root.style.setProperty("--sys-sub-color", sub); - root.style.setProperty("--mine-color", mine); - localStorage.setItem("main-color", main); - localStorage.setItem("sub-color", sub); - localStorage.setItem("mine-color", mine); - + const setColorsAndLocalStorage = (toggleType: DisplayStyleToggleType) => { setToggleType(toggleType); - localStorage.setItem("color-theme-toggle", toggleType); - }; - - const handleReset = () => { - setColorsAndLocalStorage( - "var(--sys-default-main-color)", - "var(--sys-default-sub-color)", - "var(--sys-default-mine-color)", - ColorThemeToggleType.DEVICE - ); - }; - - const handleSave = () => { - setColorsAndLocalStorage(mainColor, subColor, mineColor, toggleType); - setShowColorPicker(!showColorPicker); - }; - - const handleCancel = () => { - setColorsAndLocalStorage( - savedMainColor, - savedSubColor, - savedMineColor, - savedColorThemeToggle - ); - setShowColorPicker(!showColorPicker); + localStorage.setItem("display-style-toggle", toggleType); }; - const handlePointColorButtonClick = (pointColorType: string) => { - setSelectedColorType(pointColorType); - setShowColorPicker(true); - }; - - const handleColorThemeButtonClick = (colorThemeToggleType: string) => { - if (toggleType === colorThemeToggleType) return; + const handleDisplayStyleButtonClick = (displayStyleToggleType: string) => { + if (toggleType === displayStyleToggleType) return; setToggleType( - colorThemeToggleType as React.SetStateAction + displayStyleToggleType as React.SetStateAction ); - setShowColorPicker(true); + setColorsAndLocalStorage(displayStyleToggleType as DisplayStyleToggleType); }; - const savedColorThemeToggle = - (localStorage.getItem("color-theme-toggle") as ColorThemeToggleType) || - ColorThemeToggleType.DEVICE; - var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); - const initialColorTheme = getInitialColorTheme( - savedColorThemeToggle, - darkModeQuery - ); - const [darkMode, setDarkMode] = useState( - initialColorTheme as ColorThemeType - ); - const [toggleType, setToggleType] = useState( - savedColorThemeToggle - ); - useEffect(() => { darkModeQuery.addEventListener("change", (event) => - setDarkMode(event.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT) + setDarkMode( + event.matches ? DisplayStyleType.DARK : DisplayStyleType.LIGHT + ) ); }, []); useEffect(() => { - document.body.setAttribute("color-theme", darkMode); + document.body.setAttribute("display-style", darkMode); }, [darkMode]); useEffect(() => { - if (toggleType === ColorThemeToggleType.LIGHT) { - setDarkMode(ColorThemeType.LIGHT); - } else if (toggleType === ColorThemeToggleType.DARK) { - setDarkMode(ColorThemeType.DARK); + if (toggleType === DisplayStyleToggleType.LIGHT) { + setDarkMode(DisplayStyleType.LIGHT); + } else if (toggleType === DisplayStyleToggleType.DARK) { + setDarkMode(DisplayStyleType.DARK); } else { setDarkMode( - darkModeQuery.matches ? ColorThemeType.DARK : ColorThemeType.LIGHT + darkModeQuery.matches ? DisplayStyleType.DARK : DisplayStyleType.LIGHT ); } }, [toggleType]); - useEffect(() => { - body.style.setProperty("--sys-main-color", mainColor); - body.style.setProperty("--sys-sub-color", subColor); - body.style.setProperty("--mine-color", mineColor); - root.style.setProperty("--sys-main-color", mainColor); - root.style.setProperty("--sys-sub-color", subColor); - root.style.setProperty("--mine-color", mineColor); - const confirmBeforeUnload = (e: BeforeUnloadEvent) => { - if ( - mainColor !== savedMainColor || - subColor !== savedSubColor || - mineColor !== savedMineColor || - toggleType !== savedColorThemeToggle - ) { - e.returnValue = - "변경된 색상이 저장되지 않을 수 있습니다. 페이지를 나가시겠습니까?"; - } - }; - window.addEventListener("beforeunload", confirmBeforeUnload); - return () => { - window.removeEventListener("beforeunload", confirmBeforeUnload); - }; - }, [ - mainColor, - mineColor, - savedMainColor, - savedMineColor, - subColor, - savedSubColor, - toggleType, - ]); - return ( ); }; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx index 541975511..f24601027 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx +++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx @@ -1,110 +1,126 @@ import styled from "styled-components"; import Card from "@/Cabinet/components/Card/Card"; import { CardContentWrapper } from "@/Cabinet/components/Card/CardStyles"; -import ColorTheme from "@/Cabinet/components/Card/DisplayStyleCard/ColorTheme/ColorTheme"; -import PointColor from "@/Cabinet/components/Card/DisplayStyleCard/PointColor/PointColor"; -import { ColorThemeToggleType } from "@/Cabinet/types/enum/colorTheme.type.enum"; +import { ReactComponent as MonitorMobileIcon } from "@/Cabinet/assets/images/monitorMobile.svg"; +import { ReactComponent as MoonIcon } from "@/Cabinet/assets/images/moon.svg"; +import { ReactComponent as SunIcon } from "@/Cabinet/assets/images/sun.svg"; +import { DisplayStyleToggleType } from "@/Cabinet/types/enum/displayStyle.type.enum"; interface DisplayStyleProps { - showColorPicker: boolean; - handlePointColorChange: (mainColor: { hex: string }, type: string) => void; - handleReset: () => void; - handleSave: () => void; - handleCancel: () => void; - mainColor: string; - subColor: string; - mineColor: string; - handlePointColorButtonClick: (colorType: string) => void; - selectedColorType: string; - colorThemeToggle: ColorThemeToggleType; - handleColorThemeButtonClick: (colorThemeToggleType: string) => void; + displayStyleToggle: DisplayStyleToggleType; + handleDisplayStyleButtonClick: (DisplayStyleToggleType: string) => void; } +interface IToggleItemSeparated { + name: string; + key: string; + icon: React.ComponentType>; +} + +const toggleList: IToggleItemSeparated[] = [ + { + name: "라이트", + key: DisplayStyleToggleType.LIGHT, + icon: SunIcon, + }, + { + name: "다크", + key: DisplayStyleToggleType.DARK, + icon: MoonIcon, + }, + { + name: "기기설정", + key: DisplayStyleToggleType.DEVICE, + icon: MonitorMobileIcon, + }, +]; + const DisplayStyleCard = ({ - showColorPicker, - handlePointColorChange, - handleReset, - handleSave, - handleCancel, - mainColor, - subColor, - mineColor, - handlePointColorButtonClick, - selectedColorType, - colorThemeToggle, - handleColorThemeButtonClick, + displayStyleToggle, + handleDisplayStyleButtonClick, }: DisplayStyleProps) => { return ( <> - {showColorPicker && } - + <> - - - - + + {toggleList.map((item) => { + const DisplayStyleIcon = item.icon; + return ( + handleDisplayStyleButtonClick(item.key)} + > + {DisplayStyleIcon && } + {item.name} + + ); + })} + - + ); }; -const BackgroundOverlayStyled = styled.div` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--modal-bg-shadow-color); -`; - -const ThemeColorCardWrapper = styled.div` +const DisplayStyleCardWrapper = styled.div` z-index: 1; align-self: start; `; +const ButtonsWrapperStyled = styled.div` + display: flex; + justify-content: center; + align-items: center; + border-radius: 10px; + justify-content: space-between; + padding: 0 16px; +`; + +const ButtonStyled = styled.button<{ + isClicked: boolean; +}>` + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + min-width: 50px; + width: 90px; + min-width: 50px; + border-radius: 10px; + font-size: 1rem; + height: 90px; + font-weight: 500; + background-color: ${(props) => + props.isClicked ? "var(--sys-main-color)" : "var(--card-bg-color)"}; + color: ${(props) => + props.isClicked + ? "var(--white-text-with-bg-color)" + : "var(--normal-text-color)"}; + padding: 12px 0 16px 0; + + & > svg { + width: 30px; + height: 30px; + } + + & > svg > path { + stroke: ${(props) => + props.isClicked + ? "var(--white-text-with-bg-color)" + : "var(--normal-text-color)"}; + } +`; + export default DisplayStyleCard; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorThemeInitializer.ts b/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorThemeInitializer.ts deleted file mode 100644 index 6a1fa6574..000000000 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorThemeInitializer.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { getInitialColorTheme } from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container"; -import { - ColorThemeToggleType, - ColorThemeType, -} from "@/Cabinet/types/enum/colorTheme.type.enum"; - -(function () { - const isClient = typeof window !== "undefined"; - if (isClient) { - const savedColorThemeToggle = - (localStorage.getItem("color-theme-toggle") as ColorThemeToggleType) || - ColorThemeToggleType.DEVICE; - - const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); - - const colorMode = getInitialColorTheme( - savedColorThemeToggle, - darkModeQuery - ); - - document.documentElement.style.setProperty( - "background-color", - colorMode === ColorThemeType.DARK ? "#1f1f1f" : "#ffffff" - ); - // NOTE : 새로고침 깜박임 현상 방지 - // 이 코드가 실행중일땐 전역변수가 아직 정의가 안된 상태라 전역변수 대신 hex code 사용 - - document.addEventListener("DOMContentLoaded", function () { - document.body.setAttribute("color-theme", colorMode); - }); - } -})(); diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts b/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts new file mode 100644 index 000000000..3302a8b48 --- /dev/null +++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts @@ -0,0 +1,33 @@ +import { getInitialDisplayStyle } from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container"; +import { + DisplayStyleToggleType, + DisplayStyleType, +} from "@/Cabinet/types/enum/displayStyle.type.enum"; + +(function () { + const isClient = typeof window !== "undefined"; + if (isClient) { + const savedDisplayStyleToggle = + (localStorage.getItem( + "display-style-toggle" + ) as DisplayStyleToggleType) || DisplayStyleToggleType.DEVICE; + + const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + + const colorMode = getInitialDisplayStyle( + savedDisplayStyleToggle, + darkModeQuery + ); + + document.documentElement.style.setProperty( + "background-color", + colorMode === DisplayStyleType.DARK ? "#1f1f1f" : "#ffffff" + ); + // NOTE : 새로고침 깜박임 현상 방지 + // 이 코드가 실행중일땐 전역변수가 아직 정의가 안된 상태라 전역변수 대신 hex code 사용 + + document.addEventListener("DOMContentLoaded", function () { + document.body.setAttribute("display-style", colorMode); + }); + } +})(); diff --git a/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx index 1aec4650e..b65e7153a 100644 --- a/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx +++ b/frontend/src/Cabinet/components/Card/ExtensionCard/ExtensionCard.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; import { CardContentStyled, @@ -8,7 +9,6 @@ import { import { NotificationModal } from "@/Cabinet/components/Modals/NotificationModal/NotificationModal"; import { LentExtensionDto } from "@/Cabinet/types/dto/lent.dto"; import { formatDate } from "@/Cabinet/utils/dateUtils"; -import { useState } from "react"; interface ExtensionProps { extensionInfo: LentExtensionDto | null; diff --git a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx index c0f48e9d4..3dca53ca1 100644 --- a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.container.tsx @@ -1,12 +1,14 @@ +import { useEffect, useState } from "react"; +import { useRecoilValue, useSetRecoilState } from "recoil"; +import { myCabinetInfoState, userState } from "@/Cabinet/recoil/atoms"; import LentInfoCard from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard"; import { getDefaultCabinetInfo } from "@/Cabinet/components/TopNav/TopNavButtonGroup/TopNavButtonGroup"; -import { myCabinetInfoState } from "@/Cabinet/recoil/atoms"; import { CabinetInfo } from "@/Cabinet/types/dto/cabinet.dto"; import { LentDto } from "@/Cabinet/types/dto/lent.dto"; +import { IItemTimeRemaining } from "@/Cabinet/types/dto/store.dto"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; -import { getRemainingTime } from "@/Cabinet/utils/dateUtils"; -import { useRecoilValue } from "recoil"; +import { getRemainingTime, getTimeRemaining } from "@/Cabinet/utils/dateUtils"; export interface MyCabinetInfo { name: string | null; @@ -73,6 +75,10 @@ const LentInfoCardContainer = ({ unbannedAt: Date | null | undefined; }) => { const myCabinetInfo = useRecoilValue(myCabinetInfoState); + const [isPenaltyUser, setIsPenaltyUser] = useState(true); + const [remainPenaltyPeriod, setRemainPenaltyPeriod] = + useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); let dateUsed, dateLeft, expireDate; if (name && myCabinetInfo.lents) { @@ -102,7 +108,39 @@ const LentInfoCardContainer = ({ status: myCabinetInfo.status || "", }; - return ; + const onCLickPenaltyButton = () => { + setIsModalOpen(true); + }; + const handleCloseModal = () => { + setIsModalOpen(false); + }; + + useEffect(() => { + if (unbannedAt == null) { + setIsPenaltyUser(false); + } else { + setRemainPenaltyPeriod(getTimeRemaining(unbannedAt)); + } + }, [unbannedAt]); + + return ( + + ); }; export default LentInfoCardContainer; diff --git a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx index 527b46c00..3c88ed753 100644 --- a/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx +++ b/frontend/src/Cabinet/components/Card/LentInfoCard/LentInfoCard.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import Card from "@/Cabinet/components/Card/Card"; +import Card, { IButtonProps } from "@/Cabinet/components/Card/Card"; import { CardContentStyled, CardContentWrapper, @@ -8,8 +8,10 @@ import { } from "@/Cabinet/components/Card/CardStyles"; import { MyCabinetInfo } from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard.container"; import { cabinetIconComponentMap } from "@/Cabinet/assets/data/maps"; +import { IItemTimeRemaining } from "@/Cabinet/types/dto/store.dto"; import CabinetStatus from "@/Cabinet/types/enum/cabinet.status.enum"; import { formatDate } from "@/Cabinet/utils/dateUtils"; +import StoreBuyPenalty from "../../Modals/StoreModal/StoreBuyPenaltyModal"; const calculateFontSize = (userCount: number): string => { const baseSize = 1; @@ -26,94 +28,111 @@ const calculateFontSize = (userCount: number): string => { const LentInfoCard = ({ cabinetInfo, unbannedAt, + button, + isModalOpen, + remainPenaltyPeriod, + onClose, }: { cabinetInfo: MyCabinetInfo; unbannedAt: Date | null | undefined; + button: IButtonProps | undefined; + isModalOpen: boolean; + remainPenaltyPeriod: IItemTimeRemaining | null; + onClose: () => void; }) => { const CabinetIcon = cabinetIconComponentMap[cabinetInfo.lentType]; return ( - - <> - - - {cabinetInfo.visibleNum !== 0 - ? cabinetInfo.visibleNum - : !!unbannedAt - ? "!" - : "-"} - - - + + <> + + - {cabinetInfo.floor !== 0 - ? cabinetInfo.floor + "층 - " + cabinetInfo.section - : "대여 중이 아닌 사용자"} - - - - - - + {cabinetInfo.visibleNum !== 0 + ? cabinetInfo.visibleNum + : !!unbannedAt + ? "!" + : "-"} + + - {cabinetInfo.userNameList} + {cabinetInfo.floor !== 0 + ? cabinetInfo.floor + "층 - " + cabinetInfo.section + : "대여 중이 아닌 사용자"} - - - - - - 사용 기간 - - {cabinetInfo?.isLented && cabinetInfo.status != "IN_SESSION" - ? `${cabinetInfo.dateUsed}일` - : "-"} - - - - - {cabinetInfo?.status === "OVERDUE" ? "연체 기간" : "남은 기간"} - - - {cabinetInfo?.expireDate ? `${cabinetInfo.dateLeft}일` : "-"} - - - - - {!!unbannedAt ? "패널티 종료 일자" : "종료 일자"} - - - {!!unbannedAt - ? formatDate(new Date(unbannedAt), ".") - : cabinetInfo?.expireDate - ? formatDate(new Date(cabinetInfo?.expireDate), ".") - : "-"} - - - - - - 이전 대여자 - - {cabinetInfo?.previousUserName || "-"} - - - - - + + + + + + + {cabinetInfo.userNameList} + + + + + + + 사용 기간 + + {cabinetInfo?.isLented && cabinetInfo.status != "IN_SESSION" + ? `${cabinetInfo.dateUsed}일` + : "-"} + + + + + {cabinetInfo?.status === "OVERDUE" ? "연체 기간" : "남은 기간"} + + + {cabinetInfo?.expireDate ? `${cabinetInfo.dateLeft}일` : "-"} + + + + + {!!unbannedAt ? "페널티 종료 일자" : "종료 일자"} + + + {!!unbannedAt + ? formatDate(new Date(unbannedAt), ".") + : cabinetInfo?.expireDate + ? formatDate(new Date(cabinetInfo?.expireDate), ".") + : "-"} + + + + + + 이전 대여자 + + {cabinetInfo?.previousUserName || "-"} + + + + + + {isModalOpen && ( + + )} + ); }; @@ -188,7 +207,6 @@ const CabinetIconStyled = styled.div` & > svg > path { stroke: var(--normal-text-color); - transform: scale(0.8); } `; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/ColorPicker.tsx b/frontend/src/Cabinet/components/Card/PointColorCard/ColorPicker.tsx similarity index 93% rename from frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/ColorPicker.tsx rename to frontend/src/Cabinet/components/Card/PointColorCard/ColorPicker.tsx index 0227cec50..9cef69a2a 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/ColorPicker.tsx +++ b/frontend/src/Cabinet/components/Card/PointColorCard/ColorPicker.tsx @@ -1,6 +1,6 @@ import { TwitterPicker } from "react-color"; import styled from "styled-components"; -import { GetCustomColorsValues } from "@/Cabinet/components/Card/DisplayStyleCard/colorInfo"; +import { GetCustomColorsValues } from "@/Cabinet/components/Card/PointColorCard/colorInfo"; interface ColorPickerProps { color: string; @@ -24,7 +24,7 @@ const ColorPicker = ({ color, onChange }: ColorPickerProps) => { input: { boxShadow: "var(--color-picker-hash-bg-color) 0px 0px 0px 1px inset", - color: "var(--color-picker-input-color)", + color: "var(--gray-line-btn-color)", }, hash: { background: "var(--color-picker-hash-bg-color)", diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/PointColor.tsx b/frontend/src/Cabinet/components/Card/PointColorCard/PointColor.tsx similarity index 91% rename from frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/PointColor.tsx rename to frontend/src/Cabinet/components/Card/PointColorCard/PointColor.tsx index fd7bb11e7..eae1723e1 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/PointColor/PointColor.tsx +++ b/frontend/src/Cabinet/components/Card/PointColorCard/PointColor.tsx @@ -3,8 +3,8 @@ import { CardContentStyled, ContentInfoStyled, } from "@/Cabinet/components/Card/CardStyles"; -import ColorPicker from "@/Cabinet/components/Card/DisplayStyleCard/PointColor/ColorPicker"; -import { pointColorData } from "@/Cabinet/components/Card/DisplayStyleCard/colorInfo"; +import ColorPicker from "@/Cabinet/components/Card/PointColorCard/ColorPicker"; +import { pointColorData } from "@/Cabinet/components/Card/PointColorCard/colorInfo"; interface PointColorProps { showColorPicker: boolean; diff --git a/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.container.tsx b/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.container.tsx new file mode 100644 index 000000000..034fd2d49 --- /dev/null +++ b/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.container.tsx @@ -0,0 +1,127 @@ +import { useEffect, useState } from "react"; +import PointColorCard from "@/Cabinet/components/Card/PointColorCard/PointColorCard"; +import ColorType from "@/Cabinet/types/enum/color.type.enum"; + +const PointColorCardContainer = () => { + const savedMainColor = + localStorage.getItem("main-color") || "var(--sys-default-main-color)"; + const savedSubColor = + localStorage.getItem("sub-color") || "var(--sys-default-sub-color)"; + const savedMineColor = + localStorage.getItem("mine-color") || "var(--sys-default-mine-color)"; + + const [mainColor, setMainColor] = useState(savedMainColor); + const [subColor, setSubColor] = useState(savedSubColor); + const [mineColor, setMineColor] = useState(savedMineColor); + + const [showColorPicker, setShowColorPicker] = useState(false); + const body: HTMLElement = document.body; + const root: HTMLElement = document.documentElement; + + const [selectedColorType, setSelectedColorType] = useState( + ColorType.MAIN + ); + + const handlePointColorChange = ( + mainColor: { hex: string }, + colorType: string + ) => { + const selectedColor: string = mainColor.hex; + if (colorType === ColorType.MAIN) { + setMainColor(selectedColor); + } else if (colorType === ColorType.SUB) { + setSubColor(selectedColor); + } else if (colorType === ColorType.MINE) { + setMineColor(selectedColor); + } + }; + + const setColorsAndLocalStorage = ( + main: string, + sub: string, + mine: string + ) => { + setMainColor(main); + setSubColor(sub); + setMineColor(mine); + body.style.setProperty("--sys-main-color", main); + body.style.setProperty("--sys-sub-color", sub); + body.style.setProperty("--mine-color", mine); + root.style.setProperty("--sys-main-color", main); + root.style.setProperty("--sys-sub-color", sub); + root.style.setProperty("--mine-color", mine); + localStorage.setItem("main-color", main); + localStorage.setItem("sub-color", sub); + localStorage.setItem("mine-color", mine); + }; + + const handleReset = () => { + setColorsAndLocalStorage( + "var(--sys-default-main-color)", + "var(--sys-default-sub-color)", + "var(--sys-default-mine-color)" + ); + }; + + const handleSave = () => { + setColorsAndLocalStorage(mainColor, subColor, mineColor); + setShowColorPicker(!showColorPicker); + }; + + const handleCancel = () => { + setColorsAndLocalStorage(savedMainColor, savedSubColor, savedMineColor); + setShowColorPicker(!showColorPicker); + }; + + const handlePointColorButtonClick = (pointColorType: string) => { + setSelectedColorType(pointColorType); + setShowColorPicker(true); + }; + + useEffect(() => { + body.style.setProperty("--sys-main-color", mainColor); + body.style.setProperty("--sys-sub-color", subColor); + body.style.setProperty("--mine-color", mineColor); + root.style.setProperty("--sys-main-color", mainColor); + root.style.setProperty("--sys-sub-color", subColor); + root.style.setProperty("--mine-color", mineColor); + const confirmBeforeUnload = (e: BeforeUnloadEvent) => { + if ( + mainColor !== savedMainColor || + subColor !== savedSubColor || + mineColor !== savedMineColor + ) { + e.returnValue = + "변경된 색상이 저장되지 않을 수 있습니다. 페이지를 나가시겠습니까?"; + } + }; + window.addEventListener("beforeunload", confirmBeforeUnload); + return () => { + window.removeEventListener("beforeunload", confirmBeforeUnload); + }; + }, [ + mainColor, + mineColor, + savedMainColor, + savedMineColor, + subColor, + savedSubColor, + ]); + + return ( + + ); +}; + +export default PointColorCardContainer; diff --git a/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.tsx b/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.tsx new file mode 100644 index 000000000..a95e4af09 --- /dev/null +++ b/frontend/src/Cabinet/components/Card/PointColorCard/PointColorCard.tsx @@ -0,0 +1,98 @@ +import styled from "styled-components"; +import Card from "@/Cabinet/components/Card/Card"; +import { CardContentWrapper } from "@/Cabinet/components/Card/CardStyles"; +import PointColor from "@/Cabinet/components/Card/PointColorCard/PointColor"; + +interface PointColorProps { + showColorPicker: boolean; + handlePointColorChange: (mainColor: { hex: string }, type: string) => void; + handleReset: () => void; + handleSave: () => void; + handleCancel: () => void; + mainColor: string; + subColor: string; + mineColor: string; + handlePointColorButtonClick: (colorType: string) => void; + selectedColorType: string; +} + +const PointColorCard = ({ + showColorPicker, + handlePointColorChange, + handleReset, + handleSave, + handleCancel, + mainColor, + subColor, + mineColor, + handlePointColorButtonClick, + selectedColorType, +}: PointColorProps) => { + return ( + <> + {showColorPicker && } + + + <> + + + + + + + + ); +}; + +const BackgroundOverlayStyled = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--modal-bg-shadow-color); +`; + +const PointColorCardWrapper = styled.div` + z-index: 1; + align-self: start; +`; + +export default PointColorCard; diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorInfo.ts b/frontend/src/Cabinet/components/Card/PointColorCard/colorInfo.ts similarity index 97% rename from frontend/src/Cabinet/components/Card/DisplayStyleCard/colorInfo.ts rename to frontend/src/Cabinet/components/Card/PointColorCard/colorInfo.ts index b301c1157..f4ad1336a 100644 --- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/colorInfo.ts +++ b/frontend/src/Cabinet/components/Card/PointColorCard/colorInfo.ts @@ -29,7 +29,7 @@ export const pointColorData: ColorData[] = [ ]; export const customColors = [ - "--custom-pink", + "--custom-pink-200", "--custom-orange", "--custom-yellow", "--custom-green-100", diff --git a/frontend/src/Cabinet/components/Card/StoreItemCard/StoreItemCard.tsx b/frontend/src/Cabinet/components/Card/StoreItemCard/StoreItemCard.tsx new file mode 100644 index 000000000..717e0c2df --- /dev/null +++ b/frontend/src/Cabinet/components/Card/StoreItemCard/StoreItemCard.tsx @@ -0,0 +1,145 @@ +import styled from "styled-components"; +import Card from "@/Cabinet/components/Card/Card"; +import type { IButtonProps } from "@/Cabinet/components/Card/Card"; +import { ItemIconMap } from "@/Cabinet/assets/data/maps"; +import { ReactComponent as CoinImg } from "@/Cabinet/assets/images/coinIcon.svg"; +import { IItemDetail } from "@/Cabinet/types/dto/store.dto"; +import { StoreItemType } from "@/Cabinet/types/enum/store.enum"; + +const convertToItemType = (itemType: string) => { + switch (itemType) { + case "EXTENSION": + return StoreItemType.EXTENSION; + case "SWAP": + return StoreItemType.SWAP; + case "ALARM": + return StoreItemType.ALARM; + case "PENALTY": + return StoreItemType.PENALTY; + default: + return StoreItemType.EXTENSION; + } +}; + +const StoreItemCard = ({ + item, + button, +}: { + item: IItemDetail; + button: IButtonProps; +}) => { + const ItemIcon = ItemIconMap[convertToItemType(item.itemType)]; + return ( + + + + + + + + + + + + {item.items[0].itemPrice * -1} + + + {item.description} + + + + ); +}; + +const WrapperStyled = styled.div` + font-size: 15px; +`; + +const SectionStyled = styled.div` + display: flex; + height: 80px; + width: 90%; + align-items: center; +`; + +const BlockStyled = styled.div` + display: flex; + flex-direction: column; + margin-right: 15px; + justify-content: center; +`; + +const IconBlockStyled = styled.div` + width: 53px; + height: 53px; + border-radius: 10px; + background-color: var(--sys-main-color); + margin-bottom: 5px; + display: flex; + justify-content: center; + align-items: center; +`; + +const PriseBlockStyled = styled.div` + width: 53px; + height: 22px; + background-color: var(--card-content-bg-color); + border-radius: 5px; + font-size: 12px; + color: var(--sys-main-color); + display: flex; + justify-content: center; + align-items: center; + > span { + margin-left: 3px; + font-weight: 600; + } + + & > svg { + width: 14px; + height: 14px; + } + & > svg > path { + stroke: var(--sys-main-color); + stroke-width: 2px; + } +`; + +const ItemDetailStyled = styled.div` + width: 100%; + height: 100%; + background-color: var(--card-content-bg-color); + font-size: var(--size-base); + word-wrap: normal; + padding: 10px 16px; + line-height: 1.4; + border-radius: 10px; +`; + +const ItemIconStyled = styled.div<{ itemType: StoreItemType }>` + display: flex; + justify-content: center; + align-items: center; + width: 32px; + height: 32px; + + & > svg { + width: 32px; + height: 32px; + } + + & > svg > path { + stroke: var(--white-text-with-bg-color); + stroke-width: ${(props) => + props.itemType === StoreItemType.EXTENSION ? "2.8px" : "1.5px"}; + } +`; + +export default StoreItemCard; diff --git a/frontend/src/Cabinet/components/Club/ClubInfo.tsx b/frontend/src/Cabinet/components/Club/ClubInfo.tsx index 9e373495c..d26f04cbc 100644 --- a/frontend/src/Cabinet/components/Club/ClubInfo.tsx +++ b/frontend/src/Cabinet/components/Club/ClubInfo.tsx @@ -6,7 +6,7 @@ import ClubCabinetInfoCard from "@/Cabinet/components/Card/ClubCabinetInfoCard/C import ClubNoticeCard from "@/Cabinet/components/Card/ClubNoticeCard/ClubNoticeCard"; import ClubMemberListContainer from "@/Cabinet/components/Club/ClubMemberList/ClubMemberList.container"; import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; -import { ReactComponent as SadCcabi } from "@/Cabinet/assets/images/sadCcabi.svg"; +import UnavailableDataInfo from "@/Cabinet/components/Common/UnavailableDataInfo"; import { ClubInfoResponseDto } from "@/Cabinet/types/dto/club.dto"; import useClubInfo from "@/Cabinet/hooks/useClubInfo"; import useMenu from "@/Cabinet/hooks/useMenu"; @@ -31,12 +31,9 @@ const ClubInfo = () => { {clubInfo === undefined ? ( ) : clubInfo === STATUS_400_BAD_REQUEST ? ( - - 동아리 사물함이 없어요 - - - - + <> + + ) : ( 동아리 정보 @@ -55,34 +52,6 @@ const ClubInfo = () => { ); }; -const EmptyClubCabinetTextStyled = styled.div` - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - font-size: 1.125rem; - color: var(--gray-line-btn-color); -`; - -const SadCcabiStyled = styled.div` - display: flex; - margin-left: 5px; - width: 30px; - height: 30px; - margin-left: 8px; - padding-top: 3px; - - & > svg { - width: 30px; - height: 30px; - } - - & > svg > path { - fill: var(--gray-line-btn-color); - } -`; - const ClubInfoWrapperStyled = styled.div` display: flex; flex-direction: column; diff --git a/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx index cf41d2628..7aaa96f81 100644 --- a/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.tsx @@ -4,15 +4,14 @@ import { TClubModalState, } from "@/Cabinet/components/Club/ClubMemberInfoArea/ClubMemberInfoArea.container"; import Button from "@/Cabinet/components/Common/Button"; +import SelectInduction from "@/Cabinet/components/Common/SelectInduction"; import DeleteClubMemberModal from "@/Cabinet/components/Modals/ClubModal/DeleteClubMemberModal"; import MandateClubMemberModal from "@/Cabinet/components/Modals/ClubModal/MandateClubMemberModal"; import { - cabinetIconSrcMap, cabinetLabelColorMap, cabinetStatusColorMap, } from "@/Cabinet/assets/data/maps"; -import { ReactComponent as LeaderIcon } from "@/Cabinet/assets/images/leader.svg"; -import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; +import { ReactComponent as LeaderIcon } from "@/Cabinet/assets/images/crown.svg"; import { ReactComponent as UserImg } from "@/Cabinet/assets/images/privateIcon.svg"; import { ClubCabinetInfo, @@ -51,18 +50,10 @@ const ClubMemberInfoArea = ({ <> {selectedClubCabinetInfo === null ? ( - - - - - - 동아리를
- 선택해주세요 -
-
+ ) : ( <> @@ -120,24 +111,6 @@ const ClubMemberInfoArea = ({ ); }; -const NotSelectedStyled = styled.div` - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -`; - -const CabiLogoStyled = styled.div` - width: 35px; - height: 35px; - margin-bottom: 10px; - svg { - .logo_svg__currentPath { - fill: var(--sys-main-color); - } - } -`; const ClubMemberInfoAreaStyled = styled.div` position: fixed; top: 120px; @@ -215,13 +188,11 @@ const ClubMemberIconStyled = styled.div<{ isMasterSelected: boolean }>` & > svg { width: 24px; - height: 24px; + height: ${(props) => (props.isMasterSelected ? "20px" : "24px")}; } & > svg > path { stroke: var(--normal-text-color); - transform: ${(props) => - props.isMasterSelected ? "scale(1.3)" : "scale(1.0)"}; } `; diff --git a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx index 69c1c0179..1d772bb24 100644 --- a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberList.tsx @@ -152,6 +152,11 @@ const UserCountIconStyled = styled.div` & > svg > path { stroke: var(--normal-text-color); } + + & > svg { + width: 24px; + height: 24px; + } `; const AddMemberCardStyled = styled.div` diff --git a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx index be6e04df5..8c2e2d9ac 100644 --- a/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx +++ b/frontend/src/Cabinet/components/Club/ClubMemberList/ClubMemberListItem/ClubMemberListItem.tsx @@ -86,7 +86,6 @@ const MemberListItemStyled = styled.div<{ isMaster: boolean }>` props.isMaster ? "var(--white-text-with-bg-color)" : "var(--normal-text-color)"}; - transform: ${(props) => (props.isMaster ? "" : "scale(0.7)")}; } `; diff --git a/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx b/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx index 1c1e16001..8ae9c35a8 100644 --- a/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx +++ b/frontend/src/Cabinet/components/Common/ClubListDropdown.tsx @@ -71,7 +71,7 @@ const ClubListDropdSelectionBoxStyled = styled.div<{ isOpen: boolean }>` position: relative; display: flex; align-items: center; - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); width: 100%; height: 60px; border-radius: 10px; @@ -114,7 +114,7 @@ const ClubListDropdItemStyled = styled.div<{ isSelected: boolean }>` align-items: center; background-color: ${({ isSelected }) => isSelected ? "var(--map-floor-color)" : "var(--bg-color)"}; - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); border-width: 0px 1px 1px 1px; width: 100%; height: 60px; diff --git a/frontend/src/Cabinet/components/Common/Dropdown.tsx b/frontend/src/Cabinet/components/Common/Dropdown.tsx index c1a3f6de0..c9eb3f121 100644 --- a/frontend/src/Cabinet/components/Common/Dropdown.tsx +++ b/frontend/src/Cabinet/components/Common/Dropdown.tsx @@ -1,64 +1,88 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import styled, { css } from "styled-components"; -import { ReactComponent as ClubIcon } from "@/Cabinet/assets/images/clubIcon.svg"; -import { ReactComponent as PrivateIcon } from "@/Cabinet/assets/images/privateIcon.svg"; -import { ReactComponent as ShareIcon } from "@/Cabinet/assets/images/shareIcon.svg"; +import { cabinetIconComponentMap } from "@/Cabinet/assets/data/maps"; +import { ReactComponent as DropdownChevronIcon } from "@/Cabinet/assets/images/dropdownChevron.svg"; +import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; export interface IDropdownOptions { name: string; value: any; imageSrc?: string; + isDisabled?: boolean; + hasNoOptions?: boolean; } -export interface IDropdown { +export interface IDropdownProps { options: IDropdownOptions[]; defaultValue: string; defaultImageSrc?: string; onChangeValue?: (param: any) => any; + isOpen: boolean; + setIsOpen: React.Dispatch>; + closeOtherDropdown?: () => void; } -const Dropdown = ({ options, defaultValue, onChangeValue }: IDropdown) => { +const Dropdown = ({ + options, + defaultValue, + onChangeValue, + defaultImageSrc, + isOpen, + setIsOpen, + closeOtherDropdown, +}: IDropdownProps) => { const [currentName, setCurrentName] = useState(defaultValue); - const [isOpen, setIsOpen] = useState(false); - const selectedIdx = options.findIndex((op) => op.name === currentName) ?? 0; + const idx: number = options.findIndex((op) => op.name === currentName); + const selectedIdx: number = idx === -1 ? 0 : idx; + const DefaultOptionIcon = + defaultImageSrc && + cabinetIconComponentMap[options[selectedIdx].value as CabinetType]; + useEffect(() => { + setCurrentName(defaultValue); + }, [defaultValue]); return ( { + if (options[selectedIdx].isDisabled) return; setIsOpen(!isOpen); + closeOtherDropdown && closeOtherDropdown(); }} - isOpen={isOpen} > - {options[selectedIdx].imageSrc?.length && ( + {DefaultOptionIcon && ( - {options[selectedIdx].value === "PRIVATE" && } - {options[selectedIdx].value === "CLUB" && } - {options[selectedIdx].value === "SHARE" && } + )}

{currentName}

- + + +
- {options?.map((option) => { + {options.map((option) => { + const OptionIcon = + cabinetIconComponentMap[option.value as CabinetType]; return ( { - setCurrentName(option.name); - setIsOpen(false); - if (onChangeValue) { - onChangeValue(option.value); + if (!option.isDisabled) { + setCurrentName(option.name); + setIsOpen(false); + if (onChangeValue) { + onChangeValue(option.value); + } } }} isSelected={option.name === currentName} + isDisabled={option.isDisabled} + hasNoOptions={option.hasNoOptions} > {option.imageSrc && ( - {option.value === "PRIVATE" && } - {option.value === "CLUB" && } - {option.value === "SHARE" && } + )}

{option.name}

@@ -75,13 +99,14 @@ const DropdownContainerStyled = styled.div` flex-direction: column; width: 100%; position: relative; + cursor: pointer; `; -const DropdownSelectionBoxStyled = styled.div<{ isOpen: boolean }>` +const DropdownSelectionBoxStyled = styled.div` position: relative; display: flex; align-items: center; - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); width: 100%; height: 60px; border-radius: 10px; @@ -89,19 +114,6 @@ const DropdownSelectionBoxStyled = styled.div<{ isOpen: boolean }>` padding-left: 20px; font-size: 1.125rem; color: var(--sys-main-color); - & > img { - filter: contrast(0.6); - width: 14px; - height: 8px; - position: absolute; - top: 45%; - left: 85%; - ${({ isOpen }) => - isOpen === true && - css` - transform: scaleY(-1); - `} - } `; const DropdownItemContainerStyled = styled.div<{ isVisible: boolean }>` @@ -118,29 +130,41 @@ const DropdownItemContainerStyled = styled.div<{ isVisible: boolean }>` `} `; -const DropdownItemStyled = styled.div<{ isSelected: boolean }>` +const DropdownItemStyled = styled.div<{ + isSelected: boolean; + isDisabled?: boolean; + hasNoOptions?: boolean; +}>` position: relative; display: flex; align-items: center; background-color: ${({ isSelected }) => isSelected ? "var(--map-floor-color)" : "var(--bg-color)"}; - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); border-width: 0px 1px 1px 1px; width: 100%; height: 60px; text-align: start; padding-left: 20px; font-size: 1.125rem; - color: ${({ isSelected }) => - isSelected ? "var(--sys-main-color)" : "var(--normal-text-color)"}; - cursor: pointer; + color: ${( + { isSelected, isDisabled } // 비활성화 된 항목은 --capsule-btn-border-color 로 띄우고 클릭 못하게 + ) => + isDisabled + ? "var(--capsule-btn-border-color)" + : isSelected + ? "var(--sys-main-color)" + : "var(--normal-text-color)"}; + cursor: ${({ isDisabled }) => (isDisabled ? "not-allowed" : "pointer")}; &:first-child { border-radius: 10px 10px 0px 0px; border-width: 1px 1px 1px 1px; } &:last-child { - border-radius: 0px 0px 10px 10px; + border-radius: ${(props) => + props.hasNoOptions ? "10px" : "0px 0px 10px 10px"}; } + &:hover { background-color: var(--map-floor-color); } @@ -154,9 +178,28 @@ const OptionsImgStyled = styled.div<{ isSelected?: boolean }>` width: 18px; height: 18px; } + & > svg > path { stroke: var(--normal-text-color); - transform: scale(0.8); } `; + +const DropdownSelectionBoxIconStyled = styled.div<{ isOpen: boolean }>` + width: 14px; + height: 8px; + display: flex; + position: absolute; + top: 45%; + left: 85%; + ${({ isOpen }) => + isOpen === true && + css` + transform: scaleY(-1); + `} + + & > svg > path { + stroke: var(--line-color); + } +`; + export default Dropdown; diff --git a/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx b/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx index 9a2a71785..76355fa3b 100644 --- a/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx +++ b/frontend/src/Cabinet/components/Common/MultiSelectFilterButton.tsx @@ -50,7 +50,7 @@ const FilterTextWrapperStyled = styled.div<{ isClicked: boolean }>` justify-content: center; align-items: center; color: ${({ isClicked }) => - isClicked ? "var(--sys-main-color)" : "var(--toggle-switch-off-bg-color)"}; + isClicked ? "var(--sys-main-color)" : "var(--line-color)"}; font-size: 1rem; `; diff --git a/frontend/src/Cabinet/components/Common/SelectInduction.tsx b/frontend/src/Cabinet/components/Common/SelectInduction.tsx new file mode 100644 index 000000000..e6c36903b --- /dev/null +++ b/frontend/src/Cabinet/components/Common/SelectInduction.tsx @@ -0,0 +1,43 @@ +import styled from "styled-components"; +import { ReactComponent as LogoImg } from "@/Cabinet/assets/images/logo.svg"; + +const SelectInduction = ({ msg }: { msg: string }) => { + return ( + + + + + {msg} + + ); +}; + +const WrapperStyled = styled.div` + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +const CabiLogoStyled = styled.div` + width: 35px; + height: 35px; + margin-bottom: 10px; + svg { + .logo_svg__currentPath { + fill: var(--sys-main-color); + } + } +`; + +const MsgStyled = styled.p` + font-size: 1.125rem; + font-weight: 400; + line-height: 28px; + color: var(--gray-line-btn-color); + text-align: center; + white-space: pre-line; +`; + +export default SelectInduction; diff --git a/frontend/src/Cabinet/components/Common/Selector.tsx b/frontend/src/Cabinet/components/Common/Selector.tsx index 170761990..ea90c4b35 100644 --- a/frontend/src/Cabinet/components/Common/Selector.tsx +++ b/frontend/src/Cabinet/components/Common/Selector.tsx @@ -1,18 +1,17 @@ -import PillButton from "@/Cabinet/components/Common/PillButton"; import styled from "styled-components"; +import PillButton from "@/Cabinet/components/Common/PillButton"; interface ISelectorProps { - iconSrc?: string; + icon?: React.FunctionComponent>; selectList: { key: number; value: string }[]; onClickSelect: any; } -const Selector = ({ iconSrc, selectList, onClickSelect }: ISelectorProps) => { +const Selector = ({ icon, selectList, onClickSelect }: ISelectorProps) => { + const Icon = icon; return ( - - - + {Icon && } {selectList && selectList.map((elem) => { return ( @@ -42,6 +41,10 @@ const IconWrapperStyled = styled.div` width: 24px; height: 24px; margin-bottom: 12px; + + & > svg > path { + stroke: var(--normal-text-color); + } `; export default Selector; diff --git a/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx b/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx index 8d50baf2d..49c068d39 100644 --- a/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx +++ b/frontend/src/Cabinet/components/Common/ToggleSwitch.tsx @@ -57,9 +57,7 @@ const ToggleSwitchStyled = styled.label<{ display: inline-block; position: relative; background: ${(props) => - props.checked - ? "var(--sys-main-color)" - : "var(--toggle-switch-off-bg-color)"}; + props.checked ? "var(--sys-main-color)" : "var(--line-color)"}; width: 56px; height: 28px; border-radius: 50px; diff --git a/frontend/src/Cabinet/components/Common/UnavailableDataInfo.tsx b/frontend/src/Cabinet/components/Common/UnavailableDataInfo.tsx new file mode 100644 index 000000000..b5f703049 --- /dev/null +++ b/frontend/src/Cabinet/components/Common/UnavailableDataInfo.tsx @@ -0,0 +1,68 @@ +import styled from "styled-components"; +import { ReactComponent as SadCabiIcon } from "@/Cabinet/assets/images/sadCcabi.svg"; + +const UnavailableDataInfo = ({ + msg, + height, + fontSize, + iconWidth, + iconHeight, +}: { + msg: string; + height?: string; + fontSize?: string; + iconWidth?: string; + iconHeight?: string; +}) => { + return ( + + + {msg} + + + + + + ); +}; + +const EmptyWrapperStyled = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100%; +`; + +const EmptyItemUsageLogTextStyled = styled.div<{ + height: string | undefined; + fontSize: string | undefined; +}>` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: ${(props) => (props.fontSize ? props.fontSize : "1.125rem")}; + line-height: 1.75rem; + color: var(--gray-line-btn-color); + height: ${(props) => props.height && props.height}; +`; + +const SadCcabiIconStyled = styled.div<{ + width: string | undefined; + height: string | undefined; +}>` + width: ${(props) => (props.width ? props.width : "30px")}; + height: ${(props) => (props.height ? props.height : "30px")}; + margin-left: 10px; + + & > svg { + width: ${(props) => (props.width ? props.width : "30px")}; + height: ${(props) => (props.height ? props.height : "30px")}; + } + + & > svg > path { + fill: var(--normal-text-color); + } +`; + +export default UnavailableDataInfo; diff --git a/frontend/src/Cabinet/components/Common/WarningNotification.tsx b/frontend/src/Cabinet/components/Common/WarningNotification.tsx index 6e00574f1..455d9c8dc 100644 --- a/frontend/src/Cabinet/components/Common/WarningNotification.tsx +++ b/frontend/src/Cabinet/components/Common/WarningNotification.tsx @@ -1,5 +1,6 @@ import React from "react"; import styled from "styled-components"; +import { ReactComponent as WarningIcon } from "@/Cabinet/assets/images/warningTriangleIcon.svg"; export interface WarningNotificationProps { isVisible: boolean; @@ -12,25 +13,14 @@ const WarningNotification: React.FC = ({ }: WarningNotificationProps) => { return ( - + + + {message} ); }; -const WarningIcon = styled.div<{ isVisible: boolean }>` - display: ${({ isVisible }) => (isVisible ? "block" : "none")}; - background-image: url("/src/Cabinet/assets/images/warningTriangleIcon.svg"); - width: 24px; - height: 24px; - margin: 0px auto; - opacity: 0.6; - cursor: pointer; - &:hover { - opacity: 1; - } -`; - const WarningBox = styled.div` position: relative; margin: 10px auto; @@ -50,15 +40,27 @@ const WarningBox = styled.div` padding 0.5s ease-in-out; `; +const IconWrapperStyled = styled.div<{ isVisible: boolean }>` + display: ${({ isVisible }) => (isVisible ? "block" : "none")}; + width: 24px; + height: 24px; + cursor: pointer; + margin: 0px auto; + opacity: 0.6; + &:hover { + opacity: 1; + } +`; + const WarningWrapper = styled.div<{ isVisible: boolean }>` display: ${({ isVisible }) => (isVisible ? "block" : "none")}; position: relative; width: 100%; height: 24px; justify-content: center; - & ${WarningIcon}:hover + ${WarningBox} { + & ${IconWrapperStyled}:hover + ${WarningBox} { visibility: visible; - color: var(--normal-text-color); + color: var(--white-text-with-bg-color); background-color: var(--tooltip-shadow-color); &:before { border-color: transparent transparent var(--tooltip-shadow-color) diff --git a/frontend/src/Cabinet/components/Home/ManualContentBox.tsx b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx index 1af0d4d15..5f4885e30 100644 --- a/frontend/src/Cabinet/components/Home/ManualContentBox.tsx +++ b/frontend/src/Cabinet/components/Home/ManualContentBox.tsx @@ -1,12 +1,7 @@ import styled, { css, keyframes } from "styled-components"; import { manualContentData } from "@/Cabinet/assets/data/ManualContent"; -import { ReactComponent as ClockImg } from "@/Cabinet/assets/images/clock.svg"; -import { ReactComponent as ClubIcon } from "@/Cabinet/assets/images/clubIcon.svg"; -import { ReactComponent as ExtensionIcon } from "@/Cabinet/assets/images/extension.svg"; import { ReactComponent as ManualPeopleImg } from "@/Cabinet/assets/images/manualPeople.svg"; import { ReactComponent as MoveBtnImg } from "@/Cabinet/assets/images/moveButton.svg"; -import { ReactComponent as PrivateIcon } from "@/Cabinet/assets/images/privateIcon.svg"; -import { ReactComponent as ShareIcon } from "@/Cabinet/assets/images/shareIcon.svg"; import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface MaunalContentBoxProps { @@ -22,24 +17,17 @@ const MaunalContentBox = ({ contentStatus }: MaunalContentBoxProps) => { contentStatus={contentStatus} > {contentStatus === ContentStatus.EXTENSION && ( - + )} - {contentStatus === ContentStatus.PRIVATE && ( - - )} - {contentStatus === ContentStatus.SHARE && ( - - )} - {contentStatus === ContentStatus.CLUB && ( - - )} - {contentStatus === ContentStatus.EXTENSION && ( - - )} - - {contentStatus === ContentStatus.IN_SESSION && ( - + {contentStatus !== ContentStatus.IN_SESSION && + contentData.iconComponent && ( + )} + + {contentStatus === ContentStatus.IN_SESSION && + contentData.iconComponent && ( + + )}

{contentData.contentTitle}

@@ -49,9 +37,9 @@ const MaunalContentBox = ({ contentStatus }: MaunalContentBoxProps) => { const Rotation = keyframes` to { - transform : rotate(360deg) + transform : rotate(360deg) } -`; + `; const MaunalContentBoxStyled = styled.div<{ background: string; @@ -76,6 +64,7 @@ const MaunalContentBoxStyled = styled.div<{ margin-right: 10px; margin-top: 160px; animation: ${Rotation} 1s linear infinite; + stroke: var(--sys-main-color); } .contentImg { @@ -87,10 +76,6 @@ const MaunalContentBoxStyled = styled.div<{ props.contentStatus === ContentStatus.EXTENSION ? "var(--normal-text-color)" : "var(--white-text-with-bg-color)"}; - transform: ${(props) => - props.contentStatus === ContentStatus.EXTENSION - ? "scale(1.4)" - : "scale(3.3)"}; } } @@ -101,6 +86,7 @@ const MaunalContentBoxStyled = styled.div<{ position: absolute; right: 100px; bottom: 30px; + fill: var(--sys-main-color); } ${({ contentStatus }) => diff --git a/frontend/src/Cabinet/components/Home/ServiceManual.tsx b/frontend/src/Cabinet/components/Home/ServiceManual.tsx index 29da0ccf1..94f766f70 100644 --- a/frontend/src/Cabinet/components/Home/ServiceManual.tsx +++ b/frontend/src/Cabinet/components/Home/ServiceManual.tsx @@ -147,7 +147,7 @@ const NotionBtn = styled.button` font-size: 0.875rem; color: var(--notion-btn-text-color); background: var(--bg-color); - border: 1px solid var(--toggle-switch-off-bg-color); + border: 1px solid var(--line-color); :hover { color: var(--normal-text-color); font-weight: 400; diff --git a/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.container.tsx b/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.container.tsx new file mode 100644 index 000000000..1cca6cf96 --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.container.tsx @@ -0,0 +1,112 @@ +import { useEffect, useState } from "react"; +import AdminItemProvideLog from "@/Cabinet/components/ItemLog/AdminItemProvideLog"; +import { ItemLogResponseType } from "@/Cabinet/types/dto/admin.dto"; +import useMenu from "@/Cabinet/hooks/useMenu"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; + +const AdminItemProvideLogContainer = () => { + const { closeStore } = useMenu(); + const [logs, setLogs] = useState({ + itemHistories: [], + totalLength: 0, + }); + const [page, setPage] = useState(0); + const [totalPage, setTotalPage] = useState(-1); + const [needsUpdate, setNeedsUpdate] = useState(false); + const size = 8; + + const mockItemHistories = [ + { + items: [ + { + itemSku: "SKU1001", + itemName: "이사권", + itemDetails: "이사권", + issuedDate: "2024-05-28T10:00:00", + }, + { + itemSku: "SKU1002", + itemName: "알림 등록권", + itemDetails: "알림 등록권", + issuedDate: "2024-05-28T11:00:00", + }, + { + itemSku: "SKU1003", + itemName: "페널티권", + itemDetails: "31일", + issuedDate: "2024-05-28T12:00:00", + }, + { + itemSku: "SKU1004", + itemName: "연장권", + itemDetails: "15일", + issuedDate: "2024-05-28T13:00:00", + }, + ], + }, + ]; + + async function getData(page: number) { + try { + const items = mockItemHistories[0].items; + const startIndex = page * size; + const endIndex = startIndex + size; + const paginatedItems = items.slice(startIndex, endIndex); + + const paginatedData = { + itemHistories: paginatedItems, + totalLength: items.length, + }; + + setLogs(paginatedData); + setTotalPage(Math.ceil(paginatedData.totalLength / size)); + } catch (error) { + console.error("Failed to fetch data:", error); + setLogs({ itemHistories: [], totalLength: 0 }); + } + } + + useEffect(() => { + getData(page); + }, [page]); + + useEffect(() => { + if (needsUpdate) { + getData(page); + setNeedsUpdate(false); + } + }, [needsUpdate, page]); + + const onClickPrev = () => { + if (page > 0) { + setPage((prev) => prev - 1); + setNeedsUpdate(true); + } + }; + + const onClickNext = () => { + if (page < totalPage - 1) { + setPage((prev) => prev + 1); + setNeedsUpdate(true); + } + }; + + const closeAndResetLogPage = () => { + closeStore(); + setPage(0); + setNeedsUpdate(true); + }; + + return ( + + ); +}; + +export default AdminItemProvideLogContainer; diff --git a/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.tsx b/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.tsx new file mode 100644 index 000000000..b97f6dc4a --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/AdminItemProvideLog.tsx @@ -0,0 +1,124 @@ +import styled, { css } from "styled-components"; +import AdminItemProvideTable from "@/Cabinet/components/ItemLog/ItemLogTable/AdminItemProvideTable"; +import { IItemLog } from "@/Cabinet/types/dto/admin.dto"; + +const AdminItemProvideLog = ({ + closeItem, + logs, + page, + totalPage, + onClickPrev, + onClickNext, +}: IItemLog) => { + return ( + + + + + + + + + + + + + + + + + + + + ); +}; + +const AdminItemUsageLogStyled = styled.div` + width: 100%; + position: relative; +`; + +const ButtonContainerStyled = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.3; + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 auto; + overflow: hidden; + &:hover > .logPageButton { + opacity: 1; + } +`; + +const PageButtonStyled = styled.div<{ + page: number; + totalPage: number; + type: string; +}>` + cursor: pointer; + width: 40px; + height: 100%; + border-radius: 10px; + position: absolute; + opacity: 0.5; + transition: opacity 0.5s; + background: linear-gradient( + to left, + transparent, + var(--page-btn-shadow-color) + ); + display: ${({ page, totalPage, type }) => { + if (type == "prev" && page == 0) return "none"; + if (type == "next" && (totalPage == 0 || page == totalPage - 1)) + return "none"; + return "block"; + }}; + ${({ type }) => + type === "prev" + ? css` + left: 0; + ` + : css` + right: 0; + transform: rotate(-180deg); + `} +`; + +const ImgCenterStyled = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 100%; +`; + +const ImageStyled = styled.div` + width: 40px; + margin-right: 4px; + filter: brightness(0%); + border-radius: 50%; +`; + +export default AdminItemProvideLog; diff --git a/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.container.tsx b/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.container.tsx new file mode 100644 index 000000000..ff26aba0c --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.container.tsx @@ -0,0 +1,79 @@ +import { useEffect, useState } from "react"; +import { useRecoilState } from "recoil"; +import { targetUserInfoState } from "@/Cabinet/recoil/atoms"; +import AdminItemUsageLog from "@/Cabinet/components/ItemLog/AdminItemUsageLog"; +import { ItemLogResponseType } from "@/Cabinet/types/dto/admin.dto"; +import { axiosGetUserItems } from "@/Cabinet/api/axios/axios.custom"; +import useMenu from "@/Cabinet/hooks/useMenu"; + +const AdminItemUsageLogContainer = () => { + const { closeStore } = useMenu(); + const [userId, setUserId] = useState(0); + const [targetUserInfo] = useRecoilState(targetUserInfoState); + const [logs, setLogs] = useState({ + itemHistories: [], + totalLength: 0, + }); + const [page, setPage] = useState(0); + const [totalPage, setTotalPage] = useState(-1); + const [needsUpdate, setNeedsUpdate] = useState(true); + const size = 8; + + async function getData(page: number) { + try { + const paginatedData = await axiosGetUserItems( + targetUserInfo.userId!, + page, + size + ); + setLogs({ + itemHistories: paginatedData.data.itemHistories, + totalLength: paginatedData.data.totalLength, + }); + setTotalPage(Math.ceil(paginatedData.data.totalLength / size)); + } catch { + setLogs({ itemHistories: [], totalLength: 0 }); + setTotalPage(1); + } + } + + useEffect(() => { + if (needsUpdate) { + getData(page); + setNeedsUpdate(false); + } + }, [needsUpdate, page]); + + const onClickPrev = () => { + if (page > 0) { + setPage((prev) => prev - 1); + setNeedsUpdate(true); + } + }; + + const onClickNext = () => { + if (page < totalPage - 1) { + setPage((prev) => prev + 1); + setNeedsUpdate(true); + } + }; + + const closeAndResetLogPage = () => { + closeStore(); + setPage(0); + setNeedsUpdate(true); + }; + + return ( + + ); +}; + +export default AdminItemUsageLogContainer; diff --git a/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.tsx b/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.tsx new file mode 100644 index 000000000..164924250 --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/AdminItemUsageLog.tsx @@ -0,0 +1,124 @@ +import styled, { css } from "styled-components"; +import AdminItemLogTable from "@/Cabinet/components/ItemLog/ItemLogTable/AdminItemLogTable"; +import { IItemLog } from "@/Cabinet/types/dto/admin.dto"; + +const AdminItemUsageLog = ({ + closeItem, + logs, + page, + totalPage, + onClickPrev, + onClickNext, +}: IItemLog) => { + return ( + + + + + + + + + + + + + + + + + + + + ); +}; + +const AdminItemUsageLogStyled = styled.div` + width: 100%; + position: relative; +`; + +const ButtonWrapperStyled = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.3; + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 auto; + overflow: hidden; + &:hover > .logPageButton { + opacity: 1; + } +`; + +const PageButtonStyled = styled.div<{ + page: number; + totalPage: number; + type: string; +}>` + cursor: pointer; + width: 40px; + height: 100%; + border-radius: 10px; + position: absolute; + opacity: 0.5; + transition: opacity 0.5s; + background: linear-gradient( + to left, + transparent, + var(--page-btn-shadow-color) + ); + display: ${({ page, totalPage, type }) => { + if (type == "prev" && page == 0) return "none"; + if (type == "next" && (totalPage == 0 || page == totalPage - 1)) + return "none"; + return "block"; + }}; + ${({ type }) => + type === "prev" + ? css` + left: 0; + ` + : css` + right: 0; + transform: rotate(-180deg); + `} +`; + +const ImgCenterStyled = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 100%; +`; + +const ImageStyled = styled.div` + width: 40px; + margin-right: 4px; + filter: brightness(0%); + border-radius: 50%; +`; + +export default AdminItemUsageLog; diff --git a/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemLogTable.tsx b/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemLogTable.tsx new file mode 100644 index 000000000..07a754416 --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemLogTable.tsx @@ -0,0 +1,113 @@ +import styled from "styled-components"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ItemLogResponseType } from "@/Cabinet/types/dto/admin.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; + +const dateOptions: Intl.DateTimeFormatOptions = { + year: "2-digit", + month: "2-digit", + day: "2-digit", +}; + +const AdminItemLogTable = ({ itemLog }: { itemLog: ItemLogResponseType }) => { + if (!itemLog) return ; + return ( + + + + + 발급일 + 아이템 + 사용일 + + + {itemLog !== STATUS_400_BAD_REQUEST && + Array.isArray(itemLog.itemHistories) && ( + + {itemLog.itemHistories.map( + ({ purchaseAt, itemName, itemDetails, usedAt }, idx) => ( + + + {new Date(purchaseAt ?? "").toLocaleString( + "ko-KR", + dateOptions + )} + + + {itemName !== itemDetails + ? `${itemName} - ${itemDetails}` + : itemName} + + + {usedAt + ? new Date(usedAt).toLocaleString("ko-KR", dateOptions) + : "-"} + + + ) + )} + + )} + + {(itemLog === STATUS_400_BAD_REQUEST || + itemLog.totalLength === undefined || + itemLog.totalLength === 0) && ( + 아이템 내역이 없습니다. + )} + + ); +}; + +const LogTableWrapperstyled = styled.div` + width: 100%; + max-width: 800px; + border-radius: 10px; + overflow: hidden; + margin: 0 auto; + box-shadow: 0 0 10px 0 var(--table-border-shadow-color-100); +`; + +const LogTableStyled = styled.table` + width: 100%; + background: var(--bg-color); + overflow: scroll; +`; + +const TheadStyled = styled.thead` + width: 100%; + height: 50px; + line-height: 50px; + background-color: var(--sys-main-color); + color: var(--white-text-with-bg-color); +`; + +const TbodyStyled = styled.tbody` + & > tr { + font-size: 11px; + text-align: center; + height: 50px; + } + & > tr > td { + height: 50px; + line-height: 50px; + width: 33.3%; + } + & > tr:nth-child(2n) { + background: var(--table-even-row-bg-color); + } +`; + +const EmptyLogStyled = styled.div` + width: 100%; + text-align: center; + font-size: 1rem; + padding: 20px 0; +`; + +export default AdminItemLogTable; diff --git a/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemProvideTable.tsx b/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemProvideTable.tsx new file mode 100644 index 000000000..d9d5c2930 --- /dev/null +++ b/frontend/src/Cabinet/components/ItemLog/ItemLogTable/AdminItemProvideTable.tsx @@ -0,0 +1,111 @@ +import styled from "styled-components"; +import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; +import { ItemLogResponseType } from "@/Cabinet/types/dto/admin.dto"; +import { STATUS_400_BAD_REQUEST } from "@/Cabinet/constants/StatusCode"; + +const dateOptions: Intl.DateTimeFormatOptions = { + year: "2-digit", + month: "2-digit", + day: "2-digit", +}; + +const AdminItemProvideTable = ({ + itemLog, +}: { + itemLog: ItemLogResponseType; +}) => { + if (itemLog === undefined) return ; + + return ( + + + + + 지급일 + 아이템 + + + {itemLog !== STATUS_400_BAD_REQUEST && ( + + {itemLog.itemHistories.map( + ({ issuedDate, itemName, itemDetails, usedAt }, idx) => ( + + + {issuedDate + ? new Date(issuedDate).toLocaleString( + "ko-KR", + dateOptions + ) + : ""} + + + {itemName !== itemDetails + ? `${itemName} - ${itemDetails}` + : itemName} + + + ) + )} + + )} + + {itemLog === STATUS_400_BAD_REQUEST && ( + 아이템 사용기록이 없습니다. + )} + + ); +}; + +const LogTableWrapperstyled = styled.div` + width: 100%; + max-width: 800px; + border-radius: 10px; + overflow: hidden; + margin: 0 auto; + box-shadow: 0 0 10px 0 var(--table-border-shadow-color-100); +`; + +const LogTableStyled = styled.table` + width: 100%; + background: var(--bg-color); + overflow: scroll; +`; + +const TheadStyled = styled.thead` + width: 100%; + height: 50px; + line-height: 50px; + background-color: var(--sys-main-color); + color: var(--white-text-with-bg-color); +`; + +const TbodyStyled = styled.tbody` + & > tr { + font-size: small; + text-align: center; + height: 50px; + } + & > tr > td { + height: 50px; + line-height: 50px; + width: 33.3%; + } + & > tr:nth-child(2n) { + background: var(--table-even-row-bg-color); + } +`; + +const EmptyLogStyled = styled.div` + width: 100%; + text-align: center; + font-size: 1rem; + padding: 20px 0; +`; + +export default AdminItemProvideTable; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx b/frontend/src/Cabinet/components/LeftNav/LeftClubNav/LeftClubNav.tsx similarity index 92% rename from frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx rename to frontend/src/Cabinet/components/LeftNav/LeftClubNav/LeftClubNav.tsx index 5224eb6a8..09d363114 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftClubNav/LeftClubNav.tsx @@ -6,13 +6,11 @@ import { ClubPaginationResponseDto, ClubResponseDto, } from "@/Cabinet/types/dto/club.dto"; -import useMenu from "@/Cabinet/hooks/useMenu"; -const LeftSectionNavClubs = () => { +const LeftClubNav = ({ closeLeftNav }: { closeLeftNav: () => void }) => { const clubList = useRecoilValue(myClubListState); const [targetClubInfo, setTargetClubInfo] = useRecoilState(targetClubInfoState); - const { closeLeftNav } = useMenu(); return ( <> @@ -48,6 +46,7 @@ const ClubLeftNavOptionStyled = styled.div` padding: 32px 10px 32px; border-right: 1px solid var(--line-color); font-weight: 300; + font-size: var(--size-base); position: relative; font-size: var(--size-base); & hr { @@ -67,4 +66,4 @@ const ListTitleStyled = styled.div` font-weight: 500; `; -export default LeftSectionNavClubs; +export default LeftClubNav; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx index 90dfce091..f5ed614ab 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -12,8 +12,10 @@ import { currentFloorNumberState, currentMapFloorState, currentSectionNameState, + isCurrentSectionRenderState, myCabinetInfoState, numberOfAdminWorkState, + selectedTypeOnSearchState, } from "@/Cabinet/recoil/atoms"; import { currentBuildingFloorState } from "@/Cabinet/recoil/selectors"; import LeftMainNav from "@/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav"; @@ -21,6 +23,7 @@ import { CabinetInfoByBuildingFloorDto, MyCabinetInfoResponseDto, } from "@/Cabinet/types/dto/cabinet.dto"; +import CabinetDetailAreaType from "@/Cabinet/types/enum/cabinetDetailArea.type.enum"; import { axiosCabinetByBuildingFloor } from "@/Cabinet/api/axios/axios.custom"; import { removeCookie } from "@/Cabinet/api/react_cookie/cookies"; import useMenu from "@/Cabinet/hooks/useMenu"; @@ -45,6 +48,10 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { const numberOfAdminWork = useRecoilValue(numberOfAdminWorkState); const navigator = useNavigate(); const { pathname } = useLocation(); + const [isCurrentSectionRender] = useRecoilState(isCurrentSectionRenderState); + const setSelectedTypeOnSearch = useSetRecoilState( + selectedTypeOnSearchState + ); useEffect(() => { if (currentFloor === undefined) { @@ -79,11 +86,13 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { myCabinetInfo?.cabinetId, numberOfAdminWork, myCabinetInfo?.status, + isCurrentSectionRender, ]); const onClickFloorButton = (floor: number) => { setCurrentFloor(floor); setCurrentMapFloor(floor); + setSelectedTypeOnSearch(CabinetDetailAreaType.CABINET); if (!pathname.includes("main")) { if (floor === currentFloor) { axiosCabinetByBuildingFloor(currentBuilding, currentFloor).then( @@ -118,7 +127,12 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { navigator("slack-notification"); closeAll(); }; - + + const onClickStoreButton = (): void => { + navigator("store"); + closeAll(); + }; + const onClickMainClubButton = () => { navigator("clubs"); closeAll(); @@ -152,7 +166,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { resetCurrentSection(); navigator("/login"); }; - + return ( { onClickMainClubButton={onClickMainClubButton} onClickProfileButton={onClickProfileButton} onClickAvailableButton={onClickAvailableButton} + onClickStoreButton={onClickStoreButton} isAdmin={isAdmin} /> ); diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx index 548466036..2251e3ca4 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -1,10 +1,10 @@ import styled from "styled-components"; import { ReactComponent as LogoutImg } from "@/Cabinet/assets/images/close-square.svg"; -import { ReactComponent as CulbImg } from "@/Cabinet/assets/images/clubIconGray.svg"; +import { ReactComponent as ClubImg } from "@/Cabinet/assets/images/clubIconGray.svg"; import { ReactComponent as ProfileImg } from "@/Cabinet/assets/images/profile-circle.svg"; -import { ReactComponent as SearchImg } from "@/Cabinet/assets/images/search.svg"; import { ReactComponent as SlackNotiImg } from "@/Cabinet/assets/images/slack-notification.svg"; import { ReactComponent as SlackImg } from "@/Cabinet/assets/images/slack.svg"; +import { ReactComponent as StoreImg } from "@/Cabinet/assets/images/storeIconGray.svg"; interface ILeftMainNav { pathname: string; @@ -20,6 +20,7 @@ interface ILeftMainNav { onClickMainClubButton: React.MouseEventHandler; onClickProfileButton: React.MouseEventHandler; onClickAvailableButton: React.MouseEventHandler; + onClickStoreButton: React.MouseEventHandler; isAdmin?: boolean; } @@ -32,11 +33,11 @@ const LeftMainNav = ({ onClickFloorButton, onClickLogoutButton, onClickSlackNotiButton, - onClickSearchButton, onClickAdminClubButton, onClickMainClubButton, onClickProfileButton, onClickAvailableButton, + onClickStoreButton, isAdmin, }: ILeftMainNav) => { return ( @@ -89,25 +90,25 @@ const LeftMainNav = ({ <> - - Noti + + Store - - Search + + Noti - + Club - + Logout )} {!isAdmin && currentBuildingName === "새롬관" && ( <> + + + Store + - + Clubs - + Profile @@ -198,6 +210,8 @@ const TopBtnStyled = styled.li` width: 100%; height: 48px; line-height: 48px; + /* font-size: var(--size-base); */ + font-size: var(--size-base); font-weight: 300; margin-bottom: 2.5vh; border-radius: 10px; @@ -227,7 +241,7 @@ const BottomSectionStyled = styled.section` margin: 0 auto; width: 56px; height: 1px; - background-color: var(--toggle-switch-off-bg-color); + background-color: var(--line-color); } `; @@ -240,6 +254,7 @@ const BottomBtnStyled = styled.li` width: 100%; min-height: 48px; line-height: 1.125rem; + font-size: var(--size-base); font-weight: 300; margin-top: 2.5vh; border-radius: 10px; @@ -268,8 +283,11 @@ const BottomBtnStyled = styled.li` stroke: var(--button-line-color); } } - svg { + & > svg { margin: 0 auto; + width: 24px; + height: 24px; + stroke: var(--gray-line-btn-color); } @media (hover: hover) and (pointer: fine) { &:hover { diff --git a/frontend/src/Cabinet/components/LeftNav/LeftNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftNav.tsx index e7f68f4ce..409446e82 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftNav.tsx @@ -1,15 +1,38 @@ +import { useNavigate } from "react-router-dom"; import styled from "styled-components"; +import LeftClubNav from "@/Cabinet/components/LeftNav/LeftClubNav/LeftClubNav"; import LeftMainNavContainer from "@/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container"; -import LeftSectionNavContainer from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.container"; +import LeftProfileNav from "@/Cabinet/components/LeftNav/LeftProfileNav/LeftProfileNav"; +import LeftSectionNav from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav"; +import LeftStoreNav from "@/Cabinet/components/LeftNav/LeftStoreNav/LeftStoreNav"; +import useMenu from "@/Cabinet/hooks/useMenu"; const LeftNav: React.FC<{ isVisible: boolean; isAdmin?: boolean; }> = ({ isAdmin, isVisible }) => { + const navigator = useNavigate(); + const { closeLeftNav } = useMenu(); + const isProfilePage: boolean = location.pathname.includes("profile"); + const isMainClubPage: boolean = location.pathname === "/clubs"; + const isMainStorePage: boolean = location.pathname.includes("store"); + + const onClickRedirectButton = (location: string) => { + closeLeftNav(); + navigator(location); + }; + return ( - + {isVisible && } + {isProfilePage && ( + + )} + {isMainClubPage && } + {isMainStorePage && !isAdmin && ( + + )} ); }; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftProfileNav/LeftProfileNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftProfileNav/LeftProfileNav.tsx new file mode 100644 index 000000000..cc18f9c29 --- /dev/null +++ b/frontend/src/Cabinet/components/LeftNav/LeftProfileNav/LeftProfileNav.tsx @@ -0,0 +1,118 @@ +import { useLocation } from "react-router-dom"; +import styled from "styled-components"; +import { FloorSectionStyled } from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav"; +import { ReactComponent as LinkImg } from "@/Cabinet/assets/images/link.svg"; + +const LeftProfileNav = ({ + onClickRedirectButton, +}: { + onClickRedirectButton: (location: string) => void; +}) => { + const { pathname } = useLocation(); + + const onClickSlack = () => { + window.open( + "https://42born2code.slack.com/archives/C02V6GE8LD7", + "_blank", + "noopener noreferrer" + ); + }; + + const onClickClubForm = () => { + window.open( + "https://docs.google.com/forms/d/e/1FAIpQLSfp-d7qq8gTvmQe5i6Gtv_mluNSICwuv5pMqeTBqt9NJXXP7w/closedform", + "_blank", + "noopener noreferrer" + ); + }; + + return ( + + onClickRedirectButton("profile")} + > + 내 정보 + + onClickRedirectButton("profile/log")} + > + 대여 기록 + +
+ onClickSlack()} + title="슬랙 캐비닛 채널 새창으로 열기" + > + 문의하기 + + + onClickClubForm()} + title="동아리 사물함 사용 신청서 새창으로 열기" + > + 동아리 신청서 + + +
+ ); +}; + +const ProfileLeftNavOptionStyled = styled.div` + display: block; + min-width: 240px; + height: 100%; + padding: 32px 10px; + border-right: 1px solid var(--line-color); + font-weight: 300; + font-size: var(--size-base); + position: relative; + & hr { + width: 80%; + height: 1px; + background-color: var(--inventory-item-title-border-btm-color); + border: 0; + margin-top: 20px; + margin-bottom: 20px; + } +`; + +const SectionLinkStyled = styled.div` + width: 100%; + height: 40px; + line-height: 40px; + text-indent: 20px; + margin: 2px 0; + padding-right: 30px; + cursor: pointer; + display: flex; + align-items: center; + color: var(--gray-line-btn-color); + + #linknImg { + width: 15px; + height: 15px; + margin-left: auto; + } + + @media (hover: hover) and (pointer: fine) { + &:hover { + color: var(--sys-main-color); + } + &:hover img { + filter: invert(33%) sepia(55%) saturate(3554%) hue-rotate(230deg) + brightness(99%) contrast(107%); + } + } +`; + +export default LeftProfileNav; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.container.tsx deleted file mode 100644 index ee4171ec2..000000000 --- a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.container.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useLocation, useNavigate } from "react-router-dom"; -import { useRecoilState, useRecoilValue } from "recoil"; -import { currentSectionNameState } from "@/Cabinet/recoil/atoms"; -import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; -import LeftSectionNav from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav"; -import useMenu from "@/Cabinet/hooks/useMenu"; - -const LeftSectionNavContainer = ({ isVisible }: { isVisible: boolean }) => { - const floorSection = useRecoilValue>(currentFloorSectionState); - const [currentFloorSection, setCurrentFloorSection] = useRecoilState( - currentSectionNameState - ); - const navigator = useNavigate(); - const { pathname } = useLocation(); - const { closeLeftNav } = useMenu(); - const isProfilePage: boolean = location.pathname.includes("profile"); - const isMainClubPage: boolean = location.pathname === "/clubs"; - - const onClickSection = (section: string) => { - closeLeftNav(); - setCurrentFloorSection(section); - }; - - const onClickProfile = () => { - closeLeftNav(); - navigator("profile"); - }; - - const onClickLentLogButton = () => { - closeLeftNav(); - navigator("profile/log"); - }; - - const onClickSlack = () => { - window.open( - "https://42born2code.slack.com/archives/C02V6GE8LD7", - "_blank", - "noopener noreferrer" - ); - }; - - const onClickClubForm = () => { - window.open( - "https://docs.google.com/forms/d/e/1FAIpQLSfp-d7qq8gTvmQe5i6Gtv_mluNSICwuv5pMqeTBqt9NJXXP7w/closedform", - "_blank", - "noopener noreferrer" - ); - }; - - return ( - - ); -}; - -export default LeftSectionNavContainer; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx index 7deadb65b..5939a9405 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx @@ -1,113 +1,67 @@ +import { useLocation } from "react-router-dom"; +import { useRecoilValue } from "recoil"; +import { useRecoilState } from "recoil"; import styled from "styled-components"; +import { currentSectionNameState } from "@/Cabinet/recoil/atoms"; +import { currentFloorSectionState } from "@/Cabinet/recoil/selectors"; import CabinetColorTable from "@/Cabinet/components/LeftNav/CabinetColorTable/CabinetColorTable"; -import LeftSectionNavClubs from "@/Cabinet/components/LeftNav/LeftSectionNav/LeftSectionNavClubs"; -import { ReactComponent as LinkImg } from "@/Cabinet/assets/images/link.svg"; +import { clubSectionsData } from "@/Cabinet/assets/data/mapPositionData"; +import { ReactComponent as FilledHeartIcon } from "@/Cabinet/assets/images/filledHeart.svg"; +import { ReactComponent as LineHeartIcon } from "@/Cabinet/assets/images/lineHeart.svg"; +import { ICurrentSectionInfo } from "@/Cabinet/types/dto/cabinet.dto"; -interface ILeftSectionNav { - isVisible: boolean; - onClickSection: Function; - currentFloorSection: string; - floorSection: string[]; - isProfile: boolean; - onClickProfile: Function; - pathname: string; - onClickLentLogButton: Function; - onClickSlack: Function; - onClickClubForm: Function; - isClub: boolean; -} +const LeftSectionNav = ({ closeLeftNav }: { closeLeftNav: () => void }) => { + const floorSection = useRecoilValue>( + currentFloorSectionState + ); + const [currentFloorSection, setCurrentFloorSection] = useRecoilState( + currentSectionNameState + ); + const { pathname } = useLocation(); + const isAdmin = pathname.includes("admin"); -const LeftSectionNav = ({ - isVisible, - currentFloorSection, - onClickSection, - floorSection, - isProfile, - onClickProfile, - pathname, - onClickLentLogButton, - onClickSlack, - onClickClubForm, - isClub, -}: ILeftSectionNav) => { return ( - <> - - {floorSection.map((section: string, index: number) => ( + + {floorSection.map((section: ICurrentSectionInfo, index: number) => { + const isClubSection = clubSectionsData.find((clubSection) => { + return clubSection === section.sectionName; + }) + ? true + : false; + return ( onClickSection(section)} + onClick={() => { + closeLeftNav(); + setCurrentFloorSection(section.sectionName); + }} > - {section} + {section.sectionName} + + {!isAdmin && + !isClubSection && + (section.alarmRegistered ? ( + + ) : ( + + ))} + - ))} - - - - - onClickProfile()} - > - 내 정보 - - onClickLentLogButton()} - > - 대여 기록 - -
- onClickSlack()} - title="슬랙 캐비닛 채널 새창으로 열기" - > - 문의하기 - - - onClickClubForm()} - title="동아리 사물함 사용 신청서 새창으로 열기" - > - 동아리 신청서 - - -
- {isClub && } - + ); + })} + +
); }; -const LeftNavOptionStyled = styled.div<{ - isVisible: boolean; -}>` - display: ${(props) => (props.isVisible ? "block" : "none")}; - min-width: 240px; - height: 100%; - padding: 32px 10px; - border-right: 1px solid var(--line-color); - font-weight: 300; - position: relative; +const LeftNavOptionStyled = styled.div` font-size: var(--size-base); -`; - -const ProfileLeftNavOptionStyled = styled.div<{ - isProfile: boolean; -}>` - display: ${(props) => (props.isProfile ? "block" : "none")}; + display: block; min-width: 240px; height: 100%; padding: 32px 10px; @@ -115,14 +69,6 @@ const ProfileLeftNavOptionStyled = styled.div<{ font-weight: 300; position: relative; font-size: var(--size-base); - & hr { - width: 80%; - height: 1px; - background-color: var(--service-man-title-border-btm-color); - border: 0; - margin-top: 20px; - margin-bottom: 20px; - } `; export const FloorSectionStyled = styled.div` @@ -134,6 +80,9 @@ export const FloorSectionStyled = styled.div` color: var(--gray-line-btn-color); margin: 2px 0; cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; @media (hover: hover) and (pointer: fine) { &:hover { background-color: var(--sys-main-color); @@ -142,32 +91,15 @@ export const FloorSectionStyled = styled.div` } `; -const SectionLinkStyled = styled.div` - width: 100%; - height: 40px; - line-height: 40px; - text-indent: 20px; - margin: 2px 0; - padding-right: 30px; - cursor: pointer; +const IconWrapperStyled = styled.div` + height: 14px; + width: 14px; + margin-right: 12px; display: flex; - align-items: center; - color: var(--gray-line-btn-color); - - #linknImg { - width: 15px; - height: 15px; - margin-left: auto; - } - @media (hover: hover) and (pointer: fine) { - &:hover { - color: var(--button-line-color); - - svg { - stroke: var(--button-line-color); - } - } + & > svg { + height: 14px; + width: 14px; } `; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftStoreNav/LeftStoreNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftStoreNav/LeftStoreNav.tsx new file mode 100644 index 000000000..15bfbcebb --- /dev/null +++ b/frontend/src/Cabinet/components/LeftNav/LeftStoreNav/LeftStoreNav.tsx @@ -0,0 +1,152 @@ +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import { useRecoilState } from "recoil"; +import styled from "styled-components"; +import { userState } from "@/Cabinet/recoil/atoms"; +import { ReactComponent as CoinIcon } from "@/Cabinet/assets/images/coinIcon.svg"; + +interface IStorePageItem { + name: string; + route: string; +} + +const storePages: IStorePageItem[] = [ + { name: "까비상점", route: "/store" }, + { name: "인벤토리", route: "/store/inventory" }, + { name: "아이템 사용내역", route: "/store/item-use-log" }, + { name: "코인 내역", route: "/store/coin-log" }, +]; + +const LeftStoreNav = ({ + onClickRedirectButton, +}: { + onClickRedirectButton: (location: string) => void; +}) => { + const location = useLocation(); + const [userInfo] = useRecoilState(userState); + + const getCurrentPageName = () => { + const matchingPage = storePages.find( + (page) => page.route === location.pathname + ); + return matchingPage ? matchingPage.name : storePages[0].name; + }; + + const [currentPage, setCurrentPage] = useState(getCurrentPageName()); + + useEffect(() => { + const handleRouteChange = () => { + setCurrentPage(getCurrentPageName()); + }; + + handleRouteChange(); + window.addEventListener("popstate", handleRouteChange); + + return () => { + window.removeEventListener("popstate", handleRouteChange); + }; + }, [location]); + + return ( + <> + + + 코인 + + + + + + {userInfo.coins} 까비 + + + +
+ {storePages.map((item: IStorePageItem) => ( + { + setCurrentPage(item.name); + onClickRedirectButton(item.route); + }} + > + {item.name} + + ))} +
+ + ); +}; + +const StoreLeftNavOptionStyled = styled.div` + min-width: 240px; + height: 100%; + padding: 32px 10px; + border-right: 1px solid var(--line-color); + font-weight: 300; + font-size: var(--size-base); + position: relative; + & hr { + width: 80%; + height: 1px; + background-color: var(--inventory-item-title-border-btm-color); + border: 0; + margin-top: 20px; + margin-bottom: 20px; + } +`; + +const CoinCountStyled = styled.div` + display: flex; + width: 100%; + padding: 0px 20px 0px 20px; + align-items: center; + justify-content: space-between; + color: var(--gray-line-btn-color); +`; + +const UserCoinsWrapperStyled = styled.div` + display: flex; + align-items: center; +`; + +const CoinTextStyled = styled.span` + margin-left: 5px; + & span { + font-weight: 800; + } +`; + +const StoreSectionStyled = styled.div` + width: 100%; + height: 40px; + line-height: 40px; + border-radius: 10px; + text-indent: 20px; + color: var(--gray-line-btn-color); + margin: 2px 0; + cursor: pointer; + &.leftNavButtonActive { + background-color: var(--sys-main-color); + color: var(--white-text-with-bg-color); + } + &:hover { + background-color: var(--sys-main-color); + color: var(--white-text-with-bg-color); + } +`; + +const CoinIconStyled = styled.div` + width: 20px; + height: 20px; + & > svg > path { + stroke: var(--sys-main-color); + } +`; + +export default LeftStoreNav; diff --git a/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx b/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx index ee0942a27..142b25c0f 100644 --- a/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx +++ b/frontend/src/Cabinet/components/LentLog/AdminLentLog.tsx @@ -70,7 +70,8 @@ const TitleContainer = styled.div` display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 30px; + margin-top: 15px; + margin-bottom: 25px; `; const TitleStyled = styled.h1<{ isClick: boolean }>` diff --git a/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx index 4faea564f..87f822758 100644 --- a/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx +++ b/frontend/src/Cabinet/components/LentLog/LogTable/LogTable.tsx @@ -61,7 +61,6 @@ const LogTableWrapperstyled = styled.div` max-width: 800px; border-radius: 10px; overflow: hidden; - margin: 60px 0 0 0; box-shadow: 0 0 10px 0 var(--table-border-shadow-color-100); `; diff --git a/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx b/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx index a0962fddc..38c54f415 100644 --- a/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx +++ b/frontend/src/Cabinet/components/Login/AdminLoginTemplate.tsx @@ -258,7 +258,7 @@ const CardInputStyled = styled.input<{ isFocus: boolean }>` border: ${(props) => props.isFocus ? "1px solid var(--sys-main-color)" - : "1px solid var(--toggle-switch-off-bg-color)"}; + : "1px solid var(--line-color)"}; color: var(--normal-text-color); `; diff --git a/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx b/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx index 804d812eb..deb52f4a2 100644 --- a/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapItem/MapItem.tsx @@ -56,7 +56,7 @@ const ItemStyled = styled.div<{ cursor: ${({ info }) => (info.type === "floorInfo" ? "default" : "pointer")}; color: ${({ info }) => info.type === "floorInfo" - ? "var(--toggle-switch-off-bg-color)" + ? "var(--line-color)" : "var(--white-text-with-bg-color)"}; display: flex; justify-content: center; @@ -72,7 +72,7 @@ const ItemStyled = styled.div<{ ? "var(--sys-main-color)" : info.type === "floorInfo" ? "transparent" - : "var(--toggle-switch-off-bg-color)"}; + : "var(--line-color)"}; &:hover { opacity: ${({ info }) => (info.type === "cabinet" ? 0.9 : 1)}; } diff --git a/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx b/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx index 838f8fb59..5690e0184 100644 --- a/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx +++ b/frontend/src/Cabinet/components/Modals/BanModal/BanModal.tsx @@ -1,24 +1,24 @@ +import React, { useState } from "react"; +import { useSetRecoilState } from "recoil"; import { - axiosDeleteCurrentBanLog, - axiosGetBannedUserList, -} from "@/Cabinet/api/axios/axios.custom"; -import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; + bannedUserListState, + isCurrentSectionRenderState, + numberOfAdminWorkState, + targetUserInfoState, +} from "@/Cabinet/recoil/atoms"; import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, SuccessResponseModal, } from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; -import { - bannedUserListState, - isCurrentSectionRenderState, - numberOfAdminWorkState, - targetUserInfoState, -} from "@/Cabinet/recoil/atoms"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { + axiosDeleteCurrentBanLog, + axiosGetBannedUserList, +} from "@/Cabinet/api/axios/axios.custom"; import { handleBannedUserList } from "@/Cabinet/utils/tableUtils"; -import React, { useState } from "react"; -import { useSetRecoilState } from "recoil"; const BanModal: React.FC<{ userId: number | null; @@ -40,7 +40,7 @@ const BanModal: React.FC<{ 해제하시겠습니까?`; const tryReturnRequest = async (e: React.MouseEvent) => { try { - // 패널티 해제 API 호출 + // 페널티 해제 API 호출 await axiosDeleteCurrentBanLog(props.userId); setIsCurrentSectionRender(true); setModalTitle("해제되었습니다"); diff --git a/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx index c65d3b549..0d46eba64 100644 --- a/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/AddClubMemberModal.tsx @@ -113,7 +113,7 @@ const ContentItemInputStyled = styled.input` cursor: "input"; color: "var(--normal-text-color)"; &::placeholder { - color: "var(--toggle-switch-off-bg-color)"; + color: "var(--line-color)"; } `; diff --git a/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx b/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx index 43faeca45..e6976a345 100644 --- a/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ClubModal/ClubModal.tsx @@ -282,7 +282,7 @@ const ContentItemInputStyled = styled.input` color: var(--normal-text-color); &::placeholder { - color: var(--toggle-switch-off-bg-color); + color: var(--line-color); } `; diff --git a/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx b/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx index 60ebafbe3..7e243fbfe 100644 --- a/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ExtendModal/ExtendModal.tsx @@ -1,15 +1,6 @@ -import { - axiosCabinetById, - axiosMyLentInfo, - axiosUseExtension, // axiosExtend, // TODO: 연장권 api 생성 후 연결해야 함 -} from "@/Cabinet/api/axios/axios.custom"; -import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; -import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; -import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; -import { - FailResponseModal, - SuccessResponseModal, -} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import React, { useEffect, useState } from "react"; +import { useRecoilState, useSetRecoilState } from "recoil"; +import styled from "styled-components"; import { currentCabinetIdState, isCurrentSectionRenderState, @@ -17,11 +8,30 @@ import { targetCabinetInfoState, userState, } from "@/Cabinet/recoil/atoms"; +import Dropdown from "@/Cabinet/components/Common/Dropdown"; +import Modal, { IModalContents } from "@/Cabinet/components/Modals/Modal"; +import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; +import { + FailResponseModal, + SuccessResponseModal, +} from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; +import { IInventoryInfo } from "@/Cabinet/components/Store/Inventory/Inventory"; +import { additionalModalType, modalPropsMap } from "@/Cabinet/assets/data/maps"; import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; import IconType from "@/Cabinet/types/enum/icon.type.enum"; +import { + axiosCabinetById, + axiosMyItems, + axiosMyLentInfo, + axiosUseItem, +} from "@/Cabinet/api/axios/axios.custom"; import { getExtendedDateString } from "@/Cabinet/utils/dateUtils"; -import React, { useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; + +const extensionPeriod = [ + { sku: "EXTENSION_3", period: "3일", day: 3 }, + { sku: "EXTENSION_15", period: "15일", day: 15 }, + { sku: "EXTENSION_31", period: "31일", day: 31 }, +]; const ExtendModal: React.FC<{ onClose: () => void; @@ -30,7 +40,10 @@ const ExtendModal: React.FC<{ const [showResponseModal, setShowResponseModal] = useState(false); const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false); const [modalTitle, setModalTitle] = useState(""); - const currentCabinetId = useRecoilValue(currentCabinetIdState); + const [modalContents, setModalContents] = useState(null); + const [extensionDate, setExtensionDate] = useState(3); + const [isOpen, setIsOpen] = useState(false); + const [currentCabinetId] = useRecoilState(currentCabinetIdState); const [myInfo, setMyInfo] = useRecoilState(userState); const [myLentInfo, setMyLentInfo] = useRecoilState(myCabinetInfoState); @@ -40,7 +53,9 @@ const ExtendModal: React.FC<{ ); const formattedExtendedDate = getExtendedDateString( myLentInfo.lents[0].expiredAt, - myInfo.lentExtensionResponseDto?.extensionPeriod + // myInfo.lentExtensionResponseDto?.extensionPeriod + extensionDate + //내가 선택한 옵션의 연장 기간을 number 로 넘겨주기 ); const extensionExpiredDate = getExtendedDateString( myInfo.lentExtensionResponseDto?.expiredAt, @@ -49,24 +64,123 @@ const ExtendModal: React.FC<{ const extendDetail = `사물함 연장권 사용 시, 대여 기간이 ${formattedExtendedDate} 23:59으로 연장됩니다. - 연장권 사용은 취소할 수 없습니다. - 연장권을 사용하시겠습니까?`; + 연장권 사용은 취소할 수 없습니다.`; const extendInfoDetail = `사물함을 대여하시면 연장권 사용이 가능합니다. 연장권은 ${extensionExpiredDate} 23:59 이후 만료됩니다.`; + const getModalTitle = (cabinetId: number | null) => { return cabinetId === null ? modalPropsMap[additionalModalType.MODAL_OWN_EXTENSION].title : modalPropsMap[additionalModalType.MODAL_USE_EXTENSION].title; }; + const getModalDetail = (cabinetId: number | null) => { return cabinetId === null ? extendInfoDetail : extendDetail; }; + const getModalProceedBtnText = (cabinetId: number | null) => { return cabinetId === null ? modalPropsMap[additionalModalType.MODAL_OWN_EXTENSION].confirmMessage : modalPropsMap[additionalModalType.MODAL_USE_EXTENSION].confirmMessage; }; - const tryExtendRequest = async (e: React.MouseEvent) => { + + // 연장권 보유 여부 확인하는 부분 + const [myItems, setMyItems] = useState(null); + const [selectedOption, setSelectedOption] = useState(0); + + const findMyExtension = (period: string) => { + return !myItems?.extensionItems.some((item) => item.itemDetails === period); + }; + + // 연장권이 하나라도 없다면 true + const checkExtension = () => { + return ( + findMyExtension("3일") && + findMyExtension("15일") && + findMyExtension("31일") + ); + }; + + const getDefault = () => { + if (!findMyExtension(extensionPeriod[0].period)) return 3; + if (!findMyExtension(extensionPeriod[1].period)) return 15; + if (!findMyExtension(extensionPeriod[2].period)) return 31; + else return 0; + }; + const getDefaultOption = (option: number) => { + if (option == 3) return 0; + else if (option == 15) return 1; + else return 2; + }; + + const getMyItems = async () => { + try { + const response = await axiosMyItems(); + setMyItems(response.data); + } catch (error: any) { + console.error("Error getting inventory:", error); + } + }; + + useEffect(() => { + getMyItems(); + }, []); + + useEffect(() => { + if (checkExtension() == true) { + setShowResponseModal(true); + setHasErrorOnResponse(true); + setModalContents( + `현재 연장권을 보유하고 있지 않습니다. +연장권은 까비 상점에서 구매하실 수 있습니다.` + ); + } else { + setShowResponseModal(false); + setHasErrorOnResponse(false); + } + setExtensionDate(getDefault()); + }, [myItems]); + + useEffect(() => { + setSelectedOption(getDefaultOption(extensionDate)); + }, [extensionDate]); + + + const handleDropdownChange = (option: number) => { + setSelectedOption(option); + setExtensionDate(extensionPeriod[option].day); + }; + + const extensionDropdownProps = { + options: [ + { + name: extensionPeriod[0].period, + value: 0, + isDisabled: findMyExtension(extensionPeriod[0].period), + }, + { + name: extensionPeriod[1].period, + value: 1, + isDisabled: findMyExtension(extensionPeriod[1].period), + }, + { + name: extensionPeriod[2].period, + value: 2, + isDisabled: findMyExtension(extensionPeriod[2].period), + }, + ], + defaultValue: findMyExtension(extensionPeriod[0].period) + ? findMyExtension(extensionPeriod[1].period) + ? extensionPeriod[2].period + : extensionPeriod[1].period + : extensionPeriod[0].period, + onChangeValue: handleDropdownChange, + isOpen: isOpen, + setIsOpen: setIsOpen, + }; + + const extensionItemUse = async (item: string) => { + // 아이템 사용 if (currentCabinetId === 0 || myInfo.cabinetId === null) { setHasErrorOnResponse(true); setModalTitle("현재 대여중인 사물함이 없습니다."); @@ -74,14 +188,17 @@ const ExtendModal: React.FC<{ return; } try { - await axiosUseExtension(); + await axiosUseItem(item, null, null, null, null); setMyInfo({ ...myInfo, cabinetId: currentCabinetId, lentExtensionResponseDto: null, }); setIsCurrentSectionRender(true); - setModalTitle("연장되었습니다"); + setModalTitle("연장권 사용완료"); + setModalContents( + `대여 기간이 ${formattedExtendedDate}으로 연장되었습니다.` + ); try { const { data } = await axiosCabinetById(currentCabinetId); setTargetCabinetInfo(data); @@ -96,14 +213,23 @@ const ExtendModal: React.FC<{ } } catch (error: any) { setHasErrorOnResponse(true); - error.response - ? setModalTitle(error.response.data.message) - : setModalTitle(error.data.message); + if (error.response.status === 400) { + setModalTitle("연장권 사용실패"); + setModalContents( + `현재 연장권을 보유하고 있지 않습니다. + 연장권은 까비 상점에서 구매하실 수 있습니다.` + ); + } else + error.response + ? setModalTitle(error.response.data.message) + : setModalTitle(error.data.message); } finally { setShowResponseModal(true); } }; + + const extendModalContents: IModalContents = { type: myInfo.cabinetId === null ? "penaltyBtn" : "hasProceedBtn", title: getModalTitle(myInfo.cabinetId), @@ -114,9 +240,20 @@ const ExtendModal: React.FC<{ ? async (e: React.MouseEvent) => { props.onClose(); } - : tryExtendRequest, + : async () => { + extensionItemUse(extensionPeriod[selectedOption].sku); + }, closeModal: props.onClose, iconType: IconType.CHECKICON, + renderAdditionalComponent: () => ( + <> + + 연장권 타입 + + + + + ), }; return ( @@ -126,11 +263,15 @@ const ExtendModal: React.FC<{ (hasErrorOnResponse ? ( ) : ( ))} @@ -138,4 +279,26 @@ const ExtendModal: React.FC<{ ); }; +const ModalContainerStyled = styled.div` + padding: 10px 20px 0 20px; +`; + +const ModalDropdownNameStyled = styled.div` + display: flex; + margin: 10px 10px 15px 5px; + font-size: 18px; +`; + +const ModalDetailStyled = styled.div` + width: 100%; + height: 100%; + margin-top: 30px; + > p { + margin: 10px; + > span { + font-weight: 600; + } + } +`; + export default ExtendModal; diff --git a/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx b/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx index 2acebafdd..494d11383 100644 --- a/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx +++ b/frontend/src/Cabinet/components/Modals/ManualModal/ManualModal.tsx @@ -2,11 +2,7 @@ import React from "react"; import { useState } from "react"; import styled, { keyframes } from "styled-components"; import { manualContentData } from "@/Cabinet/assets/data/ManualContent"; -import { ReactComponent as ClubIcon } from "@/Cabinet/assets/images/clubIcon.svg"; -import { ReactComponent as ExtensionIcon } from "@/Cabinet/assets/images/extension.svg"; import { ReactComponent as MoveBtnImg } from "@/Cabinet/assets/images/moveButton.svg"; -import { ReactComponent as PrivateIcon } from "@/Cabinet/assets/images/privateIcon.svg"; -import { ReactComponent as ShareIcon } from "@/Cabinet/assets/images/shareIcon.svg"; import ContentStatus from "@/Cabinet/types/enum/content.status.enum"; interface ModalProps { @@ -60,17 +56,8 @@ const ManualModal: React.FC = ({ {hasImage && ( - {contentStatus === ContentStatus.PRIVATE && ( - - )} - {contentStatus === ContentStatus.SHARE && ( - - )} - {contentStatus === ContentStatus.CLUB && ( - - )} - {contentStatus === ContentStatus.EXTENSION && ( - + {contentData.iconComponent && ( + )} {isCabinetType && ( @@ -339,10 +326,6 @@ const ContentImgStyled = styled.div<{ props.contentStatus === ContentStatus.EXTENSION ? "var(--normal-text-color)" : "var(--white-text-with-bg-color)"}; - transform: ${(props) => - props.contentStatus === ContentStatus.EXTENSION - ? "scale(1.4)" - : "scale(3.3)"}; } } `; diff --git a/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx index aaec64b5d..9d90cb626 100644 --- a/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx +++ b/frontend/src/Cabinet/components/Modals/MemoModal/MemoModal.tsx @@ -6,14 +6,14 @@ import CabinetType from "@/Cabinet/types/enum/cabinet.type.enum"; export interface MemoModalInterface { cabinetType: CabinetType; - cabinetTitle: string | null; + cabinetTitle: string; cabinetMemo: string; } interface MemoModalContainerInterface { memoModalObj: MemoModalInterface; onClose: React.MouseEventHandler; - onSave: (newTitle: string | null, newMemo: string) => void; + onSave: (newTitle: string, newMemo: string) => void; } const MAX_INPUT_LENGTH = 14; @@ -33,6 +33,7 @@ const MemoModal = ({ newTitle.current.select(); } }; + const handleClickSave = (e: React.MouseEvent) => { //사물함 제목, 사물함 비밀메모 update api 호출 // onClose(e); @@ -40,7 +41,7 @@ const MemoModal = ({ if (newTitle.current!.value) { onSave(newTitle.current!.value, newMemo.current!.value); } else { - onSave(null, newMemo.current!.value); + onSave("", newMemo.current!.value); } setMode("read"); }; @@ -100,12 +101,12 @@ const MemoModal = ({ ? onClose : () => { setMode("read"); - if (cabinetTitle) newTitle.current!.value = cabinetTitle; + newTitle.current!.value = cabinetTitle; newMemo.current!.value = cabinetMemo; } } text={mode === "read" ? "닫기" : "취소"} - theme={mode === "read" ? "light-grayLine" : "line"} + theme={mode === "read" ? "grayLine" : "line"} /> @@ -183,9 +184,7 @@ const ContentItemInputStyled = styled.input<{ mode === "read" ? "var(--sys-main-color)" : "var(--normal-text-color)"}; &::placeholder { color: ${({ mode }) => - mode === "read" - ? "var(--sys-main-color)" - : "var(--toggle-switch-off-bg-color)"}; + mode === "read" ? "var(--sys-main-color)" : "var(--line-color)"}; } `; diff --git a/frontend/src/Cabinet/components/Modals/Modal.tsx b/frontend/src/Cabinet/components/Modals/Modal.tsx index a2256af07..a854f03a8 100644 --- a/frontend/src/Cabinet/components/Modals/Modal.tsx +++ b/frontend/src/Cabinet/components/Modals/Modal.tsx @@ -1,11 +1,11 @@ import React, { ReactElement } from "react"; +import { useNavigate } from "react-router-dom"; import styled, { css } from "styled-components"; import AdminClubLogContainer from "@/Cabinet/components/Club/AdminClubLog.container"; import Button from "@/Cabinet/components/Common/Button"; import { ReactComponent as CheckIcon } from "@/Cabinet/assets/images/checkIcon.svg"; import { ReactComponent as ErrorIcon } from "@/Cabinet/assets/images/errorIcon.svg"; import { ReactComponent as NotificationIcon } from "@/Cabinet/assets/images/notificationSign.svg"; -import IconType from "@/Cabinet/types/enum/icon.type.enum"; import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; /** @@ -24,6 +24,8 @@ import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; * @property {boolean} isClubLentModal : 동아리 (CLUB) 대여 모달인지 여부 * @property {boolean} isLoading : 로딩중 요청 버튼 비활성화 감지를 위한 변수 * @property {boolean} isCheckIcon : checkIcon인지 errorIcon인지 감지를 위한 변수 + * @property {string} urlTitle : 모달에서 링크로 이동할 url의 제목 + * @property {string} url : 모달에서 링크로 이동할 url 값 */ export interface IModalContents { type: string; @@ -38,6 +40,8 @@ export interface IModalContents { isClubLentModal?: boolean; isLoading?: boolean; iconType?: string; + urlTitle?: string | null; + url?: string | null; } const Modal: React.FC<{ modalContents: IModalContents }> = (props) => { @@ -54,8 +58,11 @@ const Modal: React.FC<{ modalContents: IModalContents }> = (props) => { isClubLentModal, isLoading, iconType, + urlTitle, + url, } = props.modalContents; const { isMultiSelect, closeMultiSelectMode } = useMultiSelect(); + const navigator = useNavigate(); return ( <> @@ -91,11 +98,6 @@ const Modal: React.FC<{ modalContents: IModalContents }> = (props) => { {renderAdditionalComponent && renderAdditionalComponent()} {type === "hasProceedBtn" && ( -