From 3fa1a94a90df95a6059108217e71f65d2993bd3e Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 13 Feb 2026 15:33:29 +0900 Subject: [PATCH 01/48] =?UTF-8?q?feat:=20=EC=A0=95=EC=A0=81=20=EB=A6=AC?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EC=B6=94=EA=B0=80(=EC=8B=AC=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/script.js | 67 ++++++++++++++++++++ src/main/resources/static/styles.css | 80 ++++++++++++++++++++++++ src/main/resources/static/user-list.html | 18 ++++++ 3 files changed, 165 insertions(+) create mode 100644 src/main/resources/static/script.js create mode 100644 src/main/resources/static/styles.css create mode 100644 src/main/resources/static/user-list.html diff --git a/src/main/resources/static/script.js b/src/main/resources/static/script.js new file mode 100644 index 000000000..e63118b89 --- /dev/null +++ b/src/main/resources/static/script.js @@ -0,0 +1,67 @@ +// API endpoints +const API_BASE_URL = '/api'; +const ENDPOINTS = { + USERS: `${API_BASE_URL}/user/findAll`, + BINARY_CONTENT: `${API_BASE_URL}/binaryContent/find` +}; + +// Initialize the application +document.addEventListener('DOMContentLoaded', () => { + fetchAndRenderUsers(); +}); + +// Fetch users from the API +async function fetchAndRenderUsers() { + try { + const response = await fetch(ENDPOINTS.USERS); + if (!response.ok) throw new Error('Failed to fetch users'); + const users = await response.json(); + renderUserList(users); + } catch (error) { + console.error('Error fetching users:', error); + } +} + +// Fetch user profile image +async function fetchUserProfile(profileId) { + try { + const response = await fetch(`${ENDPOINTS.BINARY_CONTENT}?binaryContentId=${profileId}`); + if (!response.ok) throw new Error('Failed to fetch profile'); + const profile = await response.json(); + + // Convert base64 encoded bytes to data URL + return `data:${profile.contentType};base64,${profile.bytes}`; + } catch (error) { + console.error('Error fetching profile:', error); + return '/default-avatar.png'; // Fallback to default avatar + } +} + +// Render user list +async function renderUserList(users) { + const userListElement = document.getElementById('userList'); + userListElement.innerHTML = ''; // Clear existing content + + for (const user of users) { + const userElement = document.createElement('div'); + userElement.className = 'user-item'; + + // Get profile image URL + const profileUrl = user.profileId ? + await fetchUserProfile(user.profileId) : + '/default-avatar.png'; + + userElement.innerHTML = ` + ${user.username} +
+
${user.username}
+
${user.email}
+
+
+ ${user.online ? '온라인' : '오프라인'} +
+ `; + + userListElement.appendChild(userElement); + } +} \ No newline at end of file diff --git a/src/main/resources/static/styles.css b/src/main/resources/static/styles.css new file mode 100644 index 000000000..b45f4e704 --- /dev/null +++ b/src/main/resources/static/styles.css @@ -0,0 +1,80 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; +} + +.container { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +h1 { + text-align: center; + margin-bottom: 30px; + color: #333; +} + +.user-list { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.user-item { + display: flex; + align-items: center; + padding: 20px; + border-bottom: 1px solid #eee; +} + +.user-item:last-child { + border-bottom: none; +} + +.user-avatar { + width: 60px; + height: 60px; + border-radius: 50%; + margin-right: 20px; + object-fit: cover; +} + +.user-info { + flex-grow: 1; +} + +.user-name { + font-size: 18px; + font-weight: bold; + color: #333; + margin-bottom: 5px; +} + +.user-email { + font-size: 14px; + color: #666; +} + +.status-badge { + padding: 6px 12px; + border-radius: 20px; + font-size: 14px; + font-weight: bold; +} + +.online { + background-color: #4CAF50; + color: white; +} + +.offline { + background-color: #9e9e9e; + color: white; +} \ No newline at end of file diff --git a/src/main/resources/static/user-list.html b/src/main/resources/static/user-list.html new file mode 100644 index 000000000..f3acfdb59 --- /dev/null +++ b/src/main/resources/static/user-list.html @@ -0,0 +1,18 @@ + + + + + + 사용자 목록 + + + +
+

사용자 목록

+
+ +
+
+ + + \ No newline at end of file From c1ffd5c65724e593db3532a3055900e4933a5b42 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 20 Feb 2026 08:49:59 +0900 Subject: [PATCH 02/48] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A5=BC=20MultipartFile=EB=A1=9C?= =?UTF-8?q?=20=EB=B0=9B=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20&=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/BinaryContentCreateInfo.java | 2 + .../binarycontent/dto/BinaryContentInfo.java | 2 + .../binarycontent/entity/BinaryContent.java | 14 ++++--- .../mapper/BinaryContentMapper.java | 6 ++- .../service/BinaryContentService.java | 4 +- .../message/service/BasicMessageService.java | 2 +- .../user/controller/UserController.java | 40 ++++++++++++++++--- .../discodeit/user/dto/UserCreateInfo.java | 3 +- .../discodeit/user/dto/UserUpdateInfo.java | 3 +- .../user/service/BasicUserService.java | 21 +++++----- .../discodeit/user/service/UserService.java | 6 ++- 11 files changed, 74 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java index d3a173586..fc060a972 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java @@ -1,5 +1,7 @@ package com.sprint.mission.discodeit.binarycontent.dto; public record BinaryContentCreateInfo( + String fileName, + String contentType, byte[] content ) {} diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java index 74321e83a..a64b6bdb2 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java @@ -6,5 +6,7 @@ public record BinaryContentInfo( UUID contentId, Instant createdAt, + String fileName, + String contentType, byte[] content ) {} diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java index e89b0f1e4..20fccf01c 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java @@ -12,16 +12,20 @@ public class BinaryContent implements Serializable { private static final long serialVersionUID = 1L; private final UUID id; private final Instant createdAt; - private final byte[] content; + private final String fileName; + private final String contentType; + private final byte[] bytes; - public BinaryContent(byte[] content) { + public BinaryContent(String fileName, String contentType, byte[] bytes) { this.id = UUID.randomUUID(); this.createdAt = Instant.now(); - this.content = content; + this.fileName = fileName; + this.contentType = contentType; + this.bytes = bytes; } - public byte[] getContent() { - return Arrays.copyOf(content, content.length); + public byte[] getBytes() { + return Arrays.copyOf(bytes, bytes.length); } @Override diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java index a22676c98..e537b177c 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java @@ -10,12 +10,16 @@ public static BinaryContentInfo toBinaryContentInfo(BinaryContent binaryContent) return new BinaryContentInfo( binaryContent.getId(), binaryContent.getCreatedAt(), - binaryContent.getContent() + binaryContent.getFileName(), + binaryContent.getContentType(), + binaryContent.getBytes() ); } public static BinaryContent toBinaryContent(BinaryContentInfo binaryContentInfo) { return new BinaryContent( + binaryContentInfo.fileName(), + binaryContentInfo.contentType(), binaryContentInfo.content() ); } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index 989389ad4..467bef2bc 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -9,7 +9,9 @@ import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.List; import java.util.UUID; @@ -19,7 +21,7 @@ public class BinaryContentService { private final BinaryContentRepository contentRepository; public BinaryContentInfo createBinaryContent(BinaryContentCreateInfo contentInfo) { - BinaryContent content = new BinaryContent(contentInfo.content()); + BinaryContent content = new BinaryContent(contentInfo.fileName(), contentInfo.contentType(), contentInfo.content()); contentRepository.save(content); return BinaryContentMapper.toBinaryContentInfo(content); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index c5019d171..fc35ce93c 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -34,7 +34,7 @@ public class BasicMessageService implements MessageService { public MessageInfo createMessage(MessageCreateInfo createInfo) { List attachmentIds = createInfo.attachments() .stream() - .map(BinaryContent::new) + .map(bytes -> new BinaryContent("", "", bytes)) .peek(contentRepository::save) .map(BinaryContent::getId) .toList(); diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index c4416956d..017eb0da6 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -1,6 +1,8 @@ package com.sprint.mission.discodeit.user.controller; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; +import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; import com.sprint.mission.discodeit.user.dto.UserCreateInfo; import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserInfoWithStatus; @@ -9,10 +11,14 @@ import com.sprint.mission.discodeit.userstatus.dto.UserStatusInfo; import com.sprint.mission.discodeit.userstatus.service.UserStatusService; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.UUID; @RequiredArgsConstructor @@ -32,9 +38,12 @@ public ResponseEntity> getAllUsers() { return ResponseEntity.ok(userService.findAll()); } - @RequestMapping(method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createUser(@RequestBody UserCreateInfo createInfo) { - return ResponseEntity.ok(userService.createUser(createInfo)); + @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity createUser( + @RequestPart UserCreateInfo createInfo, + @RequestPart MultipartFile image + ) { + return ResponseEntity.ok(userService.createUser(createInfo, resolveProfileFile(image))); } @RequestMapping(value = "/{userId}", method = RequestMethod.DELETE) @@ -43,12 +52,14 @@ public ResponseEntity deleteUser(@PathVariable UUID userId) { return ResponseEntity.noContent().build(); } - @RequestMapping(value = "/{userId}", method = RequestMethod.PATCH) + @RequestMapping(value = "/{userId}", method = RequestMethod.PATCH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity updateUser( @PathVariable UUID userId, - @RequestBody UserUpdateInfo updateInfo + @RequestPart UserUpdateInfo updateInfo, + @RequestPart MultipartFile image + ) { - userService.updateUser(userId, updateInfo); + userService.updateUser(userId, updateInfo, resolveProfileFile(image)); return ResponseEntity.noContent().build(); } @@ -61,4 +72,21 @@ public ResponseEntity updateUserStatusByUserId(@PathVariable UUI public ResponseEntity getUserStatus(@PathVariable UUID statusId) { return ResponseEntity.ok(userStatusService.findUserStatus(statusId)); } + + private Optional resolveProfileFile(MultipartFile profileFile) { + if (profileFile.isEmpty()) { + return Optional.empty(); + } else { + try { + BinaryContentCreateInfo contentInfo = new BinaryContentCreateInfo( + profileFile.getOriginalFilename(), + profileFile.getContentType(), + profileFile.getBytes() + ); + return Optional.of(contentInfo); + } catch (IOException e) { + throw new BinaryContentNotFoundException(); + } + } + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java index c47e191a2..4f76a5927 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java @@ -3,6 +3,5 @@ public record UserCreateInfo( String userName, String password, - String email, - byte[] profileImage + String email ) {} diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java index b4aa85b58..7e84a1dd2 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java @@ -6,6 +6,5 @@ public record UserUpdateInfo( String userName, String password, String email, - UUID profileId, - byte[] profileImage + UUID profileId ) {} diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index a46ca1c8c..a8c3950d1 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -1,5 +1,6 @@ package com.sprint.mission.discodeit.user.service; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.user.dto.*; import com.sprint.mission.discodeit.user.entity.User; @@ -16,6 +17,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -29,7 +31,7 @@ public class BasicUserService implements UserService { private final UserStatusRepository userStatusRepository; @Override - public UserInfo createUser(UserCreateInfo userInfo) { + public UserInfo createUser(UserCreateInfo userInfo, Optional image) { // 유저 이름 & 이메일 검증 validateUserExist(userInfo.userName()); validateEmailExist(userInfo.email()); @@ -41,9 +43,9 @@ public UserInfo createUser(UserCreateInfo userInfo) { UserStatus status = new UserStatus(user.getId()); // profile image가 존재한다면 생성 - BinaryContent profileImage = null; - if(userInfo.profileImage() != null) { - profileImage = new BinaryContent(userInfo.profileImage()); + if(image.isPresent()) { + BinaryContentCreateInfo createInfo = image.get(); + BinaryContent profileImage = new BinaryContent(createInfo.fileName(), createInfo.contentType(), createInfo.content()); user.setProfileId(profileImage.getId()); contentRepository.save(profileImage); } @@ -100,7 +102,7 @@ public List findAllByChannelId(UUID channelId) { } @Override - public UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo) { + public UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, Optional image) { validateUserExist(updateInfo.userName()); validateEmailExist(updateInfo.email()); User findUser = userRepository.findById(userId) @@ -113,12 +115,13 @@ public UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo) { .ifPresent(findUser::updateEmail); // profileId가 존재하면 업데이트 - if(updateInfo.profileId() != null) { + if(image.isPresent()) { if(findUser.isProfileImageUploaded()) contentRepository.deleteById(findUser.getProfileId()); - BinaryContent newContent = new BinaryContent(updateInfo.profileImage()); - findUser.setProfileId(newContent.getId()); - contentRepository.save(newContent); + BinaryContentCreateInfo createInfo = image.get(); + BinaryContent profileImage = new BinaryContent(createInfo.fileName(), createInfo.contentType(), createInfo.content()); + findUser.setProfileId(profileImage.getId()); + contentRepository.save(profileImage); } // statusRepo.findByUserId로 찾기 diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java index fa1ca4144..a9c1c8ce7 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java @@ -1,16 +1,18 @@ package com.sprint.mission.discodeit.user.service; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; import com.sprint.mission.discodeit.user.dto.*; import java.util.List; +import java.util.Optional; import java.util.UUID; public interface UserService { - UserInfo createUser(UserCreateInfo userInfo); + UserInfo createUser(UserCreateInfo userInfo, Optional image); UserInfoWithStatus findUser(UUID userId); List findAll(); List findAllWithUserDTO(); List findAllByChannelId(UUID channelId); - UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo); + UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, Optional image); void deleteUser(UUID userId); } From 7b4a190cc0f58388b4f6884d8fd88a83c261dc9a Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 20 Feb 2026 09:36:16 +0900 Subject: [PATCH 03/48] =?UTF-8?q?feat:=20/api=20context=20path=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 --- src/main/resources/application.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 778a7b778..2df4cedcd 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,6 +1,9 @@ spring: application: name: discodeit +server: + servlet: + context-path: /api discodeit: repository: type: jcf #jcf | file From 4722a11ca359a601d49aa5f458b4b2d640ea3bee Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 20 Feb 2026 10:36:38 +0900 Subject: [PATCH 04/48] =?UTF-8?q?revert:=20api=20context=20path=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 2df4cedcd..778a7b778 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,9 +1,6 @@ spring: application: name: discodeit -server: - servlet: - context-path: /api discodeit: repository: type: jcf #jcf | file From 579151b502fe1e0f6699a11691a8c325a248c146 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 20 Feb 2026 10:38:59 +0900 Subject: [PATCH 05/48] =?UTF-8?q?refactor:=20=20Google=20Java=20Style=20Gu?= =?UTF-8?q?ide=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/PULL_REQUEST_TEMPLATE.md | 8 +- build.gradle | 32 +- .../discodeit/DiscodeitApplication.java | 6 +- .../ApiBinaryContentController.java | 14 +- .../controller/BinaryContentController.java | 34 ++- .../dto/BinaryContentCreateInfo.java | 10 +- .../binarycontent/dto/BinaryContentInfo.java | 14 +- .../dto/BinaryContentsRequest.java | 6 +- .../binarycontent/entity/BinaryContent.java | 62 ++-- .../BinaryContentNotFoundException.java | 7 +- .../mapper/BinaryContentMapper.java | 36 +-- .../repository/BinaryContentRepository.java | 13 +- .../FileBinaryContentRepository.java | 145 ++++----- .../JCFBinaryContentRepository.java | 49 +-- .../service/BinaryContentService.java | 87 +++--- .../channel/controller/ChannelController.java | 100 ++++--- .../discodeit/channel/dto/ChannelInfo.java | 17 +- .../channel/dto/FindChannelInfo.java | 6 +- .../channel/dto/PrivateChannelCreateInfo.java | 6 +- .../channel/dto/PrivateChannelInfo.java | 9 +- .../channel/dto/PublicChannelCreateInfo.java | 8 +- .../channel/dto/PublicChannelInfo.java | 13 +- .../discodeit/channel/entity/Channel.java | 94 +++--- .../exception/AlreadyJoinedException.java | 7 +- .../ChannelDuplicationException.java | 7 +- .../exception/ChannelNotFoundException.java | 7 +- .../ChannelUpdateNotAllowedException.java | 7 +- .../channel/mapper/ChannelMapper.java | 116 ++++---- .../channel/repository/ChannelRepository.java | 19 +- .../repository/FileChannelRepository.java | 177 +++++------ .../repository/JCFChannelRepository.java | 89 +++--- .../channel/service/BasicChannelService.java | 281 +++++++++--------- .../channel/service/ChannelService.java | 34 ++- .../mission/discodeit/common/ChannelType.java | 30 +- .../discodeit/common/CommonEntity.java | 48 +-- .../exception/BusinessException.java | 8 +- .../discodeit/exception/ErrorResponse.java | 5 +- .../exception/GlobalExceptionHandler.java | 192 ++++++------ .../controller/ChannelMessageController.java | 16 +- .../message/controller/MessageController.java | 74 ++--- .../message/dto/MessageCreateInfo.java | 12 +- .../discodeit/message/dto/MessageInfo.java | 14 +- .../message/dto/MessageUpdateInfo.java | 6 +- .../discodeit/message/entity/Message.java | 62 ++-- .../exception/MessageNotFoundException.java | 7 +- .../message/mapper/MessageMapper.java | 63 ++-- .../repository/FileMessageRepository.java | 174 +++++------ .../repository/JCFMessageRepository.java | 81 ++--- .../message/repository/MessageRepository.java | 19 +- .../message/service/BasicMessageService.java | 198 ++++++------ .../message/service/MessageService.java | 22 +- .../controller/ReadStatusController.java | 69 +++-- .../readstatus/dto/ReadStatusCreateInfo.java | 5 +- .../readstatus/dto/ReadStatusInfo.java | 9 +- .../readstatus/dto/ReadStatusUpdateInfo.java | 5 +- .../readstatus/entity/ReadStatus.java | 28 +- .../ReadStatusDuplicationException.java | 7 +- .../ReadStatusNotFoundException.java | 7 +- .../readstatus/mapper/ReadStatusMapper.java | 44 +-- .../repository/FileReadStatusRepository.java | 192 ++++++------ .../repository/JCFReadStatusRepository.java | 95 +++--- .../repository/ReadStatusRepository.java | 22 +- .../readstatus/service/ReadStatusService.java | 97 +++--- .../user/controller/ApiUserController.java | 14 +- .../user/controller/AuthController.java | 11 +- .../user/controller/UserController.java | 124 ++++---- .../discodeit/user/dto/UserCreateInfo.java | 10 +- .../mission/discodeit/user/dto/UserDto.java | 15 +- .../mission/discodeit/user/dto/UserInfo.java | 14 +- .../user/dto/UserInfoWithStatus.java | 16 +- .../discodeit/user/dto/UserLoginInfo.java | 8 +- .../discodeit/user/dto/UserUpdateInfo.java | 12 +- .../mission/discodeit/user/entity/User.java | 106 +++---- .../AuthenticationFailedException.java | 6 +- .../exception/EmailDuplicationException.java | 7 +- .../exception/UserDuplicationException.java | 7 +- .../user/exception/UserNotFoundException.java | 7 +- .../discodeit/user/mapper/UserMapper.java | 85 +++--- .../user/repository/FileUserRepository.java | 192 ++++++------ .../user/repository/JCFUserRepository.java | 89 +++--- .../user/repository/UserRepository.java | 22 +- .../discodeit/user/service/AuthService.java | 32 +- .../user/service/BasicUserService.java | 278 ++++++++--------- .../discodeit/user/service/UserService.java | 29 +- .../userstatus/dto/UserStatusCreateInfo.java | 6 +- .../userstatus/dto/UserStatusInfo.java | 13 +- .../userstatus/dto/UserStatusUpdateInfo.java | 7 +- .../userstatus/entity/UserStatus.java | 32 +- .../UserStatusDuplicationException.java | 7 +- .../UserStatusNotFoundException.java | 7 +- .../userstatus/mapper/UserStatusMapper.java | 20 +- .../repository/FileUserStatusRepository.java | 167 ++++++----- .../repository/JCFUserStatusRepository.java | 75 ++--- .../repository/UserStatusRepository.java | 19 +- .../userstatus/service/UserStatusService.java | 88 +++--- src/main/resources/static/script.js | 69 +++-- src/main/resources/static/styles.css | 78 ++--- src/main/resources/static/user-list.html | 16 +- .../discodeit/DiscodeitApplicationTests.java | 6 +- 99 files changed, 2530 insertions(+), 2295 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ec85f6f1a..edf6bcee7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,21 +1,25 @@ - ## 요구사항 ### 기본 + - [x] 기본 항목 1 - [ ] 기본 항목 2 ### 심화 + - [ ] 심화 항목 1 - [ ] 심화 항목 2 ## 주요 변경사항 - -- + +- ## 스크린샷 + ![image](이미지url) ## 멘토에게 + - 셀프 코드 리뷰를 통해 질문 이어가겠습니다. - diff --git a/build.gradle b/build.gradle index 188838852..1ce293ae3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { - id 'java' - id 'org.springframework.boot' version '4.0.2' - id 'io.spring.dependency-management' version '1.1.7' + id 'java' + id 'org.springframework.boot' version '4.0.2' + id 'io.spring.dependency-management' version '1.1.7' } group = 'com.sprint.mission' @@ -9,29 +9,29 @@ version = '0.0.1-SNAPSHOT' description = 'Sprint Mission 3' java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-webmvc' - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-webmvc' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java b/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java index 039e1d900..8f61230d4 100644 --- a/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java +++ b/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java @@ -6,7 +6,7 @@ @SpringBootApplication public class DiscodeitApplication { - public static void main(String[] args) { - SpringApplication.run(DiscodeitApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(DiscodeitApplication.class, args); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/ApiBinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/ApiBinaryContentController.java index 39e6e7001..a92d09792 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/ApiBinaryContentController.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/ApiBinaryContentController.java @@ -2,6 +2,7 @@ import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.service.BinaryContentService; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; @@ -9,16 +10,15 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.UUID; - @RequiredArgsConstructor @RestController @RequestMapping("/api/binaryContent") public class ApiBinaryContentController { - private final BinaryContentService binaryContentService; - @RequestMapping(value = "/find", method = RequestMethod.GET) - public ResponseEntity findBinaryContent(@RequestParam UUID binaryContentId) { - return ResponseEntity.ok(binaryContentService.findBinaryContentEntity(binaryContentId)); - } + private final BinaryContentService binaryContentService; + + @RequestMapping(value = "/find", method = RequestMethod.GET) + public ResponseEntity findBinaryContent(@RequestParam UUID binaryContentId) { + return ResponseEntity.ok(binaryContentService.findBinaryContentEntity(binaryContentId)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java index d357afb58..57dce5c96 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java @@ -3,28 +3,32 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentInfo; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentsRequest; import com.sprint.mission.discodeit.binarycontent.service.BinaryContentService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RequestMethod; +import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController @RequestMapping("/binarycontents") public class BinaryContentController { - private final BinaryContentService binaryContentService; - @RequestMapping(value = "/{contentId}", method = RequestMethod.GET) - public ResponseEntity getBinaryContent(@PathVariable UUID contentId) { - return ResponseEntity.ok(binaryContentService.findBinaryContent(contentId)); - } + private final BinaryContentService binaryContentService; + + @RequestMapping(value = "/{contentId}", method = RequestMethod.GET) + public ResponseEntity getBinaryContent(@PathVariable UUID contentId) { + return ResponseEntity.ok(binaryContentService.findBinaryContent(contentId)); + } - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getBinaryContents( - @RequestBody BinaryContentsRequest request - ) { - return ResponseEntity.ok(binaryContentService.findAllByIdIn(request)); - } + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getBinaryContents( + @RequestBody BinaryContentsRequest request + ) { + return ResponseEntity.ok(binaryContentService.findAllByIdIn(request)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java index fc060a972..41a9b77c0 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java @@ -1,7 +1,9 @@ package com.sprint.mission.discodeit.binarycontent.dto; public record BinaryContentCreateInfo( - String fileName, - String contentType, - byte[] content -) {} + String fileName, + String contentType, + byte[] content +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java index a64b6bdb2..c0e8108cf 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java @@ -4,9 +4,11 @@ import java.util.UUID; public record BinaryContentInfo( - UUID contentId, - Instant createdAt, - String fileName, - String contentType, - byte[] content -) {} + UUID contentId, + Instant createdAt, + String fileName, + String contentType, + byte[] content +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentsRequest.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentsRequest.java index c68f497d8..d5648a5c6 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentsRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentsRequest.java @@ -4,5 +4,7 @@ import java.util.UUID; public record BinaryContentsRequest( - List contentIds -) {} + List contentIds +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java index 20fccf01c..08afdc235 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java @@ -1,43 +1,49 @@ package com.sprint.mission.discodeit.binarycontent.entity; -import lombok.Getter; - import java.io.Serializable; import java.time.Instant; import java.util.Arrays; import java.util.UUID; +import lombok.Getter; @Getter public class BinaryContent implements Serializable { - private static final long serialVersionUID = 1L; - private final UUID id; - private final Instant createdAt; - private final String fileName; - private final String contentType; - private final byte[] bytes; - public BinaryContent(String fileName, String contentType, byte[] bytes) { - this.id = UUID.randomUUID(); - this.createdAt = Instant.now(); - this.fileName = fileName; - this.contentType = contentType; - this.bytes = bytes; - } + private static final long serialVersionUID = 1L; + private final UUID id; + private final Instant createdAt; + private final String fileName; + private final String contentType; + private final byte[] bytes; - public byte[] getBytes() { - return Arrays.copyOf(bytes, bytes.length); - } + public BinaryContent(String fileName, String contentType, byte[] bytes) { + this.id = UUID.randomUUID(); + this.createdAt = Instant.now(); + this.fileName = fileName; + this.contentType = contentType; + this.bytes = bytes; + } - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - return id.equals(((BinaryContent) obj).id); - } + public byte[] getBytes() { + return Arrays.copyOf(bytes, bytes.length); + } - @Override - public int hashCode() { - return id.hashCode(); + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; } + if (getClass() != obj.getClass()) { + return false; + } + return id.equals(((BinaryContent) obj).id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/exception/BinaryContentNotFoundException.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/exception/BinaryContentNotFoundException.java index 6cc8c8da6..c5d734dc4 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/exception/BinaryContentNotFoundException.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/exception/BinaryContentNotFoundException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class BinaryContentNotFoundException extends BusinessException { - public BinaryContentNotFoundException() { - super("해당 콘텐츠를 찾을 수 없습니다."); - } + + public BinaryContentNotFoundException() { + super("해당 콘텐츠를 찾을 수 없습니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java index e537b177c..183086d34 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java @@ -4,23 +4,25 @@ import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; public class BinaryContentMapper { - private BinaryContentMapper(){} - public static BinaryContentInfo toBinaryContentInfo(BinaryContent binaryContent) { - return new BinaryContentInfo( - binaryContent.getId(), - binaryContent.getCreatedAt(), - binaryContent.getFileName(), - binaryContent.getContentType(), - binaryContent.getBytes() - ); - } + private BinaryContentMapper() { + } - public static BinaryContent toBinaryContent(BinaryContentInfo binaryContentInfo) { - return new BinaryContent( - binaryContentInfo.fileName(), - binaryContentInfo.contentType(), - binaryContentInfo.content() - ); - } + public static BinaryContentInfo toBinaryContentInfo(BinaryContent binaryContent) { + return new BinaryContentInfo( + binaryContent.getId(), + binaryContent.getCreatedAt(), + binaryContent.getFileName(), + binaryContent.getContentType(), + binaryContent.getBytes() + ); + } + + public static BinaryContent toBinaryContent(BinaryContentInfo binaryContentInfo) { + return new BinaryContent( + binaryContentInfo.fileName(), + binaryContentInfo.contentType(), + binaryContentInfo.content() + ); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/BinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/BinaryContentRepository.java index 1ea48b687..d1021f946 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/BinaryContentRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/BinaryContentRepository.java @@ -1,14 +1,17 @@ package com.sprint.mission.discodeit.binarycontent.repository; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; - import java.util.List; import java.util.Optional; import java.util.UUID; public interface BinaryContentRepository { - Optional findById(UUID id); - List findAll(); - void save(BinaryContent binaryContent); - void deleteById(UUID id); + + Optional findById(UUID id); + + List findAll(); + + void save(BinaryContent binaryContent); + + void deleteById(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/FileBinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/FileBinaryContentRepository.java index 0dfe94e81..f42bd81c1 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/FileBinaryContentRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/FileBinaryContentRepository.java @@ -1,99 +1,104 @@ package com.sprint.mission.discodeit.binarycontent.repository; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -import java.io.*; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") public class FileBinaryContentRepository implements BinaryContentRepository { - private final Path binaryContentPath; - public FileBinaryContentRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.binaryContentPath = Paths.get(rootPath, "binary-contents"); - } + private final Path binaryContentPath; - @Override - public Optional findById(UUID id) { - Path binaryContentPath = getBinaryContentPath(id); - return Optional.ofNullable(read(binaryContentPath)); - } + public FileBinaryContentRepository( + @Value("${discodeit.repository.file-directory:data}") String rootPath + ) { + this.binaryContentPath = Paths.get(rootPath, "binary-contents"); + } - @Override - public List findAll() { - if(Files.exists(binaryContentPath)) { - try { - return Files.list(binaryContentPath) - .map(this::read) - .toList(); - } catch (IOException e) { - throw new RuntimeException("BinaryContent 파일 목록을 불러오는데 실패했습니다."); - } - } else { - return List.of(); - } - } + @Override + public Optional findById(UUID id) { + Path binaryContentPath = getBinaryContentPath(id); + return Optional.ofNullable(read(binaryContentPath)); + } - @Override - public void save(BinaryContent binaryContent) { - Path binaryContentPath = getBinaryContentPath(binaryContent.getId()); - write(binaryContent, binaryContentPath); + @Override + public List findAll() { + if (Files.exists(binaryContentPath)) { + try { + return Files.list(binaryContentPath) + .map(this::read) + .toList(); + } catch (IOException e) { + throw new RuntimeException("BinaryContent 파일 목록을 불러오는데 실패했습니다."); + } + } else { + return List.of(); } + } - @Override - public void deleteById(UUID id) { - Path binaryContentPath = getBinaryContentPath(id); - try { - Files.delete(binaryContentPath); - } catch (IOException e) { - throw new RuntimeException("BinaryContent 파일을 삭제하는데 실패했습니다."); - } - } + @Override + public void save(BinaryContent binaryContent) { + Path binaryContentPath = getBinaryContentPath(binaryContent.getId()); + write(binaryContent, binaryContentPath); + } - private BinaryContent read(Path path) { - if (!Files.exists(path)) - throw new IllegalStateException("해당 파일이 존재하지 않습니다."); + @Override + public void deleteById(UUID id) { + Path binaryContentPath = getBinaryContentPath(id); + try { + Files.delete(binaryContentPath); + } catch (IOException e) { + throw new RuntimeException("BinaryContent 파일을 삭제하는데 실패했습니다."); + } + } - try (FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return (BinaryContent) ois.readObject(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("파일을 BinaryContent로 변환할 수 없습니다."); - } catch (IOException e) { - throw new RuntimeException("BinaryContent 파일이나 경로를 불러오는데 실패했습니다."); - } + private BinaryContent read(Path path) { + if (!Files.exists(path)) { + throw new IllegalStateException("해당 파일이 존재하지 않습니다."); } - private void write(BinaryContent binaryContent, Path path) { - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(binaryContent); - } catch (IOException e) { - throw new RuntimeException("BinaryContent를 파일로 저장하는데 실패했습니다."); - } + try (FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis)) { + return (BinaryContent) ois.readObject(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("파일을 BinaryContent로 변환할 수 없습니다."); + } catch (IOException e) { + throw new RuntimeException("BinaryContent 파일이나 경로를 불러오는데 실패했습니다."); } + } + private void write(BinaryContent binaryContent, Path path) { + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(binaryContent); + } catch (IOException e) { + throw new RuntimeException("BinaryContent를 파일로 저장하는데 실패했습니다."); + } + } - private Path getBinaryContentPath(UUID statusId) { - try { - Files.createDirectories(binaryContentPath); - } catch (IOException e) { - throw new IllegalStateException("binary-contents 경로를 만드는데 실패했습니다."); - } - return binaryContentPath.resolve(statusId.toString() + ".ser"); + private Path getBinaryContentPath(UUID statusId) { + try { + Files.createDirectories(binaryContentPath); + } catch (IOException e) { + throw new IllegalStateException("binary-contents 경로를 만드는데 실패했습니다."); } + + return binaryContentPath.resolve(statusId.toString() + ".ser"); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/JCFBinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/JCFBinaryContentRepository.java index 670f98d11..2b3d4ae9f 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/JCFBinaryContentRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/JCFBinaryContentRepository.java @@ -1,41 +1,42 @@ package com.sprint.mission.discodeit.binarycontent.repository; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) public class JCFBinaryContentRepository implements BinaryContentRepository { - private final List data = new ArrayList<>(); - @Override - public Optional findById(UUID id) { - return data.stream() - .filter(bc -> bc.getId().equals(id)) - .findFirst(); - } + private final List data = new ArrayList<>(); - @Override - public List findAll() { - return List.copyOf(data); - } + @Override + public Optional findById(UUID id) { + return data.stream() + .filter(bc -> bc.getId().equals(id)) + .findFirst(); + } - @Override - public void save(BinaryContent binaryContent) { - if(data.contains(binaryContent)) - data.set(data.indexOf(binaryContent), binaryContent); - else - data.add(binaryContent); - } + @Override + public List findAll() { + return List.copyOf(data); + } - @Override - public void deleteById(UUID id) { - data.removeIf(bc -> bc.getId().equals(id)); + @Override + public void save(BinaryContent binaryContent) { + if (data.contains(binaryContent)) { + data.set(data.indexOf(binaryContent), binaryContent); + } else { + data.add(binaryContent); } + } + + @Override + public void deleteById(UUID id) { + data.removeIf(bc -> bc.getId().equals(id)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index 467bef2bc..24c31e934 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -2,57 +2,56 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentInfo; -import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; -import com.sprint.mission.discodeit.binarycontent.mapper.BinaryContentMapper; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentsRequest; +import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; +import com.sprint.mission.discodeit.binarycontent.mapper.BinaryContentMapper; import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class BinaryContentService { - private final BinaryContentRepository contentRepository; - - public BinaryContentInfo createBinaryContent(BinaryContentCreateInfo contentInfo) { - BinaryContent content = new BinaryContent(contentInfo.fileName(), contentInfo.contentType(), contentInfo.content()); - contentRepository.save(content); - return BinaryContentMapper.toBinaryContentInfo(content); - } - - public BinaryContentInfo findBinaryContent(UUID contentId) { - BinaryContent content = contentRepository.findById(contentId) - .orElseThrow(BinaryContentNotFoundException::new); - return BinaryContentMapper.toBinaryContentInfo(content); - } - - public BinaryContent findBinaryContentEntity(UUID contentId) { - return contentRepository.findById(contentId) - .orElseThrow(BinaryContentNotFoundException::new); - } - - public List findAll() { - return contentRepository.findAll() - .stream() - .map(BinaryContentMapper::toBinaryContentInfo) - .toList(); - } - - public List findAllByIdIn(BinaryContentsRequest request) { - return contentRepository.findAll() - .stream() - .filter(content -> request.contentIds().contains(content.getId())) - .map(BinaryContentMapper::toBinaryContentInfo) - .toList(); - } - - public void deleteBinaryContent(UUID contentId) { - contentRepository.deleteById(contentId); - } + + private final BinaryContentRepository contentRepository; + + public BinaryContentInfo createBinaryContent(BinaryContentCreateInfo contentInfo) { + BinaryContent content = new BinaryContent(contentInfo.fileName(), contentInfo.contentType(), + contentInfo.content()); + contentRepository.save(content); + return BinaryContentMapper.toBinaryContentInfo(content); + } + + public BinaryContentInfo findBinaryContent(UUID contentId) { + BinaryContent content = contentRepository.findById(contentId) + .orElseThrow(BinaryContentNotFoundException::new); + return BinaryContentMapper.toBinaryContentInfo(content); + } + + public BinaryContent findBinaryContentEntity(UUID contentId) { + return contentRepository.findById(contentId) + .orElseThrow(BinaryContentNotFoundException::new); + } + + public List findAll() { + return contentRepository.findAll() + .stream() + .map(BinaryContentMapper::toBinaryContentInfo) + .toList(); + } + + public List findAllByIdIn(BinaryContentsRequest request) { + return contentRepository.findAll() + .stream() + .filter(content -> request.contentIds().contains(content.getId())) + .map(BinaryContentMapper::toBinaryContentInfo) + .toList(); + } + + public void deleteBinaryContent(UUID contentId) { + contentRepository.deleteById(contentId); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index 9d4c2bfb1..887470000 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -1,56 +1,66 @@ package com.sprint.mission.discodeit.channel.controller; -import com.sprint.mission.discodeit.channel.dto.*; +import com.sprint.mission.discodeit.channel.dto.ChannelInfo; +import com.sprint.mission.discodeit.channel.dto.FindChannelInfo; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateInfo; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelInfo; +import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateInfo; +import com.sprint.mission.discodeit.channel.dto.PublicChannelInfo; import com.sprint.mission.discodeit.channel.service.ChannelService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RequestMethod; +import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController @RequestMapping("/channels") public class ChannelController { - private final ChannelService channelService; - - @RequestMapping(value = "/public", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createChannel( - @RequestBody PublicChannelCreateInfo channelInfo - ) { - return ResponseEntity.ok(channelService.createPublicChannel(channelInfo)); - } - - @RequestMapping(value = "/private", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createChannel( - @RequestBody PrivateChannelCreateInfo channelInfo - ) { - return ResponseEntity.ok(channelService.createPrivateChannel(channelInfo)); - } - - @RequestMapping(value = "/{channelId}", method = RequestMethod.GET) - public ResponseEntity getChannel(@PathVariable UUID channelId) { - return ResponseEntity.ok(channelService.findChannel(channelId)); - } - - @RequestMapping(method = RequestMethod.GET, consumes = "application/json") - public ResponseEntity> getAllVisibleChannels(@RequestBody FindChannelInfo findChannelInfo) { - return ResponseEntity.ok(channelService.findAllByUserId(findChannelInfo.userId())); - } - - @RequestMapping(value = "/{channelId}", method = RequestMethod.PATCH, consumes = "application/json") - public ResponseEntity updateChannel( - @PathVariable UUID channelId, - @RequestBody PublicChannelCreateInfo channelInfo - ) { - channelService.updateChannel(channelId, channelInfo); - return ResponseEntity.noContent().build(); - } - - @RequestMapping(value = "/{channelId}", method = RequestMethod.DELETE) - public ResponseEntity deleteChannel(@PathVariable UUID channelId) { - channelService.deleteChannel(channelId); - return ResponseEntity.noContent().build(); - } + + private final ChannelService channelService; + + @RequestMapping(value = "/public", method = RequestMethod.POST, consumes = "application/json") + public ResponseEntity createChannel( + @RequestBody PublicChannelCreateInfo channelInfo + ) { + return ResponseEntity.ok(channelService.createPublicChannel(channelInfo)); + } + + @RequestMapping(value = "/private", method = RequestMethod.POST, consumes = "application/json") + public ResponseEntity createChannel( + @RequestBody PrivateChannelCreateInfo channelInfo + ) { + return ResponseEntity.ok(channelService.createPrivateChannel(channelInfo)); + } + + @RequestMapping(value = "/{channelId}", method = RequestMethod.GET) + public ResponseEntity getChannel(@PathVariable UUID channelId) { + return ResponseEntity.ok(channelService.findChannel(channelId)); + } + + @RequestMapping(method = RequestMethod.GET, consumes = "application/json") + public ResponseEntity> getAllVisibleChannels( + @RequestBody FindChannelInfo findChannelInfo) { + return ResponseEntity.ok(channelService.findAllByUserId(findChannelInfo.userId())); + } + + @RequestMapping(value = "/{channelId}", method = RequestMethod.PATCH, consumes = "application/json") + public ResponseEntity updateChannel( + @PathVariable UUID channelId, + @RequestBody PublicChannelCreateInfo channelInfo + ) { + channelService.updateChannel(channelId, channelInfo); + return ResponseEntity.noContent().build(); + } + + @RequestMapping(value = "/{channelId}", method = RequestMethod.DELETE) + public ResponseEntity deleteChannel(@PathVariable UUID channelId) { + channelService.deleteChannel(channelId); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelInfo.java index 7c60d6ef9..caff0bc02 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelInfo.java @@ -1,16 +1,17 @@ package com.sprint.mission.discodeit.channel.dto; import com.sprint.mission.discodeit.common.ChannelType; - import java.time.Instant; import java.util.List; import java.util.UUID; public record ChannelInfo( - UUID channelId, - String channelName, - ChannelType channelType, - String description, - Instant lastMessageTime, - List userIds -) {} + UUID channelId, + String channelName, + ChannelType channelType, + String description, + Instant lastMessageTime, + List userIds +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelInfo.java index a4e4e3869..c0e539bb2 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelInfo.java @@ -3,5 +3,7 @@ import java.util.UUID; public record FindChannelInfo( - UUID userId -) {} + UUID userId +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateInfo.java index 04ee65337..29d91cd72 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateInfo.java @@ -4,5 +4,7 @@ import java.util.UUID; public record PrivateChannelCreateInfo( - List userIds -) {} + List userIds +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelInfo.java index c5274ab94..9ca5ab515 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelInfo.java @@ -1,10 +1,11 @@ package com.sprint.mission.discodeit.channel.dto; import com.sprint.mission.discodeit.common.ChannelType; - import java.util.UUID; public record PrivateChannelInfo( - UUID channelId, - ChannelType channelType -) {} + UUID channelId, + ChannelType channelType +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateInfo.java index c18ef8502..d32536ac6 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateInfo.java @@ -1,6 +1,8 @@ package com.sprint.mission.discodeit.channel.dto; public record PublicChannelCreateInfo( - String channelName, - String description -) {} + String channelName, + String description +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelInfo.java index d94b4f1e2..e58750f34 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelInfo.java @@ -1,12 +1,13 @@ package com.sprint.mission.discodeit.channel.dto; import com.sprint.mission.discodeit.common.ChannelType; - import java.util.UUID; public record PublicChannelInfo( - UUID channelId, - String channelName, - ChannelType channelType, - String description -) {} + UUID channelId, + String channelName, + ChannelType channelType, + String description +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java index 5fe404aa2..5fe7ec148 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java @@ -2,68 +2,68 @@ import com.sprint.mission.discodeit.common.ChannelType; import com.sprint.mission.discodeit.common.CommonEntity; -import lombok.Getter; - import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import lombok.Getter; @Getter public class Channel extends CommonEntity { - private static final long serialVersionUID = 1L; - private String channelName; - private ChannelType channelType; - private String description; - private final List messageIds = new ArrayList<>(); - private final List userIds = new ArrayList<>(); - public Channel(String channelName, ChannelType channelType, String description) { - this.channelName = channelName; - this.channelType = channelType; - this.description = description; - } + private static final long serialVersionUID = 1L; + private final List messageIds = new ArrayList<>(); + private final List userIds = new ArrayList<>(); + private String channelName; + private ChannelType channelType; + private String description; + + public Channel(String channelName, ChannelType channelType, String description) { + this.channelName = channelName; + this.channelType = channelType; + this.description = description; + } - public List getMessageIds() { - return List.copyOf(messageIds); - } + public List getMessageIds() { + return List.copyOf(messageIds); + } - public List getUserIds() { - return List.copyOf(userIds); - } + public List getUserIds() { + return List.copyOf(userIds); + } - public void updateChannelName(String channelName) { - this.channelName = channelName; - this.updateAt = Instant.now(); - } + public void updateChannelName(String channelName) { + this.channelName = channelName; + this.updateAt = Instant.now(); + } - public void updateChannelType(ChannelType channelType) { - this.channelType = channelType; - this.updateAt = Instant.now(); - } + public void updateChannelType(ChannelType channelType) { + this.channelType = channelType; + this.updateAt = Instant.now(); + } - public void updateDescription(String description) { - this.description = description; - this.updateAt = Instant.now(); - } + public void updateDescription(String description) { + this.description = description; + this.updateAt = Instant.now(); + } - public void addMessageId(UUID messageId) { - messageIds.add(messageId); - this.updateAt = Instant.now(); - } + public void addMessageId(UUID messageId) { + messageIds.add(messageId); + this.updateAt = Instant.now(); + } - public void removeMessageId(UUID messageId) { - messageIds.remove(messageId); - this.updateAt = Instant.now(); - } + public void removeMessageId(UUID messageId) { + messageIds.remove(messageId); + this.updateAt = Instant.now(); + } - public void addUserId(UUID userId) { - userIds.add(userId); - this.updateAt = Instant.now(); - } + public void addUserId(UUID userId) { + userIds.add(userId); + this.updateAt = Instant.now(); + } - public void removeUserId(UUID userId) { - userIds.remove(userId); - this.updateAt = Instant.now(); - } + public void removeUserId(UUID userId) { + userIds.remove(userId); + this.updateAt = Instant.now(); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/exception/AlreadyJoinedException.java b/src/main/java/com/sprint/mission/discodeit/channel/exception/AlreadyJoinedException.java index 6c361bc12..84e0eeeac 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/exception/AlreadyJoinedException.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/exception/AlreadyJoinedException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class AlreadyJoinedException extends BusinessException { - public AlreadyJoinedException() { - super("이미 가입된 유저입니다."); - } + + public AlreadyJoinedException() { + super("이미 가입된 유저입니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelDuplicationException.java b/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelDuplicationException.java index b1e1d5cab..5d1af8a14 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelDuplicationException.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelDuplicationException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class ChannelDuplicationException extends BusinessException { - public ChannelDuplicationException() { - super("해당 채널이 이미 존재합니다."); - } + + public ChannelDuplicationException() { + super("해당 채널이 이미 존재합니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelNotFoundException.java b/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelNotFoundException.java index 38eb5d395..335718d02 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelNotFoundException.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelNotFoundException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class ChannelNotFoundException extends BusinessException { - public ChannelNotFoundException() { - super("해당 채널을 찾을 수 없습니다."); - } + + public ChannelNotFoundException() { + super("해당 채널을 찾을 수 없습니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelUpdateNotAllowedException.java b/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelUpdateNotAllowedException.java index 7661c5cdc..e2013690a 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelUpdateNotAllowedException.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/exception/ChannelUpdateNotAllowedException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class ChannelUpdateNotAllowedException extends BusinessException { - public ChannelUpdateNotAllowedException() { - super("해당 채널은 수정할 수 없습니다."); - } + + public ChannelUpdateNotAllowedException() { + super("해당 채널은 수정할 수 없습니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index e56b9def7..c1528d403 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -6,74 +6,74 @@ import com.sprint.mission.discodeit.channel.dto.PublicChannelInfo; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.common.ChannelType; - import java.time.Instant; public final class ChannelMapper { - private ChannelMapper() {} + private ChannelMapper() { + } - public static ChannelInfo toChannelInfo(Channel channel, Instant lastMessageTime) { - if(channel.getChannelType() == ChannelType.PRIVATE) { - return new ChannelInfo(channel.getId(), - null, - channel.getChannelType(), - null, - lastMessageTime, - channel.getUserIds()); - } else { - return new ChannelInfo( - channel.getId(), - channel.getChannelName(), - channel.getChannelType(), - channel.getDescription(), - null, - channel.getUserIds() - ); - } + public static ChannelInfo toChannelInfo(Channel channel, Instant lastMessageTime) { + if (channel.getChannelType() == ChannelType.PRIVATE) { + return new ChannelInfo(channel.getId(), + null, + channel.getChannelType(), + null, + lastMessageTime, + channel.getUserIds()); + } else { + return new ChannelInfo( + channel.getId(), + channel.getChannelName(), + channel.getChannelType(), + channel.getDescription(), + null, + channel.getUserIds() + ); } + } - public static PublicChannelInfo toPublicChannelInfo(Channel channel) { - return new PublicChannelInfo( - channel.getId(), - channel.getChannelName(), - channel.getChannelType(), - channel.getDescription() - ); - } + public static PublicChannelInfo toPublicChannelInfo(Channel channel) { + return new PublicChannelInfo( + channel.getId(), + channel.getChannelName(), + channel.getChannelType(), + channel.getDescription() + ); + } - public static PrivateChannelInfo toPrivateChannelInfo(Channel channel) { - return new PrivateChannelInfo( - channel.getId(), - channel.getChannelType() - ); - } + public static PrivateChannelInfo toPrivateChannelInfo(Channel channel) { + return new PrivateChannelInfo( + channel.getId(), + channel.getChannelType() + ); + } - public static PrivateChannelCreateInfo toPrivateChannelCreateInfo(Channel channel) { - return new PrivateChannelCreateInfo(channel.getUserIds()); - } + public static PrivateChannelCreateInfo toPrivateChannelCreateInfo(Channel channel) { + return new PrivateChannelCreateInfo(channel.getUserIds()); + } - public static Channel toChannel(ChannelInfo channelInfo) { - return new Channel( - channelInfo.channelName(), - channelInfo.channelType(), - channelInfo.description() - ); - } + public static Channel toChannel(ChannelInfo channelInfo) { + return new Channel( + channelInfo.channelName(), + channelInfo.channelType(), + channelInfo.description() + ); + } - public static Channel toChannel(PublicChannelInfo channelInfo) { - return new Channel( - channelInfo.channelName(), - ChannelType.PUBLIC, - channelInfo.description() - ); - } + public static Channel toChannel(PublicChannelInfo channelInfo) { + return new Channel( + channelInfo.channelName(), + ChannelType.PUBLIC, + channelInfo.description() + ); + } - public static Channel toChannel(PrivateChannelCreateInfo channelInfo) { - return new Channel( - null, - ChannelType.PRIVATE, - null - ); - } + public static Channel toChannel(PrivateChannelCreateInfo channelInfo) { + return new Channel( + null, + ChannelType.PRIVATE, + null + ); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/ChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/ChannelRepository.java index f8e02e7c4..af3b0c9dd 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/ChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/repository/ChannelRepository.java @@ -1,16 +1,21 @@ package com.sprint.mission.discodeit.channel.repository; import com.sprint.mission.discodeit.channel.entity.Channel; - import java.util.List; import java.util.Optional; import java.util.UUID; public interface ChannelRepository { - Optional findById(UUID channelId); - Optional findByName(String channelName); - List findAll(); - List findAllByUserId(UUID userId); - void save(Channel channel); - void deleteById(UUID channelId); + + Optional findById(UUID channelId); + + Optional findByName(String channelName); + + List findAll(); + + List findAllByUserId(UUID userId); + + void save(Channel channel); + + void deleteById(UUID channelId); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java index 3bbd85c7d..0f4c8a221 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java @@ -2,11 +2,11 @@ import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.common.ChannelType; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -import java.io.*; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -14,106 +14,111 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") public class FileChannelRepository implements ChannelRepository { - private final Path channelPath; - public FileChannelRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.channelPath = Paths.get(rootPath, "channels"); - } + private final Path channelPath; - @Override - public Optional findById(UUID channelId) { - Path channelPath = getChannelPath(channelId); + public FileChannelRepository( + @Value("${discodeit.repository.file-directory:data}") String rootPath + ) { + this.channelPath = Paths.get(rootPath, "channels"); + } - if (!Files.exists(channelPath)) { - return Optional.empty(); - } + @Override + public Optional findById(UUID channelId) { + Path channelPath = getChannelPath(channelId); - try (FileInputStream fis = new FileInputStream(channelPath.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return Optional.ofNullable((Channel) ois.readObject()); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("채널을 가져오는데 실패했습니다."); - } + if (!Files.exists(channelPath)) { + return Optional.empty(); } - @Override - public Optional findByName(String channelName) { - return findAll().stream() - .filter(c -> c.getChannelType() == ChannelType.PUBLIC && c.getChannelName().equals(channelName)) - .findFirst(); + try (FileInputStream fis = new FileInputStream(channelPath.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis)) { + return Optional.ofNullable((Channel) ois.readObject()); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException("채널을 가져오는데 실패했습니다."); } + } - @Override - public List findAll() { - if(Files.exists(channelPath)) { - try { - List channels = Files.list(channelPath) - .map(path -> { - try( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - Channel channel = (Channel) ois.readObject(); - return channel; - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("모든 채널을 가져오는데 실패했습니다."); - } - }) - .toList(); - return channels; - } catch (IOException e) { + @Override + public Optional findByName(String channelName) { + return findAll().stream() + .filter( + c -> c.getChannelType() == ChannelType.PUBLIC && c.getChannelName().equals(channelName)) + .findFirst(); + } + + @Override + public List findAll() { + if (Files.exists(channelPath)) { + try { + List channels = Files.list(channelPath) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Channel channel = (Channel) ois.readObject(); + return channel; + } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("모든 채널을 가져오는데 실패했습니다."); - } - } else { - return new ArrayList<>(); - } + } + }) + .toList(); + return channels; + } catch (IOException e) { + throw new RuntimeException("모든 채널을 가져오는데 실패했습니다."); + } + } else { + return new ArrayList<>(); } + } - @Override - public List findAllByUserId(UUID userId) { - return findAll().stream() - .filter(c -> c.getUserIds() - .stream() - .anyMatch(findUserId -> findUserId.equals(userId))) - .toList(); - } + @Override + public List findAllByUserId(UUID userId) { + return findAll().stream() + .filter(c -> c.getUserIds() + .stream() + .anyMatch(findUserId -> findUserId.equals(userId))) + .toList(); + } - @Override - public void save(Channel channel) { - Path channelPath = getChannelPath(channel.getId()); - try ( - FileOutputStream fos = new FileOutputStream(channelPath.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(channel); - } catch (IOException e) { - throw new RuntimeException("채널을 저장하는데 실패했습니다."); - } + @Override + public void save(Channel channel) { + Path channelPath = getChannelPath(channel.getId()); + try ( + FileOutputStream fos = new FileOutputStream(channelPath.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(channel); + } catch (IOException e) { + throw new RuntimeException("채널을 저장하는데 실패했습니다."); } + } - @Override - public void deleteById(UUID channelId) { - Path channelPath = getChannelPath(channelId); - try { - Files.delete(channelPath); - } catch (IOException e) { - throw new RuntimeException("채널을 삭제하는데 실패했습니다."); - } + @Override + public void deleteById(UUID channelId) { + Path channelPath = getChannelPath(channelId); + try { + Files.delete(channelPath); + } catch (IOException e) { + throw new RuntimeException("채널을 삭제하는데 실패했습니다."); } + } - private Path getChannelPath(UUID channelId) { - try { - Files.createDirectories(channelPath); - } catch (IOException e) { - throw new IllegalStateException("channels 경로를 만드는데 실패했습니다."); - } - - return channelPath.resolve(channelId.toString() + ".ser"); + private Path getChannelPath(UUID channelId) { + try { + Files.createDirectories(channelPath); + } catch (IOException e) { + throw new IllegalStateException("channels 경로를 만드는데 실패했습니다."); } + + return channelPath.resolve(channelId.toString() + ".ser"); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java index 0ec54c4fe..7518b73c5 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java @@ -2,59 +2,60 @@ import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.common.ChannelType; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) public class JCFChannelRepository implements ChannelRepository { - private final List data = new ArrayList<>(); - - @Override - public Optional findById(UUID channelId) { - return data.stream() - .filter(c -> c.getId().equals(channelId)) - .findFirst(); - } - @Override - public Optional findByName(String channelName) { - return data.stream() - .filter(c -> c.getChannelType() == ChannelType.PUBLIC - && c.getChannelName().equals(channelName)) - .findFirst(); + private final List data = new ArrayList<>(); + + @Override + public Optional findById(UUID channelId) { + return data.stream() + .filter(c -> c.getId().equals(channelId)) + .findFirst(); + } + + @Override + public Optional findByName(String channelName) { + return data.stream() + .filter(c -> c.getChannelType() == ChannelType.PUBLIC + && c.getChannelName().equals(channelName)) + .findFirst(); + } + + @Override + public List findAll() { + return List.copyOf(data); + } + + @Override + public List findAllByUserId(UUID userId) { + return data.stream() + .filter(c -> c.getUserIds() + .stream() + .anyMatch(findUserId -> findUserId.equals(userId))) + .toList(); + + } + + @Override + public void save(Channel channel) { + if (data.contains(channel)) { + data.set(data.indexOf(channel), channel); + } else { + data.add(channel); } + } - @Override - public List findAll() { - return List.copyOf(data); - } - - @Override - public List findAllByUserId(UUID userId) { - return data.stream() - .filter(c -> c.getUserIds() - .stream() - .anyMatch(findUserId -> findUserId.equals(userId))) - .toList(); - - } - - @Override - public void save(Channel channel) { - if(data.contains(channel)) - data.set(data.indexOf(channel), channel); - else - data.add(channel); - } - - @Override - public void deleteById(UUID channelId) { - data.removeIf(c -> c.getId().equals(channelId)); - } + @Override + public void deleteById(UUID channelId) { + data.removeIf(c -> c.getId().equals(channelId)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 50cdb656f..a41f9d801 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -1,159 +1,172 @@ package com.sprint.mission.discodeit.channel.service; +import com.sprint.mission.discodeit.channel.dto.ChannelInfo; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateInfo; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelInfo; +import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateInfo; +import com.sprint.mission.discodeit.channel.dto.PublicChannelInfo; import com.sprint.mission.discodeit.channel.entity.Channel; -import com.sprint.mission.discodeit.channel.dto.*; import com.sprint.mission.discodeit.channel.exception.ChannelDuplicationException; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; import com.sprint.mission.discodeit.channel.exception.ChannelUpdateNotAllowedException; -import com.sprint.mission.discodeit.common.ChannelType; import com.sprint.mission.discodeit.channel.mapper.ChannelMapper; import com.sprint.mission.discodeit.channel.repository.ChannelRepository; +import com.sprint.mission.discodeit.common.ChannelType; import com.sprint.mission.discodeit.message.entity.Message; import com.sprint.mission.discodeit.message.repository.MessageRepository; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; -import com.sprint.mission.discodeit.user.entity.User; +import java.time.Instant; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.time.Instant; -import java.util.*; - @RequiredArgsConstructor @Service public class BasicChannelService implements ChannelService { - private final ChannelRepository channelRepository; - private final UserRepository userRepository; - private final MessageRepository messageRepository; - private final ReadStatusRepository readStatusRepository; - - public PublicChannelInfo createPublicChannel(PublicChannelCreateInfo channelInfo) { - validateChannelExist(channelInfo.channelName()); - Channel channel = new Channel(channelInfo.channelName(), ChannelType.PUBLIC, channelInfo.description()); - channelRepository.save(channel); - return ChannelMapper.toPublicChannelInfo(channel); - } - public PrivateChannelInfo createPrivateChannel(PrivateChannelCreateInfo channelInfo) { - Channel channel = new Channel(null, ChannelType.PRIVATE, null); - channelRepository.save(channel); - channelInfo.userIds().forEach(userId -> joinChannel(channel.getId(), userId)); - return ChannelMapper.toPrivateChannelInfo(channel); + private final ChannelRepository channelRepository; + private final UserRepository userRepository; + private final MessageRepository messageRepository; + private final ReadStatusRepository readStatusRepository; + + public PublicChannelInfo createPublicChannel(PublicChannelCreateInfo channelInfo) { + validateChannelExist(channelInfo.channelName()); + Channel channel = new Channel(channelInfo.channelName(), ChannelType.PUBLIC, + channelInfo.description()); + channelRepository.save(channel); + return ChannelMapper.toPublicChannelInfo(channel); + } + + public PrivateChannelInfo createPrivateChannel(PrivateChannelCreateInfo channelInfo) { + Channel channel = new Channel(null, ChannelType.PRIVATE, null); + channelRepository.save(channel); + channelInfo.userIds().forEach(userId -> joinChannel(channel.getId(), userId)); + return ChannelMapper.toPrivateChannelInfo(channel); + } + + @Override + public ChannelInfo findChannel(UUID channelId) { + Channel channel = channelRepository.findById(channelId) + .orElseThrow(ChannelNotFoundException::new); + + return ChannelMapper.toChannelInfo(channel, getLastMessageTime(channelId)); + } + + @Override + public List findAll() { + return channelRepository.findAll() + .stream() + .map(channel -> + ChannelMapper.toChannelInfo(channel, getLastMessageTime(channel.getId())) + ) + .toList(); + } + + @Override + public List findAllByUserId(UUID userId) { + return channelRepository.findAll() + .stream() + .filter(channel -> channel.getChannelType() == ChannelType.PUBLIC + || (channel.getChannelType() == ChannelType.PRIVATE && channel.getUserIds() + .contains(userId))) + .map(channel -> + ChannelMapper.toChannelInfo(channel, getLastMessageTime(channel.getId())) + ) + .toList(); + } + + @Override + public ChannelInfo updateChannel(UUID channelId, PublicChannelCreateInfo channelInfo) { + Channel findChannel = channelRepository.findById(channelId) + .orElseThrow(ChannelNotFoundException::new); + if (findChannel.getChannelType() == ChannelType.PRIVATE) { + throw new ChannelUpdateNotAllowedException(); } - - @Override - public ChannelInfo findChannel(UUID channelId) { - Channel channel = channelRepository.findById(channelId) - .orElseThrow(ChannelNotFoundException::new); - - return ChannelMapper.toChannelInfo(channel, getLastMessageTime(channelId)); - } - - @Override - public List findAll() { - return channelRepository.findAll() - .stream() - .map(channel -> - ChannelMapper.toChannelInfo(channel, getLastMessageTime(channel.getId())) - ) - .toList(); - } - - @Override - public List findAllByUserId(UUID userId) { - return channelRepository.findAll() - .stream() - .filter(channel -> channel.getChannelType() == ChannelType.PUBLIC - || (channel.getChannelType() == ChannelType.PRIVATE && channel.getUserIds().contains(userId))) - .map(channel -> - ChannelMapper.toChannelInfo(channel, getLastMessageTime(channel.getId())) - ) - .toList(); + validateChannelExist(channelInfo.channelName()); + + Optional.ofNullable(channelInfo.channelName()) + .ifPresent(findChannel::updateChannelName); + Optional.ofNullable(channelInfo.description()) + .ifPresent(findChannel::updateDescription); + + channelRepository.save(findChannel); + + return ChannelMapper.toChannelInfo(findChannel, getLastMessageTime(findChannel.getId())); + } + + @Override + public void deleteChannel(UUID channelId) { + channelRepository.findById(channelId) + .orElseThrow(ChannelNotFoundException::new); + userRepository.findAllByChannelId(channelId).forEach(user -> { + user.removeChannelId(channelId); + userRepository.save(user); + }); + + messageRepository.findAllByChannelId(channelId).forEach(message -> + messageRepository.deleteById(message.getId())); + + readStatusRepository.findAllByChannelId(channelId).forEach(readStatus -> + readStatusRepository.deleteById(readStatus.getId())); + + channelRepository.deleteById(channelId); + } + + @Override + public void joinChannel(UUID channelId, UUID userId) { + Channel channel = channelRepository.findById(channelId) + .orElseThrow(ChannelNotFoundException::new); + User user = userRepository.findById(userId) + .orElseThrow(UserNotFoundException::new); + + channel.addUserId(userId); + user.addChannelId(channelId); + + channelRepository.save(channel); + userRepository.save(user); + readStatusRepository.save(new ReadStatus(userId, channelId)); + } + + @Override + public void leaveChannel(UUID channelId, UUID userId) { + Channel channel = channelRepository.findById(channelId) + .orElseThrow(ChannelNotFoundException::new); + User user = userRepository.findById(userId) + .orElseThrow(UserNotFoundException::new); + ReadStatus findReadStatus = readStatusRepository.findByUserIdAndChannelId(userId, channelId) + .orElseThrow(() -> new IllegalStateException("유저가 채널에 가입되어 있지 않습니다.")); + + channel.removeUserId(userId); + user.removeChannelId(channelId); + + channelRepository.save(channel); + userRepository.save(user); + readStatusRepository.deleteById(findReadStatus.getId()); + } + + private void validateChannelExist(String channelName) { + if (channelRepository.findByName(channelName).isPresent()) { + throw new ChannelDuplicationException(); } - - @Override - public ChannelInfo updateChannel(UUID channelId, PublicChannelCreateInfo channelInfo) { - Channel findChannel = channelRepository.findById(channelId) - .orElseThrow(ChannelNotFoundException::new); - if(findChannel.getChannelType() == ChannelType.PRIVATE) - throw new ChannelUpdateNotAllowedException(); - validateChannelExist(channelInfo.channelName()); - - Optional.ofNullable(channelInfo.channelName()) - .ifPresent(findChannel::updateChannelName); - Optional.ofNullable(channelInfo.description()) - .ifPresent(findChannel::updateDescription); - - channelRepository.save(findChannel); - - return ChannelMapper.toChannelInfo(findChannel, getLastMessageTime(findChannel.getId())); - } - - @Override - public void deleteChannel(UUID channelId) { - channelRepository.findById(channelId) - .orElseThrow(ChannelNotFoundException::new); - userRepository.findAllByChannelId(channelId).forEach(user -> { - user.removeChannelId(channelId); - userRepository.save(user); - }); - - messageRepository.findAllByChannelId(channelId).forEach(message -> - messageRepository.deleteById(message.getId())); - - readStatusRepository.findAllByChannelId(channelId).forEach(readStatus -> - readStatusRepository.deleteById(readStatus.getId())); - - channelRepository.deleteById(channelId); - } - - @Override - public void joinChannel(UUID channelId, UUID userId) { - Channel channel = channelRepository.findById(channelId) - .orElseThrow(ChannelNotFoundException::new); - User user = userRepository.findById(userId) - .orElseThrow(UserNotFoundException::new); - - channel.addUserId(userId); - user.addChannelId(channelId); - - channelRepository.save(channel); - userRepository.save(user); - readStatusRepository.save(new ReadStatus(userId, channelId)); - } - - @Override - public void leaveChannel(UUID channelId, UUID userId) { - Channel channel = channelRepository.findById(channelId) - .orElseThrow(ChannelNotFoundException::new); - User user = userRepository.findById(userId) - .orElseThrow(UserNotFoundException::new); - ReadStatus findReadStatus = readStatusRepository.findByUserIdAndChannelId(userId, channelId) - .orElseThrow(() -> new IllegalStateException("유저가 채널에 가입되어 있지 않습니다.")); - - channel.removeUserId(userId); - user.removeChannelId(channelId); - - channelRepository.save(channel); - userRepository.save(user); - readStatusRepository.deleteById(findReadStatus.getId()); - } - - private void validateChannelExist(String channelName) { - if(channelRepository.findByName(channelName).isPresent()) - throw new ChannelDuplicationException(); - } - - private Instant getLastMessageTime(UUID channelId) { - List messages = messageRepository.findAllByChannelId(channelId); - if(messages.isEmpty()) return null; - else - return messages - .stream() - .max(Comparator.comparing(Message::getUpdateAt)) - .orElseThrow(() -> new IllegalStateException("메세지가 존재하지 않습니다.")) - .getUpdateAt(); + } + + private Instant getLastMessageTime(UUID channelId) { + List messages = messageRepository.findAllByChannelId(channelId); + if (messages.isEmpty()) { + return null; + } else { + return messages + .stream() + .max(Comparator.comparing(Message::getUpdateAt)) + .orElseThrow(() -> new IllegalStateException("메세지가 존재하지 않습니다.")) + .getUpdateAt(); } + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java index 44e7f3f1a..a07560d56 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java @@ -1,18 +1,30 @@ package com.sprint.mission.discodeit.channel.service; -import com.sprint.mission.discodeit.channel.dto.*; - +import com.sprint.mission.discodeit.channel.dto.ChannelInfo; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateInfo; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelInfo; +import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateInfo; +import com.sprint.mission.discodeit.channel.dto.PublicChannelInfo; import java.util.List; import java.util.UUID; public interface ChannelService { - PublicChannelInfo createPublicChannel(PublicChannelCreateInfo channelInfo); - PrivateChannelInfo createPrivateChannel(PrivateChannelCreateInfo channelInfo); - ChannelInfo findChannel(UUID channelId); - List findAll(); - List findAllByUserId(UUID userId); - ChannelInfo updateChannel(UUID channelId, PublicChannelCreateInfo channelInfo); - void deleteChannel(UUID channelId); - void joinChannel(UUID channelId, UUID userId); - void leaveChannel(UUID channelId, UUID userId); + + PublicChannelInfo createPublicChannel(PublicChannelCreateInfo channelInfo); + + PrivateChannelInfo createPrivateChannel(PrivateChannelCreateInfo channelInfo); + + ChannelInfo findChannel(UUID channelId); + + List findAll(); + + List findAllByUserId(UUID userId); + + ChannelInfo updateChannel(UUID channelId, PublicChannelCreateInfo channelInfo); + + void deleteChannel(UUID channelId); + + void joinChannel(UUID channelId, UUID userId); + + void leaveChannel(UUID channelId, UUID userId); } diff --git a/src/main/java/com/sprint/mission/discodeit/common/ChannelType.java b/src/main/java/com/sprint/mission/discodeit/common/ChannelType.java index 04f5f48f3..2a04204c7 100644 --- a/src/main/java/com/sprint/mission/discodeit/common/ChannelType.java +++ b/src/main/java/com/sprint/mission/discodeit/common/ChannelType.java @@ -1,24 +1,24 @@ package com.sprint.mission.discodeit.common; public enum ChannelType { - PUBLIC(0), PRIVATE(1); + PUBLIC(0), PRIVATE(1); - private final int value; + private final int value; - ChannelType(int value) { - this.value = value; - } + ChannelType(int value) { + this.value = value; + } - public int getValue() { - return value; + public static ChannelType fromValue(int value) { + for (ChannelType type : ChannelType.values()) { + if (type.getValue() == value) { + return type; + } } + throw new IllegalArgumentException("No matching enum constant for [" + value + "]"); + } - public static ChannelType fromValue(int value) { - for (ChannelType type : ChannelType.values()) { - if (type.getValue() == value) { - return type; - } - } - throw new IllegalArgumentException("No matching enum constant for [" + value + "]"); - } + public int getValue() { + return value; + } } diff --git a/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java b/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java index 1f23494fb..ad286f3e7 100644 --- a/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java +++ b/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java @@ -1,34 +1,40 @@ package com.sprint.mission.discodeit.common; -import lombok.Getter; - import java.io.Serializable; import java.time.Instant; import java.util.UUID; +import lombok.Getter; @Getter public abstract class CommonEntity implements Serializable { - private static final long serialVersionUID = 1L; - protected final UUID id; - protected Instant createdAt; - protected Instant updateAt; - public CommonEntity() { - this.id = UUID.randomUUID(); - this.createdAt = Instant.now(); - this.updateAt = this.createdAt; - } + private static final long serialVersionUID = 1L; + protected final UUID id; + protected Instant createdAt; + protected Instant updateAt; - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - return id.equals(((CommonEntity) obj).id); - } + public CommonEntity() { + this.id = UUID.randomUUID(); + this.createdAt = Instant.now(); + this.updateAt = this.createdAt; + } - @Override - public int hashCode() { - return id.hashCode(); + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; } + if (getClass() != obj.getClass()) { + return false; + } + return id.equals(((CommonEntity) obj).id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } } \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/exception/BusinessException.java b/src/main/java/com/sprint/mission/discodeit/exception/BusinessException.java index eb2ce7fc9..0a93730af 100644 --- a/src/main/java/com/sprint/mission/discodeit/exception/BusinessException.java +++ b/src/main/java/com/sprint/mission/discodeit/exception/BusinessException.java @@ -3,9 +3,9 @@ import lombok.Getter; @Getter -public class BusinessException extends RuntimeException{ +public class BusinessException extends RuntimeException { - public BusinessException(String message) { - super(message); - } + public BusinessException(String message) { + super(message); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/exception/ErrorResponse.java b/src/main/java/com/sprint/mission/discodeit/exception/ErrorResponse.java index 45ce99db4..5c67de37b 100644 --- a/src/main/java/com/sprint/mission/discodeit/exception/ErrorResponse.java +++ b/src/main/java/com/sprint/mission/discodeit/exception/ErrorResponse.java @@ -6,6 +6,7 @@ @Getter @AllArgsConstructor public class ErrorResponse { - String code; - String message; + + String code; + String message; } diff --git a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java index 9eec452fb..c35812657 100644 --- a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java @@ -8,8 +8,8 @@ import com.sprint.mission.discodeit.message.exception.MessageNotFoundException; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusDuplicationException; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusNotFoundException; -import com.sprint.mission.discodeit.user.exception.EmailDuplicationException; import com.sprint.mission.discodeit.user.exception.AuthenticationFailedException; +import com.sprint.mission.discodeit.user.exception.EmailDuplicationException; import com.sprint.mission.discodeit.user.exception.UserDuplicationException; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.userstatus.exception.UserStatusDuplicationException; @@ -23,99 +23,99 @@ @RestControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(EmailDuplicationException.class) - public ResponseEntity handle(EmailDuplicationException e) { - ErrorResponse response = new ErrorResponse("EMAIL_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); - } - - @ExceptionHandler(UserNotFoundException.class) - public ResponseEntity handle(UserNotFoundException e) { - ErrorResponse response = new ErrorResponse("USER_NOT_FOUND", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); - } - - @ExceptionHandler(UserDuplicationException.class) - public ResponseEntity handle(UserDuplicationException e) { - ErrorResponse response = new ErrorResponse("USER_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); - } - - @ExceptionHandler(UserStatusNotFoundException.class) - public ResponseEntity handle(UserStatusNotFoundException e) { - ErrorResponse response = new ErrorResponse("USER_STATUS_NOT_FOUND", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); - } - - @ExceptionHandler(UserStatusDuplicationException.class) - public ResponseEntity handle(UserStatusDuplicationException e) { - ErrorResponse response = new ErrorResponse("USER_STATUS_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); - } - - @ExceptionHandler(AuthenticationFailedException.class) - public ResponseEntity handle(AuthenticationFailedException e) { - ErrorResponse response = new ErrorResponse("AUTHENTICATION_FAILED", e.getMessage()); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); - } - - @ExceptionHandler(ChannelNotFoundException.class) - public ResponseEntity handle(ChannelNotFoundException e) { - ErrorResponse response = new ErrorResponse("CHANNEL_NOT_FOUND", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); - } - - @ExceptionHandler(ChannelDuplicationException.class) - public ResponseEntity handle(ChannelDuplicationException e) { - ErrorResponse response = new ErrorResponse("CHANNEL_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); - } - - @ExceptionHandler(ChannelUpdateNotAllowedException.class) - public ResponseEntity handle(ChannelUpdateNotAllowedException e) { - ErrorResponse response = new ErrorResponse("CHANNEL_UPDATE_NOT_ALLOWED", e.getMessage()); - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response); - } - - @ExceptionHandler(AlreadyJoinedException.class) - public ResponseEntity handle(AlreadyJoinedException e) { - ErrorResponse response = new ErrorResponse("ALREADY_JOINED", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); - } - - @ExceptionHandler(MessageNotFoundException.class) - public ResponseEntity handle(MessageNotFoundException e) { - ErrorResponse response = new ErrorResponse("MESSAGE_NOT_FOUND", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); - } - - @ExceptionHandler(ReadStatusNotFoundException.class) - public ResponseEntity handle(ReadStatusNotFoundException e) { - ErrorResponse response = new ErrorResponse("READ_STATUS_NOT_FOUND", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); - } - - @ExceptionHandler(ReadStatusDuplicationException.class) - public ResponseEntity handle(ReadStatusDuplicationException e) { - ErrorResponse response = new ErrorResponse("READ_STATUS_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); - } - - @ExceptionHandler(BinaryContentNotFoundException.class) - public ResponseEntity handle(BinaryContentNotFoundException e) { - ErrorResponse response = new ErrorResponse("BINARY_CONTENT_NOT_FOUND", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); - } - - @ExceptionHandler(MethodArgumentTypeMismatchException.class) - public ResponseEntity handle(MethodArgumentTypeMismatchException e) { - ErrorResponse response = new ErrorResponse("INVALID_PARAMETER_TYPE", "해당 파라미터가 유효하지 않습니다."); - return ResponseEntity.badRequest().body(response); - } - - @ExceptionHandler(BusinessException.class) - public ResponseEntity handle(BusinessException e) { - ErrorResponse response = new ErrorResponse("UNKNOWN_ERROR", "알 수 없는 오류가 발생했습니다."); - return ResponseEntity.badRequest().body(response); - } + @ExceptionHandler(EmailDuplicationException.class) + public ResponseEntity handle(EmailDuplicationException e) { + ErrorResponse response = new ErrorResponse("EMAIL_DUPLICATION_ERROR", e.getMessage()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity handle(UserNotFoundException e) { + ErrorResponse response = new ErrorResponse("USER_NOT_FOUND", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + } + + @ExceptionHandler(UserDuplicationException.class) + public ResponseEntity handle(UserDuplicationException e) { + ErrorResponse response = new ErrorResponse("USER_DUPLICATION_ERROR", e.getMessage()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + + @ExceptionHandler(UserStatusNotFoundException.class) + public ResponseEntity handle(UserStatusNotFoundException e) { + ErrorResponse response = new ErrorResponse("USER_STATUS_NOT_FOUND", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + } + + @ExceptionHandler(UserStatusDuplicationException.class) + public ResponseEntity handle(UserStatusDuplicationException e) { + ErrorResponse response = new ErrorResponse("USER_STATUS_DUPLICATION_ERROR", e.getMessage()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + + @ExceptionHandler(AuthenticationFailedException.class) + public ResponseEntity handle(AuthenticationFailedException e) { + ErrorResponse response = new ErrorResponse("AUTHENTICATION_FAILED", e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); + } + + @ExceptionHandler(ChannelNotFoundException.class) + public ResponseEntity handle(ChannelNotFoundException e) { + ErrorResponse response = new ErrorResponse("CHANNEL_NOT_FOUND", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + } + + @ExceptionHandler(ChannelDuplicationException.class) + public ResponseEntity handle(ChannelDuplicationException e) { + ErrorResponse response = new ErrorResponse("CHANNEL_DUPLICATION_ERROR", e.getMessage()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + + @ExceptionHandler(ChannelUpdateNotAllowedException.class) + public ResponseEntity handle(ChannelUpdateNotAllowedException e) { + ErrorResponse response = new ErrorResponse("CHANNEL_UPDATE_NOT_ALLOWED", e.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response); + } + + @ExceptionHandler(AlreadyJoinedException.class) + public ResponseEntity handle(AlreadyJoinedException e) { + ErrorResponse response = new ErrorResponse("ALREADY_JOINED", e.getMessage()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + + @ExceptionHandler(MessageNotFoundException.class) + public ResponseEntity handle(MessageNotFoundException e) { + ErrorResponse response = new ErrorResponse("MESSAGE_NOT_FOUND", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + } + + @ExceptionHandler(ReadStatusNotFoundException.class) + public ResponseEntity handle(ReadStatusNotFoundException e) { + ErrorResponse response = new ErrorResponse("READ_STATUS_NOT_FOUND", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + } + + @ExceptionHandler(ReadStatusDuplicationException.class) + public ResponseEntity handle(ReadStatusDuplicationException e) { + ErrorResponse response = new ErrorResponse("READ_STATUS_DUPLICATION_ERROR", e.getMessage()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + + @ExceptionHandler(BinaryContentNotFoundException.class) + public ResponseEntity handle(BinaryContentNotFoundException e) { + ErrorResponse response = new ErrorResponse("BINARY_CONTENT_NOT_FOUND", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handle(MethodArgumentTypeMismatchException e) { + ErrorResponse response = new ErrorResponse("INVALID_PARAMETER_TYPE", "해당 파라미터가 유효하지 않습니다."); + return ResponseEntity.badRequest().body(response); + } + + @ExceptionHandler(BusinessException.class) + public ResponseEntity handle(BusinessException e) { + ErrorResponse response = new ErrorResponse("UNKNOWN_ERROR", "알 수 없는 오류가 발생했습니다."); + return ResponseEntity.badRequest().body(response); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java index 7cad2aa87..0a86518d6 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java @@ -2,6 +2,8 @@ import com.sprint.mission.discodeit.message.dto.MessageInfo; import com.sprint.mission.discodeit.message.service.MessageService; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; @@ -9,17 +11,15 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; -import java.util.List; -import java.util.UUID; - @RequiredArgsConstructor @RestController @RequestMapping("/channels/{channelId}/messages") public class ChannelMessageController { - private final MessageService messageService; - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getMessages(@PathVariable UUID channelId) { - return ResponseEntity.ok(messageService.findAllByChannelId(channelId)); - } + private final MessageService messageService; + + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getMessages(@PathVariable UUID channelId) { + return ResponseEntity.ok(messageService.findAllByChannelId(channelId)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index 7a3458b36..db87f8e56 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -4,46 +4,50 @@ import com.sprint.mission.discodeit.message.dto.MessageInfo; import com.sprint.mission.discodeit.message.dto.MessageUpdateInfo; import com.sprint.mission.discodeit.message.service.MessageService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RequestMethod; +import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController @RequestMapping("/messages") public class MessageController { - private final MessageService messageService; - - @RequestMapping(method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity sendMessage(@RequestBody MessageCreateInfo messageInfo) { - return ResponseEntity.ok(messageService.createMessage(messageInfo)); - } - - @RequestMapping(value = "/{messageId}", method = RequestMethod.GET) - public ResponseEntity getMessage(@PathVariable UUID messageId) { - return ResponseEntity.ok(messageService.findMessage(messageId)); - } - - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getAllMessages() { - return ResponseEntity.ok(messageService.findAll()); - } - - @RequestMapping(value = "/{messageId}", method = RequestMethod.PATCH) - public ResponseEntity updateMessage( - @PathVariable UUID messageId, - @RequestBody MessageUpdateInfo messageInfo - ) { - messageService.updateMessage(messageId, messageInfo); - return ResponseEntity.noContent().build(); - } - - @RequestMapping(value = "/{messageId}", method = RequestMethod.DELETE) - public ResponseEntity deleteMessage(@PathVariable UUID messageId) { - messageService.deleteMessage(messageId); - return ResponseEntity.noContent().build(); - } + + private final MessageService messageService; + + @RequestMapping(method = RequestMethod.POST, consumes = "application/json") + public ResponseEntity sendMessage(@RequestBody MessageCreateInfo messageInfo) { + return ResponseEntity.ok(messageService.createMessage(messageInfo)); + } + + @RequestMapping(value = "/{messageId}", method = RequestMethod.GET) + public ResponseEntity getMessage(@PathVariable UUID messageId) { + return ResponseEntity.ok(messageService.findMessage(messageId)); + } + + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getAllMessages() { + return ResponseEntity.ok(messageService.findAll()); + } + + @RequestMapping(value = "/{messageId}", method = RequestMethod.PATCH) + public ResponseEntity updateMessage( + @PathVariable UUID messageId, + @RequestBody MessageUpdateInfo messageInfo + ) { + messageService.updateMessage(messageId, messageInfo); + return ResponseEntity.noContent().build(); + } + + @RequestMapping(value = "/{messageId}", method = RequestMethod.DELETE) + public ResponseEntity deleteMessage(@PathVariable UUID messageId) { + messageService.deleteMessage(messageId); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateInfo.java index e4b4f86af..8e9126845 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateInfo.java @@ -4,8 +4,10 @@ import java.util.UUID; public record MessageCreateInfo( - String content, - UUID senderId, - UUID channelId, - List attachments -) {} + String content, + UUID senderId, + UUID channelId, + List attachments +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageInfo.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageInfo.java index 55eb8d55c..3f2e42475 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageInfo.java @@ -4,9 +4,11 @@ import java.util.UUID; public record MessageInfo( - UUID messageId, - String content, - UUID senderId, - UUID channelId, - List attachmentIds -) {} + UUID messageId, + String content, + UUID senderId, + UUID channelId, + List attachmentIds +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateInfo.java index 0a9c87ce6..9e2604211 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateInfo.java @@ -1,5 +1,7 @@ package com.sprint.mission.discodeit.message.dto; public record MessageUpdateInfo( - String content -) {} + String content +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java index 4cd9eb636..c3de23c40 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java +++ b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java @@ -1,41 +1,41 @@ package com.sprint.mission.discodeit.message.entity; import com.sprint.mission.discodeit.common.CommonEntity; -import lombok.Getter; - import java.time.Instant; import java.util.List; import java.util.UUID; +import lombok.Getter; @Getter public class Message extends CommonEntity { - private static final long serialVersionUID = 1L; - private String content; - private final UUID senderId; - private final UUID channelId; - private final List attachmentIds; - - public Message(String content, UUID sender, UUID channel, List attachmentIds) { - this.content = content; - this.senderId = sender; - this.channelId = channel; - this.attachmentIds = attachmentIds; - } - - public void updateContent(String content) { - this.content = content; - this.updateAt = Instant.now(); - } - - public List getAttachmentIds() { - return List.copyOf(attachmentIds); - } - - public void addAttachmentId(UUID attachmentId) { - attachmentIds.add(attachmentId); - } - - public void removeAttachmentId(UUID attachmentId) { - attachmentIds.remove(attachmentId); - } + + private static final long serialVersionUID = 1L; + private final UUID senderId; + private final UUID channelId; + private final List attachmentIds; + private String content; + + public Message(String content, UUID sender, UUID channel, List attachmentIds) { + this.content = content; + this.senderId = sender; + this.channelId = channel; + this.attachmentIds = attachmentIds; + } + + public void updateContent(String content) { + this.content = content; + this.updateAt = Instant.now(); + } + + public List getAttachmentIds() { + return List.copyOf(attachmentIds); + } + + public void addAttachmentId(UUID attachmentId) { + attachmentIds.add(attachmentId); + } + + public void removeAttachmentId(UUID attachmentId) { + attachmentIds.remove(attachmentId); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/exception/MessageNotFoundException.java b/src/main/java/com/sprint/mission/discodeit/message/exception/MessageNotFoundException.java index 52570c93f..1e729e08d 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/exception/MessageNotFoundException.java +++ b/src/main/java/com/sprint/mission/discodeit/message/exception/MessageNotFoundException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class MessageNotFoundException extends BusinessException { - public MessageNotFoundException() { - super("해당 메시지를 찾을 수 없습니다."); - } + + public MessageNotFoundException() { + super("해당 메시지를 찾을 수 없습니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java index 3c79a9461..d18d50b59 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java @@ -4,42 +4,43 @@ import com.sprint.mission.discodeit.message.dto.MessageInfo; import com.sprint.mission.discodeit.message.dto.MessageUpdateInfo; import com.sprint.mission.discodeit.message.entity.Message; - import java.util.List; import java.util.UUID; public final class MessageMapper { - private MessageMapper(){} - public static MessageInfo toMessageInfo(Message message) { - return new MessageInfo( - message.getId(), - message.getContent(), - message.getSenderId(), - message.getChannelId(), - message.getAttachmentIds() - ); - } + private MessageMapper() { + } + + public static MessageInfo toMessageInfo(Message message) { + return new MessageInfo( + message.getId(), + message.getContent(), + message.getSenderId(), + message.getChannelId(), + message.getAttachmentIds() + ); + } - public static MessageCreateInfo toMessageCreateInfo( - String content, - UUID senderId, - UUID channelId, - List attachments - ) { - return new MessageCreateInfo( - content, - senderId, - channelId, - attachments - ); - } + public static MessageCreateInfo toMessageCreateInfo( + String content, + UUID senderId, + UUID channelId, + List attachments + ) { + return new MessageCreateInfo( + content, + senderId, + channelId, + attachments + ); + } - public static MessageUpdateInfo toMessageUpdateInfo( - String content - ) { - return new MessageUpdateInfo( - content - ); - } + public static MessageUpdateInfo toMessageUpdateInfo( + String content + ) { + return new MessageUpdateInfo( + content + ); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java index f022c481f..960c9d45f 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java @@ -1,11 +1,11 @@ package com.sprint.mission.discodeit.message.repository; import com.sprint.mission.discodeit.message.entity.Message; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -import java.io.*; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,104 +13,108 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") public class FileMessageRepository implements MessageRepository { - private final Path messagePath; - public FileMessageRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.messagePath = Paths.get(rootPath, "messages"); - } + private final Path messagePath; - @Override - public Optional findById(UUID messageId) { - Path messagePath = getMessagePath(messageId); + public FileMessageRepository( + @Value("${discodeit.repository.file-directory:data}") String rootPath + ) { + this.messagePath = Paths.get(rootPath, "messages"); + } - if (!Files.exists(messagePath)) { - return Optional.empty(); - } + @Override + public Optional findById(UUID messageId) { + Path messagePath = getMessagePath(messageId); - try (FileInputStream fis = new FileInputStream(messagePath.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return Optional.ofNullable((Message) ois.readObject()); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("메세지를 가져오는데 실패했습니다."); - } + if (!Files.exists(messagePath)) { + return Optional.empty(); } - @Override - public List findAll() { - if(Files.exists(messagePath)) { - try { - List messages = Files.list(messagePath) - .map(path -> { - try( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - Message message = (Message) ois.readObject(); - return message; - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("모든 메세지를 가져오는데 실패했습니다."); - } - }) - .toList(); - return messages; - } catch (IOException e) { - throw new RuntimeException("모든 메세지를 가져오는데 실패했습니다."); - } - } else { - return new ArrayList<>(); - } + try (FileInputStream fis = new FileInputStream(messagePath.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis)) { + return Optional.ofNullable((Message) ois.readObject()); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException("메세지를 가져오는데 실패했습니다."); } + } - @Override - public List findAllByUserId(UUID userId) { - return findAll().stream() - .filter(m -> m.getSenderId().equals(userId)) - .toList(); + @Override + public List findAll() { + if (Files.exists(messagePath)) { + try { + List messages = Files.list(messagePath) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Message message = (Message) ois.readObject(); + return message; + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException("모든 메세지를 가져오는데 실패했습니다."); + } + }) + .toList(); + return messages; + } catch (IOException e) { + throw new RuntimeException("모든 메세지를 가져오는데 실패했습니다."); + } + } else { + return new ArrayList<>(); } + } - @Override - public List findAllByChannelId(UUID channelId) { - return findAll().stream() - .filter(m -> m.getChannelId().equals(channelId)) - .toList(); - } + @Override + public List findAllByUserId(UUID userId) { + return findAll().stream() + .filter(m -> m.getSenderId().equals(userId)) + .toList(); + } - @Override - public void save(Message message) { - Path messagePath = getMessagePath(message.getId()); - try ( - FileOutputStream fos = new FileOutputStream(messagePath.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(message); - } catch (IOException e) { - throw new RuntimeException("메세지를 저장하는데 실패했습니다."); - } - } + @Override + public List findAllByChannelId(UUID channelId) { + return findAll().stream() + .filter(m -> m.getChannelId().equals(channelId)) + .toList(); + } - @Override - public void deleteById(UUID messageId) { - Path messagePath = getMessagePath(messageId); - try { - Files.delete(messagePath); - } catch (IOException e) { - throw new RuntimeException("메세지를 삭제하는데 실패했습니다."); - } + @Override + public void save(Message message) { + Path messagePath = getMessagePath(message.getId()); + try ( + FileOutputStream fos = new FileOutputStream(messagePath.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(message); + } catch (IOException e) { + throw new RuntimeException("메세지를 저장하는데 실패했습니다."); } + } - private Path getMessagePath(UUID messageId) { - try { - Files.createDirectories(messagePath); - } catch (IOException e) { - throw new IllegalStateException("messages 경로를 만드는데 실패했습니다."); - } + @Override + public void deleteById(UUID messageId) { + Path messagePath = getMessagePath(messageId); + try { + Files.delete(messagePath); + } catch (IOException e) { + throw new RuntimeException("메세지를 삭제하는데 실패했습니다."); + } + } - return messagePath.resolve(messageId.toString() + ".ser"); + private Path getMessagePath(UUID messageId) { + try { + Files.createDirectories(messagePath); + } catch (IOException e) { + throw new IllegalStateException("messages 경로를 만드는데 실패했습니다."); } + + return messagePath.resolve(messageId.toString() + ".ser"); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java index 342cd58d0..2bf8669cf 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java @@ -1,55 +1,56 @@ package com.sprint.mission.discodeit.message.repository; import com.sprint.mission.discodeit.message.entity.Message; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) public class JCFMessageRepository implements MessageRepository { - private final List data = new ArrayList<>(); - - @Override - public Optional findById(UUID messageId) { - return data.stream() - .filter(m -> m.getId().equals(messageId)) - .findFirst(); - } - @Override - public List findAll() { - return List.copyOf(data); + private final List data = new ArrayList<>(); + + @Override + public Optional findById(UUID messageId) { + return data.stream() + .filter(m -> m.getId().equals(messageId)) + .findFirst(); + } + + @Override + public List findAll() { + return List.copyOf(data); + } + + @Override + public List findAllByUserId(UUID userId) { + return data.stream() + .filter(m -> m.getSenderId().equals(userId)) + .toList(); + } + + @Override + public List findAllByChannelId(UUID channelId) { + return data.stream() + .filter(m -> m.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public void save(Message message) { + if (data.contains(message)) { + data.set(data.indexOf(message), message); + } else { + data.add(message); } + } - @Override - public List findAllByUserId(UUID userId) { - return data.stream() - .filter(m -> m.getSenderId().equals(userId)) - .toList(); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return data.stream() - .filter(m -> m.getChannelId().equals(channelId)) - .toList(); - } - - @Override - public void save(Message message) { - if(data.contains(message)) - data.set(data.indexOf(message), message); - else - data.add(message); - } - - @Override - public void deleteById(UUID messageId) { - data.removeIf(m -> m.getId().equals(messageId)); - } + @Override + public void deleteById(UUID messageId) { + data.removeIf(m -> m.getId().equals(messageId)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java index 6dc0bcd1d..befaf22f2 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java @@ -1,16 +1,21 @@ package com.sprint.mission.discodeit.message.repository; import com.sprint.mission.discodeit.message.entity.Message; - import java.util.List; import java.util.Optional; import java.util.UUID; public interface MessageRepository { - Optional findById(UUID messageId); - List findAll(); - List findAllByUserId(UUID userId); - List findAllByChannelId(UUID channelId); - void save(Message message); - void deleteById(UUID messageId); + + Optional findById(UUID messageId); + + List findAll(); + + List findAllByUserId(UUID userId); + + List findAllByChannelId(UUID channelId); + + void save(Message message); + + void deleteById(UUID messageId); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index fc35ce93c..3a12ce480 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -1,119 +1,119 @@ package com.sprint.mission.discodeit.message.service; +import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; +import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; +import com.sprint.mission.discodeit.channel.repository.ChannelRepository; import com.sprint.mission.discodeit.message.dto.MessageCreateInfo; import com.sprint.mission.discodeit.message.dto.MessageInfo; import com.sprint.mission.discodeit.message.dto.MessageUpdateInfo; -import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; -import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.message.entity.Message; import com.sprint.mission.discodeit.message.exception.MessageNotFoundException; -import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.message.mapper.MessageMapper; -import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; -import com.sprint.mission.discodeit.channel.repository.ChannelRepository; import com.sprint.mission.discodeit.message.repository.MessageRepository; +import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - import java.util.List; import java.util.Optional; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class BasicMessageService implements MessageService { - private final MessageRepository messageRepository; - private final UserRepository userRepository; - private final ChannelRepository channelRepository; - private final BinaryContentRepository contentRepository; - - @Override - public MessageInfo createMessage(MessageCreateInfo createInfo) { - List attachmentIds = createInfo.attachments() - .stream() - .map(bytes -> new BinaryContent("", "", bytes)) - .peek(contentRepository::save) - .map(BinaryContent::getId) - .toList(); - - Message message = new Message(createInfo.content(), createInfo.senderId(), - createInfo.channelId(), attachmentIds); - - User sender = userRepository.findById(message.getSenderId()) - .orElseThrow(UserNotFoundException::new); - Channel findChannel = channelRepository.findById(message.getChannelId()) - .orElseThrow(ChannelNotFoundException::new); - - sender.addMessageId(message.getId()); - findChannel.addMessageId(message.getId()); - - userRepository.save(sender); - channelRepository.save(findChannel); - messageRepository.save(message); - return MessageMapper.toMessageInfo(message); - } - - @Override - public MessageInfo findMessage(UUID messageId) { - Message message = messageRepository.findById(messageId) - .orElseThrow(MessageNotFoundException::new); - - return MessageMapper.toMessageInfo(message); - } - - @Override - public List findAll() { - return toMessageInfoList(messageRepository.findAll()); - } - - @Override - public List findAllByUserId(UUID userId) { - return toMessageInfoList(messageRepository.findAllByUserId(userId)); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return toMessageInfoList(messageRepository.findAllByChannelId(channelId)); - } - - @Override - public MessageInfo updateMessage(UUID messageId, MessageUpdateInfo messageInfo) { - Message findMessage = messageRepository.findById(messageId) - .orElseThrow(MessageNotFoundException::new); - - Optional.ofNullable(messageInfo.content()) - .ifPresent(findMessage::updateContent); - - messageRepository.save(findMessage); - return MessageMapper.toMessageInfo(findMessage); - } - - @Override - public void deleteMessage(UUID messageId) { - Message findMessage = messageRepository.findById(messageId) - .orElseThrow(MessageNotFoundException::new); - User findUser = userRepository.findById(findMessage.getSenderId()) - .orElseThrow(UserNotFoundException::new); - Channel findChannel = channelRepository.findById(findMessage.getChannelId()) - .orElseThrow(ChannelNotFoundException::new); - - findMessage.getAttachmentIds() - .forEach(contentRepository::deleteById); - - findUser.removeMessageId(messageId); - findChannel.removeMessageId(messageId); - - userRepository.save(findUser); - channelRepository.save(findChannel); - messageRepository.deleteById(messageId); - } - - private List toMessageInfoList(List messages) { - return messages.stream() - .map(MessageMapper::toMessageInfo) - .toList(); - } + + private final MessageRepository messageRepository; + private final UserRepository userRepository; + private final ChannelRepository channelRepository; + private final BinaryContentRepository contentRepository; + + @Override + public MessageInfo createMessage(MessageCreateInfo createInfo) { + List attachmentIds = createInfo.attachments() + .stream() + .map(bytes -> new BinaryContent("", "", bytes)) + .peek(contentRepository::save) + .map(BinaryContent::getId) + .toList(); + + Message message = new Message(createInfo.content(), createInfo.senderId(), + createInfo.channelId(), attachmentIds); + + User sender = userRepository.findById(message.getSenderId()) + .orElseThrow(UserNotFoundException::new); + Channel findChannel = channelRepository.findById(message.getChannelId()) + .orElseThrow(ChannelNotFoundException::new); + + sender.addMessageId(message.getId()); + findChannel.addMessageId(message.getId()); + + userRepository.save(sender); + channelRepository.save(findChannel); + messageRepository.save(message); + return MessageMapper.toMessageInfo(message); + } + + @Override + public MessageInfo findMessage(UUID messageId) { + Message message = messageRepository.findById(messageId) + .orElseThrow(MessageNotFoundException::new); + + return MessageMapper.toMessageInfo(message); + } + + @Override + public List findAll() { + return toMessageInfoList(messageRepository.findAll()); + } + + @Override + public List findAllByUserId(UUID userId) { + return toMessageInfoList(messageRepository.findAllByUserId(userId)); + } + + @Override + public List findAllByChannelId(UUID channelId) { + return toMessageInfoList(messageRepository.findAllByChannelId(channelId)); + } + + @Override + public MessageInfo updateMessage(UUID messageId, MessageUpdateInfo messageInfo) { + Message findMessage = messageRepository.findById(messageId) + .orElseThrow(MessageNotFoundException::new); + + Optional.ofNullable(messageInfo.content()) + .ifPresent(findMessage::updateContent); + + messageRepository.save(findMessage); + return MessageMapper.toMessageInfo(findMessage); + } + + @Override + public void deleteMessage(UUID messageId) { + Message findMessage = messageRepository.findById(messageId) + .orElseThrow(MessageNotFoundException::new); + User findUser = userRepository.findById(findMessage.getSenderId()) + .orElseThrow(UserNotFoundException::new); + Channel findChannel = channelRepository.findById(findMessage.getChannelId()) + .orElseThrow(ChannelNotFoundException::new); + + findMessage.getAttachmentIds() + .forEach(contentRepository::deleteById); + + findUser.removeMessageId(messageId); + findChannel.removeMessageId(messageId); + + userRepository.save(findUser); + channelRepository.save(findChannel); + messageRepository.deleteById(messageId); + } + + private List toMessageInfoList(List messages) { + return messages.stream() + .map(MessageMapper::toMessageInfo) + .toList(); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java index d8ca83e45..adb457a58 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java @@ -3,16 +3,22 @@ import com.sprint.mission.discodeit.message.dto.MessageCreateInfo; import com.sprint.mission.discodeit.message.dto.MessageInfo; import com.sprint.mission.discodeit.message.dto.MessageUpdateInfo; - import java.util.List; import java.util.UUID; public interface MessageService { - MessageInfo createMessage(MessageCreateInfo createInfo); - MessageInfo findMessage(UUID messageId); - List findAll(); - List findAllByUserId(UUID userId); - List findAllByChannelId(UUID channelId); - MessageInfo updateMessage(UUID messageId, MessageUpdateInfo messageInfo); - void deleteMessage(UUID messageId); + + MessageInfo createMessage(MessageCreateInfo createInfo); + + MessageInfo findMessage(UUID messageId); + + List findAll(); + + List findAllByUserId(UUID userId); + + List findAllByChannelId(UUID channelId); + + MessageInfo updateMessage(UUID messageId, MessageUpdateInfo messageInfo); + + void deleteMessage(UUID messageId); } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index 4e98d3442..b60fd1e1c 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -4,43 +4,48 @@ import com.sprint.mission.discodeit.readstatus.dto.ReadStatusInfo; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateInfo; import com.sprint.mission.discodeit.readstatus.service.ReadStatusService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RequestMethod; +import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController @RequestMapping("/readstatus") public class ReadStatusController { - private final ReadStatusService readStatusService; - - @RequestMapping(method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createReadStatus(@RequestBody ReadStatusCreateInfo statusInfo) { - return ResponseEntity.ok(readStatusService.createReadStatus(statusInfo)); - } - - @RequestMapping(value = "/{statusId}", method = RequestMethod.PATCH) - public ResponseEntity updateReadStatus(@PathVariable UUID statusId) { - readStatusService.updateReadStatus(statusId); - return ResponseEntity.noContent().build(); - } - - @RequestMapping(method = RequestMethod.PATCH) - public ResponseEntity updateReadStatus(ReadStatusUpdateInfo updateInfo) { - readStatusService.updateReadStatus(updateInfo); - return ResponseEntity.noContent().build(); - } - - @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) - public ResponseEntity> getReadStatuses(@PathVariable UUID userId) { - return ResponseEntity.ok(readStatusService.findAllByUserId(userId)); - } - - @RequestMapping(value = "/{statusId}", method = RequestMethod.GET) - public ResponseEntity getReadStatus(@PathVariable UUID statusId) { - return ResponseEntity.ok(readStatusService.find(statusId)); - } + + private final ReadStatusService readStatusService; + + @RequestMapping(method = RequestMethod.POST, consumes = "application/json") + public ResponseEntity createReadStatus( + @RequestBody ReadStatusCreateInfo statusInfo) { + return ResponseEntity.ok(readStatusService.createReadStatus(statusInfo)); + } + + @RequestMapping(value = "/{statusId}", method = RequestMethod.PATCH) + public ResponseEntity updateReadStatus(@PathVariable UUID statusId) { + readStatusService.updateReadStatus(statusId); + return ResponseEntity.noContent().build(); + } + + @RequestMapping(method = RequestMethod.PATCH) + public ResponseEntity updateReadStatus(ReadStatusUpdateInfo updateInfo) { + readStatusService.updateReadStatus(updateInfo); + return ResponseEntity.noContent().build(); + } + + @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) + public ResponseEntity> getReadStatuses(@PathVariable UUID userId) { + return ResponseEntity.ok(readStatusService.findAllByUserId(userId)); + } + + @RequestMapping(value = "/{statusId}", method = RequestMethod.GET) + public ResponseEntity getReadStatus(@PathVariable UUID statusId) { + return ResponseEntity.ok(readStatusService.find(statusId)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateInfo.java index 8388dab53..adfde18f4 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateInfo.java @@ -3,7 +3,8 @@ import java.util.UUID; public record ReadStatusCreateInfo( - UUID userId, - UUID channelId + UUID userId, + UUID channelId ) { + } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusInfo.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusInfo.java index 186b65bd8..cdbe16f40 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusInfo.java @@ -4,9 +4,10 @@ import java.util.UUID; public record ReadStatusInfo( - UUID statusId, - UUID userId, - UUID channelId, - Instant lastReadAt + UUID statusId, + UUID userId, + UUID channelId, + Instant lastReadAt ) { + } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateInfo.java index 50a3fd365..0abc64761 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateInfo.java @@ -3,7 +3,8 @@ import java.util.UUID; public record ReadStatusUpdateInfo( - UUID userId, - UUID channelId + UUID userId, + UUID channelId ) { + } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java index f5405e1fd..6dd3b408b 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java @@ -1,25 +1,25 @@ package com.sprint.mission.discodeit.readstatus.entity; import com.sprint.mission.discodeit.common.CommonEntity; -import lombok.Getter; - import java.time.Instant; import java.util.UUID; +import lombok.Getter; @Getter public class ReadStatus extends CommonEntity { - private static final long serialVersionUID = 1L; - private final UUID userId; - private final UUID channelId; - private Instant lastReadAt; - public ReadStatus(UUID userId, UUID channelId) { - this.userId = userId; - this.channelId = channelId; - this.lastReadAt = Instant.now(); - } + private static final long serialVersionUID = 1L; + private final UUID userId; + private final UUID channelId; + private Instant lastReadAt; + + public ReadStatus(UUID userId, UUID channelId) { + this.userId = userId; + this.channelId = channelId; + this.lastReadAt = Instant.now(); + } - public void updateLastReadAt() { - lastReadAt = Instant.now(); - } + public void updateLastReadAt() { + lastReadAt = Instant.now(); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/exception/ReadStatusDuplicationException.java b/src/main/java/com/sprint/mission/discodeit/readstatus/exception/ReadStatusDuplicationException.java index 0700ee27c..7319f3d01 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/exception/ReadStatusDuplicationException.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/exception/ReadStatusDuplicationException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class ReadStatusDuplicationException extends BusinessException { - public ReadStatusDuplicationException() { - super("해당 수신 정보가 이미 존재합니다."); - } + + public ReadStatusDuplicationException() { + super("해당 수신 정보가 이미 존재합니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/exception/ReadStatusNotFoundException.java b/src/main/java/com/sprint/mission/discodeit/readstatus/exception/ReadStatusNotFoundException.java index fae20278f..571bdefea 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/exception/ReadStatusNotFoundException.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/exception/ReadStatusNotFoundException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class ReadStatusNotFoundException extends BusinessException { - public ReadStatusNotFoundException() { - super("해당 수신 정보가 존재하지 않습니다."); - } + + public ReadStatusNotFoundException() { + super("해당 수신 정보가 존재하지 않습니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java index 206f5e7e1..23806dd43 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java @@ -5,28 +5,30 @@ import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; public class ReadStatusMapper { - private ReadStatusMapper(){} - public static ReadStatusInfo toReadStatusInfo(ReadStatus readStatus) { - return new ReadStatusInfo( - readStatus.getId(), - readStatus.getUserId(), - readStatus.getChannelId(), - readStatus.getLastReadAt() - ); - } + private ReadStatusMapper() { + } - public static ReadStatusCreateInfo toReadStatusCreateInfo(ReadStatus readStatus) { - return new ReadStatusCreateInfo( - readStatus.getUserId(), - readStatus.getChannelId() - ); - } + public static ReadStatusInfo toReadStatusInfo(ReadStatus readStatus) { + return new ReadStatusInfo( + readStatus.getId(), + readStatus.getUserId(), + readStatus.getChannelId(), + readStatus.getLastReadAt() + ); + } - public static ReadStatus toReadStatus(ReadStatusCreateInfo readStatusCreateInfo) { - return new ReadStatus( - readStatusCreateInfo.userId(), - readStatusCreateInfo.channelId() - ); - } + public static ReadStatusCreateInfo toReadStatusCreateInfo(ReadStatus readStatus) { + return new ReadStatusCreateInfo( + readStatus.getUserId(), + readStatus.getChannelId() + ); + } + + public static ReadStatus toReadStatus(ReadStatusCreateInfo readStatusCreateInfo) { + return new ReadStatus( + readStatusCreateInfo.userId(), + readStatusCreateInfo.channelId() + ); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/FileReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/FileReadStatusRepository.java index 2318dbd87..fe248c8ba 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/FileReadStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/FileReadStatusRepository.java @@ -1,120 +1,126 @@ package com.sprint.mission.discodeit.readstatus.repository; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -import java.io.*; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") public class FileReadStatusRepository implements ReadStatusRepository { - private final Path readStatusPath; - public FileReadStatusRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.readStatusPath = Paths.get(rootPath, "read-statuses"); + private final Path readStatusPath; + + public FileReadStatusRepository( + @Value("${discodeit.repository.file-directory:data}") String rootPath + ) { + this.readStatusPath = Paths.get(rootPath, "read-statuses"); + } + + @Override + public Optional findById(UUID id) { + Path readStatusPath = getReadStatusPath(id); + return Optional.ofNullable(read(readStatusPath)); + } + + @Override + public Optional findByUserIdAndChannelId(UUID userId, UUID channelId) { + return findAll().stream() + .filter(rs -> rs.getUserId().equals(userId) && rs.getChannelId().equals(channelId)) + .findFirst(); + } + + @Override + public List findAll() { + if (Files.exists(readStatusPath)) { + try { + return Files.list(readStatusPath) + .map(this::read) + .toList(); + } catch (IOException e) { + throw new RuntimeException("ReadStatus 파일 목록을 불러오는데 실패했습니다."); + } + } else { + return List.of(); } - - @Override - public Optional findById(UUID id) { - Path readStatusPath = getReadStatusPath(id); - return Optional.ofNullable(read(readStatusPath)); - } - - @Override - public Optional findByUserIdAndChannelId(UUID userId, UUID channelId) { - return findAll().stream() - .filter(rs -> rs.getUserId().equals(userId) && rs.getChannelId().equals(channelId)) - .findFirst(); + } + + @Override + public List findAllByUserId(UUID userId) { + return findAll().stream() + .filter(rs -> rs.getUserId().equals(userId)) + .toList(); + } + + @Override + public List findAllByChannelId(UUID channelId) { + return findAll().stream() + .filter(rs -> rs.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public void save(ReadStatus readStatus) { + Path readStatusPath = getReadStatusPath(readStatus.getId()); + write(readStatus, readStatusPath); + + } + + @Override + public void deleteById(UUID id) { + Path readStatusPath = getReadStatusPath(id); + try { + Files.delete(readStatusPath); + } catch (IOException e) { + throw new RuntimeException("ReadStatus 파일을 삭제하는데 실패했습니다."); } + } - @Override - public List findAll() { - if(Files.exists(readStatusPath)) { - try { - return Files.list(readStatusPath) - .map(this::read) - .toList(); - } catch (IOException e) { - throw new RuntimeException("ReadStatus 파일 목록을 불러오는데 실패했습니다."); - } - } else - return List.of(); + private ReadStatus read(Path path) { + if (!Files.exists(path)) { + throw new IllegalStateException("해당 파일이 존재하지 않습니다."); } - @Override - public List findAllByUserId(UUID userId) { - return findAll().stream() - .filter(rs -> rs.getUserId().equals(userId)) - .toList(); + try (FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis)) { + return (ReadStatus) ois.readObject(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("파일을 ReadStatus로 변환할 수 없습니다."); + } catch (IOException e) { + throw new RuntimeException("ReadStatus 파일이나 경로를 불러오는데 실패했습니다."); } + } - @Override - public List findAllByChannelId(UUID channelId) { - return findAll().stream() - .filter(rs -> rs.getChannelId().equals(channelId)) - .toList(); - } - - @Override - public void save(ReadStatus readStatus) { - Path readStatusPath = getReadStatusPath(readStatus.getId()); - write(readStatus, readStatusPath); - - } - - @Override - public void deleteById(UUID id) { - Path readStatusPath = getReadStatusPath(id); - try { - Files.delete(readStatusPath); - } catch (IOException e) { - throw new RuntimeException("ReadStatus 파일을 삭제하는데 실패했습니다."); - } + private void write(ReadStatus readStatus, Path path) { + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(readStatus); + } catch (IOException e) { + throw new RuntimeException("ReadStatus를 파일로 저장하는데 실패했습니다."); } + } - private ReadStatus read(Path path) { - if (!Files.exists(path)) - throw new IllegalStateException("해당 파일이 존재하지 않습니다."); - - try (FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return (ReadStatus) ois.readObject(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("파일을 ReadStatus로 변환할 수 없습니다."); - } catch (IOException e) { - throw new RuntimeException("ReadStatus 파일이나 경로를 불러오는데 실패했습니다."); - } - } - private void write(ReadStatus readStatus, Path path) { - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(readStatus); - } catch (IOException e) { - throw new RuntimeException("ReadStatus를 파일로 저장하는데 실패했습니다."); - } + private Path getReadStatusPath(UUID statusId) { + try { + Files.createDirectories(readStatusPath); + } catch (IOException e) { + throw new IllegalStateException("read-statuses 경로를 만드는데 실패했습니다."); } - - private Path getReadStatusPath(UUID statusId) { - try { - Files.createDirectories(readStatusPath); - } catch (IOException e) { - throw new IllegalStateException("read-statuses 경로를 만드는데 실패했습니다."); - } - - return readStatusPath.resolve(statusId.toString() + ".ser"); - } + return readStatusPath.resolve(statusId.toString() + ".ser"); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/JCFReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/JCFReadStatusRepository.java index 7dcbd6243..9f12006cb 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/JCFReadStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/JCFReadStatusRepository.java @@ -1,62 +1,63 @@ package com.sprint.mission.discodeit.readstatus.repository; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) public class JCFReadStatusRepository implements ReadStatusRepository { - private final List data = new ArrayList<>(); - - @Override - public Optional findById(UUID id) { - return data.stream() - .filter(rs -> rs.getId().equals(id)) - .findFirst(); - } - - @Override - public Optional findByUserIdAndChannelId(UUID userId, UUID channelId) { - return data.stream() - .filter(rs -> rs.getUserId().equals(userId) && rs.getChannelId().equals(channelId)) - .findFirst(); - } - - @Override - public List findAll() { - return List.copyOf(data); - } - - @Override - public List findAllByUserId(UUID userId) { - return data.stream() - .filter(rs -> rs.getUserId().equals(userId)) - .toList(); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return data.stream() - .filter(rs -> rs.getChannelId().equals(channelId)) - .toList(); - } - @Override - public void save(ReadStatus readStatus) { - if(data.contains(readStatus)) - data.set(data.indexOf(readStatus), readStatus); - else - data.add(readStatus); + private final List data = new ArrayList<>(); + + @Override + public Optional findById(UUID id) { + return data.stream() + .filter(rs -> rs.getId().equals(id)) + .findFirst(); + } + + @Override + public Optional findByUserIdAndChannelId(UUID userId, UUID channelId) { + return data.stream() + .filter(rs -> rs.getUserId().equals(userId) && rs.getChannelId().equals(channelId)) + .findFirst(); + } + + @Override + public List findAll() { + return List.copyOf(data); + } + + @Override + public List findAllByUserId(UUID userId) { + return data.stream() + .filter(rs -> rs.getUserId().equals(userId)) + .toList(); + } + + @Override + public List findAllByChannelId(UUID channelId) { + return data.stream() + .filter(rs -> rs.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public void save(ReadStatus readStatus) { + if (data.contains(readStatus)) { + data.set(data.indexOf(readStatus), readStatus); + } else { + data.add(readStatus); } + } - @Override - public void deleteById(UUID id) { - data.removeIf(rs -> rs.getId().equals(id)); - } + @Override + public void deleteById(UUID id) { + data.removeIf(rs -> rs.getId().equals(id)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/ReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/ReadStatusRepository.java index d81423be0..cf0cd5ec0 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/ReadStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/ReadStatusRepository.java @@ -1,17 +1,23 @@ package com.sprint.mission.discodeit.readstatus.repository; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; - import java.util.List; import java.util.Optional; import java.util.UUID; public interface ReadStatusRepository { - Optional findById(UUID id); - Optional findByUserIdAndChannelId(UUID userId, UUID channelId); - List findAll(); - List findAllByUserId(UUID userId); - List findAllByChannelId(UUID channelId); - void save(ReadStatus readStatus); - void deleteById(UUID id); + + Optional findById(UUID id); + + Optional findByUserIdAndChannelId(UUID userId, UUID channelId); + + List findAll(); + + List findAllByUserId(UUID userId); + + List findAllByChannelId(UUID channelId); + + void save(ReadStatus readStatus); + + void deleteById(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index 2f999c475..4e7f4f6b6 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -1,76 +1,79 @@ package com.sprint.mission.discodeit.readstatus.service; +import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; +import com.sprint.mission.discodeit.channel.repository.ChannelRepository; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateInfo; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusInfo; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateInfo; -import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusDuplicationException; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusNotFoundException; -import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.readstatus.mapper.ReadStatusMapper; -import com.sprint.mission.discodeit.channel.repository.ChannelRepository; import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class ReadStatusService { - private final ReadStatusRepository readStatusRepository; - private final UserRepository userRepository; - private final ChannelRepository channelRepository; - public ReadStatusInfo createReadStatus(ReadStatusCreateInfo statusInfo) { - User user = userRepository.findById(statusInfo.userId()) - .orElseThrow(UserNotFoundException::new); - Channel channel = channelRepository.findById(statusInfo.channelId()) - .orElseThrow(ChannelNotFoundException::new); + private final ReadStatusRepository readStatusRepository; + private final UserRepository userRepository; + private final ChannelRepository channelRepository; - if(readStatusRepository.findByUserIdAndChannelId(user.getId(), channel.getId()) - .isPresent()) throw new ReadStatusDuplicationException(); + public ReadStatusInfo createReadStatus(ReadStatusCreateInfo statusInfo) { + User user = userRepository.findById(statusInfo.userId()) + .orElseThrow(UserNotFoundException::new); + Channel channel = channelRepository.findById(statusInfo.channelId()) + .orElseThrow(ChannelNotFoundException::new); - ReadStatus readStatus = new ReadStatus(channel.getId(), user.getId()); - readStatusRepository.save(readStatus); - return ReadStatusMapper.toReadStatusInfo(readStatus); + if (readStatusRepository.findByUserIdAndChannelId(user.getId(), channel.getId()) + .isPresent()) { + throw new ReadStatusDuplicationException(); } - public ReadStatusInfo find(UUID statusId) { - ReadStatus readStatus = readStatusRepository.findById(statusId) - .orElseThrow(ReadStatusNotFoundException::new); - return ReadStatusMapper.toReadStatusInfo(readStatus); - } + ReadStatus readStatus = new ReadStatus(channel.getId(), user.getId()); + readStatusRepository.save(readStatus); + return ReadStatusMapper.toReadStatusInfo(readStatus); + } - public List findAllByUserId(UUID userId) { - return readStatusRepository.findAllByUserId(userId) - .stream() - .map(ReadStatusMapper::toReadStatusInfo) - .toList(); - } + public ReadStatusInfo find(UUID statusId) { + ReadStatus readStatus = readStatusRepository.findById(statusId) + .orElseThrow(ReadStatusNotFoundException::new); + return ReadStatusMapper.toReadStatusInfo(readStatus); + } - public ReadStatusInfo updateReadStatus(UUID statusId) { - ReadStatus readStatus = readStatusRepository.findById(statusId) - .orElseThrow(ReadStatusNotFoundException::new); - readStatus.updateLastReadAt(); - readStatusRepository.save(readStatus); - return ReadStatusMapper.toReadStatusInfo(readStatus); - } + public List findAllByUserId(UUID userId) { + return readStatusRepository.findAllByUserId(userId) + .stream() + .map(ReadStatusMapper::toReadStatusInfo) + .toList(); + } - public ReadStatusInfo updateReadStatus(ReadStatusUpdateInfo updateInfo) { - ReadStatus readStatus = readStatusRepository.findByUserIdAndChannelId(updateInfo.userId(), updateInfo.channelId()) - .orElseThrow(); - readStatus.updateLastReadAt(); - readStatusRepository.save(readStatus); - return ReadStatusMapper.toReadStatusInfo(readStatus); - } + public ReadStatusInfo updateReadStatus(UUID statusId) { + ReadStatus readStatus = readStatusRepository.findById(statusId) + .orElseThrow(ReadStatusNotFoundException::new); + readStatus.updateLastReadAt(); + readStatusRepository.save(readStatus); + return ReadStatusMapper.toReadStatusInfo(readStatus); + } - public void deleteReadStatus(UUID statusId) { - readStatusRepository.deleteById(statusId); - } + public ReadStatusInfo updateReadStatus(ReadStatusUpdateInfo updateInfo) { + ReadStatus readStatus = readStatusRepository.findByUserIdAndChannelId(updateInfo.userId(), + updateInfo.channelId()) + .orElseThrow(); + readStatus.updateLastReadAt(); + readStatusRepository.save(readStatus); + return ReadStatusMapper.toReadStatusInfo(readStatus); + } + + public void deleteReadStatus(UUID statusId) { + readStatusRepository.deleteById(statusId); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java index 3361bb0c1..5939ee9ee 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java @@ -2,22 +2,22 @@ import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.service.UserService; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RequiredArgsConstructor @RestController @RequestMapping("/api/user") public class ApiUserController { - private final UserService userService; - @RequestMapping(value = "/findAll", method = RequestMethod.GET) - public ResponseEntity> findAll() { - return ResponseEntity.ok(userService.findAllWithUserDTO()); - } + private final UserService userService; + + @RequestMapping(value = "/findAll", method = RequestMethod.GET) + public ResponseEntity> findAll() { + return ResponseEntity.ok(userService.findAllWithUserDTO()); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java index e26e41514..87c606247 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java @@ -14,10 +14,11 @@ @RequiredArgsConstructor @RequestMapping("/auth") public class AuthController { - private final AuthService authService; - @RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity login(@RequestBody UserLoginInfo loginInfo) { - return ResponseEntity.ok(authService.login(loginInfo)); - } + private final AuthService authService; + + @RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "application/json") + public ResponseEntity login(@RequestBody UserLoginInfo loginInfo) { + return ResponseEntity.ok(authService.login(loginInfo)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index 017eb0da6..9b9ecf1b5 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -10,83 +10,87 @@ import com.sprint.mission.discodeit.user.service.UserService; import com.sprint.mission.discodeit.userstatus.dto.UserStatusInfo; import com.sprint.mission.discodeit.userstatus.service.UserStatusService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @RequiredArgsConstructor @RestController @RequestMapping("/users") public class UserController { - private final UserService userService; - private final UserStatusService userStatusService; - @RequestMapping(value = "/{userId}", method = RequestMethod.GET) - public ResponseEntity getUser(@PathVariable UUID userId) { - return ResponseEntity.ok(userService.findUser(userId)); - } + private final UserService userService; + private final UserStatusService userStatusService; - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getAllUsers() { - return ResponseEntity.ok(userService.findAll()); - } + @RequestMapping(value = "/{userId}", method = RequestMethod.GET) + public ResponseEntity getUser(@PathVariable UUID userId) { + return ResponseEntity.ok(userService.findUser(userId)); + } - @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity createUser( - @RequestPart UserCreateInfo createInfo, - @RequestPart MultipartFile image - ) { - return ResponseEntity.ok(userService.createUser(createInfo, resolveProfileFile(image))); - } + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getAllUsers() { + return ResponseEntity.ok(userService.findAll()); + } - @RequestMapping(value = "/{userId}", method = RequestMethod.DELETE) - public ResponseEntity deleteUser(@PathVariable UUID userId) { - userService.deleteUser(userId); - return ResponseEntity.noContent().build(); - } + @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity createUser( + @RequestPart UserCreateInfo createInfo, + @RequestPart MultipartFile image + ) { + return ResponseEntity.ok(userService.createUser(createInfo, resolveProfileFile(image))); + } - @RequestMapping(value = "/{userId}", method = RequestMethod.PATCH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity updateUser( - @PathVariable UUID userId, - @RequestPart UserUpdateInfo updateInfo, - @RequestPart MultipartFile image + @RequestMapping(value = "/{userId}", method = RequestMethod.DELETE) + public ResponseEntity deleteUser(@PathVariable UUID userId) { + userService.deleteUser(userId); + return ResponseEntity.noContent().build(); + } - ) { - userService.updateUser(userId, updateInfo, resolveProfileFile(image)); - return ResponseEntity.noContent().build(); - } + @RequestMapping(value = "/{userId}", method = RequestMethod.PATCH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity updateUser( + @PathVariable UUID userId, + @RequestPart UserUpdateInfo updateInfo, + @RequestPart MultipartFile image - @RequestMapping(value = "/status/{userId}", method = RequestMethod.PATCH) - public ResponseEntity updateUserStatusByUserId(@PathVariable UUID userId) { - return ResponseEntity.ok(userStatusService.updateUserStatusByUserId(userId)); - } + ) { + userService.updateUser(userId, updateInfo, resolveProfileFile(image)); + return ResponseEntity.noContent().build(); + } - @RequestMapping(value = "/status/{statusId}", method = RequestMethod.GET) - public ResponseEntity getUserStatus(@PathVariable UUID statusId) { - return ResponseEntity.ok(userStatusService.findUserStatus(statusId)); - } + @RequestMapping(value = "/status/{userId}", method = RequestMethod.PATCH) + public ResponseEntity updateUserStatusByUserId(@PathVariable UUID userId) { + return ResponseEntity.ok(userStatusService.updateUserStatusByUserId(userId)); + } + + @RequestMapping(value = "/status/{statusId}", method = RequestMethod.GET) + public ResponseEntity getUserStatus(@PathVariable UUID statusId) { + return ResponseEntity.ok(userStatusService.findUserStatus(statusId)); + } - private Optional resolveProfileFile(MultipartFile profileFile) { - if (profileFile.isEmpty()) { - return Optional.empty(); - } else { - try { - BinaryContentCreateInfo contentInfo = new BinaryContentCreateInfo( - profileFile.getOriginalFilename(), - profileFile.getContentType(), - profileFile.getBytes() - ); - return Optional.of(contentInfo); - } catch (IOException e) { - throw new BinaryContentNotFoundException(); - } - } + private Optional resolveProfileFile(MultipartFile profileFile) { + if (profileFile.isEmpty()) { + return Optional.empty(); + } else { + try { + BinaryContentCreateInfo contentInfo = new BinaryContentCreateInfo( + profileFile.getOriginalFilename(), + profileFile.getContentType(), + profileFile.getBytes() + ); + return Optional.of(contentInfo); + } catch (IOException e) { + throw new BinaryContentNotFoundException(); + } } + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java index 4f76a5927..3fc849bab 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java @@ -1,7 +1,9 @@ package com.sprint.mission.discodeit.user.dto; public record UserCreateInfo( - String userName, - String password, - String email -) {} + String userName, + String password, + String email +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserDto.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserDto.java index b5ce8847d..df2df97dc 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserDto.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserDto.java @@ -4,12 +4,13 @@ import java.util.UUID; public record UserDto( - UUID id, - Instant createdAt, - Instant updatedAt, - String username, - String email, - UUID profileId, - Boolean online + UUID id, + Instant createdAt, + Instant updatedAt, + String username, + String email, + UUID profileId, + Boolean online ) { + } diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java index 73a0436d2..af9231de8 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java @@ -3,9 +3,11 @@ import java.util.UUID; public record UserInfo( - UUID userId, - String userName, - String email, - UUID profileId, - UUID statusId -) {} + UUID userId, + String userName, + String email, + UUID profileId, + UUID statusId +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfoWithStatus.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfoWithStatus.java index b75f375f0..6ed3ef92f 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfoWithStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfoWithStatus.java @@ -3,10 +3,12 @@ import java.util.UUID; public record UserInfoWithStatus( - UUID userId, - String userName, - String email, - UUID profileId, - UUID statusId, - boolean isOnline -) {} + UUID userId, + String userName, + String email, + UUID profileId, + UUID statusId, + boolean isOnline +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserLoginInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserLoginInfo.java index e2e2a2a21..7563195f1 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserLoginInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserLoginInfo.java @@ -1,6 +1,8 @@ package com.sprint.mission.discodeit.user.dto; public record UserLoginInfo( - String userName, - String password -) {} + String userName, + String password +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java index 7e84a1dd2..a7712e2cf 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java @@ -3,8 +3,10 @@ import java.util.UUID; public record UserUpdateInfo( - String userName, - String password, - String email, - UUID profileId -) {} + String userName, + String password, + String email, + UUID profileId +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java index de3661079..6733b0635 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java +++ b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java @@ -1,75 +1,75 @@ package com.sprint.mission.discodeit.user.entity; import com.sprint.mission.discodeit.common.CommonEntity; -import lombok.Getter; -import lombok.Setter; - import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import lombok.Getter; +import lombok.Setter; @Getter public class User extends CommonEntity { - private static final long serialVersionUID = 1L; - private String userName; - private String password; - private String email; - @Setter - private UUID profileId; - private final List channelIds = new ArrayList<>(); - private final List messageIds = new ArrayList<>(); - public User(String userName, String password, String email) { - this.userName = userName; - this.password = password; - this.email = email; - } + private static final long serialVersionUID = 1L; + private final List channelIds = new ArrayList<>(); + private final List messageIds = new ArrayList<>(); + private String userName; + private String password; + private String email; + @Setter + private UUID profileId; + + public User(String userName, String password, String email) { + this.userName = userName; + this.password = password; + this.email = email; + } - public List getChannelIds() { - return List.copyOf(channelIds); - } + public List getChannelIds() { + return List.copyOf(channelIds); + } - public List getMessageIds() { - return List.copyOf(messageIds); - } + public List getMessageIds() { + return List.copyOf(messageIds); + } - public void updateUserName(String userName) { - this.userName = userName; - this.updateAt = Instant.now(); - } + public void updateUserName(String userName) { + this.userName = userName; + this.updateAt = Instant.now(); + } - public void updatePassword(String password) { - this.password = password; - this.updateAt = Instant.now(); - } + public void updatePassword(String password) { + this.password = password; + this.updateAt = Instant.now(); + } - public void updateEmail(String email) { - this.email = email; - this.updateAt = Instant.now(); - } + public void updateEmail(String email) { + this.email = email; + this.updateAt = Instant.now(); + } - public void addChannelId(UUID channelId) { - channelIds.add(channelId); - this.updateAt = Instant.now(); - } + public void addChannelId(UUID channelId) { + channelIds.add(channelId); + this.updateAt = Instant.now(); + } - public void removeChannelId(UUID channelId) { - channelIds.remove(channelId); - this.updateAt = Instant.now(); - } + public void removeChannelId(UUID channelId) { + channelIds.remove(channelId); + this.updateAt = Instant.now(); + } - public void addMessageId(UUID messageId) { - messageIds.add(messageId); - this.updateAt = Instant.now(); - } + public void addMessageId(UUID messageId) { + messageIds.add(messageId); + this.updateAt = Instant.now(); + } - public void removeMessageId(UUID messageId) { - messageIds.remove(messageId); - this.updateAt = Instant.now(); - } + public void removeMessageId(UUID messageId) { + messageIds.remove(messageId); + this.updateAt = Instant.now(); + } - public boolean isProfileImageUploaded() { - return profileId != null; - } + public boolean isProfileImageUploaded() { + return profileId != null; + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/exception/AuthenticationFailedException.java b/src/main/java/com/sprint/mission/discodeit/user/exception/AuthenticationFailedException.java index a18638fb0..8e3441be9 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/exception/AuthenticationFailedException.java +++ b/src/main/java/com/sprint/mission/discodeit/user/exception/AuthenticationFailedException.java @@ -4,7 +4,7 @@ public class AuthenticationFailedException extends BusinessException { - public AuthenticationFailedException() { - super("아이디 또는 비밀번호가 올바르지 않습니다."); - } + public AuthenticationFailedException() { + super("아이디 또는 비밀번호가 올바르지 않습니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/exception/EmailDuplicationException.java b/src/main/java/com/sprint/mission/discodeit/user/exception/EmailDuplicationException.java index 628aa45da..e274a79ae 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/exception/EmailDuplicationException.java +++ b/src/main/java/com/sprint/mission/discodeit/user/exception/EmailDuplicationException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class EmailDuplicationException extends BusinessException { - public EmailDuplicationException() { - super("해당 이메일이 이미 존재합니다."); - } + + public EmailDuplicationException() { + super("해당 이메일이 이미 존재합니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/exception/UserDuplicationException.java b/src/main/java/com/sprint/mission/discodeit/user/exception/UserDuplicationException.java index 275fa9a1a..0da713beb 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/exception/UserDuplicationException.java +++ b/src/main/java/com/sprint/mission/discodeit/user/exception/UserDuplicationException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class UserDuplicationException extends BusinessException { - public UserDuplicationException() { - super("해당 사용자가 이미 존재합니다."); - } + + public UserDuplicationException() { + super("해당 사용자가 이미 존재합니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/exception/UserNotFoundException.java b/src/main/java/com/sprint/mission/discodeit/user/exception/UserNotFoundException.java index 8f12bdde6..2d4f02bf3 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/exception/UserNotFoundException.java +++ b/src/main/java/com/sprint/mission/discodeit/user/exception/UserNotFoundException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class UserNotFoundException extends BusinessException { - public UserNotFoundException() { - super("해당 사용자를 찾을 수 없습니다."); - } + + public UserNotFoundException() { + super("해당 사용자를 찾을 수 없습니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java index 672a48e28..6aed42a68 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java @@ -1,54 +1,55 @@ package com.sprint.mission.discodeit.user.mapper; -import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.entity.User; -import com.sprint.mission.discodeit.userstatus.entity.UserStatus; import com.sprint.mission.discodeit.user.dto.UserCreateInfo; +import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserInfoWithStatus; +import com.sprint.mission.discodeit.user.entity.User; +import com.sprint.mission.discodeit.userstatus.entity.UserStatus; public class UserMapper { - private UserMapper() { - } - public static UserInfo toUserInfo(User user, UserStatus userStatus) { - return new UserInfo( - user.getId(), - user.getUserName(), - user.getEmail(), - user.getProfileId(), - userStatus.getId() - ); - } + private UserMapper() { + } + + public static UserInfo toUserInfo(User user, UserStatus userStatus) { + return new UserInfo( + user.getId(), + user.getUserName(), + user.getEmail(), + user.getProfileId(), + userStatus.getId() + ); + } - public static UserInfoWithStatus toUserInfoWithStatus(User user, UserStatus userStatus) { - return new UserInfoWithStatus( - user.getId(), - user.getUserName(), - user.getEmail(), - user.getProfileId(), - userStatus.getId(), - userStatus.isOnline() - ); - } + public static UserInfoWithStatus toUserInfoWithStatus(User user, UserStatus userStatus) { + return new UserInfoWithStatus( + user.getId(), + user.getUserName(), + user.getEmail(), + user.getProfileId(), + userStatus.getId(), + userStatus.isOnline() + ); + } - public static UserDto toUserDto(User user, UserStatus userStatus) { - return new UserDto( - user.getId(), - user.getCreatedAt(), - user.getUpdateAt(), - user.getUserName(), - user.getEmail(), - user.getProfileId(), - userStatus.isOnline() - ); - } + public static UserDto toUserDto(User user, UserStatus userStatus) { + return new UserDto( + user.getId(), + user.getCreatedAt(), + user.getUpdateAt(), + user.getUserName(), + user.getEmail(), + user.getProfileId(), + userStatus.isOnline() + ); + } - public static User toUser(UserCreateInfo userInfo) { - return new User( - userInfo.userName(), - userInfo.password(), - userInfo.email() - ); - } + public static User toUser(UserCreateInfo userInfo) { + return new User( + userInfo.userName(), + userInfo.password(), + userInfo.email() + ); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java b/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java index 53f1cdf16..e6733305f 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java @@ -1,11 +1,11 @@ package com.sprint.mission.discodeit.user.repository; import com.sprint.mission.discodeit.user.entity.User; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -import java.io.*; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,114 +13,118 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") public class FileUserRepository implements UserRepository { - private final Path userPath; - public FileUserRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.userPath = Paths.get(rootPath, "users"); - } + private final Path userPath; - @Override - public Optional findById(UUID userId) { - Path userPath = getUserPath(userId); - if (!Files.exists(userPath)) { - return Optional.empty(); - } + public FileUserRepository( + @Value("${discodeit.repository.file-directory:data}") String rootPath + ) { + this.userPath = Paths.get(rootPath, "users"); + } - try ( - FileInputStream fis = new FileInputStream(userPath.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - return Optional.ofNullable((User) ois.readObject()); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("유저를 가져오는데 실패했습니다."); - } + @Override + public Optional findById(UUID userId) { + Path userPath = getUserPath(userId); + if (!Files.exists(userPath)) { + return Optional.empty(); } - @Override - public Optional findByName(String userName) { - return findAll().stream() - .filter(u -> u.getUserName().equals(userName)) - .findFirst(); + try ( + FileInputStream fis = new FileInputStream(userPath.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return Optional.ofNullable((User) ois.readObject()); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException("유저를 가져오는데 실패했습니다."); } + } - @Override - public Optional findByEmail(String email) { - return findAll().stream() - .filter(u -> u.getEmail().equals(email)) - .findFirst(); - } + @Override + public Optional findByName(String userName) { + return findAll().stream() + .filter(u -> u.getUserName().equals(userName)) + .findFirst(); + } + + @Override + public Optional findByEmail(String email) { + return findAll().stream() + .filter(u -> u.getEmail().equals(email)) + .findFirst(); + } - @Override - public List findAll() { - if(Files.exists(userPath)) { - try { - List users = Files.list(userPath) - .map(path -> { - try( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - User user = (User) ois.readObject(); - return user; - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("모든 유저를 가져오는데 실패했습니다."); - } - }) - .toList(); - return users; - } catch (IOException e) { + @Override + public List findAll() { + if (Files.exists(userPath)) { + try { + List users = Files.list(userPath) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + User user = (User) ois.readObject(); + return user; + } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("모든 유저를 가져오는데 실패했습니다."); - } - } else { - return new ArrayList<>(); - } + } + }) + .toList(); + return users; + } catch (IOException e) { + throw new RuntimeException("모든 유저를 가져오는데 실패했습니다."); + } + } else { + return new ArrayList<>(); } + } - @Override - public List findAllByChannelId(UUID channelId) { - return findAll().stream() - .filter(u -> u.getChannelIds() - .stream() - .anyMatch(findChannelId -> findChannelId.equals(channelId))) - .toList(); - } + @Override + public List findAllByChannelId(UUID channelId) { + return findAll().stream() + .filter(u -> u.getChannelIds() + .stream() + .anyMatch(findChannelId -> findChannelId.equals(channelId))) + .toList(); + } - @Override - public void save(User user) { - Path userPath = getUserPath(user.getId()); - try ( - FileOutputStream fos = new FileOutputStream(userPath.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(user); - } catch (IOException e) { - throw new RuntimeException("유저를 저장하는데 실패했습니다."); - } + @Override + public void save(User user) { + Path userPath = getUserPath(user.getId()); + try ( + FileOutputStream fos = new FileOutputStream(userPath.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(user); + } catch (IOException e) { + throw new RuntimeException("유저를 저장하는데 실패했습니다."); } + } - @Override - public void deleteById(UUID userId) { - Path userPath = getUserPath(userId); - try { - Files.deleteIfExists(userPath); - } catch (IOException e) { - throw new RuntimeException("유저를 삭제하는데 실패했습니다."); - } + @Override + public void deleteById(UUID userId) { + Path userPath = getUserPath(userId); + try { + Files.deleteIfExists(userPath); + } catch (IOException e) { + throw new RuntimeException("유저를 삭제하는데 실패했습니다."); } + } - private Path getUserPath(UUID userId) { - try { - Files.createDirectories(userPath); - } catch (IOException e) { - throw new IllegalStateException("users 경로를 만드는데 실패했습니다."); - } - - return userPath.resolve(userId.toString() + ".ser"); + private Path getUserPath(UUID userId) { + try { + Files.createDirectories(userPath); + } catch (IOException e) { + throw new IllegalStateException("users 경로를 만드는데 실패했습니다."); } + + return userPath.resolve(userId.toString() + ".ser"); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java b/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java index a61500d34..213c80462 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java @@ -1,64 +1,65 @@ package com.sprint.mission.discodeit.user.repository; import com.sprint.mission.discodeit.user.entity.User; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) public class JCFUserRepository implements UserRepository { - private final List data = new ArrayList<>(); - @Override - public Optional findById(UUID userId) { - return data.stream() - .filter(u -> u.getId().equals(userId)) - .findFirst(); - } + private final List data = new ArrayList<>(); - @Override - public Optional findByName(String userName) { - return data.stream() - .filter(u -> u.getUserName().equals(userName)) - .findFirst(); - } + @Override + public Optional findById(UUID userId) { + return data.stream() + .filter(u -> u.getId().equals(userId)) + .findFirst(); + } - @Override - public List findAll() { - return List.copyOf(data); - } + @Override + public Optional findByName(String userName) { + return data.stream() + .filter(u -> u.getUserName().equals(userName)) + .findFirst(); + } - @Override - public List findAllByChannelId(UUID channelId) { - return data.stream() - .filter(u -> u.getChannelIds() - .stream() - .anyMatch(findChannelId -> findChannelId.equals(channelId))) - .toList(); - } + @Override + public List findAll() { + return List.copyOf(data); + } - @Override - public Optional findByEmail(String email) { - return data.stream() - .filter(u -> u.getEmail().equals(email)) - .findFirst(); - } + @Override + public List findAllByChannelId(UUID channelId) { + return data.stream() + .filter(u -> u.getChannelIds() + .stream() + .anyMatch(findChannelId -> findChannelId.equals(channelId))) + .toList(); + } - @Override - public void save(User user) { - if(data.contains(user)) - data.set(data.indexOf(user), user); - else - data.add(user); - } + @Override + public Optional findByEmail(String email) { + return data.stream() + .filter(u -> u.getEmail().equals(email)) + .findFirst(); + } - @Override - public void deleteById(UUID userId) { - data.removeIf(u -> u.getId().equals(userId)); + @Override + public void save(User user) { + if (data.contains(user)) { + data.set(data.indexOf(user), user); + } else { + data.add(user); } + } + + @Override + public void deleteById(UUID userId) { + data.removeIf(u -> u.getId().equals(userId)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/repository/UserRepository.java b/src/main/java/com/sprint/mission/discodeit/user/repository/UserRepository.java index bea412823..639c3c69e 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/repository/UserRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/user/repository/UserRepository.java @@ -1,17 +1,23 @@ package com.sprint.mission.discodeit.user.repository; import com.sprint.mission.discodeit.user.entity.User; - import java.util.List; import java.util.Optional; import java.util.UUID; public interface UserRepository { - Optional findById(UUID userId); - Optional findByName(String userName); - Optional findByEmail(String email); - List findAll(); - List findAllByChannelId(UUID channelId); - void save(User user); - void deleteById(UUID userId); + + Optional findById(UUID userId); + + Optional findByName(String userName); + + Optional findByEmail(String email); + + List findAll(); + + List findAllByChannelId(UUID channelId); + + void save(User user); + + void deleteById(UUID userId); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java index f01a3860d..ccda97e83 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java @@ -1,13 +1,13 @@ package com.sprint.mission.discodeit.user.service; +import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserLoginInfo; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.AuthenticationFailedException; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; -import com.sprint.mission.discodeit.userstatus.entity.UserStatus; import com.sprint.mission.discodeit.user.mapper.UserMapper; import com.sprint.mission.discodeit.user.repository.UserRepository; -import com.sprint.mission.discodeit.user.dto.UserInfo; +import com.sprint.mission.discodeit.userstatus.entity.UserStatus; import com.sprint.mission.discodeit.userstatus.exception.UserStatusNotFoundException; import com.sprint.mission.discodeit.userstatus.repository.UserStatusRepository; import lombok.RequiredArgsConstructor; @@ -16,17 +16,21 @@ @RequiredArgsConstructor @Service public class AuthService { - private final UserRepository userRepository; - private final UserStatusRepository userStatusRepository; - public UserInfo login(UserLoginInfo userLoginInfo) { - User findUser = userRepository.findByName(userLoginInfo.userName()) - .orElseThrow(UserNotFoundException::new); - if (!findUser.getPassword().equals(userLoginInfo.password())) - throw new AuthenticationFailedException(); - UserStatus status = userStatusRepository.findByUserId(findUser.getId()) - .orElseThrow(UserStatusNotFoundException::new);; - status.updateLastOnlineAt(); - userStatusRepository.save(status); - return UserMapper.toUserInfo(findUser, status); + + private final UserRepository userRepository; + private final UserStatusRepository userStatusRepository; + + public UserInfo login(UserLoginInfo userLoginInfo) { + User findUser = userRepository.findByName(userLoginInfo.userName()) + .orElseThrow(UserNotFoundException::new); + if (!findUser.getPassword().equals(userLoginInfo.password())) { + throw new AuthenticationFailedException(); } + UserStatus status = userStatusRepository.findByUserId(findUser.getId()) + .orElseThrow(UserStatusNotFoundException::new); + ; + status.updateLastOnlineAt(); + userStatusRepository.save(status); + return UserMapper.toUserInfo(findUser, status); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index a8c3950d1..c847de1dd 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -2,163 +2,173 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; -import com.sprint.mission.discodeit.user.dto.*; +import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.channel.repository.ChannelRepository; +import com.sprint.mission.discodeit.user.dto.UserCreateInfo; +import com.sprint.mission.discodeit.user.dto.UserDto; +import com.sprint.mission.discodeit.user.dto.UserInfo; +import com.sprint.mission.discodeit.user.dto.UserInfoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserUpdateInfo; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.EmailDuplicationException; import com.sprint.mission.discodeit.user.exception.UserDuplicationException; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; -import com.sprint.mission.discodeit.userstatus.entity.UserStatus; import com.sprint.mission.discodeit.user.mapper.UserMapper; -import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; -import com.sprint.mission.discodeit.channel.repository.ChannelRepository; import com.sprint.mission.discodeit.user.repository.UserRepository; +import com.sprint.mission.discodeit.userstatus.entity.UserStatus; import com.sprint.mission.discodeit.userstatus.exception.UserStatusNotFoundException; import com.sprint.mission.discodeit.userstatus.repository.UserStatusRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class BasicUserService implements UserService { - private final UserRepository userRepository; - private final ChannelRepository channelRepository; - private final BinaryContentRepository contentRepository; - private final UserStatusRepository userStatusRepository; - - @Override - public UserInfo createUser(UserCreateInfo userInfo, Optional image) { - // 유저 이름 & 이메일 검증 - validateUserExist(userInfo.userName()); - validateEmailExist(userInfo.email()); - - // 유저 생성 -> mapper로 대체 가능 - User user = new User(userInfo.userName(), userInfo.password(), userInfo.email()); - - // status 생성 - UserStatus status = new UserStatus(user.getId()); - - // profile image가 존재한다면 생성 - if(image.isPresent()) { - BinaryContentCreateInfo createInfo = image.get(); - BinaryContent profileImage = new BinaryContent(createInfo.fileName(), createInfo.contentType(), createInfo.content()); - user.setProfileId(profileImage.getId()); - contentRepository.save(profileImage); - } - - // Repo 저장 - userStatusRepository.save(status); - userRepository.save(user); - return UserMapper.toUserInfo(user, status); - } - - @Override - public UserInfoWithStatus findUser(UUID userId) { - User user = userRepository.findById(userId) - .orElseThrow(UserNotFoundException::new); - UserStatus status = userStatusRepository.findByUserId(user.getId()) - .orElseThrow(UserStatusNotFoundException::new); - return UserMapper.toUserInfoWithStatus(user, status); - } - - @Override - public List findAll() { - return userRepository.findAll() - .stream() - .map(user -> { - UserStatus status = userStatusRepository.findByUserId(user.getId()) - .orElseThrow(UserStatusNotFoundException::new); - return UserMapper.toUserInfoWithStatus(user, status); - }) - .toList(); - } - - public List findAllWithUserDTO() { - return userRepository.findAll() - .stream() - .map(user -> { - UserStatus status = userStatusRepository.findByUserId(user.getId()) - .orElseThrow(UserStatusNotFoundException::new); - return UserMapper.toUserDto(user, status); - }) - .toList(); - } - @Override - public List findAllByChannelId(UUID channelId) { - return userRepository.findAll() - .stream() - .filter(user -> user.getChannelIds().contains(channelId)) - .map(user -> { - UserStatus status = userStatusRepository.findByUserId(user.getId()) - .orElseThrow(UserStatusNotFoundException::new); - return UserMapper.toUserInfoWithStatus(user, status); - }) - .toList(); + private final UserRepository userRepository; + private final ChannelRepository channelRepository; + private final BinaryContentRepository contentRepository; + private final UserStatusRepository userStatusRepository; + + @Override + public UserInfo createUser(UserCreateInfo userInfo, Optional image) { + // 유저 이름 & 이메일 검증 + validateUserExist(userInfo.userName()); + validateEmailExist(userInfo.email()); + + // 유저 생성 -> mapper로 대체 가능 + User user = new User(userInfo.userName(), userInfo.password(), userInfo.email()); + + // status 생성 + UserStatus status = new UserStatus(user.getId()); + + // profile image가 존재한다면 생성 + if (image.isPresent()) { + BinaryContentCreateInfo createInfo = image.get(); + BinaryContent profileImage = new BinaryContent(createInfo.fileName(), + createInfo.contentType(), createInfo.content()); + user.setProfileId(profileImage.getId()); + contentRepository.save(profileImage); } - @Override - public UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, Optional image) { - validateUserExist(updateInfo.userName()); - validateEmailExist(updateInfo.email()); - User findUser = userRepository.findById(userId) - .orElseThrow(UserNotFoundException::new); - Optional.ofNullable(updateInfo.userName()) - .ifPresent(findUser::updateUserName); - Optional.ofNullable(updateInfo.password()) - .ifPresent(findUser::updatePassword); - Optional.ofNullable(updateInfo.email()) - .ifPresent(findUser::updateEmail); - - // profileId가 존재하면 업데이트 - if(image.isPresent()) { - if(findUser.isProfileImageUploaded()) - contentRepository.deleteById(findUser.getProfileId()); - BinaryContentCreateInfo createInfo = image.get(); - BinaryContent profileImage = new BinaryContent(createInfo.fileName(), createInfo.contentType(), createInfo.content()); - findUser.setProfileId(profileImage.getId()); - contentRepository.save(profileImage); - } - - // statusRepo.findByUserId로 찾기 - UserStatus status = userStatusRepository.findByUserId(findUser.getId()) - .map(findStatus -> { - findStatus.updateLastOnlineAt(); - return findStatus; - }) - .orElseThrow(UserStatusNotFoundException::new); - // status 업데이트 & save - userStatusRepository.save(status); - - userRepository.save(findUser); - return UserMapper.toUserInfo(findUser, status); + // Repo 저장 + userStatusRepository.save(status); + userRepository.save(user); + return UserMapper.toUserInfo(user, status); + } + + @Override + public UserInfoWithStatus findUser(UUID userId) { + User user = userRepository.findById(userId) + .orElseThrow(UserNotFoundException::new); + UserStatus status = userStatusRepository.findByUserId(user.getId()) + .orElseThrow(UserStatusNotFoundException::new); + return UserMapper.toUserInfoWithStatus(user, status); + } + + @Override + public List findAll() { + return userRepository.findAll() + .stream() + .map(user -> { + UserStatus status = userStatusRepository.findByUserId(user.getId()) + .orElseThrow(UserStatusNotFoundException::new); + return UserMapper.toUserInfoWithStatus(user, status); + }) + .toList(); + } + + public List findAllWithUserDTO() { + return userRepository.findAll() + .stream() + .map(user -> { + UserStatus status = userStatusRepository.findByUserId(user.getId()) + .orElseThrow(UserStatusNotFoundException::new); + return UserMapper.toUserDto(user, status); + }) + .toList(); + } + + @Override + public List findAllByChannelId(UUID channelId) { + return userRepository.findAll() + .stream() + .filter(user -> user.getChannelIds().contains(channelId)) + .map(user -> { + UserStatus status = userStatusRepository.findByUserId(user.getId()) + .orElseThrow(UserStatusNotFoundException::new); + return UserMapper.toUserInfoWithStatus(user, status); + }) + .toList(); + } + + @Override + public UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, + Optional image) { + validateUserExist(updateInfo.userName()); + validateEmailExist(updateInfo.email()); + User findUser = userRepository.findById(userId) + .orElseThrow(UserNotFoundException::new); + Optional.ofNullable(updateInfo.userName()) + .ifPresent(findUser::updateUserName); + Optional.ofNullable(updateInfo.password()) + .ifPresent(findUser::updatePassword); + Optional.ofNullable(updateInfo.email()) + .ifPresent(findUser::updateEmail); + + // profileId가 존재하면 업데이트 + if (image.isPresent()) { + if (findUser.isProfileImageUploaded()) { + contentRepository.deleteById(findUser.getProfileId()); + } + BinaryContentCreateInfo createInfo = image.get(); + BinaryContent profileImage = new BinaryContent(createInfo.fileName(), + createInfo.contentType(), createInfo.content()); + findUser.setProfileId(profileImage.getId()); + contentRepository.save(profileImage); } - @Override - public void deleteUser(UUID userId) { - User user = userRepository.findById(userId) - .orElseThrow(UserNotFoundException::new); - channelRepository.findAllByUserId(userId).forEach(channel -> { - channel.removeUserId(userId); - channelRepository.save(channel); - }); - if(user.isProfileImageUploaded()) - contentRepository.deleteById(user.getProfileId()); - userStatusRepository.deleteByUserId(userId); - userRepository.deleteById(userId); + // statusRepo.findByUserId로 찾기 + UserStatus status = userStatusRepository.findByUserId(findUser.getId()) + .map(findStatus -> { + findStatus.updateLastOnlineAt(); + return findStatus; + }) + .orElseThrow(UserStatusNotFoundException::new); + // status 업데이트 & save + userStatusRepository.save(status); + + userRepository.save(findUser); + return UserMapper.toUserInfo(findUser, status); + } + + @Override + public void deleteUser(UUID userId) { + User user = userRepository.findById(userId) + .orElseThrow(UserNotFoundException::new); + channelRepository.findAllByUserId(userId).forEach(channel -> { + channel.removeUserId(userId); + channelRepository.save(channel); + }); + if (user.isProfileImageUploaded()) { + contentRepository.deleteById(user.getProfileId()); } + userStatusRepository.deleteByUserId(userId); + userRepository.deleteById(userId); + } - private void validateUserExist(String userName) { - if(userRepository.findByName(userName).isPresent()) - throw new UserDuplicationException(); + private void validateUserExist(String userName) { + if (userRepository.findByName(userName).isPresent()) { + throw new UserDuplicationException(); } + } - private void validateEmailExist(String email) { - if(userRepository.findByEmail(email).isPresent()) - throw new EmailDuplicationException(); + private void validateEmailExist(String email) { + if (userRepository.findByEmail(email).isPresent()) { + throw new EmailDuplicationException(); } + } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java index a9c1c8ce7..884b98d97 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java @@ -1,18 +1,29 @@ package com.sprint.mission.discodeit.user.service; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; -import com.sprint.mission.discodeit.user.dto.*; - +import com.sprint.mission.discodeit.user.dto.UserCreateInfo; +import com.sprint.mission.discodeit.user.dto.UserDto; +import com.sprint.mission.discodeit.user.dto.UserInfo; +import com.sprint.mission.discodeit.user.dto.UserInfoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserUpdateInfo; import java.util.List; import java.util.Optional; import java.util.UUID; public interface UserService { - UserInfo createUser(UserCreateInfo userInfo, Optional image); - UserInfoWithStatus findUser(UUID userId); - List findAll(); - List findAllWithUserDTO(); - List findAllByChannelId(UUID channelId); - UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, Optional image); - void deleteUser(UUID userId); + + UserInfo createUser(UserCreateInfo userInfo, Optional image); + + UserInfoWithStatus findUser(UUID userId); + + List findAll(); + + List findAllWithUserDTO(); + + List findAllByChannelId(UUID channelId); + + UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, + Optional image); + + void deleteUser(UUID userId); } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateInfo.java index fad6a170a..2a12cc818 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateInfo.java @@ -3,5 +3,7 @@ import java.util.UUID; public record UserStatusCreateInfo( - UUID userId -) {} + UUID userId +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusInfo.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusInfo.java index 1967121a5..e17c93a6e 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusInfo.java @@ -4,9 +4,10 @@ import java.util.UUID; public record UserStatusInfo( - UUID statusId, - UUID userId, - Instant lastOnlineAt, - boolean isOnline -) -{} + UUID statusId, + UUID userId, + Instant lastOnlineAt, + boolean isOnline +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateInfo.java index 8d1d5cfa2..ee3fe96e9 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateInfo.java @@ -3,6 +3,7 @@ import java.util.UUID; public record UserStatusUpdateInfo( - UUID statusId -) -{} + UUID statusId +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java index fed0d6796..c716eb6bd 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java @@ -1,28 +1,28 @@ package com.sprint.mission.discodeit.userstatus.entity; import com.sprint.mission.discodeit.common.CommonEntity; -import lombok.Getter; - import java.time.Instant; import java.util.UUID; +import lombok.Getter; @Getter public class UserStatus extends CommonEntity { - private static final long serialVersionUID = 1L; - private final UUID userId; - private Instant lastOnlineAt; - private final int loginLimitSeconds = 60 * 5; - public UserStatus(UUID userId) { - this.userId = userId; - lastOnlineAt = Instant.now(); - } + private static final long serialVersionUID = 1L; + private final UUID userId; + private final int loginLimitSeconds = 60 * 5; + private Instant lastOnlineAt; + + public UserStatus(UUID userId) { + this.userId = userId; + lastOnlineAt = Instant.now(); + } - public void updateLastOnlineAt() { - lastOnlineAt = Instant.now(); - } + public void updateLastOnlineAt() { + lastOnlineAt = Instant.now(); + } - public boolean isOnline() { - return lastOnlineAt.isAfter(Instant.now().minusSeconds(loginLimitSeconds)); - } + public boolean isOnline() { + return lastOnlineAt.isAfter(Instant.now().minusSeconds(loginLimitSeconds)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/exception/UserStatusDuplicationException.java b/src/main/java/com/sprint/mission/discodeit/userstatus/exception/UserStatusDuplicationException.java index 7175b1b6f..7012e9c8b 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/exception/UserStatusDuplicationException.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/exception/UserStatusDuplicationException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class UserStatusDuplicationException extends BusinessException { - public UserStatusDuplicationException() { - super("해당 사용자 상태가 이미 존재합니다."); - } + + public UserStatusDuplicationException() { + super("해당 사용자 상태가 이미 존재합니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/exception/UserStatusNotFoundException.java b/src/main/java/com/sprint/mission/discodeit/userstatus/exception/UserStatusNotFoundException.java index 8f5f800cf..cff2fff88 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/exception/UserStatusNotFoundException.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/exception/UserStatusNotFoundException.java @@ -3,7 +3,8 @@ import com.sprint.mission.discodeit.exception.BusinessException; public class UserStatusNotFoundException extends BusinessException { - public UserStatusNotFoundException() { - super("해당 사용자 상태를 찾을 수 없습니다."); - } + + public UserStatusNotFoundException() { + super("해당 사용자 상태를 찾을 수 없습니다."); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java index 370cf81a5..7caa3b4ef 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java @@ -4,16 +4,18 @@ import com.sprint.mission.discodeit.userstatus.entity.UserStatus; public class UserStatusMapper { - private UserStatusMapper(){} - public static UserStatusInfo toUserStatusInfo(UserStatus userStatus) { - return new UserStatusInfo( - userStatus.getId(), - userStatus.getUserId(), - userStatus.getLastOnlineAt(), - userStatus.isOnline() - ); - } + private UserStatusMapper() { + } + + public static UserStatusInfo toUserStatusInfo(UserStatus userStatus) { + return new UserStatusInfo( + userStatus.getId(), + userStatus.getUserId(), + userStatus.getLastOnlineAt(), + userStatus.isOnline() + ); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/FileUserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/FileUserStatusRepository.java index ce8d1d620..af1e08500 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/FileUserStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/FileUserStatusRepository.java @@ -1,11 +1,11 @@ package com.sprint.mission.discodeit.userstatus.repository; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -import java.io.*; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,100 +13,105 @@ import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") public class FileUserStatusRepository implements UserStatusRepository { - private final Path userStatusPath; - public FileUserStatusRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - userStatusPath = Paths.get(rootPath, "user-statuses"); - } + private final Path userStatusPath; - @Override - public Optional findById(UUID id) { - Path userStatusPath = getUserStatusPath(id); - return Optional.ofNullable(read(userStatusPath)); - } + public FileUserStatusRepository( + @Value("${discodeit.repository.file-directory:data}") String rootPath + ) { + userStatusPath = Paths.get(rootPath, "user-statuses"); + } - @Override - public Optional findByUserId(UUID userId) { - return findAll().stream() - .filter(us -> us.getUserId().equals(userId)) - .findFirst(); - } + @Override + public Optional findById(UUID id) { + Path userStatusPath = getUserStatusPath(id); + return Optional.ofNullable(read(userStatusPath)); + } - @Override - public List findAll() { - if(Files.exists(userStatusPath)) { - try { - return Files.list(userStatusPath) - .map(this::read) - .toList(); - } catch (IOException e) { - throw new RuntimeException("UserStatus 파일 목록을 불러오는데 실패했습니다."); - } - } else { - return List.of(); - } - } + @Override + public Optional findByUserId(UUID userId) { + return findAll().stream() + .filter(us -> us.getUserId().equals(userId)) + .findFirst(); + } - @Override - public void save(UserStatus userStatus) { - Path userStatusPath = getUserStatusPath(userStatus.getId()); - write(userStatus, userStatusPath); + @Override + public List findAll() { + if (Files.exists(userStatusPath)) { + try { + return Files.list(userStatusPath) + .map(this::read) + .toList(); + } catch (IOException e) { + throw new RuntimeException("UserStatus 파일 목록을 불러오는데 실패했습니다."); + } + } else { + return List.of(); } + } - @Override - public void deleteById(UUID id) { - Path userStatusPath = getUserStatusPath(id); - try { - Files.delete(userStatusPath); - } catch (IOException e) { - throw new RuntimeException("UserStatus 파일을 삭제하는데 실패했습니다."); - } - } + @Override + public void save(UserStatus userStatus) { + Path userStatusPath = getUserStatusPath(userStatus.getId()); + write(userStatus, userStatusPath); + } - @Override - public void deleteByUserId(UUID userId) { - UserStatus userStatus = findByUserId(userId) - .orElseThrow(() -> new NoSuchElementException("해당 사용자를 찾을 수 없습니다.")); - deleteById(userStatus.getId()); + @Override + public void deleteById(UUID id) { + Path userStatusPath = getUserStatusPath(id); + try { + Files.delete(userStatusPath); + } catch (IOException e) { + throw new RuntimeException("UserStatus 파일을 삭제하는데 실패했습니다."); } + } + + @Override + public void deleteByUserId(UUID userId) { + UserStatus userStatus = findByUserId(userId) + .orElseThrow(() -> new NoSuchElementException("해당 사용자를 찾을 수 없습니다.")); + deleteById(userStatus.getId()); + } - private UserStatus read(Path path) { - if (!Files.exists(path)) - throw new IllegalStateException("해당 파일이 존재하지 않습니다."); + private UserStatus read(Path path) { + if (!Files.exists(path)) { + throw new IllegalStateException("해당 파일이 존재하지 않습니다."); + } - try (FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return (UserStatus) ois.readObject(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("파일을 UserStatus로 변환할 수 없습니다."); - } catch (IOException e) { - throw new RuntimeException("UserStatus 파일이나 경로를 불러오는데 실패했습니다."); - } + try (FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis)) { + return (UserStatus) ois.readObject(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("파일을 UserStatus로 변환할 수 없습니다."); + } catch (IOException e) { + throw new RuntimeException("UserStatus 파일이나 경로를 불러오는데 실패했습니다."); } + } - private void write(UserStatus userStatus, Path path) { - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(userStatus); - } catch (IOException e) { - throw new RuntimeException("UserStatus를 파일로 저장하는데 실패했습니다."); - } + private void write(UserStatus userStatus, Path path) { + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(userStatus); + } catch (IOException e) { + throw new RuntimeException("UserStatus를 파일로 저장하는데 실패했습니다."); } + } - private Path getUserStatusPath(UUID userId) { - try { - Files.createDirectories(userStatusPath); - } catch (IOException e) { - throw new IllegalStateException("user-statuses 경로를 만드는데 실패했습니다."); - } - return userStatusPath.resolve(userId.toString() + ".ser"); + private Path getUserStatusPath(UUID userId) { + try { + Files.createDirectories(userStatusPath); + } catch (IOException e) { + throw new IllegalStateException("user-statuses 경로를 만드는데 실패했습니다."); } + return userStatusPath.resolve(userId.toString() + ".ser"); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/JCFUserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/JCFUserStatusRepository.java index 58c84248c..3ebbd42bb 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/JCFUserStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/JCFUserStatusRepository.java @@ -1,53 +1,54 @@ package com.sprint.mission.discodeit.userstatus.repository; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; @Repository @ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) public class JCFUserStatusRepository implements UserStatusRepository { - private final List data = new ArrayList<>(); - @Override - public Optional findById(UUID id) { - return data.stream() - .filter(us -> us.getId().equals(id)) - .findFirst(); + private final List data = new ArrayList<>(); + + @Override + public Optional findById(UUID id) { + return data.stream() + .filter(us -> us.getId().equals(id)) + .findFirst(); + } + + @Override + public Optional findByUserId(UUID userId) { + return data.stream() + .filter(us -> us.getUserId().equals(userId)) + .findFirst(); + } + + @Override + public List findAll() { + return List.copyOf(data); + } + + @Override + public void save(UserStatus userStatus) { + if (data.contains(userStatus)) { + data.set(data.indexOf(userStatus), userStatus); + } else { + data.add(userStatus); } + } - @Override - public Optional findByUserId(UUID userId) { - return data.stream() - .filter(us -> us.getUserId().equals(userId)) - .findFirst(); - } + @Override + public void deleteById(UUID id) { + data.removeIf(us -> us.getId().equals(id)); + } - @Override - public List findAll() { - return List.copyOf(data); - } - - @Override - public void save(UserStatus userStatus) { - if(data.contains(userStatus)) - data.set(data.indexOf(userStatus), userStatus); - else - data.add(userStatus); - } - - @Override - public void deleteById(UUID id) { - data.removeIf(us -> us.getId().equals(id)); - } - - @Override - public void deleteByUserId(UUID userId) { - data.removeIf(us -> us.getUserId().equals(userId)); - } + @Override + public void deleteByUserId(UUID userId) { + data.removeIf(us -> us.getUserId().equals(userId)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/UserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/UserStatusRepository.java index dddd7517c..95c5c4e4b 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/UserStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/UserStatusRepository.java @@ -1,16 +1,21 @@ package com.sprint.mission.discodeit.userstatus.repository; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; - import java.util.List; import java.util.Optional; import java.util.UUID; public interface UserStatusRepository { - Optional findById(UUID id); - Optional findByUserId(UUID userId); - List findAll(); - void save(UserStatus userStatus); - void deleteById(UUID id); - void deleteByUserId(UUID userId); + + Optional findById(UUID id); + + Optional findByUserId(UUID userId); + + List findAll(); + + void save(UserStatus userStatus); + + void deleteById(UUID id); + + void deleteByUserId(UUID userId); } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index 9d68e5e6c..4396a10d0 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -1,67 +1,69 @@ package com.sprint.mission.discodeit.userstatus.service; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; +import com.sprint.mission.discodeit.user.repository.UserRepository; import com.sprint.mission.discodeit.userstatus.dto.UserStatusCreateInfo; import com.sprint.mission.discodeit.userstatus.dto.UserStatusInfo; import com.sprint.mission.discodeit.userstatus.dto.UserStatusUpdateInfo; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; -import com.sprint.mission.discodeit.userstatus.mapper.UserStatusMapper; -import com.sprint.mission.discodeit.user.repository.UserRepository; import com.sprint.mission.discodeit.userstatus.exception.UserStatusDuplicationException; import com.sprint.mission.discodeit.userstatus.exception.UserStatusNotFoundException; +import com.sprint.mission.discodeit.userstatus.mapper.UserStatusMapper; import com.sprint.mission.discodeit.userstatus.repository.UserStatusRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class UserStatusService { - private final UserStatusRepository userStatusRepository; - private final UserRepository userRepository; - public UserStatusInfo createUserStatus(UserStatusCreateInfo statusInfo) { - userRepository.findById(statusInfo.userId()) - .orElseThrow(UserNotFoundException::new); - if (userStatusRepository.findByUserId(statusInfo.userId()) - .isPresent()) throw new UserStatusDuplicationException(); - UserStatus userStatus = new UserStatus(statusInfo.userId()); - userStatusRepository.save(userStatus); - return UserStatusMapper.toUserStatusInfo(userStatus); - } + private final UserStatusRepository userStatusRepository; + private final UserRepository userRepository; - public UserStatusInfo findUserStatus(UUID statusId) { - UserStatus userStatus = userStatusRepository.findById(statusId) - .orElseThrow(UserStatusNotFoundException::new); - return UserStatusMapper.toUserStatusInfo(userStatus); + public UserStatusInfo createUserStatus(UserStatusCreateInfo statusInfo) { + userRepository.findById(statusInfo.userId()) + .orElseThrow(UserNotFoundException::new); + if (userStatusRepository.findByUserId(statusInfo.userId()) + .isPresent()) { + throw new UserStatusDuplicationException(); } + UserStatus userStatus = new UserStatus(statusInfo.userId()); + userStatusRepository.save(userStatus); + return UserStatusMapper.toUserStatusInfo(userStatus); + } - public List findAll() { - return userStatusRepository.findAll() - .stream() - .map(UserStatusMapper::toUserStatusInfo) - .toList(); - } + public UserStatusInfo findUserStatus(UUID statusId) { + UserStatus userStatus = userStatusRepository.findById(statusId) + .orElseThrow(UserStatusNotFoundException::new); + return UserStatusMapper.toUserStatusInfo(userStatus); + } - public UserStatusInfo updateUserStatus(UserStatusUpdateInfo statusInfo) { - UserStatus userStatus = userStatusRepository.findById(statusInfo.statusId()) - .orElseThrow(UserStatusNotFoundException::new); - userStatus.updateLastOnlineAt(); - userStatusRepository.save(userStatus); - return UserStatusMapper.toUserStatusInfo(userStatus); - } + public List findAll() { + return userStatusRepository.findAll() + .stream() + .map(UserStatusMapper::toUserStatusInfo) + .toList(); + } - public UserStatusInfo updateUserStatusByUserId(UUID userId) { - UserStatus userStatus = userStatusRepository.findByUserId(userId) - .orElseThrow(UserStatusNotFoundException::new); - userStatus.updateLastOnlineAt(); - userStatusRepository.save(userStatus); - return UserStatusMapper.toUserStatusInfo(userStatus); - } + public UserStatusInfo updateUserStatus(UserStatusUpdateInfo statusInfo) { + UserStatus userStatus = userStatusRepository.findById(statusInfo.statusId()) + .orElseThrow(UserStatusNotFoundException::new); + userStatus.updateLastOnlineAt(); + userStatusRepository.save(userStatus); + return UserStatusMapper.toUserStatusInfo(userStatus); + } - void deleteUserStatus(UUID statusId) { - userStatusRepository.deleteById(statusId); - } + public UserStatusInfo updateUserStatusByUserId(UUID userId) { + UserStatus userStatus = userStatusRepository.findByUserId(userId) + .orElseThrow(UserStatusNotFoundException::new); + userStatus.updateLastOnlineAt(); + userStatusRepository.save(userStatus); + return UserStatusMapper.toUserStatusInfo(userStatus); + } + + void deleteUserStatus(UUID statusId) { + userStatusRepository.deleteById(statusId); + } } diff --git a/src/main/resources/static/script.js b/src/main/resources/static/script.js index e63118b89..241352cb5 100644 --- a/src/main/resources/static/script.js +++ b/src/main/resources/static/script.js @@ -1,57 +1,62 @@ // API endpoints const API_BASE_URL = '/api'; const ENDPOINTS = { - USERS: `${API_BASE_URL}/user/findAll`, - BINARY_CONTENT: `${API_BASE_URL}/binaryContent/find` + USERS: `${API_BASE_URL}/user/findAll`, + BINARY_CONTENT: `${API_BASE_URL}/binaryContent/find` }; // Initialize the application document.addEventListener('DOMContentLoaded', () => { - fetchAndRenderUsers(); + fetchAndRenderUsers(); }); // Fetch users from the API async function fetchAndRenderUsers() { - try { - const response = await fetch(ENDPOINTS.USERS); - if (!response.ok) throw new Error('Failed to fetch users'); - const users = await response.json(); - renderUserList(users); - } catch (error) { - console.error('Error fetching users:', error); + try { + const response = await fetch(ENDPOINTS.USERS); + if (!response.ok) { + throw new Error('Failed to fetch users'); } + const users = await response.json(); + renderUserList(users); + } catch (error) { + console.error('Error fetching users:', error); + } } // Fetch user profile image async function fetchUserProfile(profileId) { - try { - const response = await fetch(`${ENDPOINTS.BINARY_CONTENT}?binaryContentId=${profileId}`); - if (!response.ok) throw new Error('Failed to fetch profile'); - const profile = await response.json(); - - // Convert base64 encoded bytes to data URL - return `data:${profile.contentType};base64,${profile.bytes}`; - } catch (error) { - console.error('Error fetching profile:', error); - return '/default-avatar.png'; // Fallback to default avatar + try { + const response = await fetch( + `${ENDPOINTS.BINARY_CONTENT}?binaryContentId=${profileId}`); + if (!response.ok) { + throw new Error('Failed to fetch profile'); } + const profile = await response.json(); + + // Convert base64 encoded bytes to data URL + return `data:${profile.contentType};base64,${profile.bytes}`; + } catch (error) { + console.error('Error fetching profile:', error); + return '/default-avatar.png'; // Fallback to default avatar + } } // Render user list async function renderUserList(users) { - const userListElement = document.getElementById('userList'); - userListElement.innerHTML = ''; // Clear existing content + const userListElement = document.getElementById('userList'); + userListElement.innerHTML = ''; // Clear existing content - for (const user of users) { - const userElement = document.createElement('div'); - userElement.className = 'user-item'; + for (const user of users) { + const userElement = document.createElement('div'); + userElement.className = 'user-item'; - // Get profile image URL - const profileUrl = user.profileId ? - await fetchUserProfile(user.profileId) : - '/default-avatar.png'; + // Get profile image URL + const profileUrl = user.profileId ? + await fetchUserProfile(user.profileId) : + '/default-avatar.png'; - userElement.innerHTML = ` + userElement.innerHTML = ` ${user.username} `; - userListElement.appendChild(userElement); - } + userListElement.appendChild(userElement); + } } \ No newline at end of file diff --git a/src/main/resources/static/styles.css b/src/main/resources/static/styles.css index b45f4e704..14f6172a5 100644 --- a/src/main/resources/static/styles.css +++ b/src/main/resources/static/styles.css @@ -1,80 +1,80 @@ * { - margin: 0; - padding: 0; - box-sizing: border-box; + margin: 0; + padding: 0; + box-sizing: border-box; } body { - font-family: Arial, sans-serif; - background-color: #f5f5f5; + font-family: Arial, sans-serif; + background-color: #f5f5f5; } .container { - max-width: 800px; - margin: 0 auto; - padding: 20px; + max-width: 800px; + margin: 0 auto; + padding: 20px; } h1 { - text-align: center; - margin-bottom: 30px; - color: #333; + text-align: center; + margin-bottom: 30px; + color: #333; } .user-list { - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .user-item { - display: flex; - align-items: center; - padding: 20px; - border-bottom: 1px solid #eee; + display: flex; + align-items: center; + padding: 20px; + border-bottom: 1px solid #eee; } .user-item:last-child { - border-bottom: none; + border-bottom: none; } .user-avatar { - width: 60px; - height: 60px; - border-radius: 50%; - margin-right: 20px; - object-fit: cover; + width: 60px; + height: 60px; + border-radius: 50%; + margin-right: 20px; + object-fit: cover; } .user-info { - flex-grow: 1; + flex-grow: 1; } .user-name { - font-size: 18px; - font-weight: bold; - color: #333; - margin-bottom: 5px; + font-size: 18px; + font-weight: bold; + color: #333; + margin-bottom: 5px; } .user-email { - font-size: 14px; - color: #666; + font-size: 14px; + color: #666; } .status-badge { - padding: 6px 12px; - border-radius: 20px; - font-size: 14px; - font-weight: bold; + padding: 6px 12px; + border-radius: 20px; + font-size: 14px; + font-weight: bold; } .online { - background-color: #4CAF50; - color: white; + background-color: #4CAF50; + color: white; } .offline { - background-color: #9e9e9e; - color: white; + background-color: #9e9e9e; + color: white; } \ No newline at end of file diff --git a/src/main/resources/static/user-list.html b/src/main/resources/static/user-list.html index f3acfdb59..622884144 100644 --- a/src/main/resources/static/user-list.html +++ b/src/main/resources/static/user-list.html @@ -1,17 +1,17 @@ - - - 사용자 목록 - + + + 사용자 목록 +
-

사용자 목록

-
- -
+

사용자 목록

+
+ +
diff --git a/src/test/java/com/sprint/mission/discodeit/DiscodeitApplicationTests.java b/src/test/java/com/sprint/mission/discodeit/DiscodeitApplicationTests.java index 3a987a214..0cea81919 100644 --- a/src/test/java/com/sprint/mission/discodeit/DiscodeitApplicationTests.java +++ b/src/test/java/com/sprint/mission/discodeit/DiscodeitApplicationTests.java @@ -6,8 +6,8 @@ @SpringBootTest class DiscodeitApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } From 3ea1f0c74cdaf6ea989a3644fd7564612dbca51a Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 20 Feb 2026 11:12:21 +0900 Subject: [PATCH 06/48] =?UTF-8?q?feat:=20/api=20url=20prefix=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 --- .../ApiBinaryContentController.java | 24 ------------------- .../controller/BinaryContentController.java | 9 ++++++- .../channel/controller/ChannelController.java | 2 +- .../controller/ChannelMessageController.java | 2 +- .../message/controller/MessageController.java | 2 +- .../controller/ReadStatusController.java | 2 +- .../user/controller/AuthController.java | 2 +- .../user/controller/UserController.java | 2 +- 8 files changed, 14 insertions(+), 31 deletions(-) delete mode 100644 src/main/java/com/sprint/mission/discodeit/binarycontent/controller/ApiBinaryContentController.java diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/ApiBinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/ApiBinaryContentController.java deleted file mode 100644 index a92d09792..000000000 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/ApiBinaryContentController.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.sprint.mission.discodeit.binarycontent.controller; - -import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; -import com.sprint.mission.discodeit.binarycontent.service.BinaryContentService; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/binaryContent") -public class ApiBinaryContentController { - - private final BinaryContentService binaryContentService; - - @RequestMapping(value = "/find", method = RequestMethod.GET) - public ResponseEntity findBinaryContent(@RequestParam UUID binaryContentId) { - return ResponseEntity.ok(binaryContentService.findBinaryContentEntity(binaryContentId)); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java index 57dce5c96..74e900166 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java @@ -2,6 +2,7 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentInfo; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentsRequest; +import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.service.BinaryContentService; import java.util.List; import java.util.UUID; @@ -11,11 +12,12 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController -@RequestMapping("/binarycontents") +@RequestMapping("/api/binarycontents") public class BinaryContentController { private final BinaryContentService binaryContentService; @@ -31,4 +33,9 @@ public ResponseEntity> getBinaryContents( ) { return ResponseEntity.ok(binaryContentService.findAllByIdIn(request)); } + + @RequestMapping(value = "/find", method = RequestMethod.GET) + public ResponseEntity findBinaryContent(@RequestParam UUID binaryContentId) { + return ResponseEntity.ok(binaryContentService.findBinaryContentEntity(binaryContentId)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index 887470000..a773f43c1 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -19,7 +19,7 @@ @RequiredArgsConstructor @RestController -@RequestMapping("/channels") +@RequestMapping("/api/channels") public class ChannelController { private final ChannelService channelService; diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java index 0a86518d6..bc54dbe2a 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java @@ -13,7 +13,7 @@ @RequiredArgsConstructor @RestController -@RequestMapping("/channels/{channelId}/messages") +@RequestMapping("/api/channels/{channelId}/messages") public class ChannelMessageController { private final MessageService messageService; diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index db87f8e56..058865e15 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -16,7 +16,7 @@ @RequiredArgsConstructor @RestController -@RequestMapping("/messages") +@RequestMapping("/api/messages") public class MessageController { private final MessageService messageService; diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index b60fd1e1c..1f2027bbf 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -16,7 +16,7 @@ @RequiredArgsConstructor @RestController -@RequestMapping("/readstatus") +@RequestMapping("/api/readstatus") public class ReadStatusController { private final ReadStatusService readStatusService; diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java index 87c606247..58848a88e 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java @@ -12,7 +12,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/auth") +@RequestMapping("/api/auth") public class AuthController { private final AuthService authService; diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index 9b9ecf1b5..661ae28ba 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -26,7 +26,7 @@ @RequiredArgsConstructor @RestController -@RequestMapping("/users") +@RequestMapping("/api/users") public class UserController { private final UserService userService; From c1efce252e55f9f084f523a207967c23ab6f0782 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 20 Feb 2026 13:06:23 +0900 Subject: [PATCH 07/48] =?UTF-8?q?feat:=20api=20=EB=AA=85=EC=84=B8=EC=97=90?= =?UTF-8?q?=20=EB=A7=9E=EA=B2=8C=20api=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?1=EC=B0=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../binarycontent/controller/BinaryContentController.java | 2 +- .../readstatus/controller/ReadStatusController.java | 7 ++++++- .../discodeit/readstatus/service/ReadStatusService.java | 7 +++++++ .../mission/discodeit/user/controller/UserController.java | 8 ++++---- .../discodeit/userstatus/service/UserStatusService.java | 6 ++++++ 6 files changed, 25 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 1ce293ae3..9e3c6f62f 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-webmvc' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.0' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java index 74e900166..50d33b7dc 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java @@ -17,7 +17,7 @@ @RequiredArgsConstructor @RestController -@RequestMapping("/api/binarycontents") +@RequestMapping("/api/binaryContents") public class BinaryContentController { private final BinaryContentService binaryContentService; diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index 1f2027bbf..970f6287f 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -16,7 +16,7 @@ @RequiredArgsConstructor @RestController -@RequestMapping("/api/readstatus") +@RequestMapping("/api/readStatuses") public class ReadStatusController { private final ReadStatusService readStatusService; @@ -44,6 +44,11 @@ public ResponseEntity> getReadStatuses(@PathVariable UUID u return ResponseEntity.ok(readStatusService.findAllByUserId(userId)); } + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getAllReadStatuses() { + return ResponseEntity.ok(readStatusService.findAll()); + } + @RequestMapping(value = "/{statusId}", method = RequestMethod.GET) public ResponseEntity getReadStatus(@PathVariable UUID statusId) { return ResponseEntity.ok(readStatusService.find(statusId)); diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index 4e7f4f6b6..be1be2790 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -56,6 +56,13 @@ public List findAllByUserId(UUID userId) { .toList(); } + public List findAll() { + return readStatusRepository.findAll() + .stream() + .map(ReadStatusMapper::toReadStatusInfo) + .toList(); + } + public ReadStatusInfo updateReadStatus(UUID statusId) { ReadStatus readStatus = readStatusRepository.findById(statusId) .orElseThrow(ReadStatusNotFoundException::new); diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index 661ae28ba..2bc8c5bb3 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -67,14 +67,14 @@ public ResponseEntity updateUser( return ResponseEntity.noContent().build(); } - @RequestMapping(value = "/status/{userId}", method = RequestMethod.PATCH) + @RequestMapping(value = "/{userId}/userStatus", method = RequestMethod.PATCH) public ResponseEntity updateUserStatusByUserId(@PathVariable UUID userId) { return ResponseEntity.ok(userStatusService.updateUserStatusByUserId(userId)); } - @RequestMapping(value = "/status/{statusId}", method = RequestMethod.GET) - public ResponseEntity getUserStatus(@PathVariable UUID statusId) { - return ResponseEntity.ok(userStatusService.findUserStatus(statusId)); + @RequestMapping(value = "/{userId}/userStatus", method = RequestMethod.GET) + public ResponseEntity getUserStatus(@PathVariable UUID userId) { + return ResponseEntity.ok(userStatusService.findUserStatusByUserId(userId)); } private Optional resolveProfileFile(MultipartFile profileFile) { diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index 4396a10d0..c77723584 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -40,6 +40,12 @@ public UserStatusInfo findUserStatus(UUID statusId) { return UserStatusMapper.toUserStatusInfo(userStatus); } + public UserStatusInfo findUserStatusByUserId(UUID userId) { + UserStatus userStatus = userStatusRepository.findByUserId(userId) + .orElseThrow(UserStatusNotFoundException::new); + return UserStatusMapper.toUserStatusInfo(userStatus); + } + public List findAll() { return userStatusRepository.findAll() .stream() From ed011de349116c0d267fbd29f6df082ffadbd555 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 20 Feb 2026 13:45:58 +0900 Subject: [PATCH 08/48] =?UTF-8?q?feat:=20swagger=20info=20=EB=B0=8F=20Tag?= =?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 --- .../controller/BinaryContentController.java | 2 ++ .../channel/controller/ChannelController.java | 2 ++ .../discodeit/config/OpenApiConfig.java | 20 +++++++++++++++++++ .../controller/ChannelMessageController.java | 2 ++ .../message/controller/MessageController.java | 2 ++ .../controller/ReadStatusController.java | 2 ++ .../user/controller/ApiUserController.java | 2 ++ .../user/controller/AuthController.java | 2 ++ .../user/controller/UserController.java | 2 ++ src/main/resources/application.yaml | 8 ++++++++ 10 files changed, 44 insertions(+) create mode 100644 src/main/java/com/sprint/mission/discodeit/config/OpenApiConfig.java diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java index 50d33b7dc..4b1f7b2ca 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java @@ -4,6 +4,7 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentsRequest; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.service.BinaryContentService; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -18,6 +19,7 @@ @RequiredArgsConstructor @RestController @RequestMapping("/api/binaryContents") +@Tag(name = "BinaryContent", description = "첨부 파일 API") public class BinaryContentController { private final BinaryContentService binaryContentService; diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index a773f43c1..73347dc84 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -7,6 +7,7 @@ import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateInfo; import com.sprint.mission.discodeit.channel.dto.PublicChannelInfo; import com.sprint.mission.discodeit.channel.service.ChannelService; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -20,6 +21,7 @@ @RequiredArgsConstructor @RestController @RequestMapping("/api/channels") +@Tag(name = "Channel", description = "Channel API") public class ChannelController { private final ChannelService channelService; diff --git a/src/main/java/com/sprint/mission/discodeit/config/OpenApiConfig.java b/src/main/java/com/sprint/mission/discodeit/config/OpenApiConfig.java new file mode 100644 index 000000000..497ea312e --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/config/OpenApiConfig.java @@ -0,0 +1,20 @@ +package com.sprint.mission.discodeit.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenApiConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info() + .title("Discodeit API 문서") + .description("Discodeit 프로젝트의 Swagger API 문서입니다.") + .version("") + ); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java index bc54dbe2a..abfa29c39 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java @@ -2,6 +2,7 @@ import com.sprint.mission.discodeit.message.dto.MessageInfo; import com.sprint.mission.discodeit.message.service.MessageService; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -14,6 +15,7 @@ @RequiredArgsConstructor @RestController @RequestMapping("/api/channels/{channelId}/messages") +@Tag(name = "Message", description = "Message API") public class ChannelMessageController { private final MessageService messageService; diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index 058865e15..c8b1b7611 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -4,6 +4,7 @@ import com.sprint.mission.discodeit.message.dto.MessageInfo; import com.sprint.mission.discodeit.message.dto.MessageUpdateInfo; import com.sprint.mission.discodeit.message.service.MessageService; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -17,6 +18,7 @@ @RequiredArgsConstructor @RestController @RequestMapping("/api/messages") +@Tag(name = "Message", description = "Message API") public class MessageController { private final MessageService messageService; diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index 970f6287f..4de79ca61 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -4,6 +4,7 @@ import com.sprint.mission.discodeit.readstatus.dto.ReadStatusInfo; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateInfo; import com.sprint.mission.discodeit.readstatus.service.ReadStatusService; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -17,6 +18,7 @@ @RequiredArgsConstructor @RestController @RequestMapping("/api/readStatuses") +@Tag(name = "ReadStatus", description = "Message 읽음 상태 API") public class ReadStatusController { private final ReadStatusService readStatusService; diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java index 5939ee9ee..95bef8198 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java @@ -2,6 +2,7 @@ import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.service.UserService; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -12,6 +13,7 @@ @RequiredArgsConstructor @RestController @RequestMapping("/api/user") +@Tag(name = "User", description = "User API") public class ApiUserController { private final UserService userService; diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java index 58848a88e..721390818 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java @@ -3,6 +3,7 @@ import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserLoginInfo; import com.sprint.mission.discodeit.user.service.AuthService; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; @@ -13,6 +14,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/auth") +@Tag(name = "Auth", description = "인증 API") public class AuthController { private final AuthService authService; diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index 2bc8c5bb3..613960d63 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -10,6 +10,7 @@ import com.sprint.mission.discodeit.user.service.UserService; import com.sprint.mission.discodeit.userstatus.dto.UserStatusInfo; import com.sprint.mission.discodeit.userstatus.service.UserStatusService; +import io.swagger.v3.oas.annotations.tags.Tag; import java.io.IOException; import java.util.List; import java.util.Optional; @@ -27,6 +28,7 @@ @RequiredArgsConstructor @RestController @RequestMapping("/api/users") +@Tag(name = "User", description = "User API") public class UserController { private final UserService userService; diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 778a7b778..626f3545d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,6 +1,14 @@ spring: application: name: discodeit + +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + default-produces-media-type: application/json + discodeit: repository: type: jcf #jcf | file From 1fb211a20e32ee832538e9d60ae4b0b12659a8f0 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 20 Feb 2026 15:16:00 +0900 Subject: [PATCH 09/48] =?UTF-8?q?fix:=20spring=20boot=20=EB=B2=84=EC=A0=84?= =?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 --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 9e3c6f62f..433f0f649 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' - id 'org.springframework.boot' version '4.0.2' - id 'io.spring.dependency-management' version '1.1.7' + id 'org.springframework.boot' version '3.4.0' + id 'io.spring.dependency-management' version '1.1.6' } group = 'com.sprint.mission' @@ -25,11 +25,11 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-webmvc' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.0' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.14' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } From f9cf827920f3be9b23406e83e388043bee01d74f Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Sun, 22 Feb 2026 21:36:47 +0900 Subject: [PATCH 10/48] =?UTF-8?q?refactor:=20=EB=AC=B8=EC=84=9C=EC=97=90?= =?UTF-8?q?=20=EB=A7=9E=EA=B2=8C=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EB=B0=8F?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../binarycontent/entity/BinaryContent.java | 4 +++- .../binarycontent/mapper/BinaryContentMapper.java | 8 -------- .../service/BinaryContentService.java | 6 ++++-- .../mission/discodeit/channel/entity/Channel.java | 14 +++++++------- .../discodeit/channel/mapper/ChannelMapper.java | 14 +++++++------- .../channel/repository/FileChannelRepository.java | 2 +- .../channel/repository/JCFChannelRepository.java | 4 ++-- .../channel/service/BasicChannelService.java | 6 +++--- .../mission/discodeit/message/entity/Message.java | 10 +++++----- .../discodeit/message/mapper/MessageMapper.java | 2 +- .../message/repository/FileMessageRepository.java | 2 +- .../message/repository/JCFMessageRepository.java | 2 +- .../message/service/BasicMessageService.java | 8 ++++---- .../sprint/mission/discodeit/user/entity/User.java | 10 +++++----- .../mission/discodeit/user/mapper/UserMapper.java | 6 +++--- .../user/repository/FileUserRepository.java | 2 +- .../user/repository/JCFUserRepository.java | 2 +- .../discodeit/user/service/AuthService.java | 2 +- .../discodeit/user/service/BasicUserService.java | 12 +++++++----- .../discodeit/userstatus/entity/UserStatus.java | 10 +++++----- .../userstatus/mapper/UserStatusMapper.java | 2 +- .../userstatus/service/UserStatusService.java | 4 ++-- 22 files changed, 65 insertions(+), 67 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java index 08afdc235..f750eb3b3 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java @@ -13,13 +13,15 @@ public class BinaryContent implements Serializable { private final UUID id; private final Instant createdAt; private final String fileName; + private final Long size; private final String contentType; private final byte[] bytes; - public BinaryContent(String fileName, String contentType, byte[] bytes) { + public BinaryContent(String fileName, Long size, String contentType, byte[] bytes) { this.id = UUID.randomUUID(); this.createdAt = Instant.now(); this.fileName = fileName; + this.size = size; this.contentType = contentType; this.bytes = bytes; } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java index 183086d34..82764546a 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java @@ -17,12 +17,4 @@ public static BinaryContentInfo toBinaryContentInfo(BinaryContent binaryContent) binaryContent.getBytes() ); } - - public static BinaryContent toBinaryContent(BinaryContentInfo binaryContentInfo) { - return new BinaryContent( - binaryContentInfo.fileName(), - binaryContentInfo.contentType(), - binaryContentInfo.content() - ); - } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index 24c31e934..cdf731ee4 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -19,8 +19,10 @@ public class BinaryContentService { private final BinaryContentRepository contentRepository; public BinaryContentInfo createBinaryContent(BinaryContentCreateInfo contentInfo) { - BinaryContent content = new BinaryContent(contentInfo.fileName(), contentInfo.contentType(), - contentInfo.content()); + byte[] bytes = contentInfo.content(); + BinaryContent content = new BinaryContent(contentInfo.fileName(), (long) bytes.length, + contentInfo.contentType(), + bytes); contentRepository.save(content); return BinaryContentMapper.toBinaryContentInfo(content); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java index 5fe7ec148..14e71189d 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java @@ -14,13 +14,13 @@ public class Channel extends CommonEntity { private static final long serialVersionUID = 1L; private final List messageIds = new ArrayList<>(); private final List userIds = new ArrayList<>(); - private String channelName; - private ChannelType channelType; + private String name; + private ChannelType type; private String description; - public Channel(String channelName, ChannelType channelType, String description) { - this.channelName = channelName; - this.channelType = channelType; + public Channel(String name, ChannelType type, String description) { + this.name = name; + this.type = type; this.description = description; } @@ -33,12 +33,12 @@ public List getUserIds() { } public void updateChannelName(String channelName) { - this.channelName = channelName; + this.name = channelName; this.updateAt = Instant.now(); } public void updateChannelType(ChannelType channelType) { - this.channelType = channelType; + this.type = channelType; this.updateAt = Instant.now(); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index c1528d403..1d5ae7b6d 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -14,18 +14,18 @@ private ChannelMapper() { } public static ChannelInfo toChannelInfo(Channel channel, Instant lastMessageTime) { - if (channel.getChannelType() == ChannelType.PRIVATE) { + if (channel.getType() == ChannelType.PRIVATE) { return new ChannelInfo(channel.getId(), null, - channel.getChannelType(), + channel.getType(), null, lastMessageTime, channel.getUserIds()); } else { return new ChannelInfo( channel.getId(), - channel.getChannelName(), - channel.getChannelType(), + channel.getName(), + channel.getType(), channel.getDescription(), null, channel.getUserIds() @@ -36,8 +36,8 @@ public static ChannelInfo toChannelInfo(Channel channel, Instant lastMessageTime public static PublicChannelInfo toPublicChannelInfo(Channel channel) { return new PublicChannelInfo( channel.getId(), - channel.getChannelName(), - channel.getChannelType(), + channel.getName(), + channel.getType(), channel.getDescription() ); } @@ -45,7 +45,7 @@ public static PublicChannelInfo toPublicChannelInfo(Channel channel) { public static PrivateChannelInfo toPrivateChannelInfo(Channel channel) { return new PrivateChannelInfo( channel.getId(), - channel.getChannelType() + channel.getType() ); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java index 0f4c8a221..2b8b9711c 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java @@ -50,7 +50,7 @@ public Optional findById(UUID channelId) { public Optional findByName(String channelName) { return findAll().stream() .filter( - c -> c.getChannelType() == ChannelType.PUBLIC && c.getChannelName().equals(channelName)) + c -> c.getType() == ChannelType.PUBLIC && c.getName().equals(channelName)) .findFirst(); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java index 7518b73c5..e5024cfe2 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java @@ -25,8 +25,8 @@ public Optional findById(UUID channelId) { @Override public Optional findByName(String channelName) { return data.stream() - .filter(c -> c.getChannelType() == ChannelType.PUBLIC - && c.getChannelName().equals(channelName)) + .filter(c -> c.getType() == ChannelType.PUBLIC + && c.getName().equals(channelName)) .findFirst(); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index a41f9d801..5552f4892 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -73,8 +73,8 @@ public List findAll() { public List findAllByUserId(UUID userId) { return channelRepository.findAll() .stream() - .filter(channel -> channel.getChannelType() == ChannelType.PUBLIC - || (channel.getChannelType() == ChannelType.PRIVATE && channel.getUserIds() + .filter(channel -> channel.getType() == ChannelType.PUBLIC + || (channel.getType() == ChannelType.PRIVATE && channel.getUserIds() .contains(userId))) .map(channel -> ChannelMapper.toChannelInfo(channel, getLastMessageTime(channel.getId())) @@ -86,7 +86,7 @@ public List findAllByUserId(UUID userId) { public ChannelInfo updateChannel(UUID channelId, PublicChannelCreateInfo channelInfo) { Channel findChannel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); - if (findChannel.getChannelType() == ChannelType.PRIVATE) { + if (findChannel.getType() == ChannelType.PRIVATE) { throw new ChannelUpdateNotAllowedException(); } validateChannelExist(channelInfo.channelName()); diff --git a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java index c3de23c40..e8f68227d 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java +++ b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java @@ -10,20 +10,20 @@ public class Message extends CommonEntity { private static final long serialVersionUID = 1L; - private final UUID senderId; + private final UUID authorId; private final UUID channelId; private final List attachmentIds; private String content; - public Message(String content, UUID sender, UUID channel, List attachmentIds) { + public Message(String content, UUID authorId, UUID channel, List attachmentIds) { this.content = content; - this.senderId = sender; + this.authorId = authorId; this.channelId = channel; this.attachmentIds = attachmentIds; } - public void updateContent(String content) { - this.content = content; + public void update(String newContent) { + this.content = newContent; this.updateAt = Instant.now(); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java index d18d50b59..a314eeac4 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java @@ -16,7 +16,7 @@ public static MessageInfo toMessageInfo(Message message) { return new MessageInfo( message.getId(), message.getContent(), - message.getSenderId(), + message.getAuthorId(), message.getChannelId(), message.getAttachmentIds() ); diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java index 960c9d45f..f03518900 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java @@ -74,7 +74,7 @@ public List findAll() { @Override public List findAllByUserId(UUID userId) { return findAll().stream() - .filter(m -> m.getSenderId().equals(userId)) + .filter(m -> m.getAuthorId().equals(userId)) .toList(); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java index 2bf8669cf..3e4e12241 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java @@ -29,7 +29,7 @@ public List findAll() { @Override public List findAllByUserId(UUID userId) { return data.stream() - .filter(m -> m.getSenderId().equals(userId)) + .filter(m -> m.getAuthorId().equals(userId)) .toList(); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 3a12ce480..6b479b4b6 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -34,7 +34,7 @@ public class BasicMessageService implements MessageService { public MessageInfo createMessage(MessageCreateInfo createInfo) { List attachmentIds = createInfo.attachments() .stream() - .map(bytes -> new BinaryContent("", "", bytes)) + .map(bytes -> new BinaryContent("", (long) bytes.length, "", bytes)) .peek(contentRepository::save) .map(BinaryContent::getId) .toList(); @@ -42,7 +42,7 @@ public MessageInfo createMessage(MessageCreateInfo createInfo) { Message message = new Message(createInfo.content(), createInfo.senderId(), createInfo.channelId(), attachmentIds); - User sender = userRepository.findById(message.getSenderId()) + User sender = userRepository.findById(message.getAuthorId()) .orElseThrow(UserNotFoundException::new); Channel findChannel = channelRepository.findById(message.getChannelId()) .orElseThrow(ChannelNotFoundException::new); @@ -85,7 +85,7 @@ public MessageInfo updateMessage(UUID messageId, MessageUpdateInfo messageInfo) .orElseThrow(MessageNotFoundException::new); Optional.ofNullable(messageInfo.content()) - .ifPresent(findMessage::updateContent); + .ifPresent(findMessage::update); messageRepository.save(findMessage); return MessageMapper.toMessageInfo(findMessage); @@ -95,7 +95,7 @@ public MessageInfo updateMessage(UUID messageId, MessageUpdateInfo messageInfo) public void deleteMessage(UUID messageId) { Message findMessage = messageRepository.findById(messageId) .orElseThrow(MessageNotFoundException::new); - User findUser = userRepository.findById(findMessage.getSenderId()) + User findUser = userRepository.findById(findMessage.getAuthorId()) .orElseThrow(UserNotFoundException::new); Channel findChannel = channelRepository.findById(findMessage.getChannelId()) .orElseThrow(ChannelNotFoundException::new); diff --git a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java index 6733b0635..74123345e 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java +++ b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java @@ -14,14 +14,14 @@ public class User extends CommonEntity { private static final long serialVersionUID = 1L; private final List channelIds = new ArrayList<>(); private final List messageIds = new ArrayList<>(); - private String userName; + private String username; private String password; private String email; @Setter private UUID profileId; - public User(String userName, String password, String email) { - this.userName = userName; + public User(String username, String password, String email) { + this.username = username; this.password = password; this.email = email; } @@ -34,8 +34,8 @@ public List getMessageIds() { return List.copyOf(messageIds); } - public void updateUserName(String userName) { - this.userName = userName; + public void updateUserName(String username) { + this.username = username; this.updateAt = Instant.now(); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java index 6aed42a68..568cb7d8c 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java @@ -15,7 +15,7 @@ private UserMapper() { public static UserInfo toUserInfo(User user, UserStatus userStatus) { return new UserInfo( user.getId(), - user.getUserName(), + user.getUsername(), user.getEmail(), user.getProfileId(), userStatus.getId() @@ -25,7 +25,7 @@ public static UserInfo toUserInfo(User user, UserStatus userStatus) { public static UserInfoWithStatus toUserInfoWithStatus(User user, UserStatus userStatus) { return new UserInfoWithStatus( user.getId(), - user.getUserName(), + user.getUsername(), user.getEmail(), user.getProfileId(), userStatus.getId(), @@ -38,7 +38,7 @@ public static UserDto toUserDto(User user, UserStatus userStatus) { user.getId(), user.getCreatedAt(), user.getUpdateAt(), - user.getUserName(), + user.getUsername(), user.getEmail(), user.getProfileId(), userStatus.isOnline() diff --git a/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java b/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java index e6733305f..3c652eb39 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java @@ -49,7 +49,7 @@ public Optional findById(UUID userId) { @Override public Optional findByName(String userName) { return findAll().stream() - .filter(u -> u.getUserName().equals(userName)) + .filter(u -> u.getUsername().equals(userName)) .findFirst(); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java b/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java index 213c80462..832d85e73 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java @@ -24,7 +24,7 @@ public Optional findById(UUID userId) { @Override public Optional findByName(String userName) { return data.stream() - .filter(u -> u.getUserName().equals(userName)) + .filter(u -> u.getUsername().equals(userName)) .findFirst(); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java index ccda97e83..27aa794f2 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java @@ -29,7 +29,7 @@ public UserInfo login(UserLoginInfo userLoginInfo) { UserStatus status = userStatusRepository.findByUserId(findUser.getId()) .orElseThrow(UserStatusNotFoundException::new); ; - status.updateLastOnlineAt(); + status.update(); userStatusRepository.save(status); return UserMapper.toUserInfo(findUser, status); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index c847de1dd..3417f8459 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -48,8 +48,9 @@ public UserInfo createUser(UserCreateInfo userInfo, Optional { - findStatus.updateLastOnlineAt(); + findStatus.update(); return findStatus; }) .orElseThrow(UserStatusNotFoundException::new); diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java index c716eb6bd..5e39db485 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java @@ -11,18 +11,18 @@ public class UserStatus extends CommonEntity { private static final long serialVersionUID = 1L; private final UUID userId; private final int loginLimitSeconds = 60 * 5; - private Instant lastOnlineAt; + private Instant lastActiveAt; public UserStatus(UUID userId) { this.userId = userId; - lastOnlineAt = Instant.now(); + lastActiveAt = Instant.now(); } - public void updateLastOnlineAt() { - lastOnlineAt = Instant.now(); + public void update() { + lastActiveAt = Instant.now(); } public boolean isOnline() { - return lastOnlineAt.isAfter(Instant.now().minusSeconds(loginLimitSeconds)); + return lastActiveAt.isAfter(Instant.now().minusSeconds(loginLimitSeconds)); } } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java index 7caa3b4ef..dbf75c5ad 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java @@ -12,7 +12,7 @@ public static UserStatusInfo toUserStatusInfo(UserStatus userStatus) { return new UserStatusInfo( userStatus.getId(), userStatus.getUserId(), - userStatus.getLastOnlineAt(), + userStatus.getLastActiveAt(), userStatus.isOnline() ); } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index c77723584..b40a68b9f 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -56,7 +56,7 @@ public List findAll() { public UserStatusInfo updateUserStatus(UserStatusUpdateInfo statusInfo) { UserStatus userStatus = userStatusRepository.findById(statusInfo.statusId()) .orElseThrow(UserStatusNotFoundException::new); - userStatus.updateLastOnlineAt(); + userStatus.update(); userStatusRepository.save(userStatus); return UserStatusMapper.toUserStatusInfo(userStatus); } @@ -64,7 +64,7 @@ public UserStatusInfo updateUserStatus(UserStatusUpdateInfo statusInfo) { public UserStatusInfo updateUserStatusByUserId(UUID userId) { UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(UserStatusNotFoundException::new); - userStatus.updateLastOnlineAt(); + userStatus.update(); userStatusRepository.save(userStatus); return UserStatusMapper.toUserStatusInfo(userStatus); } From 57ca135181cfde75f40946ad9ad5d45b233e431c Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Sun, 22 Feb 2026 22:25:08 +0900 Subject: [PATCH 11/48] =?UTF-8?q?refactor:=20=EB=AC=B8=EC=84=9C=EC=97=90?= =?UTF-8?q?=20=EB=A7=9E=EA=B2=8C=20dto=20=EC=88=98=EC=A0=95=20=EB=B0=8F=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 --- .../controller/BinaryContentController.java | 6 +-- ...o.java => BinaryContentCreateRequest.java} | 2 +- ...ContentInfo.java => BinaryContentDto.java} | 2 +- .../mapper/BinaryContentMapper.java | 6 +-- .../service/BinaryContentService.java | 12 +++--- .../channel/controller/ChannelController.java | 30 +++++++------- .../dto/{ChannelInfo.java => ChannelDto.java} | 12 +++--- ...ndChannelInfo.java => FindChannelDto.java} | 2 +- ....java => PrivateChannelCreateRequest.java} | 4 +- ...hannelInfo.java => PrivateChannelDto.java} | 2 +- ...o.java => PublicChannelCreateRequest.java} | 4 +- ...ChannelInfo.java => PublicChannelDto.java} | 2 +- .../dto/PublicChannelUpdateRequest.java | 8 ++++ .../channel/mapper/ChannelMapper.java | 38 +++++++++--------- .../channel/service/BasicChannelService.java | 32 +++++++-------- .../channel/service/ChannelService.java | 22 +++++------ .../controller/ChannelMessageController.java | 4 +- .../message/controller/MessageController.java | 14 +++---- .../message/dto/MessageCreateInfo.java | 13 ------- .../message/dto/MessageCreateRequest.java | 11 ++++++ .../dto/{MessageInfo.java => MessageDto.java} | 2 +- ...ateInfo.java => MessageUpdateRequest.java} | 4 +- .../message/mapper/MessageMapper.java | 25 ++++++------ .../message/service/BasicMessageService.java | 39 ++++++++----------- .../message/service/MessageService.java | 18 ++++----- .../controller/ReadStatusController.java | 14 +++---- .../readstatus/dto/ReadStatusCreateInfo.java | 10 ----- .../dto/ReadStatusCreateRequest.java | 12 ++++++ ...ReadStatusInfo.java => ReadStatusDto.java} | 2 +- .../dto/ReadStatusUpdateRequest.java | 9 +++++ .../readstatus/mapper/ReadStatusMapper.java | 21 +++++----- .../readstatus/service/ReadStatusService.java | 16 ++++---- .../user/controller/AuthController.java | 4 +- .../user/controller/UserController.java | 22 +++++------ .../{UserLoginInfo.java => LoginRequest.java} | 4 +- ...CreateInfo.java => UserCreateRequest.java} | 4 +- ...WithStatus.java => UserDtoWithStatus.java} | 2 +- .../discodeit/user/dto/UserUpdateInfo.java | 9 ++--- .../discodeit/user/mapper/UserMapper.java | 12 +++--- .../discodeit/user/service/AuthService.java | 8 ++-- .../user/service/BasicUserService.java | 35 +++++++++-------- .../discodeit/user/service/UserService.java | 16 ++++---- .../userstatus/dto/UserStatusCreateInfo.java | 9 ----- .../dto/UserStatusCreateRequest.java | 11 ++++++ ...UserStatusInfo.java => UserStatusDto.java} | 2 +- .../dto/UserStatusUpdateRequest.java | 9 +++++ .../userstatus/mapper/UserStatusMapper.java | 6 +-- .../userstatus/service/UserStatusService.java | 16 ++++---- 48 files changed, 294 insertions(+), 273 deletions(-) rename src/main/java/com/sprint/mission/discodeit/binarycontent/dto/{BinaryContentCreateInfo.java => BinaryContentCreateRequest.java} (75%) rename src/main/java/com/sprint/mission/discodeit/binarycontent/dto/{BinaryContentInfo.java => BinaryContentDto.java} (87%) rename src/main/java/com/sprint/mission/discodeit/channel/dto/{ChannelInfo.java => ChannelDto.java} (58%) rename src/main/java/com/sprint/mission/discodeit/channel/dto/{FindChannelInfo.java => FindChannelDto.java} (75%) rename src/main/java/com/sprint/mission/discodeit/channel/dto/{PrivateChannelCreateInfo.java => PrivateChannelCreateRequest.java} (58%) rename src/main/java/com/sprint/mission/discodeit/channel/dto/{PrivateChannelInfo.java => PrivateChannelDto.java} (84%) rename src/main/java/com/sprint/mission/discodeit/channel/dto/{PublicChannelCreateInfo.java => PublicChannelCreateRequest.java} (56%) rename src/main/java/com/sprint/mission/discodeit/channel/dto/{PublicChannelInfo.java => PublicChannelDto.java} (87%) create mode 100644 src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelUpdateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateInfo.java create mode 100644 src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateRequest.java rename src/main/java/com/sprint/mission/discodeit/message/dto/{MessageInfo.java => MessageDto.java} (88%) rename src/main/java/com/sprint/mission/discodeit/message/dto/{MessageUpdateInfo.java => MessageUpdateRequest.java} (50%) delete mode 100644 src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateInfo.java create mode 100644 src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateRequest.java rename src/main/java/com/sprint/mission/discodeit/readstatus/dto/{ReadStatusInfo.java => ReadStatusDto.java} (86%) create mode 100644 src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateRequest.java rename src/main/java/com/sprint/mission/discodeit/user/dto/{UserLoginInfo.java => LoginRequest.java} (60%) rename src/main/java/com/sprint/mission/discodeit/user/dto/{UserCreateInfo.java => UserCreateRequest.java} (63%) rename src/main/java/com/sprint/mission/discodeit/user/dto/{UserInfoWithStatus.java => UserDtoWithStatus.java} (85%) delete mode 100644 src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateInfo.java create mode 100644 src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateRequest.java rename src/main/java/com/sprint/mission/discodeit/userstatus/dto/{UserStatusInfo.java => UserStatusDto.java} (86%) create mode 100644 src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateRequest.java diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java index 4b1f7b2ca..00be636d4 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.binarycontent.controller; -import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentInfo; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentsRequest; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.service.BinaryContentService; @@ -25,12 +25,12 @@ public class BinaryContentController { private final BinaryContentService binaryContentService; @RequestMapping(value = "/{contentId}", method = RequestMethod.GET) - public ResponseEntity getBinaryContent(@PathVariable UUID contentId) { + public ResponseEntity getBinaryContent(@PathVariable UUID contentId) { return ResponseEntity.ok(binaryContentService.findBinaryContent(contentId)); } @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getBinaryContents( + public ResponseEntity> getBinaryContents( @RequestBody BinaryContentsRequest request ) { return ResponseEntity.ok(binaryContentService.findAllByIdIn(request)); diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateRequest.java similarity index 75% rename from src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java rename to src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateRequest.java index 41a9b77c0..66ca37cf8 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateRequest.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.binarycontent.dto; -public record BinaryContentCreateInfo( +public record BinaryContentCreateRequest( String fileName, String contentType, byte[] content diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java similarity index 87% rename from src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java rename to src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java index c0e8108cf..ffca512d5 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java @@ -3,7 +3,7 @@ import java.time.Instant; import java.util.UUID; -public record BinaryContentInfo( +public record BinaryContentDto( UUID contentId, Instant createdAt, String fileName, diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java index 82764546a..8c8927f7a 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.binarycontent.mapper; -import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentInfo; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; public class BinaryContentMapper { @@ -8,8 +8,8 @@ public class BinaryContentMapper { private BinaryContentMapper() { } - public static BinaryContentInfo toBinaryContentInfo(BinaryContent binaryContent) { - return new BinaryContentInfo( + public static BinaryContentDto toBinaryContentInfo(BinaryContent binaryContent) { + return new BinaryContentDto( binaryContent.getId(), binaryContent.getCreatedAt(), binaryContent.getFileName(), diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index cdf731ee4..6bba899fa 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.binarycontent.service; -import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; -import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentInfo; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentsRequest; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; @@ -18,7 +18,7 @@ public class BinaryContentService { private final BinaryContentRepository contentRepository; - public BinaryContentInfo createBinaryContent(BinaryContentCreateInfo contentInfo) { + public BinaryContentDto createBinaryContent(BinaryContentCreateRequest contentInfo) { byte[] bytes = contentInfo.content(); BinaryContent content = new BinaryContent(contentInfo.fileName(), (long) bytes.length, contentInfo.contentType(), @@ -27,7 +27,7 @@ public BinaryContentInfo createBinaryContent(BinaryContentCreateInfo contentInfo return BinaryContentMapper.toBinaryContentInfo(content); } - public BinaryContentInfo findBinaryContent(UUID contentId) { + public BinaryContentDto findBinaryContent(UUID contentId) { BinaryContent content = contentRepository.findById(contentId) .orElseThrow(BinaryContentNotFoundException::new); return BinaryContentMapper.toBinaryContentInfo(content); @@ -38,14 +38,14 @@ public BinaryContent findBinaryContentEntity(UUID contentId) { .orElseThrow(BinaryContentNotFoundException::new); } - public List findAll() { + public List findAll() { return contentRepository.findAll() .stream() .map(BinaryContentMapper::toBinaryContentInfo) .toList(); } - public List findAllByIdIn(BinaryContentsRequest request) { + public List findAllByIdIn(BinaryContentsRequest request) { return contentRepository.findAll() .stream() .filter(content -> request.contentIds().contains(content.getId())) diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index 73347dc84..9fe6870e6 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -1,11 +1,11 @@ package com.sprint.mission.discodeit.channel.controller; -import com.sprint.mission.discodeit.channel.dto.ChannelInfo; -import com.sprint.mission.discodeit.channel.dto.FindChannelInfo; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateInfo; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelInfo; -import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateInfo; -import com.sprint.mission.discodeit.channel.dto.PublicChannelInfo; +import com.sprint.mission.discodeit.channel.dto.ChannelDto; +import com.sprint.mission.discodeit.channel.dto.FindChannelDto; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; +import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; +import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; import com.sprint.mission.discodeit.channel.service.ChannelService; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; @@ -27,34 +27,34 @@ public class ChannelController { private final ChannelService channelService; @RequestMapping(value = "/public", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createChannel( - @RequestBody PublicChannelCreateInfo channelInfo + public ResponseEntity createChannel( + @RequestBody PublicChannelCreateRequest channelInfo ) { return ResponseEntity.ok(channelService.createPublicChannel(channelInfo)); } @RequestMapping(value = "/private", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createChannel( - @RequestBody PrivateChannelCreateInfo channelInfo + public ResponseEntity createChannel( + @RequestBody PrivateChannelCreateRequest channelInfo ) { return ResponseEntity.ok(channelService.createPrivateChannel(channelInfo)); } @RequestMapping(value = "/{channelId}", method = RequestMethod.GET) - public ResponseEntity getChannel(@PathVariable UUID channelId) { + public ResponseEntity getChannel(@PathVariable UUID channelId) { return ResponseEntity.ok(channelService.findChannel(channelId)); } @RequestMapping(method = RequestMethod.GET, consumes = "application/json") - public ResponseEntity> getAllVisibleChannels( - @RequestBody FindChannelInfo findChannelInfo) { - return ResponseEntity.ok(channelService.findAllByUserId(findChannelInfo.userId())); + public ResponseEntity> getAllVisibleChannels( + @RequestBody FindChannelDto findChannelDto) { + return ResponseEntity.ok(channelService.findAllByUserId(findChannelDto.userId())); } @RequestMapping(value = "/{channelId}", method = RequestMethod.PATCH, consumes = "application/json") public ResponseEntity updateChannel( @PathVariable UUID channelId, - @RequestBody PublicChannelCreateInfo channelInfo + @RequestBody PublicChannelCreateRequest channelInfo ) { channelService.updateChannel(channelId, channelInfo); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java similarity index 58% rename from src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelInfo.java rename to src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java index caff0bc02..dcf43e609 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java @@ -5,13 +5,13 @@ import java.util.List; import java.util.UUID; -public record ChannelInfo( - UUID channelId, - String channelName, - ChannelType channelType, +public record ChannelDto( + UUID id, + String name, + ChannelType type, String description, - Instant lastMessageTime, - List userIds + Instant lastMessageAt, + List participantIds ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelDto.java similarity index 75% rename from src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelInfo.java rename to src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelDto.java index c0e539bb2..50ac939ae 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelDto.java @@ -2,7 +2,7 @@ import java.util.UUID; -public record FindChannelInfo( +public record FindChannelDto( UUID userId ) { diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateRequest.java similarity index 58% rename from src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateInfo.java rename to src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateRequest.java index 29d91cd72..c446fee93 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateRequest.java @@ -3,8 +3,8 @@ import java.util.List; import java.util.UUID; -public record PrivateChannelCreateInfo( - List userIds +public record PrivateChannelCreateRequest( + List participantIds ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelDto.java similarity index 84% rename from src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelInfo.java rename to src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelDto.java index 9ca5ab515..33a336dd1 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelDto.java @@ -3,7 +3,7 @@ import com.sprint.mission.discodeit.common.ChannelType; import java.util.UUID; -public record PrivateChannelInfo( +public record PrivateChannelDto( UUID channelId, ChannelType channelType ) { diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateRequest.java similarity index 56% rename from src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateInfo.java rename to src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateRequest.java index d32536ac6..b86a4f525 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateRequest.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.channel.dto; -public record PublicChannelCreateInfo( - String channelName, +public record PublicChannelCreateRequest( + String name, String description ) { diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelInfo.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java similarity index 87% rename from src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelInfo.java rename to src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java index e58750f34..09c49ef32 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java @@ -3,7 +3,7 @@ import com.sprint.mission.discodeit.common.ChannelType; import java.util.UUID; -public record PublicChannelInfo( +public record PublicChannelDto( UUID channelId, String channelName, ChannelType channelType, diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelUpdateRequest.java new file mode 100644 index 000000000..47c5b7ae0 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelUpdateRequest.java @@ -0,0 +1,8 @@ +package com.sprint.mission.discodeit.channel.dto; + +public record PublicChannelUpdateRequest( + String newName, + String newDescription +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index 1d5ae7b6d..c764fb7c0 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -1,9 +1,9 @@ package com.sprint.mission.discodeit.channel.mapper; -import com.sprint.mission.discodeit.channel.dto.ChannelInfo; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateInfo; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelInfo; -import com.sprint.mission.discodeit.channel.dto.PublicChannelInfo; +import com.sprint.mission.discodeit.channel.dto.ChannelDto; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; +import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.common.ChannelType; import java.time.Instant; @@ -13,16 +13,16 @@ public final class ChannelMapper { private ChannelMapper() { } - public static ChannelInfo toChannelInfo(Channel channel, Instant lastMessageTime) { + public static ChannelDto toChannelInfo(Channel channel, Instant lastMessageTime) { if (channel.getType() == ChannelType.PRIVATE) { - return new ChannelInfo(channel.getId(), + return new ChannelDto(channel.getId(), null, channel.getType(), null, lastMessageTime, channel.getUserIds()); } else { - return new ChannelInfo( + return new ChannelDto( channel.getId(), channel.getName(), channel.getType(), @@ -33,8 +33,8 @@ public static ChannelInfo toChannelInfo(Channel channel, Instant lastMessageTime } } - public static PublicChannelInfo toPublicChannelInfo(Channel channel) { - return new PublicChannelInfo( + public static PublicChannelDto toPublicChannelInfo(Channel channel) { + return new PublicChannelDto( channel.getId(), channel.getName(), channel.getType(), @@ -42,26 +42,26 @@ public static PublicChannelInfo toPublicChannelInfo(Channel channel) { ); } - public static PrivateChannelInfo toPrivateChannelInfo(Channel channel) { - return new PrivateChannelInfo( + public static PrivateChannelDto toPrivateChannelInfo(Channel channel) { + return new PrivateChannelDto( channel.getId(), channel.getType() ); } - public static PrivateChannelCreateInfo toPrivateChannelCreateInfo(Channel channel) { - return new PrivateChannelCreateInfo(channel.getUserIds()); + public static PrivateChannelCreateRequest toPrivateChannelCreateInfo(Channel channel) { + return new PrivateChannelCreateRequest(channel.getUserIds()); } - public static Channel toChannel(ChannelInfo channelInfo) { + public static Channel toChannel(ChannelDto channelDto) { return new Channel( - channelInfo.channelName(), - channelInfo.channelType(), - channelInfo.description() + channelDto.name(), + channelDto.type(), + channelDto.description() ); } - public static Channel toChannel(PublicChannelInfo channelInfo) { + public static Channel toChannel(PublicChannelDto channelInfo) { return new Channel( channelInfo.channelName(), ChannelType.PUBLIC, @@ -69,7 +69,7 @@ public static Channel toChannel(PublicChannelInfo channelInfo) { ); } - public static Channel toChannel(PrivateChannelCreateInfo channelInfo) { + public static Channel toChannel(PrivateChannelCreateRequest channelInfo) { return new Channel( null, ChannelType.PRIVATE, diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 5552f4892..3df6631d9 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -1,10 +1,10 @@ package com.sprint.mission.discodeit.channel.service; -import com.sprint.mission.discodeit.channel.dto.ChannelInfo; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateInfo; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelInfo; -import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateInfo; -import com.sprint.mission.discodeit.channel.dto.PublicChannelInfo; +import com.sprint.mission.discodeit.channel.dto.ChannelDto; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; +import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; +import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelDuplicationException; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; @@ -36,23 +36,23 @@ public class BasicChannelService implements ChannelService { private final MessageRepository messageRepository; private final ReadStatusRepository readStatusRepository; - public PublicChannelInfo createPublicChannel(PublicChannelCreateInfo channelInfo) { - validateChannelExist(channelInfo.channelName()); - Channel channel = new Channel(channelInfo.channelName(), ChannelType.PUBLIC, + public PublicChannelDto createPublicChannel(PublicChannelCreateRequest channelInfo) { + validateChannelExist(channelInfo.name()); + Channel channel = new Channel(channelInfo.name(), ChannelType.PUBLIC, channelInfo.description()); channelRepository.save(channel); return ChannelMapper.toPublicChannelInfo(channel); } - public PrivateChannelInfo createPrivateChannel(PrivateChannelCreateInfo channelInfo) { + public PrivateChannelDto createPrivateChannel(PrivateChannelCreateRequest channelInfo) { Channel channel = new Channel(null, ChannelType.PRIVATE, null); channelRepository.save(channel); - channelInfo.userIds().forEach(userId -> joinChannel(channel.getId(), userId)); + channelInfo.participantIds().forEach(userId -> joinChannel(channel.getId(), userId)); return ChannelMapper.toPrivateChannelInfo(channel); } @Override - public ChannelInfo findChannel(UUID channelId) { + public ChannelDto findChannel(UUID channelId) { Channel channel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); @@ -60,7 +60,7 @@ public ChannelInfo findChannel(UUID channelId) { } @Override - public List findAll() { + public List findAll() { return channelRepository.findAll() .stream() .map(channel -> @@ -70,7 +70,7 @@ public List findAll() { } @Override - public List findAllByUserId(UUID userId) { + public List findAllByUserId(UUID userId) { return channelRepository.findAll() .stream() .filter(channel -> channel.getType() == ChannelType.PUBLIC @@ -83,15 +83,15 @@ public List findAllByUserId(UUID userId) { } @Override - public ChannelInfo updateChannel(UUID channelId, PublicChannelCreateInfo channelInfo) { + public ChannelDto updateChannel(UUID channelId, PublicChannelCreateRequest channelInfo) { Channel findChannel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); if (findChannel.getType() == ChannelType.PRIVATE) { throw new ChannelUpdateNotAllowedException(); } - validateChannelExist(channelInfo.channelName()); + validateChannelExist(channelInfo.name()); - Optional.ofNullable(channelInfo.channelName()) + Optional.ofNullable(channelInfo.name()) .ifPresent(findChannel::updateChannelName); Optional.ofNullable(channelInfo.description()) .ifPresent(findChannel::updateDescription); diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java index a07560d56..5699e141e 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java @@ -1,26 +1,26 @@ package com.sprint.mission.discodeit.channel.service; -import com.sprint.mission.discodeit.channel.dto.ChannelInfo; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateInfo; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelInfo; -import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateInfo; -import com.sprint.mission.discodeit.channel.dto.PublicChannelInfo; +import com.sprint.mission.discodeit.channel.dto.ChannelDto; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; +import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; +import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; import java.util.List; import java.util.UUID; public interface ChannelService { - PublicChannelInfo createPublicChannel(PublicChannelCreateInfo channelInfo); + PublicChannelDto createPublicChannel(PublicChannelCreateRequest channelInfo); - PrivateChannelInfo createPrivateChannel(PrivateChannelCreateInfo channelInfo); + PrivateChannelDto createPrivateChannel(PrivateChannelCreateRequest channelInfo); - ChannelInfo findChannel(UUID channelId); + ChannelDto findChannel(UUID channelId); - List findAll(); + List findAll(); - List findAllByUserId(UUID userId); + List findAllByUserId(UUID userId); - ChannelInfo updateChannel(UUID channelId, PublicChannelCreateInfo channelInfo); + ChannelDto updateChannel(UUID channelId, PublicChannelCreateRequest channelInfo); void deleteChannel(UUID channelId); diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java index abfa29c39..ea147731e 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.message.controller; -import com.sprint.mission.discodeit.message.dto.MessageInfo; +import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.service.MessageService; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; @@ -21,7 +21,7 @@ public class ChannelMessageController { private final MessageService messageService; @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getMessages(@PathVariable UUID channelId) { + public ResponseEntity> getMessages(@PathVariable UUID channelId) { return ResponseEntity.ok(messageService.findAllByChannelId(channelId)); } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index c8b1b7611..25c06cd6e 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -1,8 +1,8 @@ package com.sprint.mission.discodeit.message.controller; -import com.sprint.mission.discodeit.message.dto.MessageCreateInfo; -import com.sprint.mission.discodeit.message.dto.MessageInfo; -import com.sprint.mission.discodeit.message.dto.MessageUpdateInfo; +import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; +import com.sprint.mission.discodeit.message.dto.MessageDto; +import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; import com.sprint.mission.discodeit.message.service.MessageService; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; @@ -24,24 +24,24 @@ public class MessageController { private final MessageService messageService; @RequestMapping(method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity sendMessage(@RequestBody MessageCreateInfo messageInfo) { + public ResponseEntity sendMessage(@RequestBody MessageCreateRequest messageInfo) { return ResponseEntity.ok(messageService.createMessage(messageInfo)); } @RequestMapping(value = "/{messageId}", method = RequestMethod.GET) - public ResponseEntity getMessage(@PathVariable UUID messageId) { + public ResponseEntity getMessage(@PathVariable UUID messageId) { return ResponseEntity.ok(messageService.findMessage(messageId)); } @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getAllMessages() { + public ResponseEntity> getAllMessages() { return ResponseEntity.ok(messageService.findAll()); } @RequestMapping(value = "/{messageId}", method = RequestMethod.PATCH) public ResponseEntity updateMessage( @PathVariable UUID messageId, - @RequestBody MessageUpdateInfo messageInfo + @RequestBody MessageUpdateRequest messageInfo ) { messageService.updateMessage(messageId, messageInfo); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateInfo.java deleted file mode 100644 index 8e9126845..000000000 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateInfo.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.sprint.mission.discodeit.message.dto; - -import java.util.List; -import java.util.UUID; - -public record MessageCreateInfo( - String content, - UUID senderId, - UUID channelId, - List attachments -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateRequest.java new file mode 100644 index 000000000..d45bbad1d --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateRequest.java @@ -0,0 +1,11 @@ +package com.sprint.mission.discodeit.message.dto; + +import java.util.UUID; + +public record MessageCreateRequest( + String content, + UUID authorId, + UUID channelId +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageInfo.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java similarity index 88% rename from src/main/java/com/sprint/mission/discodeit/message/dto/MessageInfo.java rename to src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java index 3f2e42475..dc6b0bafb 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.UUID; -public record MessageInfo( +public record MessageDto( UUID messageId, String content, UUID senderId, diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateRequest.java similarity index 50% rename from src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateInfo.java rename to src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateRequest.java index 9e2604211..546df22bd 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateRequest.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.message.dto; -public record MessageUpdateInfo( - String content +public record MessageUpdateRequest( + String newContent ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java index a314eeac4..f710ed812 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java @@ -1,8 +1,8 @@ package com.sprint.mission.discodeit.message.mapper; -import com.sprint.mission.discodeit.message.dto.MessageCreateInfo; -import com.sprint.mission.discodeit.message.dto.MessageInfo; -import com.sprint.mission.discodeit.message.dto.MessageUpdateInfo; +import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; +import com.sprint.mission.discodeit.message.dto.MessageDto; +import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; import com.sprint.mission.discodeit.message.entity.Message; import java.util.List; import java.util.UUID; @@ -12,8 +12,8 @@ public final class MessageMapper { private MessageMapper() { } - public static MessageInfo toMessageInfo(Message message) { - return new MessageInfo( + public static MessageDto toMessageInfo(Message message) { + return new MessageDto( message.getId(), message.getContent(), message.getAuthorId(), @@ -22,24 +22,23 @@ public static MessageInfo toMessageInfo(Message message) { ); } - public static MessageCreateInfo toMessageCreateInfo( + public static MessageCreateRequest toMessageCreateInfo( String content, - UUID senderId, + UUID authorId, UUID channelId, List attachments ) { - return new MessageCreateInfo( + return new MessageCreateRequest( content, - senderId, - channelId, - attachments + authorId, + channelId ); } - public static MessageUpdateInfo toMessageUpdateInfo( + public static MessageUpdateRequest toMessageUpdateInfo( String content ) { - return new MessageUpdateInfo( + return new MessageUpdateRequest( content ); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 6b479b4b6..a5c91968c 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -5,9 +5,9 @@ import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; import com.sprint.mission.discodeit.channel.repository.ChannelRepository; -import com.sprint.mission.discodeit.message.dto.MessageCreateInfo; -import com.sprint.mission.discodeit.message.dto.MessageInfo; -import com.sprint.mission.discodeit.message.dto.MessageUpdateInfo; +import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; +import com.sprint.mission.discodeit.message.dto.MessageDto; +import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; import com.sprint.mission.discodeit.message.entity.Message; import com.sprint.mission.discodeit.message.exception.MessageNotFoundException; import com.sprint.mission.discodeit.message.mapper.MessageMapper; @@ -15,6 +15,7 @@ import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -31,33 +32,27 @@ public class BasicMessageService implements MessageService { private final BinaryContentRepository contentRepository; @Override - public MessageInfo createMessage(MessageCreateInfo createInfo) { - List attachmentIds = createInfo.attachments() - .stream() - .map(bytes -> new BinaryContent("", (long) bytes.length, "", bytes)) - .peek(contentRepository::save) - .map(BinaryContent::getId) - .toList(); + public MessageDto createMessage(MessageCreateRequest createInfo) { - Message message = new Message(createInfo.content(), createInfo.senderId(), - createInfo.channelId(), attachmentIds); + Message message = new Message(createInfo.content(), createInfo.authorId(), + createInfo.channelId(), new ArrayList<>()); - User sender = userRepository.findById(message.getAuthorId()) + User author = userRepository.findById(message.getAuthorId()) .orElseThrow(UserNotFoundException::new); Channel findChannel = channelRepository.findById(message.getChannelId()) .orElseThrow(ChannelNotFoundException::new); - sender.addMessageId(message.getId()); + author.addMessageId(message.getId()); findChannel.addMessageId(message.getId()); - userRepository.save(sender); + userRepository.save(author); channelRepository.save(findChannel); messageRepository.save(message); return MessageMapper.toMessageInfo(message); } @Override - public MessageInfo findMessage(UUID messageId) { + public MessageDto findMessage(UUID messageId) { Message message = messageRepository.findById(messageId) .orElseThrow(MessageNotFoundException::new); @@ -65,26 +60,26 @@ public MessageInfo findMessage(UUID messageId) { } @Override - public List findAll() { + public List findAll() { return toMessageInfoList(messageRepository.findAll()); } @Override - public List findAllByUserId(UUID userId) { + public List findAllByUserId(UUID userId) { return toMessageInfoList(messageRepository.findAllByUserId(userId)); } @Override - public List findAllByChannelId(UUID channelId) { + public List findAllByChannelId(UUID channelId) { return toMessageInfoList(messageRepository.findAllByChannelId(channelId)); } @Override - public MessageInfo updateMessage(UUID messageId, MessageUpdateInfo messageInfo) { + public MessageDto updateMessage(UUID messageId, MessageUpdateRequest messageInfo) { Message findMessage = messageRepository.findById(messageId) .orElseThrow(MessageNotFoundException::new); - Optional.ofNullable(messageInfo.content()) + Optional.ofNullable(messageInfo.newContent()) .ifPresent(findMessage::update); messageRepository.save(findMessage); @@ -111,7 +106,7 @@ public void deleteMessage(UUID messageId) { messageRepository.deleteById(messageId); } - private List toMessageInfoList(List messages) { + private List toMessageInfoList(List messages) { return messages.stream() .map(MessageMapper::toMessageInfo) .toList(); diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java index adb457a58..f2ca502cd 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java @@ -1,24 +1,24 @@ package com.sprint.mission.discodeit.message.service; -import com.sprint.mission.discodeit.message.dto.MessageCreateInfo; -import com.sprint.mission.discodeit.message.dto.MessageInfo; -import com.sprint.mission.discodeit.message.dto.MessageUpdateInfo; +import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; +import com.sprint.mission.discodeit.message.dto.MessageDto; +import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; import java.util.List; import java.util.UUID; public interface MessageService { - MessageInfo createMessage(MessageCreateInfo createInfo); + MessageDto createMessage(MessageCreateRequest createInfo); - MessageInfo findMessage(UUID messageId); + MessageDto findMessage(UUID messageId); - List findAll(); + List findAll(); - List findAllByUserId(UUID userId); + List findAllByUserId(UUID userId); - List findAllByChannelId(UUID channelId); + List findAllByChannelId(UUID channelId); - MessageInfo updateMessage(UUID messageId, MessageUpdateInfo messageInfo); + MessageDto updateMessage(UUID messageId, MessageUpdateRequest messageInfo); void deleteMessage(UUID messageId); } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index 4de79ca61..47f52de37 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.readstatus.controller; -import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateInfo; -import com.sprint.mission.discodeit.readstatus.dto.ReadStatusInfo; +import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateRequest; +import com.sprint.mission.discodeit.readstatus.dto.ReadStatusDto; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateInfo; import com.sprint.mission.discodeit.readstatus.service.ReadStatusService; import io.swagger.v3.oas.annotations.tags.Tag; @@ -24,8 +24,8 @@ public class ReadStatusController { private final ReadStatusService readStatusService; @RequestMapping(method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createReadStatus( - @RequestBody ReadStatusCreateInfo statusInfo) { + public ResponseEntity createReadStatus( + @RequestBody ReadStatusCreateRequest statusInfo) { return ResponseEntity.ok(readStatusService.createReadStatus(statusInfo)); } @@ -42,17 +42,17 @@ public ResponseEntity updateReadStatus(ReadStatusUpdateInfo updateInfo) { } @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) - public ResponseEntity> getReadStatuses(@PathVariable UUID userId) { + public ResponseEntity> getReadStatuses(@PathVariable UUID userId) { return ResponseEntity.ok(readStatusService.findAllByUserId(userId)); } @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getAllReadStatuses() { + public ResponseEntity> getAllReadStatuses() { return ResponseEntity.ok(readStatusService.findAll()); } @RequestMapping(value = "/{statusId}", method = RequestMethod.GET) - public ResponseEntity getReadStatus(@PathVariable UUID statusId) { + public ResponseEntity getReadStatus(@PathVariable UUID statusId) { return ResponseEntity.ok(readStatusService.find(statusId)); } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateInfo.java deleted file mode 100644 index adfde18f4..000000000 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateInfo.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.sprint.mission.discodeit.readstatus.dto; - -import java.util.UUID; - -public record ReadStatusCreateInfo( - UUID userId, - UUID channelId -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateRequest.java new file mode 100644 index 000000000..18a44853e --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateRequest.java @@ -0,0 +1,12 @@ +package com.sprint.mission.discodeit.readstatus.dto; + +import java.time.Instant; +import java.util.UUID; + +public record ReadStatusCreateRequest( + UUID userId, + UUID channelId, + Instant lastReadAt +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusInfo.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java similarity index 86% rename from src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusInfo.java rename to src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java index cdbe16f40..9671afa79 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java @@ -3,7 +3,7 @@ import java.time.Instant; import java.util.UUID; -public record ReadStatusInfo( +public record ReadStatusDto( UUID statusId, UUID userId, UUID channelId, diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateRequest.java new file mode 100644 index 000000000..f6a34e75c --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateRequest.java @@ -0,0 +1,9 @@ +package com.sprint.mission.discodeit.readstatus.dto; + +import java.time.Instant; + +public record ReadStatusUpdateRequest( + Instant newLastReadAt +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java index 23806dd43..97ad5db1f 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.readstatus.mapper; -import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateInfo; -import com.sprint.mission.discodeit.readstatus.dto.ReadStatusInfo; +import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateRequest; +import com.sprint.mission.discodeit.readstatus.dto.ReadStatusDto; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; public class ReadStatusMapper { @@ -9,8 +9,8 @@ public class ReadStatusMapper { private ReadStatusMapper() { } - public static ReadStatusInfo toReadStatusInfo(ReadStatus readStatus) { - return new ReadStatusInfo( + public static ReadStatusDto toReadStatusInfo(ReadStatus readStatus) { + return new ReadStatusDto( readStatus.getId(), readStatus.getUserId(), readStatus.getChannelId(), @@ -18,17 +18,18 @@ public static ReadStatusInfo toReadStatusInfo(ReadStatus readStatus) { ); } - public static ReadStatusCreateInfo toReadStatusCreateInfo(ReadStatus readStatus) { - return new ReadStatusCreateInfo( + public static ReadStatusCreateRequest toReadStatusCreateInfo(ReadStatus readStatus) { + return new ReadStatusCreateRequest( readStatus.getUserId(), - readStatus.getChannelId() + readStatus.getChannelId(), + readStatus.getLastReadAt() ); } - public static ReadStatus toReadStatus(ReadStatusCreateInfo readStatusCreateInfo) { + public static ReadStatus toReadStatus(ReadStatusCreateRequest readStatusCreateRequest) { return new ReadStatus( - readStatusCreateInfo.userId(), - readStatusCreateInfo.channelId() + readStatusCreateRequest.userId(), + readStatusCreateRequest.channelId() ); } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index be1be2790..068437afd 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -3,8 +3,8 @@ import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; import com.sprint.mission.discodeit.channel.repository.ChannelRepository; -import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateInfo; -import com.sprint.mission.discodeit.readstatus.dto.ReadStatusInfo; +import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateRequest; +import com.sprint.mission.discodeit.readstatus.dto.ReadStatusDto; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateInfo; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusDuplicationException; @@ -27,7 +27,7 @@ public class ReadStatusService { private final UserRepository userRepository; private final ChannelRepository channelRepository; - public ReadStatusInfo createReadStatus(ReadStatusCreateInfo statusInfo) { + public ReadStatusDto createReadStatus(ReadStatusCreateRequest statusInfo) { User user = userRepository.findById(statusInfo.userId()) .orElseThrow(UserNotFoundException::new); Channel channel = channelRepository.findById(statusInfo.channelId()) @@ -43,27 +43,27 @@ public ReadStatusInfo createReadStatus(ReadStatusCreateInfo statusInfo) { return ReadStatusMapper.toReadStatusInfo(readStatus); } - public ReadStatusInfo find(UUID statusId) { + public ReadStatusDto find(UUID statusId) { ReadStatus readStatus = readStatusRepository.findById(statusId) .orElseThrow(ReadStatusNotFoundException::new); return ReadStatusMapper.toReadStatusInfo(readStatus); } - public List findAllByUserId(UUID userId) { + public List findAllByUserId(UUID userId) { return readStatusRepository.findAllByUserId(userId) .stream() .map(ReadStatusMapper::toReadStatusInfo) .toList(); } - public List findAll() { + public List findAll() { return readStatusRepository.findAll() .stream() .map(ReadStatusMapper::toReadStatusInfo) .toList(); } - public ReadStatusInfo updateReadStatus(UUID statusId) { + public ReadStatusDto updateReadStatus(UUID statusId) { ReadStatus readStatus = readStatusRepository.findById(statusId) .orElseThrow(ReadStatusNotFoundException::new); readStatus.updateLastReadAt(); @@ -71,7 +71,7 @@ public ReadStatusInfo updateReadStatus(UUID statusId) { return ReadStatusMapper.toReadStatusInfo(readStatus); } - public ReadStatusInfo updateReadStatus(ReadStatusUpdateInfo updateInfo) { + public ReadStatusDto updateReadStatus(ReadStatusUpdateInfo updateInfo) { ReadStatus readStatus = readStatusRepository.findByUserIdAndChannelId(updateInfo.userId(), updateInfo.channelId()) .orElseThrow(); diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java index 721390818..d508519a2 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.user.controller; import com.sprint.mission.discodeit.user.dto.UserInfo; -import com.sprint.mission.discodeit.user.dto.UserLoginInfo; +import com.sprint.mission.discodeit.user.dto.LoginRequest; import com.sprint.mission.discodeit.user.service.AuthService; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -20,7 +20,7 @@ public class AuthController { private final AuthService authService; @RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity login(@RequestBody UserLoginInfo loginInfo) { + public ResponseEntity login(@RequestBody LoginRequest loginInfo) { return ResponseEntity.ok(authService.login(loginInfo)); } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index 613960d63..5eda4a852 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -1,14 +1,14 @@ package com.sprint.mission.discodeit.user.controller; -import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; -import com.sprint.mission.discodeit.user.dto.UserCreateInfo; +import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserInfo; -import com.sprint.mission.discodeit.user.dto.UserInfoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; import com.sprint.mission.discodeit.user.dto.UserUpdateInfo; import com.sprint.mission.discodeit.user.service.UserService; -import com.sprint.mission.discodeit.userstatus.dto.UserStatusInfo; +import com.sprint.mission.discodeit.userstatus.dto.UserStatusDto; import com.sprint.mission.discodeit.userstatus.service.UserStatusService; import io.swagger.v3.oas.annotations.tags.Tag; import java.io.IOException; @@ -35,18 +35,18 @@ public class UserController { private final UserStatusService userStatusService; @RequestMapping(value = "/{userId}", method = RequestMethod.GET) - public ResponseEntity getUser(@PathVariable UUID userId) { + public ResponseEntity getUser(@PathVariable UUID userId) { return ResponseEntity.ok(userService.findUser(userId)); } @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getAllUsers() { + public ResponseEntity> getAllUsers() { return ResponseEntity.ok(userService.findAll()); } @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity createUser( - @RequestPart UserCreateInfo createInfo, + @RequestPart UserCreateRequest createInfo, @RequestPart MultipartFile image ) { return ResponseEntity.ok(userService.createUser(createInfo, resolveProfileFile(image))); @@ -70,21 +70,21 @@ public ResponseEntity updateUser( } @RequestMapping(value = "/{userId}/userStatus", method = RequestMethod.PATCH) - public ResponseEntity updateUserStatusByUserId(@PathVariable UUID userId) { + public ResponseEntity updateUserStatusByUserId(@PathVariable UUID userId) { return ResponseEntity.ok(userStatusService.updateUserStatusByUserId(userId)); } @RequestMapping(value = "/{userId}/userStatus", method = RequestMethod.GET) - public ResponseEntity getUserStatus(@PathVariable UUID userId) { + public ResponseEntity getUserStatus(@PathVariable UUID userId) { return ResponseEntity.ok(userStatusService.findUserStatusByUserId(userId)); } - private Optional resolveProfileFile(MultipartFile profileFile) { + private Optional resolveProfileFile(MultipartFile profileFile) { if (profileFile.isEmpty()) { return Optional.empty(); } else { try { - BinaryContentCreateInfo contentInfo = new BinaryContentCreateInfo( + BinaryContentCreateRequest contentInfo = new BinaryContentCreateRequest( profileFile.getOriginalFilename(), profileFile.getContentType(), profileFile.getBytes() diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserLoginInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/LoginRequest.java similarity index 60% rename from src/main/java/com/sprint/mission/discodeit/user/dto/UserLoginInfo.java rename to src/main/java/com/sprint/mission/discodeit/user/dto/LoginRequest.java index 7563195f1..0e05c0ca4 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserLoginInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/LoginRequest.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.user.dto; -public record UserLoginInfo( - String userName, +public record LoginRequest( + String username, String password ) { diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateRequest.java similarity index 63% rename from src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java rename to src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateRequest.java index 3fc849bab..f0e025f41 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateRequest.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.user.dto; -public record UserCreateInfo( - String userName, +public record UserCreateRequest( + String username, String password, String email ) { diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfoWithStatus.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java similarity index 85% rename from src/main/java/com/sprint/mission/discodeit/user/dto/UserInfoWithStatus.java rename to src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java index 6ed3ef92f..094caefb9 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfoWithStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java @@ -2,7 +2,7 @@ import java.util.UUID; -public record UserInfoWithStatus( +public record UserDtoWithStatus( UUID userId, String userName, String email, diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java index a7712e2cf..5b8d75600 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java @@ -1,12 +1,9 @@ package com.sprint.mission.discodeit.user.dto; -import java.util.UUID; - public record UserUpdateInfo( - String userName, - String password, - String email, - UUID profileId + String newUsername, + String newPassword, + String newEmail ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java index 568cb7d8c..152c82249 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java @@ -1,9 +1,9 @@ package com.sprint.mission.discodeit.user.mapper; -import com.sprint.mission.discodeit.user.dto.UserCreateInfo; +import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.dto.UserInfo; -import com.sprint.mission.discodeit.user.dto.UserInfoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; @@ -22,8 +22,8 @@ public static UserInfo toUserInfo(User user, UserStatus userStatus) { ); } - public static UserInfoWithStatus toUserInfoWithStatus(User user, UserStatus userStatus) { - return new UserInfoWithStatus( + public static UserDtoWithStatus toUserInfoWithStatus(User user, UserStatus userStatus) { + return new UserDtoWithStatus( user.getId(), user.getUsername(), user.getEmail(), @@ -45,9 +45,9 @@ public static UserDto toUserDto(User user, UserStatus userStatus) { ); } - public static User toUser(UserCreateInfo userInfo) { + public static User toUser(UserCreateRequest userInfo) { return new User( - userInfo.userName(), + userInfo.username(), userInfo.password(), userInfo.email() ); diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java index 27aa794f2..68df7343e 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.user.service; import com.sprint.mission.discodeit.user.dto.UserInfo; -import com.sprint.mission.discodeit.user.dto.UserLoginInfo; +import com.sprint.mission.discodeit.user.dto.LoginRequest; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.AuthenticationFailedException; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; @@ -20,10 +20,10 @@ public class AuthService { private final UserRepository userRepository; private final UserStatusRepository userStatusRepository; - public UserInfo login(UserLoginInfo userLoginInfo) { - User findUser = userRepository.findByName(userLoginInfo.userName()) + public UserInfo login(LoginRequest loginRequest) { + User findUser = userRepository.findByName(loginRequest.username()) .orElseThrow(UserNotFoundException::new); - if (!findUser.getPassword().equals(userLoginInfo.password())) { + if (!findUser.getPassword().equals(loginRequest.password())) { throw new AuthenticationFailedException(); } UserStatus status = userStatusRepository.findByUserId(findUser.getId()) diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index 3417f8459..dbe59465d 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -1,13 +1,13 @@ package com.sprint.mission.discodeit.user.service; -import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; import com.sprint.mission.discodeit.channel.repository.ChannelRepository; -import com.sprint.mission.discodeit.user.dto.UserCreateInfo; +import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.dto.UserInfo; -import com.sprint.mission.discodeit.user.dto.UserInfoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; import com.sprint.mission.discodeit.user.dto.UserUpdateInfo; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.EmailDuplicationException; @@ -34,20 +34,21 @@ public class BasicUserService implements UserService { private final UserStatusRepository userStatusRepository; @Override - public UserInfo createUser(UserCreateInfo userInfo, Optional image) { + public UserInfo createUser(UserCreateRequest userInfo, + Optional image) { // 유저 이름 & 이메일 검증 - validateUserExist(userInfo.userName()); + validateUserExist(userInfo.username()); validateEmailExist(userInfo.email()); // 유저 생성 -> mapper로 대체 가능 - User user = new User(userInfo.userName(), userInfo.password(), userInfo.email()); + User user = new User(userInfo.username(), userInfo.password(), userInfo.email()); // status 생성 UserStatus status = new UserStatus(user.getId()); // profile image가 존재한다면 생성 if (image.isPresent()) { - BinaryContentCreateInfo createInfo = image.get(); + BinaryContentCreateRequest createInfo = image.get(); byte[] bytes = createInfo.content(); BinaryContent profileImage = new BinaryContent(createInfo.fileName(), (long) bytes.length, createInfo.contentType(), bytes); @@ -62,7 +63,7 @@ public UserInfo createUser(UserCreateInfo userInfo, Optional findAll() { + public List findAll() { return userRepository.findAll() .stream() .map(user -> { @@ -94,7 +95,7 @@ public List findAllWithUserDTO() { } @Override - public List findAllByChannelId(UUID channelId) { + public List findAllByChannelId(UUID channelId) { return userRepository.findAll() .stream() .filter(user -> user.getChannelIds().contains(channelId)) @@ -108,16 +109,16 @@ public List findAllByChannelId(UUID channelId) { @Override public UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, - Optional image) { - validateUserExist(updateInfo.userName()); - validateEmailExist(updateInfo.email()); + Optional image) { + validateUserExist(updateInfo.newUsername()); + validateEmailExist(updateInfo.newEmail()); User findUser = userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); - Optional.ofNullable(updateInfo.userName()) + Optional.ofNullable(updateInfo.newUsername()) .ifPresent(findUser::updateUserName); - Optional.ofNullable(updateInfo.password()) + Optional.ofNullable(updateInfo.newPassword()) .ifPresent(findUser::updatePassword); - Optional.ofNullable(updateInfo.email()) + Optional.ofNullable(updateInfo.newEmail()) .ifPresent(findUser::updateEmail); // profileId가 존재하면 업데이트 @@ -125,7 +126,7 @@ public UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, if (findUser.isProfileImageUploaded()) { contentRepository.deleteById(findUser.getProfileId()); } - BinaryContentCreateInfo createInfo = image.get(); + BinaryContentCreateRequest createInfo = image.get(); byte[] bytes = createInfo.content(); BinaryContent profileImage = new BinaryContent(createInfo.fileName(), (long) bytes.length, createInfo.contentType(), bytes); diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java index 884b98d97..3004652c8 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java @@ -1,10 +1,10 @@ package com.sprint.mission.discodeit.user.service; -import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateInfo; -import com.sprint.mission.discodeit.user.dto.UserCreateInfo; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; +import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.dto.UserInfo; -import com.sprint.mission.discodeit.user.dto.UserInfoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; import com.sprint.mission.discodeit.user.dto.UserUpdateInfo; import java.util.List; import java.util.Optional; @@ -12,18 +12,18 @@ public interface UserService { - UserInfo createUser(UserCreateInfo userInfo, Optional image); + UserInfo createUser(UserCreateRequest userInfo, Optional image); - UserInfoWithStatus findUser(UUID userId); + UserDtoWithStatus findUser(UUID userId); - List findAll(); + List findAll(); List findAllWithUserDTO(); - List findAllByChannelId(UUID channelId); + List findAllByChannelId(UUID channelId); UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, - Optional image); + Optional image); void deleteUser(UUID userId); } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateInfo.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateInfo.java deleted file mode 100644 index 2a12cc818..000000000 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateInfo.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sprint.mission.discodeit.userstatus.dto; - -import java.util.UUID; - -public record UserStatusCreateInfo( - UUID userId -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateRequest.java new file mode 100644 index 000000000..a5e07968f --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusCreateRequest.java @@ -0,0 +1,11 @@ +package com.sprint.mission.discodeit.userstatus.dto; + +import java.time.Instant; +import java.util.UUID; + +public record UserStatusCreateRequest( + UUID userId, + Instant lastActiveAt +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusInfo.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java similarity index 86% rename from src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusInfo.java rename to src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java index e17c93a6e..e48ac4e3e 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java @@ -3,7 +3,7 @@ import java.time.Instant; import java.util.UUID; -public record UserStatusInfo( +public record UserStatusDto( UUID statusId, UUID userId, Instant lastOnlineAt, diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateRequest.java new file mode 100644 index 000000000..e10eae480 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateRequest.java @@ -0,0 +1,9 @@ +package com.sprint.mission.discodeit.userstatus.dto; + +import java.time.Instant; + +public record UserStatusUpdateRequest( + Instant newLastActiveAt +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java index dbf75c5ad..74fb8db85 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.userstatus.mapper; -import com.sprint.mission.discodeit.userstatus.dto.UserStatusInfo; +import com.sprint.mission.discodeit.userstatus.dto.UserStatusDto; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; public class UserStatusMapper { @@ -8,8 +8,8 @@ public class UserStatusMapper { private UserStatusMapper() { } - public static UserStatusInfo toUserStatusInfo(UserStatus userStatus) { - return new UserStatusInfo( + public static UserStatusDto toUserStatusInfo(UserStatus userStatus) { + return new UserStatusDto( userStatus.getId(), userStatus.getUserId(), userStatus.getLastActiveAt(), diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index b40a68b9f..be3a7ed33 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -2,8 +2,8 @@ import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; -import com.sprint.mission.discodeit.userstatus.dto.UserStatusCreateInfo; -import com.sprint.mission.discodeit.userstatus.dto.UserStatusInfo; +import com.sprint.mission.discodeit.userstatus.dto.UserStatusCreateRequest; +import com.sprint.mission.discodeit.userstatus.dto.UserStatusDto; import com.sprint.mission.discodeit.userstatus.dto.UserStatusUpdateInfo; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; import com.sprint.mission.discodeit.userstatus.exception.UserStatusDuplicationException; @@ -22,7 +22,7 @@ public class UserStatusService { private final UserStatusRepository userStatusRepository; private final UserRepository userRepository; - public UserStatusInfo createUserStatus(UserStatusCreateInfo statusInfo) { + public UserStatusDto createUserStatus(UserStatusCreateRequest statusInfo) { userRepository.findById(statusInfo.userId()) .orElseThrow(UserNotFoundException::new); if (userStatusRepository.findByUserId(statusInfo.userId()) @@ -34,26 +34,26 @@ public UserStatusInfo createUserStatus(UserStatusCreateInfo statusInfo) { return UserStatusMapper.toUserStatusInfo(userStatus); } - public UserStatusInfo findUserStatus(UUID statusId) { + public UserStatusDto findUserStatus(UUID statusId) { UserStatus userStatus = userStatusRepository.findById(statusId) .orElseThrow(UserStatusNotFoundException::new); return UserStatusMapper.toUserStatusInfo(userStatus); } - public UserStatusInfo findUserStatusByUserId(UUID userId) { + public UserStatusDto findUserStatusByUserId(UUID userId) { UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(UserStatusNotFoundException::new); return UserStatusMapper.toUserStatusInfo(userStatus); } - public List findAll() { + public List findAll() { return userStatusRepository.findAll() .stream() .map(UserStatusMapper::toUserStatusInfo) .toList(); } - public UserStatusInfo updateUserStatus(UserStatusUpdateInfo statusInfo) { + public UserStatusDto updateUserStatus(UserStatusUpdateInfo statusInfo) { UserStatus userStatus = userStatusRepository.findById(statusInfo.statusId()) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(); @@ -61,7 +61,7 @@ public UserStatusInfo updateUserStatus(UserStatusUpdateInfo statusInfo) { return UserStatusMapper.toUserStatusInfo(userStatus); } - public UserStatusInfo updateUserStatusByUserId(UUID userId) { + public UserStatusDto updateUserStatusByUserId(UUID userId) { UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(); From 4788e96056bd2121e7d30d253b26267ae71cf790 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Sun, 22 Feb 2026 23:19:01 +0900 Subject: [PATCH 12/48] =?UTF-8?q?feat:=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=ED=8C=8C=EC=9D=BC=20=EC=B2=A8?= =?UTF-8?q?=EB=B6=80=20=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 --- .../BinaryContentFileProcessingException.java | 10 ++++++ .../exception/GlobalExceptionHandler.java | 7 ++++ .../message/controller/MessageController.java | 35 +++++++++++++++++-- .../message/service/BasicMessageService.java | 27 ++++++++++---- .../message/service/MessageService.java | 4 ++- 5 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/sprint/mission/discodeit/binarycontent/exception/BinaryContentFileProcessingException.java diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/exception/BinaryContentFileProcessingException.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/exception/BinaryContentFileProcessingException.java new file mode 100644 index 000000000..92aa39528 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/exception/BinaryContentFileProcessingException.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.binarycontent.exception; + +import com.sprint.mission.discodeit.exception.BusinessException; + +public class BinaryContentFileProcessingException extends BusinessException { + + public BinaryContentFileProcessingException() { + super("첨부 파일 처리 중 오류가 발생했습니다."); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java index c35812657..502e1fed0 100644 --- a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package com.sprint.mission.discodeit.exception; +import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentFileProcessingException; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; import com.sprint.mission.discodeit.channel.exception.AlreadyJoinedException; import com.sprint.mission.discodeit.channel.exception.ChannelDuplicationException; @@ -107,6 +108,12 @@ public ResponseEntity handle(BinaryContentNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); } + @ExceptionHandler(BinaryContentFileProcessingException.class) + public ResponseEntity handle(BinaryContentFileProcessingException e) { + ErrorResponse response = new ErrorResponse("FILE_PROCESSING_ERROR", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } + @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity handle(MethodArgumentTypeMismatchException e) { ErrorResponse response = new ErrorResponse("INVALID_PARAMETER_TYPE", "해당 파라미터가 유효하지 않습니다."); diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index 25c06cd6e..01f0726aa 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -1,19 +1,30 @@ package com.sprint.mission.discodeit.message.controller; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; +import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; +import com.sprint.mission.discodeit.exception.BusinessException; import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; import com.sprint.mission.discodeit.message.service.MessageService; import io.swagger.v3.oas.annotations.tags.Tag; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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.RequestMethod; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; @RequiredArgsConstructor @RestController @@ -23,9 +34,27 @@ public class MessageController { private final MessageService messageService; - @RequestMapping(method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity sendMessage(@RequestBody MessageCreateRequest messageInfo) { - return ResponseEntity.ok(messageService.createMessage(messageInfo)); + @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity sendMessage( + @RequestPart MessageCreateRequest messageCreateRequest, + @RequestPart(required = false) List attachments) { + List attachmentRequests = Optional.ofNullable(attachments) + .map(files -> files.stream() + .map(file -> { + try { + return new BinaryContentCreateRequest( + file.getOriginalFilename(), + file.getContentType(), + file.getBytes() + ); + } catch (IOException e) { + throw new BinaryContentNotFoundException(); + } + }) + .toList()) + .orElse(new ArrayList<>()); + return ResponseEntity.ok( + messageService.createMessage(messageCreateRequest, attachmentRequests)); } @RequestMapping(value = "/{messageId}", method = RequestMethod.GET) diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index a5c91968c..13257a1ba 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -1,5 +1,6 @@ package com.sprint.mission.discodeit.message.service; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; import com.sprint.mission.discodeit.channel.entity.Channel; @@ -32,16 +33,30 @@ public class BasicMessageService implements MessageService { private final BinaryContentRepository contentRepository; @Override - public MessageDto createMessage(MessageCreateRequest createInfo) { + public MessageDto createMessage(MessageCreateRequest createInfo, + List binaryContentCreateRequests) { - Message message = new Message(createInfo.content(), createInfo.authorId(), - createInfo.channelId(), new ArrayList<>()); - - User author = userRepository.findById(message.getAuthorId()) + User author = userRepository.findById(createInfo.authorId()) .orElseThrow(UserNotFoundException::new); - Channel findChannel = channelRepository.findById(message.getChannelId()) + Channel findChannel = channelRepository.findById(createInfo.channelId()) .orElseThrow(ChannelNotFoundException::new); + List attachmentIds = binaryContentCreateRequests.stream() + .map(request -> { + BinaryContent binaryContent = new BinaryContent( + request.fileName(), + (long) request.content().length, + request.contentType(), + request.content() + ); + contentRepository.save(binaryContent); + return binaryContent.getId(); + }) + .toList(); + + Message message = new Message(createInfo.content(), author.getId(), findChannel.getId(), + attachmentIds); + author.addMessageId(message.getId()); findChannel.addMessageId(message.getId()); diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java index f2ca502cd..54c4e67b2 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java @@ -1,5 +1,6 @@ package com.sprint.mission.discodeit.message.service; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; @@ -8,7 +9,8 @@ public interface MessageService { - MessageDto createMessage(MessageCreateRequest createInfo); + MessageDto createMessage(MessageCreateRequest createInfo, + List binaryContentCreateRequests); MessageDto findMessage(UUID messageId); From 99479d8a5fe498dc241cb584f6f9761180717f6e Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Mon, 23 Feb 2026 11:21:29 +0900 Subject: [PATCH 13/48] =?UTF-8?q?refactor:=20updateInfo=EB=A5=BC=20updateR?= =?UTF-8?q?equest=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 --- .../channel/controller/ChannelController.java | 3 ++- .../mission/discodeit/channel/entity/Channel.java | 14 +++++++------- .../channel/service/BasicChannelService.java | 13 +++++++------ .../discodeit/channel/service/ChannelService.java | 3 ++- .../mission/discodeit/common/CommonEntity.java | 4 ++-- .../mission/discodeit/message/entity/Message.java | 2 +- .../message/service/BasicMessageService.java | 1 - .../controller/ReadStatusController.java | 9 ++++++--- .../readstatus/dto/ReadStatusUpdateInfo.java | 10 ---------- .../discodeit/readstatus/entity/ReadStatus.java | 12 ++++++++++++ .../readstatus/service/ReadStatusService.java | 11 +++++------ .../discodeit/user/controller/UserController.java | 6 +++--- ...{UserUpdateInfo.java => UserUpdateRequest.java} | 2 +- .../sprint/mission/discodeit/user/entity/User.java | 14 +++++++------- .../mission/discodeit/user/mapper/UserMapper.java | 2 +- .../discodeit/user/service/BasicUserService.java | 14 +++++++------- .../discodeit/user/service/UserService.java | 4 ++-- .../userstatus/dto/UserStatusUpdateInfo.java | 9 --------- .../discodeit/userstatus/entity/UserStatus.java | 4 ++++ .../userstatus/service/UserStatusService.java | 14 +++++++++++--- 20 files changed, 80 insertions(+), 71 deletions(-) delete mode 100644 src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateInfo.java rename src/main/java/com/sprint/mission/discodeit/user/dto/{UserUpdateInfo.java => UserUpdateRequest.java} (78%) delete mode 100644 src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateInfo.java diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index 9fe6870e6..98b948206 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -6,6 +6,7 @@ import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; +import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; import com.sprint.mission.discodeit.channel.service.ChannelService; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; @@ -54,7 +55,7 @@ public ResponseEntity> getAllVisibleChannels( @RequestMapping(value = "/{channelId}", method = RequestMethod.PATCH, consumes = "application/json") public ResponseEntity updateChannel( @PathVariable UUID channelId, - @RequestBody PublicChannelCreateRequest channelInfo + @RequestBody PublicChannelUpdateRequest channelInfo ) { channelService.updateChannel(channelId, channelInfo); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java index 14e71189d..4b55a91b6 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java @@ -34,36 +34,36 @@ public List getUserIds() { public void updateChannelName(String channelName) { this.name = channelName; - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void updateChannelType(ChannelType channelType) { this.type = channelType; - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void updateDescription(String description) { this.description = description; - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void addMessageId(UUID messageId) { messageIds.add(messageId); - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void removeMessageId(UUID messageId) { messageIds.remove(messageId); - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void addUserId(UUID userId) { userIds.add(userId); - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void removeUserId(UUID userId) { userIds.remove(userId); - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 3df6631d9..9b5b3e0dd 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -5,6 +5,7 @@ import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; +import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelDuplicationException; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; @@ -83,17 +84,17 @@ public List findAllByUserId(UUID userId) { } @Override - public ChannelDto updateChannel(UUID channelId, PublicChannelCreateRequest channelInfo) { + public ChannelDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo) { Channel findChannel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); if (findChannel.getType() == ChannelType.PRIVATE) { throw new ChannelUpdateNotAllowedException(); } - validateChannelExist(channelInfo.name()); + validateChannelExist(channelInfo.newName()); - Optional.ofNullable(channelInfo.name()) + Optional.ofNullable(channelInfo.newName()) .ifPresent(findChannel::updateChannelName); - Optional.ofNullable(channelInfo.description()) + Optional.ofNullable(channelInfo.newDescription()) .ifPresent(findChannel::updateDescription); channelRepository.save(findChannel); @@ -164,9 +165,9 @@ private Instant getLastMessageTime(UUID channelId) { } else { return messages .stream() - .max(Comparator.comparing(Message::getUpdateAt)) + .max(Comparator.comparing(Message::getUpdatedAt)) .orElseThrow(() -> new IllegalStateException("메세지가 존재하지 않습니다.")) - .getUpdateAt(); + .getUpdatedAt(); } } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java index 5699e141e..7d3a133ff 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java @@ -5,6 +5,7 @@ import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; +import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; import java.util.List; import java.util.UUID; @@ -20,7 +21,7 @@ public interface ChannelService { List findAllByUserId(UUID userId); - ChannelDto updateChannel(UUID channelId, PublicChannelCreateRequest channelInfo); + ChannelDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo); void deleteChannel(UUID channelId); diff --git a/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java b/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java index ad286f3e7..0c51aa6f4 100644 --- a/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java +++ b/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java @@ -11,12 +11,12 @@ public abstract class CommonEntity implements Serializable { private static final long serialVersionUID = 1L; protected final UUID id; protected Instant createdAt; - protected Instant updateAt; + protected Instant updatedAt; public CommonEntity() { this.id = UUID.randomUUID(); this.createdAt = Instant.now(); - this.updateAt = this.createdAt; + this.updatedAt = this.createdAt; } @Override diff --git a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java index e8f68227d..1c11227fd 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java +++ b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java @@ -24,7 +24,7 @@ public Message(String content, UUID authorId, UUID channel, List attachmen public void update(String newContent) { this.content = newContent; - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public List getAttachmentIds() { diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 13257a1ba..b4ea4d906 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -16,7 +16,6 @@ import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index 47f52de37..f45342352 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -2,7 +2,7 @@ import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateRequest; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusDto; -import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateInfo; +import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateRequest; import com.sprint.mission.discodeit.readstatus.service.ReadStatusService; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @@ -36,8 +37,10 @@ public ResponseEntity updateReadStatus(@PathVariable UUID statusId) { } @RequestMapping(method = RequestMethod.PATCH) - public ResponseEntity updateReadStatus(ReadStatusUpdateInfo updateInfo) { - readStatusService.updateReadStatus(updateInfo); + public ResponseEntity updateReadStatus( + @RequestParam UUID readStatusId, + @RequestBody ReadStatusUpdateRequest request) { + readStatusService.updateReadStatus(readStatusId, request); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateInfo.java deleted file mode 100644 index 0abc64761..000000000 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateInfo.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.sprint.mission.discodeit.readstatus.dto; - -import java.util.UUID; - -public record ReadStatusUpdateInfo( - UUID userId, - UUID channelId -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java index 6dd3b408b..8ab1d1667 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java @@ -19,6 +19,18 @@ public ReadStatus(UUID userId, UUID channelId) { this.lastReadAt = Instant.now(); } + public void update(Instant newLastReadAt) { + boolean anyValueUpdated = false; + if (newLastReadAt != null && !newLastReadAt.equals(this.lastReadAt)) { + this.lastReadAt = newLastReadAt; + anyValueUpdated = true; + } + + if (anyValueUpdated) { + this.updatedAt = Instant.now(); + } + } + public void updateLastReadAt() { lastReadAt = Instant.now(); } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index 068437afd..c7687075d 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -5,7 +5,7 @@ import com.sprint.mission.discodeit.channel.repository.ChannelRepository; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateRequest; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusDto; -import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateInfo; +import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateRequest; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusDuplicationException; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusNotFoundException; @@ -71,11 +71,10 @@ public ReadStatusDto updateReadStatus(UUID statusId) { return ReadStatusMapper.toReadStatusInfo(readStatus); } - public ReadStatusDto updateReadStatus(ReadStatusUpdateInfo updateInfo) { - ReadStatus readStatus = readStatusRepository.findByUserIdAndChannelId(updateInfo.userId(), - updateInfo.channelId()) - .orElseThrow(); - readStatus.updateLastReadAt(); + public ReadStatusDto updateReadStatus(UUID readStatusId, ReadStatusUpdateRequest request) { + ReadStatus readStatus = readStatusRepository.findById(readStatusId) + .orElseThrow(ReadStatusNotFoundException::new); + readStatus.update(request.newLastReadAt()); readStatusRepository.save(readStatus); return ReadStatusMapper.toReadStatusInfo(readStatus); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index 5eda4a852..8a760b960 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -6,7 +6,7 @@ import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; -import com.sprint.mission.discodeit.user.dto.UserUpdateInfo; +import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import com.sprint.mission.discodeit.user.service.UserService; import com.sprint.mission.discodeit.userstatus.dto.UserStatusDto; import com.sprint.mission.discodeit.userstatus.service.UserStatusService; @@ -61,11 +61,11 @@ public ResponseEntity deleteUser(@PathVariable UUID userId) { @RequestMapping(value = "/{userId}", method = RequestMethod.PATCH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity updateUser( @PathVariable UUID userId, - @RequestPart UserUpdateInfo updateInfo, + @RequestPart UserUpdateRequest request, @RequestPart MultipartFile image ) { - userService.updateUser(userId, updateInfo, resolveProfileFile(image)); + userService.updateUser(userId, request, resolveProfileFile(image)); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateRequest.java similarity index 78% rename from src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java rename to src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateRequest.java index 5b8d75600..600072b47 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateRequest.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.user.dto; -public record UserUpdateInfo( +public record UserUpdateRequest( String newUsername, String newPassword, String newEmail diff --git a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java index 74123345e..1bfeee0c2 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java +++ b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java @@ -36,37 +36,37 @@ public List getMessageIds() { public void updateUserName(String username) { this.username = username; - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void updatePassword(String password) { this.password = password; - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void updateEmail(String email) { this.email = email; - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void addChannelId(UUID channelId) { channelIds.add(channelId); - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void removeChannelId(UUID channelId) { channelIds.remove(channelId); - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void addMessageId(UUID messageId) { messageIds.add(messageId); - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public void removeMessageId(UUID messageId) { messageIds.remove(messageId); - this.updateAt = Instant.now(); + this.updatedAt = Instant.now(); } public boolean isProfileImageUploaded() { diff --git a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java index 152c82249..0a4e4e9f1 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java @@ -37,7 +37,7 @@ public static UserDto toUserDto(User user, UserStatus userStatus) { return new UserDto( user.getId(), user.getCreatedAt(), - user.getUpdateAt(), + user.getUpdatedAt(), user.getUsername(), user.getEmail(), user.getProfileId(), diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index dbe59465d..f16ce5962 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -8,7 +8,7 @@ import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; -import com.sprint.mission.discodeit.user.dto.UserUpdateInfo; +import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.EmailDuplicationException; import com.sprint.mission.discodeit.user.exception.UserDuplicationException; @@ -108,17 +108,17 @@ public List findAllByChannelId(UUID channelId) { } @Override - public UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, + public UserInfo updateUser(UUID userId, UserUpdateRequest request, Optional image) { - validateUserExist(updateInfo.newUsername()); - validateEmailExist(updateInfo.newEmail()); + validateUserExist(request.newUsername()); + validateEmailExist(request.newEmail()); User findUser = userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); - Optional.ofNullable(updateInfo.newUsername()) + Optional.ofNullable(request.newUsername()) .ifPresent(findUser::updateUserName); - Optional.ofNullable(updateInfo.newPassword()) + Optional.ofNullable(request.newPassword()) .ifPresent(findUser::updatePassword); - Optional.ofNullable(updateInfo.newEmail()) + Optional.ofNullable(request.newEmail()) .ifPresent(findUser::updateEmail); // profileId가 존재하면 업데이트 diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java index 3004652c8..cba153b0d 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java @@ -5,7 +5,7 @@ import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; -import com.sprint.mission.discodeit.user.dto.UserUpdateInfo; +import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -22,7 +22,7 @@ public interface UserService { List findAllByChannelId(UUID channelId); - UserInfo updateUser(UUID userId, UserUpdateInfo updateInfo, + UserInfo updateUser(UUID userId, UserUpdateRequest updateInfo, Optional image); void deleteUser(UUID userId); diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateInfo.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateInfo.java deleted file mode 100644 index ee3fe96e9..000000000 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateInfo.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sprint.mission.discodeit.userstatus.dto; - -import java.util.UUID; - -public record UserStatusUpdateInfo( - UUID statusId -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java index 5e39db485..fc46c8ef6 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java @@ -22,6 +22,10 @@ public void update() { lastActiveAt = Instant.now(); } + public void update(Instant newLastActiveAt) { + lastActiveAt = newLastActiveAt; + } + public boolean isOnline() { return lastActiveAt.isAfter(Instant.now().minusSeconds(loginLimitSeconds)); } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index be3a7ed33..4a5c21327 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -4,7 +4,7 @@ import com.sprint.mission.discodeit.user.repository.UserRepository; import com.sprint.mission.discodeit.userstatus.dto.UserStatusCreateRequest; import com.sprint.mission.discodeit.userstatus.dto.UserStatusDto; -import com.sprint.mission.discodeit.userstatus.dto.UserStatusUpdateInfo; +import com.sprint.mission.discodeit.userstatus.dto.UserStatusUpdateRequest; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; import com.sprint.mission.discodeit.userstatus.exception.UserStatusDuplicationException; import com.sprint.mission.discodeit.userstatus.exception.UserStatusNotFoundException; @@ -53,8 +53,8 @@ public List findAll() { .toList(); } - public UserStatusDto updateUserStatus(UserStatusUpdateInfo statusInfo) { - UserStatus userStatus = userStatusRepository.findById(statusInfo.statusId()) + public UserStatusDto update(UUID userStatusId) { + UserStatus userStatus = userStatusRepository.findById(userStatusId) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(); userStatusRepository.save(userStatus); @@ -69,6 +69,14 @@ public UserStatusDto updateUserStatusByUserId(UUID userId) { return UserStatusMapper.toUserStatusInfo(userStatus); } + public UserStatusDto update(UUID userStatusId, UserStatusUpdateRequest request) { + UserStatus userStatus = userStatusRepository.findById(userStatusId) + .orElseThrow(UserStatusNotFoundException::new); + userStatus.update(request.newLastActiveAt()); + userStatusRepository.save(userStatus); + return UserStatusMapper.toUserStatusInfo(userStatus); + } + void deleteUserStatus(UUID statusId) { userStatusRepository.deleteById(statusId); } From b5a4934d14cc613a83b0d0de814133b754e88ad2 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Tue, 24 Feb 2026 09:32:39 +0900 Subject: [PATCH 14/48] =?UTF-8?q?feat:=20swagger=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=8F=20dto=20=ED=95=84=EB=93=9C=EC=9D=B4=EB=A6=84=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 --- .../controller/BinaryContentController.java | 59 +++++++-- .../dto/BinaryContentCreateRequest.java | 2 +- .../binarycontent/dto/BinaryContentDto.java | 4 +- .../dto/BinaryContentsRequest.java | 2 +- .../service/BinaryContentService.java | 4 +- .../channel/controller/ChannelController.java | 97 ++++++++++++-- .../discodeit/channel/dto/FindChannelDto.java | 9 -- .../dto/PrivateChannelCreateRequest.java | 2 + .../dto/PublicChannelCreateRequest.java | 3 + .../channel/dto/PublicChannelDto.java | 4 +- .../dto/PublicChannelUpdateRequest.java | 3 + .../channel/mapper/ChannelMapper.java | 2 +- .../discodeit/config/OpenApiConfig.java | 7 + .../controller/ChannelMessageController.java | 26 +++- .../message/controller/MessageController.java | 76 +++++++++-- .../message/dto/MessageCreateRequest.java | 2 + .../discodeit/message/dto/MessageDto.java | 4 +- .../message/dto/MessageUpdateRequest.java | 3 + .../message/service/BasicMessageService.java | 4 +- .../controller/ReadStatusController.java | 67 ++++++++-- .../dto/ReadStatusCreateRequest.java | 2 + .../readstatus/dto/ReadStatusDto.java | 2 +- .../dto/ReadStatusUpdateRequest.java | 2 + .../user/controller/ApiUserController.java | 25 ---- .../user/controller/AuthController.java | 30 ++++- .../user/controller/UserController.java | 120 ++++++++++++++++-- .../discodeit/user/dto/LoginRequest.java | 3 + .../discodeit/user/dto/UserCreateRequest.java | 3 + .../discodeit/user/dto/UserDtoWithStatus.java | 6 +- .../mission/discodeit/user/dto/UserInfo.java | 4 +- .../discodeit/user/dto/UserUpdateRequest.java | 3 + .../discodeit/user/mapper/UserMapper.java | 2 +- .../discodeit/user/service/AuthService.java | 2 +- .../user/service/BasicUserService.java | 6 +- .../discodeit/user/service/UserService.java | 2 +- .../userstatus/dto/UserStatusDto.java | 6 +- .../dto/UserStatusUpdateRequest.java | 2 + src/main/resources/application.yaml | 1 - 38 files changed, 482 insertions(+), 119 deletions(-) delete mode 100644 src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelDto.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java index 00be636d4..d080613e7 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java @@ -2,15 +2,22 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentsRequest; -import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.service.BinaryContentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -24,20 +31,48 @@ public class BinaryContentController { private final BinaryContentService binaryContentService; - @RequestMapping(value = "/{contentId}", method = RequestMethod.GET) - public ResponseEntity getBinaryContent(@PathVariable UUID contentId) { - return ResponseEntity.ok(binaryContentService.findBinaryContent(contentId)); + @Operation(summary = "첨부 파일 조회") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", description = "첨부 파일 조회 성공", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = BinaryContentDto.class) + ) + ), + @ApiResponse( + responseCode = "404", description = "첨부 파일을 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("BinaryContent with id {binaryContentId} not found") + ) + ) + }) + @RequestMapping(value = "/{binaryContentId}", method = RequestMethod.GET) + public ResponseEntity find( + @Parameter(description = "조회할 첨부 파일 ID") @PathVariable UUID binaryContentId + ) { + return ResponseEntity.ok(binaryContentService.findBinaryContent(binaryContentId)); } + @Operation(summary = "여러 첨부 파일 조회") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", description = "첨부 파일 목록 조회 성공", + content = @Content( + mediaType = MediaType.ALL_VALUE, + array = @ArraySchema(schema = @Schema(implementation = BinaryContentDto.class)) + ) + ) + }) @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getBinaryContents( - @RequestBody BinaryContentsRequest request + public ResponseEntity> findAllByIdIn( + @Parameter(description = "조회할 첨부 파일 ID 목록", + array = @ArraySchema(schema = @Schema(implementation = UUID.class)) + ) + @RequestParam List binaryContentIds ) { + BinaryContentsRequest request = new BinaryContentsRequest(binaryContentIds); return ResponseEntity.ok(binaryContentService.findAllByIdIn(request)); } - - @RequestMapping(value = "/find", method = RequestMethod.GET) - public ResponseEntity findBinaryContent(@RequestParam UUID binaryContentId) { - return ResponseEntity.ok(binaryContentService.findBinaryContentEntity(binaryContentId)); - } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateRequest.java index 66ca37cf8..244385f44 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentCreateRequest.java @@ -3,7 +3,7 @@ public record BinaryContentCreateRequest( String fileName, String contentType, - byte[] content + byte[] bytes ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java index ffca512d5..4467472e2 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java @@ -4,11 +4,11 @@ import java.util.UUID; public record BinaryContentDto( - UUID contentId, + UUID id, Instant createdAt, String fileName, String contentType, - byte[] content + byte[] bytes ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentsRequest.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentsRequest.java index d5648a5c6..e1df3dbcc 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentsRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentsRequest.java @@ -4,7 +4,7 @@ import java.util.UUID; public record BinaryContentsRequest( - List contentIds + List ids ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index 6bba899fa..26268787d 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -19,7 +19,7 @@ public class BinaryContentService { private final BinaryContentRepository contentRepository; public BinaryContentDto createBinaryContent(BinaryContentCreateRequest contentInfo) { - byte[] bytes = contentInfo.content(); + byte[] bytes = contentInfo.bytes(); BinaryContent content = new BinaryContent(contentInfo.fileName(), (long) bytes.length, contentInfo.contentType(), bytes); @@ -48,7 +48,7 @@ public List findAll() { public List findAllByIdIn(BinaryContentsRequest request) { return contentRepository.findAll() .stream() - .filter(content -> request.contentIds().contains(content.getId())) + .filter(content -> request.ids().contains(content.getId())) .map(BinaryContentMapper::toBinaryContentInfo) .toList(); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index 98b948206..66580dc5d 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -1,22 +1,31 @@ package com.sprint.mission.discodeit.channel.controller; import com.sprint.mission.discodeit.channel.dto.ChannelDto; -import com.sprint.mission.discodeit.channel.dto.FindChannelDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; import com.sprint.mission.discodeit.channel.service.ChannelService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @@ -27,15 +36,35 @@ public class ChannelController { private final ChannelService channelService; + @Operation(summary = "Public Channel 생성") + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", description = "Public Channel이 성공적으로 생성됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = PublicChannelDto.class) + ) + ) + }) @RequestMapping(value = "/public", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createChannel( + public ResponseEntity create_3( @RequestBody PublicChannelCreateRequest channelInfo ) { return ResponseEntity.ok(channelService.createPublicChannel(channelInfo)); } + @Operation(summary = "Private Channel 생성") + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", description = "Private Channel이 성공적으로 생성됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = PublicChannelDto.class) + ) + ) + }) @RequestMapping(value = "/private", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createChannel( + public ResponseEntity create_4( @RequestBody PrivateChannelCreateRequest channelInfo ) { return ResponseEntity.ok(channelService.createPrivateChannel(channelInfo)); @@ -46,23 +75,71 @@ public ResponseEntity getChannel(@PathVariable UUID channelId) { return ResponseEntity.ok(channelService.findChannel(channelId)); } - @RequestMapping(method = RequestMethod.GET, consumes = "application/json") - public ResponseEntity> getAllVisibleChannels( - @RequestBody FindChannelDto findChannelDto) { - return ResponseEntity.ok(channelService.findAllByUserId(findChannelDto.userId())); + @Operation(summary = "User가 참여 중인 Channel 목록 조회") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", description = "Channel 목록 조회 성공", + content = @Content( + mediaType = MediaType.ALL_VALUE, + array = @ArraySchema(schema = @Schema(implementation = ChannelDto.class)) + ) + ) + }) + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> findAll_1( + @Parameter(description = "조회할 User ID") @RequestParam UUID userId + ) { + return ResponseEntity.ok(channelService.findAllByUserId(userId)); } + @Operation(summary = "Channel 정보 수정") + @ApiResponses(value = { + @ApiResponse( + responseCode = "404", description = "Channel을 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("Channel with id {channelId} not found") + ) + ), + @ApiResponse( + responseCode = "400", description = "Private Channel은 수정할 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("Private channel cannot be updated") + ) + ), + @ApiResponse( + responseCode = "200", description = "Channel 정보가 성공적으로 수정됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = Void.class) + ) + ) + }) @RequestMapping(value = "/{channelId}", method = RequestMethod.PATCH, consumes = "application/json") - public ResponseEntity updateChannel( - @PathVariable UUID channelId, + public ResponseEntity update_3( + @Parameter(description = "수정할 Channel ID") @PathVariable UUID channelId, @RequestBody PublicChannelUpdateRequest channelInfo ) { channelService.updateChannel(channelId, channelInfo); return ResponseEntity.noContent().build(); } + @Operation(summary = "Channel 삭제") + @ApiResponses(value = { + @ApiResponse( + responseCode = "404", description = "Channel을 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("Channel with id {channelId} not found") + ) + ), + @ApiResponse(responseCode = "204", description = "Channel이 성공적으로 삭제됨") + }) @RequestMapping(value = "/{channelId}", method = RequestMethod.DELETE) - public ResponseEntity deleteChannel(@PathVariable UUID channelId) { + public ResponseEntity delete_2( + @Parameter(description = "삭제할 Channel ID") @PathVariable UUID channelId + ) { channelService.deleteChannel(channelId); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelDto.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelDto.java deleted file mode 100644 index 50ac939ae..000000000 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/FindChannelDto.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sprint.mission.discodeit.channel.dto; - -import java.util.UUID; - -public record FindChannelDto( - UUID userId -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateRequest.java index c446fee93..2190f05a7 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelCreateRequest.java @@ -1,8 +1,10 @@ package com.sprint.mission.discodeit.channel.dto; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import java.util.UUID; +@Schema(description = "Private Channel 생성 정보") public record PrivateChannelCreateRequest( List participantIds ) { diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateRequest.java index b86a4f525..954bbbf35 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelCreateRequest.java @@ -1,5 +1,8 @@ package com.sprint.mission.discodeit.channel.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "Public Channel 생성 정보") public record PublicChannelCreateRequest( String name, String description diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java index 09c49ef32..ab5599c13 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java @@ -5,8 +5,8 @@ public record PublicChannelDto( UUID channelId, - String channelName, - ChannelType channelType, + String name, + ChannelType type, String description ) { diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelUpdateRequest.java index 47c5b7ae0..2b6a24d66 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelUpdateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelUpdateRequest.java @@ -1,5 +1,8 @@ package com.sprint.mission.discodeit.channel.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "수정할 Channel 정보") public record PublicChannelUpdateRequest( String newName, String newDescription diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index c764fb7c0..825f782ec 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -63,7 +63,7 @@ public static Channel toChannel(ChannelDto channelDto) { public static Channel toChannel(PublicChannelDto channelInfo) { return new Channel( - channelInfo.channelName(), + channelInfo.name(), ChannelType.PUBLIC, channelInfo.description() ); diff --git a/src/main/java/com/sprint/mission/discodeit/config/OpenApiConfig.java b/src/main/java/com/sprint/mission/discodeit/config/OpenApiConfig.java index 497ea312e..d5191b50e 100644 --- a/src/main/java/com/sprint/mission/discodeit/config/OpenApiConfig.java +++ b/src/main/java/com/sprint/mission/discodeit/config/OpenApiConfig.java @@ -2,6 +2,8 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,6 +13,11 @@ public class OpenApiConfig { @Bean public OpenAPI openAPI() { return new OpenAPI() + .servers(List.of( + new Server() + .url("http://localhost:8080") + .description("로컬 서버") + )) .info(new Info() .title("Discodeit API 문서") .description("Discodeit 프로젝트의 Swagger API 문서입니다.") diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java index ea147731e..3fa53a806 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java @@ -2,26 +2,46 @@ import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.service.MessageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController -@RequestMapping("/api/channels/{channelId}/messages") +@RequestMapping("/api/messages") @Tag(name = "Message", description = "Message API") public class ChannelMessageController { private final MessageService messageService; + @Operation(summary = "Channel의 Message 목록 조회") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", description = "Message 목록 조회 성공", + content = @Content( + mediaType = MediaType.ALL_VALUE, + array = @ArraySchema(schema = @Schema(implementation = MessageDto.class)) + ) + ) + }) @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getMessages(@PathVariable UUID channelId) { + public ResponseEntity> findAllByChannelId( + @Parameter(description = "조회할 Channel ID") @RequestParam UUID channelId + ) { return ResponseEntity.ok(messageService.findAllByChannelId(channelId)); } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index 01f0726aa..852320cb9 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -2,11 +2,18 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; -import com.sprint.mission.discodeit.exception.BusinessException; import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; import com.sprint.mission.discodeit.message.service.MessageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Encoding; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.io.IOException; import java.util.ArrayList; @@ -14,7 +21,6 @@ import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; @@ -24,7 +30,6 @@ import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.server.ResponseStatusException; @RequiredArgsConstructor @RestController @@ -34,10 +39,33 @@ public class MessageController { private final MessageService messageService; + @Operation(summary = "Message 생성", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + content = @Content( + encoding = @Encoding(name = "messageCreateRequest", contentType = MediaType.APPLICATION_JSON_VALUE) + ) + ) + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "404", description = "Channel 또는 User를 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("Channel | Author with id {channelId | authorId} not found") + ) + ), + @ApiResponse( + responseCode = "201", description = "Message가 성공적으로 생성됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = MessageDto.class) + ) + ) + }) @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity sendMessage( + public ResponseEntity create_2( @RequestPart MessageCreateRequest messageCreateRequest, - @RequestPart(required = false) List attachments) { + @Parameter(description = "Message 첨부 파일들") @RequestPart(required = false) List attachments) { List attachmentRequests = Optional.ofNullable(attachments) .map(files -> files.stream() .map(file -> { @@ -62,22 +90,52 @@ public ResponseEntity getMessage(@PathVariable UUID messageId) { return ResponseEntity.ok(messageService.findMessage(messageId)); } - @RequestMapping(method = RequestMethod.GET) + @RequestMapping(value = "/all", method = RequestMethod.GET) public ResponseEntity> getAllMessages() { return ResponseEntity.ok(messageService.findAll()); } + @Operation(summary = "Message 내용 수정") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", description = "Message가 성공적으로 수정됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = Void.class) + ) + ), + @ApiResponse( + responseCode = "404", description = "Message를 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("Message with id {messageId} not found") + ) + ) + }) @RequestMapping(value = "/{messageId}", method = RequestMethod.PATCH) - public ResponseEntity updateMessage( - @PathVariable UUID messageId, + public ResponseEntity update_2( + @Parameter(description = "수정할 Message ID") @PathVariable UUID messageId, @RequestBody MessageUpdateRequest messageInfo ) { messageService.updateMessage(messageId, messageInfo); return ResponseEntity.noContent().build(); } + @Operation(summary = "Message 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Message가 성공적으로 삭제됨"), + @ApiResponse( + responseCode = "404", description = "Message를 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("Message with id {messageId} not found") + ) + ) + }) @RequestMapping(value = "/{messageId}", method = RequestMethod.DELETE) - public ResponseEntity deleteMessage(@PathVariable UUID messageId) { + public ResponseEntity delete_1( + @Parameter(description = "삭제할 Message ID") @PathVariable UUID messageId + ) { messageService.deleteMessage(messageId); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateRequest.java index d45bbad1d..0a912fc00 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageCreateRequest.java @@ -1,7 +1,9 @@ package com.sprint.mission.discodeit.message.dto; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; +@Schema(description = "Message 생성 정보") public record MessageCreateRequest( String content, UUID authorId, diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java index dc6b0bafb..7917839ff 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java @@ -4,9 +4,9 @@ import java.util.UUID; public record MessageDto( - UUID messageId, + UUID id, String content, - UUID senderId, + UUID authorId, UUID channelId, List attachmentIds ) { diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateRequest.java index 546df22bd..f4f109e6a 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageUpdateRequest.java @@ -1,5 +1,8 @@ package com.sprint.mission.discodeit.message.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "수정할 Message 내용") public record MessageUpdateRequest( String newContent ) { diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index b4ea4d906..b71372c00 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -44,9 +44,9 @@ public MessageDto createMessage(MessageCreateRequest createInfo, .map(request -> { BinaryContent binaryContent = new BinaryContent( request.fileName(), - (long) request.content().length, + (long) request.bytes().length, request.contentType(), - request.content() + request.bytes() ); contentRepository.save(binaryContent); return binaryContent.getId(); diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index f45342352..4213440be 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -4,10 +4,19 @@ import com.sprint.mission.discodeit.readstatus.dto.ReadStatusDto; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusUpdateRequest; import com.sprint.mission.discodeit.readstatus.service.ReadStatusService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -24,32 +33,74 @@ public class ReadStatusController { private final ReadStatusService readStatusService; + @Operation(summary = "Message 읽음 상태 생성") + @ApiResponses(value = { + @ApiResponse(responseCode = "404", description = "Channel 또는 User를 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("Channel | User with id {channelId | userId} not found")) + ), + @ApiResponse(responseCode = "400", description = "이미 읽음 상태가 존재함", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("ReadStatus with userId {userId} and channelId {channelId} already exists")) + ), + @ApiResponse(responseCode = "201", description = "Message 읽음 상태가 성공적으로 생성됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = ReadStatusDto.class)) + ) + }) @RequestMapping(method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity createReadStatus( + public ResponseEntity create_1( @RequestBody ReadStatusCreateRequest statusInfo) { return ResponseEntity.ok(readStatusService.createReadStatus(statusInfo)); } - @RequestMapping(value = "/{statusId}", method = RequestMethod.PATCH) + @RequestMapping(value = "/{statusId}/updated-at", method = RequestMethod.PATCH) public ResponseEntity updateReadStatus(@PathVariable UUID statusId) { readStatusService.updateReadStatus(statusId); return ResponseEntity.noContent().build(); } - @RequestMapping(method = RequestMethod.PATCH) - public ResponseEntity updateReadStatus( - @RequestParam UUID readStatusId, + @Operation(summary = "Message 읽음 상태 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Message 읽음 상태가 성공적으로 수정됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + array = @ArraySchema(schema = @Schema(implementation = Void.class)) + ) + ), + @ApiResponse(responseCode = "404", description = "Message 읽음 상태를 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("ReadStatus with id {readStatusId} not found") + ) + ) + }) + @RequestMapping(value = "/{readStatusId}", method = RequestMethod.PATCH, consumes = "application/json") + public ResponseEntity update_1( + @Parameter(description = "수정할 읽음 상태 ID") @PathVariable UUID readStatusId, @RequestBody ReadStatusUpdateRequest request) { readStatusService.updateReadStatus(readStatusId, request); return ResponseEntity.noContent().build(); } - @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) - public ResponseEntity> getReadStatuses(@PathVariable UUID userId) { + @Operation(summary = "User의 Message 읽음 상태 목록 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Message 읽음 상태 목록 조회 성공", + content = @Content( + mediaType = MediaType.ALL_VALUE, + array = @ArraySchema(schema = @Schema(implementation = ReadStatusDto.class))) + ) + }) + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> findAllByUserId( + @Parameter(description = "조회할 User ID") @RequestParam UUID userId) { return ResponseEntity.ok(readStatusService.findAllByUserId(userId)); } - @RequestMapping(method = RequestMethod.GET) + @RequestMapping(value = "/all", method = RequestMethod.GET) public ResponseEntity> getAllReadStatuses() { return ResponseEntity.ok(readStatusService.findAll()); } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateRequest.java index 18a44853e..8fb4975de 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusCreateRequest.java @@ -1,8 +1,10 @@ package com.sprint.mission.discodeit.readstatus.dto; +import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; import java.util.UUID; +@Schema(description = "Message 읽음 상태 생성 정보") public record ReadStatusCreateRequest( UUID userId, UUID channelId, diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java index 9671afa79..1e09762c6 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java @@ -4,7 +4,7 @@ import java.util.UUID; public record ReadStatusDto( - UUID statusId, + UUID id, UUID userId, UUID channelId, Instant lastReadAt diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateRequest.java index f6a34e75c..35c1efbb0 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusUpdateRequest.java @@ -1,7 +1,9 @@ package com.sprint.mission.discodeit.readstatus.dto; +import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; +@Schema(description = "수정할 읽음 상태 정보") public record ReadStatusUpdateRequest( Instant newLastReadAt ) { diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java deleted file mode 100644 index 95bef8198..000000000 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/ApiUserController.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.sprint.mission.discodeit.user.controller; - -import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.service.UserService; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/user") -@Tag(name = "User", description = "User API") -public class ApiUserController { - - private final UserService userService; - - @RequestMapping(value = "/findAll", method = RequestMethod.GET) - public ResponseEntity> findAll() { - return ResponseEntity.ok(userService.findAllWithUserDTO()); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java index d508519a2..d90c02c70 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java @@ -1,10 +1,17 @@ package com.sprint.mission.discodeit.user.controller; -import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.LoginRequest; +import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.service.AuthService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -19,6 +26,27 @@ public class AuthController { private final AuthService authService; + @Operation(summary = "로그인") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "로그인 성공", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = UserInfo.class) + ) + ), + @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("User with username {username} not found") + ) + ), + @ApiResponse(responseCode = "400", description = "비밀번호가 일치하지 않음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("Wrong password") + ) + ) + }) @RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "application/json") public ResponseEntity login(@RequestBody LoginRequest loginInfo) { return ResponseEntity.ok(authService.login(loginInfo)); diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index 8a760b960..d7f8be2c5 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -4,12 +4,22 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; -import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import com.sprint.mission.discodeit.user.service.UserService; import com.sprint.mission.discodeit.userstatus.dto.UserStatusDto; import com.sprint.mission.discodeit.userstatus.service.UserStatusService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Encoding; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.io.IOException; import java.util.List; @@ -39,38 +49,122 @@ public ResponseEntity getUser(@PathVariable UUID userId) { return ResponseEntity.ok(userService.findUser(userId)); } + @Operation(summary = "전체 User 목록 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "User 목록 조회 성공", + content = @Content( + mediaType = MediaType.ALL_VALUE, + array = @ArraySchema(schema = @Schema(implementation = UserDtoWithStatus.class)) + ) + ) + }) @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getAllUsers() { + public ResponseEntity> findAll() { return ResponseEntity.ok(userService.findAll()); } + @Operation(summary = "User 등록", + requestBody = @RequestBody( + content = @Content( + encoding = @Encoding(name = "userCreateRequest", contentType = MediaType.APPLICATION_JSON_VALUE) + ) + ) + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "User가 성공적으로 생성됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = UserInfo.class) + ) + ), + @ApiResponse(responseCode = "400", description = "같은 email 또는 username를 사용하는 User가 이미 존재함", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("User with email {email} already exists") + ) + ) + }) @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity createUser( - @RequestPart UserCreateRequest createInfo, - @RequestPart MultipartFile image + public ResponseEntity create( + @Parameter() @RequestPart UserCreateRequest userCreateRequest, + @Parameter(description = "User 프로필 이미지") @RequestPart(required = false) MultipartFile profile ) { - return ResponseEntity.ok(userService.createUser(createInfo, resolveProfileFile(image))); + return ResponseEntity.ok( + userService.createUser(userCreateRequest, resolveProfileFile(profile))); } + @Operation(summary = "User 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "User가 성공적으로 삭제됨"), + @ApiResponse(responseCode = "404", description = "User를 찾을 수 없음", + content = @Content(examples = @ExampleObject("User with id {id} not found")) + ) + }) @RequestMapping(value = "/{userId}", method = RequestMethod.DELETE) - public ResponseEntity deleteUser(@PathVariable UUID userId) { + public ResponseEntity delete( + @Parameter(description = "삭제할 User ID") @PathVariable UUID userId + ) { userService.deleteUser(userId); return ResponseEntity.noContent().build(); } + @Operation(summary = "User 정보 수정", + requestBody = @RequestBody( + content = @Content( + encoding = @Encoding(name = "userUpdateRequest", contentType = MediaType.APPLICATION_JSON_VALUE) + ) + ) + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "404", description = "User를 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("User with id {userId} not found") + ) + ), + @ApiResponse(responseCode = "400", description = "같은 email 또는 username를 사용하는 User가 이미 존재함", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("user with email {newEmail} already exists") + ) + ), + @ApiResponse(responseCode = "200", description = "User 정보가 성공적으로 수정됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = Void.class) + ) + ) + }) @RequestMapping(value = "/{userId}", method = RequestMethod.PATCH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity updateUser( - @PathVariable UUID userId, - @RequestPart UserUpdateRequest request, - @RequestPart MultipartFile image + public ResponseEntity update( + @Parameter(description = "수정할 User ID") @PathVariable UUID userId, + @RequestPart UserUpdateRequest userUpdateRequest, + @Parameter(description = "수정할 User 프로필 이미지") @RequestPart(required = false) MultipartFile profile ) { - userService.updateUser(userId, request, resolveProfileFile(image)); + userService.updateUser(userId, userUpdateRequest, resolveProfileFile(profile)); return ResponseEntity.noContent().build(); } + @Operation(summary = "User 온라인 상태 업데이트") + @ApiResponses(value = { + @ApiResponse(responseCode = "404", description = "해당 User의 UserStatus를 찾을 수 없음", + content = @Content( + mediaType = MediaType.ALL_VALUE, + examples = @ExampleObject("UserStatus with userId {userId} not found") + ) + ), + @ApiResponse(responseCode = "200", description = "User 온라인 상태가 성공적으로 업데이트됨", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(implementation = UserStatusDto.class) + ) + ) + }) @RequestMapping(value = "/{userId}/userStatus", method = RequestMethod.PATCH) - public ResponseEntity updateUserStatusByUserId(@PathVariable UUID userId) { + public ResponseEntity updateUserStatusByUserId( + @Parameter(description = "상태를 변경할 User ID") @PathVariable UUID userId + ) { return ResponseEntity.ok(userStatusService.updateUserStatusByUserId(userId)); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/LoginRequest.java b/src/main/java/com/sprint/mission/discodeit/user/dto/LoginRequest.java index 0e05c0ca4..d5e173d06 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/LoginRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/LoginRequest.java @@ -1,5 +1,8 @@ package com.sprint.mission.discodeit.user.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "로그인 정보") public record LoginRequest( String username, String password diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateRequest.java index f0e025f41..abaf67f6b 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserCreateRequest.java @@ -1,5 +1,8 @@ package com.sprint.mission.discodeit.user.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "User 생성 정보") public record UserCreateRequest( String username, String password, diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java index 094caefb9..9a7b7bf0f 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java @@ -3,12 +3,12 @@ import java.util.UUID; public record UserDtoWithStatus( - UUID userId, - String userName, + UUID id, + String username, String email, UUID profileId, UUID statusId, - boolean isOnline + boolean online ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java index af9231de8..2f22d5c7f 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java @@ -3,8 +3,8 @@ import java.util.UUID; public record UserInfo( - UUID userId, - String userName, + UUID id, + String username, String email, UUID profileId, UUID statusId diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateRequest.java index 600072b47..1ed10b1de 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserUpdateRequest.java @@ -1,5 +1,8 @@ package com.sprint.mission.discodeit.user.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "수정할 User 정보") public record UserUpdateRequest( String newUsername, String newPassword, diff --git a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java index 0a4e4e9f1..e8d418ad8 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java @@ -2,8 +2,8 @@ import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java index 68df7343e..1b8b58771 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.user.service; -import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.LoginRequest; +import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.AuthenticationFailedException; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index f16ce5962..7a2a0ec68 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -6,8 +6,8 @@ import com.sprint.mission.discodeit.channel.repository.ChannelRepository; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.EmailDuplicationException; @@ -49,7 +49,7 @@ public UserInfo createUser(UserCreateRequest userInfo, // profile image가 존재한다면 생성 if (image.isPresent()) { BinaryContentCreateRequest createInfo = image.get(); - byte[] bytes = createInfo.content(); + byte[] bytes = createInfo.bytes(); BinaryContent profileImage = new BinaryContent(createInfo.fileName(), (long) bytes.length, createInfo.contentType(), bytes); user.setProfileId(profileImage.getId()); @@ -127,7 +127,7 @@ public UserInfo updateUser(UUID userId, UserUpdateRequest request, contentRepository.deleteById(findUser.getProfileId()); } BinaryContentCreateRequest createInfo = image.get(); - byte[] bytes = createInfo.content(); + byte[] bytes = createInfo.bytes(); BinaryContent profileImage = new BinaryContent(createInfo.fileName(), (long) bytes.length, createInfo.contentType(), bytes); findUser.setProfileId(profileImage.getId()); diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java index cba153b0d..fad1e5734 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java @@ -3,8 +3,8 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; +import com.sprint.mission.discodeit.user.dto.UserInfo; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java index e48ac4e3e..0c03a2f6e 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java @@ -4,10 +4,10 @@ import java.util.UUID; public record UserStatusDto( - UUID statusId, + UUID id, UUID userId, - Instant lastOnlineAt, - boolean isOnline + Instant lastActiveAt, + boolean online ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateRequest.java index e10eae480..546533e53 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusUpdateRequest.java @@ -1,7 +1,9 @@ package com.sprint.mission.discodeit.userstatus.dto; +import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; +@Schema(description = "변경할 User 온라인 상태 정보") public record UserStatusUpdateRequest( Instant newLastActiveAt ) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 626f3545d..8eacd95a3 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -7,7 +7,6 @@ springdoc: enabled: true swagger-ui: enabled: true - default-produces-media-type: application/json discodeit: repository: From e07015b95423a1b710ddea9bd5f3cf3ca2fbd767 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Tue, 24 Feb 2026 11:41:21 +0900 Subject: [PATCH 15/48] =?UTF-8?q?refactor:=20=EB=AC=B8=EC=84=9C=EC=97=90?= =?UTF-8?q?=20=EB=A7=9E=EA=B2=8C=20dto=20=ED=98=95=EC=8B=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 --- .../binarycontent/dto/BinaryContentDto.java | 1 + .../mapper/BinaryContentMapper.java | 1 + .../channel/controller/ChannelController.java | 18 +++---- ...cChannelDto.java => ChannelResultDto.java} | 9 ++-- .../channel/dto/PrivateChannelDto.java | 11 ---- .../channel/mapper/ChannelMapper.java | 50 +++++++++---------- .../channel/service/BasicChannelService.java | 22 ++++---- .../channel/service/ChannelService.java | 9 ++-- .../message/controller/MessageController.java | 7 ++- .../discodeit/message/dto/MessageDto.java | 3 ++ .../message/mapper/MessageMapper.java | 2 + .../controller/ReadStatusController.java | 7 ++- .../readstatus/dto/ReadStatusDto.java | 2 + .../readstatus/mapper/ReadStatusMapper.java | 2 + .../user/controller/AuthController.java | 6 +-- .../user/controller/UserController.java | 34 +++++++------ .../mission/discodeit/user/dto/UserInfo.java | 13 ----- ...rDtoWithStatus.java => UserResultDto.java} | 9 ++-- .../discodeit/user/mapper/UserMapper.java | 27 +++------- .../discodeit/user/service/AuthService.java | 15 ++---- .../user/service/BasicUserService.java | 29 ++++------- .../discodeit/user/service/UserService.java | 13 +++-- .../userstatus/dto/UserStatusDto.java | 2 + .../userstatus/mapper/UserStatusMapper.java | 2 + .../userstatus/service/UserStatusService.java | 5 +- 25 files changed, 131 insertions(+), 168 deletions(-) rename src/main/java/com/sprint/mission/discodeit/channel/dto/{PublicChannelDto.java => ChannelResultDto.java} (63%) delete mode 100644 src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelDto.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java rename src/main/java/com/sprint/mission/discodeit/user/dto/{UserDtoWithStatus.java => UserResultDto.java} (52%) diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java index 4467472e2..56f91fb17 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java @@ -7,6 +7,7 @@ public record BinaryContentDto( UUID id, Instant createdAt, String fileName, + Long size, String contentType, byte[] bytes ) { diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java index 8c8927f7a..11f8cec92 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java @@ -13,6 +13,7 @@ public static BinaryContentDto toBinaryContentInfo(BinaryContent binaryContent) binaryContent.getId(), binaryContent.getCreatedAt(), binaryContent.getFileName(), + binaryContent.getSize(), binaryContent.getContentType(), binaryContent.getBytes() ); diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index 66580dc5d..c99605a2b 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -2,10 +2,9 @@ import com.sprint.mission.discodeit.channel.dto.ChannelDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; -import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; +import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.service.ChannelService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -42,12 +41,12 @@ public class ChannelController { responseCode = "201", description = "Public Channel이 성공적으로 생성됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = PublicChannelDto.class) + schema = @Schema(implementation = ChannelResultDto.class) ) ) }) @RequestMapping(value = "/public", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity create_3( + public ResponseEntity create_3( @RequestBody PublicChannelCreateRequest channelInfo ) { return ResponseEntity.ok(channelService.createPublicChannel(channelInfo)); @@ -59,12 +58,12 @@ public ResponseEntity create_3( responseCode = "201", description = "Private Channel이 성공적으로 생성됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = PublicChannelDto.class) + schema = @Schema(implementation = ChannelResultDto.class) ) ) }) @RequestMapping(value = "/private", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity create_4( + public ResponseEntity create_4( @RequestBody PrivateChannelCreateRequest channelInfo ) { return ResponseEntity.ok(channelService.createPrivateChannel(channelInfo)); @@ -112,17 +111,16 @@ public ResponseEntity> findAll_1( responseCode = "200", description = "Channel 정보가 성공적으로 수정됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = Void.class) + schema = @Schema(implementation = ChannelResultDto.class) ) ) }) @RequestMapping(value = "/{channelId}", method = RequestMethod.PATCH, consumes = "application/json") - public ResponseEntity update_3( + public ResponseEntity update_3( @Parameter(description = "수정할 Channel ID") @PathVariable UUID channelId, @RequestBody PublicChannelUpdateRequest channelInfo ) { - channelService.updateChannel(channelId, channelInfo); - return ResponseEntity.noContent().build(); + return ResponseEntity.ok(channelService.updateChannel(channelId, channelInfo)); } @Operation(summary = "Channel 삭제") diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java similarity index 63% rename from src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java rename to src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java index ab5599c13..5c699d070 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PublicChannelDto.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java @@ -1,12 +1,15 @@ package com.sprint.mission.discodeit.channel.dto; import com.sprint.mission.discodeit.common.ChannelType; +import java.time.Instant; import java.util.UUID; -public record PublicChannelDto( - UUID channelId, - String name, +public record ChannelResultDto( + UUID id, + Instant createdAt, + Instant updatedAt, ChannelType type, + String name, String description ) { diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelDto.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelDto.java deleted file mode 100644 index 33a336dd1..000000000 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/PrivateChannelDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.sprint.mission.discodeit.channel.dto; - -import com.sprint.mission.discodeit.common.ChannelType; -import java.util.UUID; - -public record PrivateChannelDto( - UUID channelId, - ChannelType channelType -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index 825f782ec..22aa1a078 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -1,9 +1,8 @@ package com.sprint.mission.discodeit.channel.mapper; import com.sprint.mission.discodeit.channel.dto.ChannelDto; +import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; -import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.common.ChannelType; import java.time.Instant; @@ -13,9 +12,10 @@ public final class ChannelMapper { private ChannelMapper() { } - public static ChannelDto toChannelInfo(Channel channel, Instant lastMessageTime) { + public static ChannelDto toChannelDto(Channel channel, Instant lastMessageTime) { if (channel.getType() == ChannelType.PRIVATE) { - return new ChannelDto(channel.getId(), + return new ChannelDto( + channel.getId(), null, channel.getType(), null, @@ -33,20 +33,26 @@ public static ChannelDto toChannelInfo(Channel channel, Instant lastMessageTime) } } - public static PublicChannelDto toPublicChannelInfo(Channel channel) { - return new PublicChannelDto( - channel.getId(), - channel.getName(), - channel.getType(), - channel.getDescription() - ); - } - - public static PrivateChannelDto toPrivateChannelInfo(Channel channel) { - return new PrivateChannelDto( - channel.getId(), - channel.getType() - ); + public static ChannelResultDto toChannelResultDto(Channel channel) { + if (channel.getType() == ChannelType.PRIVATE) { + return new ChannelResultDto( + channel.getId(), + channel.getCreatedAt(), + channel.getUpdatedAt(), + channel.getType(), + null, + null + ); + } else { + return new ChannelResultDto( + channel.getId(), + channel.getCreatedAt(), + channel.getUpdatedAt(), + channel.getType(), + channel.getName(), + channel.getDescription() + ); + } } public static PrivateChannelCreateRequest toPrivateChannelCreateInfo(Channel channel) { @@ -61,14 +67,6 @@ public static Channel toChannel(ChannelDto channelDto) { ); } - public static Channel toChannel(PublicChannelDto channelInfo) { - return new Channel( - channelInfo.name(), - ChannelType.PUBLIC, - channelInfo.description() - ); - } - public static Channel toChannel(PrivateChannelCreateRequest channelInfo) { return new Channel( null, diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 9b5b3e0dd..f8da94066 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -2,10 +2,9 @@ import com.sprint.mission.discodeit.channel.dto.ChannelDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; -import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; +import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelDuplicationException; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; @@ -37,19 +36,19 @@ public class BasicChannelService implements ChannelService { private final MessageRepository messageRepository; private final ReadStatusRepository readStatusRepository; - public PublicChannelDto createPublicChannel(PublicChannelCreateRequest channelInfo) { + public ChannelResultDto createPublicChannel(PublicChannelCreateRequest channelInfo) { validateChannelExist(channelInfo.name()); Channel channel = new Channel(channelInfo.name(), ChannelType.PUBLIC, channelInfo.description()); channelRepository.save(channel); - return ChannelMapper.toPublicChannelInfo(channel); + return ChannelMapper.toChannelResultDto(channel); } - public PrivateChannelDto createPrivateChannel(PrivateChannelCreateRequest channelInfo) { + public ChannelResultDto createPrivateChannel(PrivateChannelCreateRequest channelInfo) { Channel channel = new Channel(null, ChannelType.PRIVATE, null); channelRepository.save(channel); channelInfo.participantIds().forEach(userId -> joinChannel(channel.getId(), userId)); - return ChannelMapper.toPrivateChannelInfo(channel); + return ChannelMapper.toChannelResultDto(channel); } @Override @@ -57,7 +56,7 @@ public ChannelDto findChannel(UUID channelId) { Channel channel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); - return ChannelMapper.toChannelInfo(channel, getLastMessageTime(channelId)); + return ChannelMapper.toChannelDto(channel, getLastMessageTime(channelId)); } @Override @@ -65,7 +64,7 @@ public List findAll() { return channelRepository.findAll() .stream() .map(channel -> - ChannelMapper.toChannelInfo(channel, getLastMessageTime(channel.getId())) + ChannelMapper.toChannelDto(channel, getLastMessageTime(channel.getId())) ) .toList(); } @@ -78,13 +77,14 @@ public List findAllByUserId(UUID userId) { || (channel.getType() == ChannelType.PRIVATE && channel.getUserIds() .contains(userId))) .map(channel -> - ChannelMapper.toChannelInfo(channel, getLastMessageTime(channel.getId())) + ChannelMapper.toChannelDto(channel, getLastMessageTime(channel.getId())) ) .toList(); } @Override - public ChannelDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo) { + public ChannelResultDto updateChannel(UUID channelId, + PublicChannelUpdateRequest channelInfo) { Channel findChannel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); if (findChannel.getType() == ChannelType.PRIVATE) { @@ -99,7 +99,7 @@ public ChannelDto updateChannel(UUID channelId, PublicChannelUpdateRequest chann channelRepository.save(findChannel); - return ChannelMapper.toChannelInfo(findChannel, getLastMessageTime(findChannel.getId())); + return ChannelMapper.toChannelResultDto(findChannel); } @Override diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java index 7d3a133ff..bb9acc185 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java @@ -2,18 +2,17 @@ import com.sprint.mission.discodeit.channel.dto.ChannelDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; -import com.sprint.mission.discodeit.channel.dto.PublicChannelDto; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; +import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import java.util.List; import java.util.UUID; public interface ChannelService { - PublicChannelDto createPublicChannel(PublicChannelCreateRequest channelInfo); + ChannelResultDto createPublicChannel(PublicChannelCreateRequest channelInfo); - PrivateChannelDto createPrivateChannel(PrivateChannelCreateRequest channelInfo); + ChannelResultDto createPrivateChannel(PrivateChannelCreateRequest channelInfo); ChannelDto findChannel(UUID channelId); @@ -21,7 +20,7 @@ public interface ChannelService { List findAllByUserId(UUID userId); - ChannelDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo); + ChannelResultDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo); void deleteChannel(UUID channelId); diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index 852320cb9..ffa7dfec1 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -101,7 +101,7 @@ public ResponseEntity> getAllMessages() { responseCode = "200", description = "Message가 성공적으로 수정됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = Void.class) + schema = @Schema(implementation = MessageDto.class) ) ), @ApiResponse( @@ -113,12 +113,11 @@ public ResponseEntity> getAllMessages() { ) }) @RequestMapping(value = "/{messageId}", method = RequestMethod.PATCH) - public ResponseEntity update_2( + public ResponseEntity update_2( @Parameter(description = "수정할 Message ID") @PathVariable UUID messageId, @RequestBody MessageUpdateRequest messageInfo ) { - messageService.updateMessage(messageId, messageInfo); - return ResponseEntity.noContent().build(); + return ResponseEntity.ok(messageService.updateMessage(messageId, messageInfo)); } @Operation(summary = "Message 삭제") diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java index 7917839ff..407490aa0 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java @@ -1,10 +1,13 @@ package com.sprint.mission.discodeit.message.dto; +import java.time.Instant; import java.util.List; import java.util.UUID; public record MessageDto( UUID id, + Instant createdAt, + Instant updatedAt, String content, UUID authorId, UUID channelId, diff --git a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java index f710ed812..cc433a05f 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java @@ -15,6 +15,8 @@ private MessageMapper() { public static MessageDto toMessageInfo(Message message) { return new MessageDto( message.getId(), + message.getCreatedAt(), + message.getUpdatedAt(), message.getContent(), message.getAuthorId(), message.getChannelId(), diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index 4213440be..0410a47b9 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -68,7 +68,7 @@ public ResponseEntity updateReadStatus(@PathVariable UUID statusId) { @ApiResponse(responseCode = "200", description = "Message 읽음 상태가 성공적으로 수정됨", content = @Content( mediaType = MediaType.ALL_VALUE, - array = @ArraySchema(schema = @Schema(implementation = Void.class)) + schema = @Schema(implementation = ReadStatusDto.class) ) ), @ApiResponse(responseCode = "404", description = "Message 읽음 상태를 찾을 수 없음", @@ -79,11 +79,10 @@ public ResponseEntity updateReadStatus(@PathVariable UUID statusId) { ) }) @RequestMapping(value = "/{readStatusId}", method = RequestMethod.PATCH, consumes = "application/json") - public ResponseEntity update_1( + public ResponseEntity update_1( @Parameter(description = "수정할 읽음 상태 ID") @PathVariable UUID readStatusId, @RequestBody ReadStatusUpdateRequest request) { - readStatusService.updateReadStatus(readStatusId, request); - return ResponseEntity.noContent().build(); + return ResponseEntity.ok(readStatusService.updateReadStatus(readStatusId, request)); } @Operation(summary = "User의 Message 읽음 상태 목록 조회") diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java index 1e09762c6..3615bfe73 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java @@ -5,6 +5,8 @@ public record ReadStatusDto( UUID id, + Instant createdAt, + Instant updatedAt, UUID userId, UUID channelId, Instant lastReadAt diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java index 97ad5db1f..0939a2d63 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java @@ -12,6 +12,8 @@ private ReadStatusMapper() { public static ReadStatusDto toReadStatusInfo(ReadStatus readStatus) { return new ReadStatusDto( readStatus.getId(), + readStatus.getCreatedAt(), + readStatus.getUpdatedAt(), readStatus.getUserId(), readStatus.getChannelId(), readStatus.getLastReadAt() diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java index d90c02c70..1a623fc66 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.user.controller; import com.sprint.mission.discodeit.user.dto.LoginRequest; -import com.sprint.mission.discodeit.user.dto.UserInfo; +import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.service.AuthService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -31,7 +31,7 @@ public class AuthController { @ApiResponse(responseCode = "200", description = "로그인 성공", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = UserInfo.class) + schema = @Schema(implementation = UserResultDto.class) ) ), @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", @@ -48,7 +48,7 @@ public class AuthController { ) }) @RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "application/json") - public ResponseEntity login(@RequestBody LoginRequest loginInfo) { + public ResponseEntity login(@RequestBody LoginRequest loginInfo) { return ResponseEntity.ok(authService.login(loginInfo)); } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index d7f8be2c5..cdcd21fc8 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -4,11 +4,12 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; -import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; -import com.sprint.mission.discodeit.user.dto.UserInfo; +import com.sprint.mission.discodeit.user.dto.UserDto; +import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import com.sprint.mission.discodeit.user.service.UserService; import com.sprint.mission.discodeit.userstatus.dto.UserStatusDto; +import com.sprint.mission.discodeit.userstatus.dto.UserStatusUpdateRequest; import com.sprint.mission.discodeit.userstatus.service.UserStatusService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -17,7 +18,6 @@ import io.swagger.v3.oas.annotations.media.Encoding; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; @@ -29,6 +29,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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.RequestMethod; import org.springframework.web.bind.annotation.RequestPart; @@ -45,7 +46,7 @@ public class UserController { private final UserStatusService userStatusService; @RequestMapping(value = "/{userId}", method = RequestMethod.GET) - public ResponseEntity getUser(@PathVariable UUID userId) { + public ResponseEntity getUser(@PathVariable UUID userId) { return ResponseEntity.ok(userService.findUser(userId)); } @@ -54,17 +55,17 @@ public ResponseEntity getUser(@PathVariable UUID userId) { @ApiResponse(responseCode = "200", description = "User 목록 조회 성공", content = @Content( mediaType = MediaType.ALL_VALUE, - array = @ArraySchema(schema = @Schema(implementation = UserDtoWithStatus.class)) + array = @ArraySchema(schema = @Schema(implementation = UserDto.class)) ) ) }) @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> findAll() { + public ResponseEntity> findAll() { return ResponseEntity.ok(userService.findAll()); } @Operation(summary = "User 등록", - requestBody = @RequestBody( + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( content = @Content( encoding = @Encoding(name = "userCreateRequest", contentType = MediaType.APPLICATION_JSON_VALUE) ) @@ -74,7 +75,7 @@ public ResponseEntity> findAll() { @ApiResponse(responseCode = "201", description = "User가 성공적으로 생성됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = UserInfo.class) + schema = @Schema(implementation = UserResultDto.class) ) ), @ApiResponse(responseCode = "400", description = "같은 email 또는 username를 사용하는 User가 이미 존재함", @@ -85,7 +86,7 @@ public ResponseEntity> findAll() { ) }) @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity create( + public ResponseEntity create( @Parameter() @RequestPart UserCreateRequest userCreateRequest, @Parameter(description = "User 프로필 이미지") @RequestPart(required = false) MultipartFile profile ) { @@ -109,7 +110,7 @@ public ResponseEntity delete( } @Operation(summary = "User 정보 수정", - requestBody = @RequestBody( + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( content = @Content( encoding = @Encoding(name = "userUpdateRequest", contentType = MediaType.APPLICATION_JSON_VALUE) ) @@ -131,19 +132,19 @@ public ResponseEntity delete( @ApiResponse(responseCode = "200", description = "User 정보가 성공적으로 수정됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = Void.class) + schema = @Schema(implementation = UserResultDto.class) ) ) }) @RequestMapping(value = "/{userId}", method = RequestMethod.PATCH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity update( + public ResponseEntity update( @Parameter(description = "수정할 User ID") @PathVariable UUID userId, @RequestPart UserUpdateRequest userUpdateRequest, @Parameter(description = "수정할 User 프로필 이미지") @RequestPart(required = false) MultipartFile profile ) { - userService.updateUser(userId, userUpdateRequest, resolveProfileFile(profile)); - return ResponseEntity.noContent().build(); + return ResponseEntity.ok( + userService.updateUser(userId, userUpdateRequest, resolveProfileFile(profile))); } @Operation(summary = "User 온라인 상태 업데이트") @@ -163,9 +164,10 @@ public ResponseEntity update( }) @RequestMapping(value = "/{userId}/userStatus", method = RequestMethod.PATCH) public ResponseEntity updateUserStatusByUserId( - @Parameter(description = "상태를 변경할 User ID") @PathVariable UUID userId + @Parameter(description = "상태를 변경할 User ID") @PathVariable UUID userId, + @RequestBody UserStatusUpdateRequest request ) { - return ResponseEntity.ok(userStatusService.updateUserStatusByUserId(userId)); + return ResponseEntity.ok(userStatusService.update(userId, request)); } @RequestMapping(value = "/{userId}/userStatus", method = RequestMethod.GET) diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java deleted file mode 100644 index 2f22d5c7f..000000000 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserInfo.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.sprint.mission.discodeit.user.dto; - -import java.util.UUID; - -public record UserInfo( - UUID id, - String username, - String email, - UUID profileId, - UUID statusId -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserResultDto.java similarity index 52% rename from src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java rename to src/main/java/com/sprint/mission/discodeit/user/dto/UserResultDto.java index 9a7b7bf0f..cadea7dc2 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserDtoWithStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserResultDto.java @@ -1,14 +1,15 @@ package com.sprint.mission.discodeit.user.dto; +import java.time.Instant; import java.util.UUID; -public record UserDtoWithStatus( +public record UserResultDto( UUID id, + Instant createdAt, + Instant updatedAt, String username, String email, - UUID profileId, - UUID statusId, - boolean online + UUID profileId ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java index e8d418ad8..e5ae7819d 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java @@ -2,8 +2,7 @@ import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; -import com.sprint.mission.discodeit.user.dto.UserInfo; +import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; @@ -12,36 +11,26 @@ public class UserMapper { private UserMapper() { } - public static UserInfo toUserInfo(User user, UserStatus userStatus) { - return new UserInfo( - user.getId(), - user.getUsername(), - user.getEmail(), - user.getProfileId(), - userStatus.getId() - ); - } - - public static UserDtoWithStatus toUserInfoWithStatus(User user, UserStatus userStatus) { - return new UserDtoWithStatus( + public static UserDto toUserDto(User user, UserStatus userStatus) { + return new UserDto( user.getId(), + user.getCreatedAt(), + user.getUpdatedAt(), user.getUsername(), user.getEmail(), user.getProfileId(), - userStatus.getId(), userStatus.isOnline() ); } - public static UserDto toUserDto(User user, UserStatus userStatus) { - return new UserDto( + public static UserResultDto toUserResultDto(User user) { + return new UserResultDto( user.getId(), user.getCreatedAt(), user.getUpdatedAt(), user.getUsername(), user.getEmail(), - user.getProfileId(), - userStatus.isOnline() + user.getProfileId() ); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java index 1b8b58771..10b314667 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java @@ -1,15 +1,12 @@ package com.sprint.mission.discodeit.user.service; import com.sprint.mission.discodeit.user.dto.LoginRequest; -import com.sprint.mission.discodeit.user.dto.UserInfo; +import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.AuthenticationFailedException; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.mapper.UserMapper; import com.sprint.mission.discodeit.user.repository.UserRepository; -import com.sprint.mission.discodeit.userstatus.entity.UserStatus; -import com.sprint.mission.discodeit.userstatus.exception.UserStatusNotFoundException; -import com.sprint.mission.discodeit.userstatus.repository.UserStatusRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,19 +15,13 @@ public class AuthService { private final UserRepository userRepository; - private final UserStatusRepository userStatusRepository; - public UserInfo login(LoginRequest loginRequest) { + public UserResultDto login(LoginRequest loginRequest) { User findUser = userRepository.findByName(loginRequest.username()) .orElseThrow(UserNotFoundException::new); if (!findUser.getPassword().equals(loginRequest.password())) { throw new AuthenticationFailedException(); } - UserStatus status = userStatusRepository.findByUserId(findUser.getId()) - .orElseThrow(UserStatusNotFoundException::new); - ; - status.update(); - userStatusRepository.save(status); - return UserMapper.toUserInfo(findUser, status); + return UserMapper.toUserResultDto(findUser); } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index 7a2a0ec68..2343004ed 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -6,8 +6,7 @@ import com.sprint.mission.discodeit.channel.repository.ChannelRepository; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; -import com.sprint.mission.discodeit.user.dto.UserInfo; +import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.EmailDuplicationException; @@ -34,7 +33,7 @@ public class BasicUserService implements UserService { private final UserStatusRepository userStatusRepository; @Override - public UserInfo createUser(UserCreateRequest userInfo, + public UserResultDto createUser(UserCreateRequest userInfo, Optional image) { // 유저 이름 & 이메일 검증 validateUserExist(userInfo.username()); @@ -59,26 +58,24 @@ public UserInfo createUser(UserCreateRequest userInfo, // Repo 저장 userStatusRepository.save(status); userRepository.save(user); - return UserMapper.toUserInfo(user, status); + return UserMapper.toUserResultDto(user); } @Override - public UserDtoWithStatus findUser(UUID userId) { + public UserResultDto findUser(UUID userId) { User user = userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); - UserStatus status = userStatusRepository.findByUserId(user.getId()) - .orElseThrow(UserStatusNotFoundException::new); - return UserMapper.toUserInfoWithStatus(user, status); + return UserMapper.toUserResultDto(user); } @Override - public List findAll() { + public List findAll() { return userRepository.findAll() .stream() .map(user -> { UserStatus status = userStatusRepository.findByUserId(user.getId()) .orElseThrow(UserStatusNotFoundException::new); - return UserMapper.toUserInfoWithStatus(user, status); + return UserMapper.toUserDto(user, status); }) .toList(); } @@ -95,20 +92,16 @@ public List findAllWithUserDTO() { } @Override - public List findAllByChannelId(UUID channelId) { + public List findAllByChannelId(UUID channelId) { return userRepository.findAll() .stream() .filter(user -> user.getChannelIds().contains(channelId)) - .map(user -> { - UserStatus status = userStatusRepository.findByUserId(user.getId()) - .orElseThrow(UserStatusNotFoundException::new); - return UserMapper.toUserInfoWithStatus(user, status); - }) + .map(UserMapper::toUserResultDto) .toList(); } @Override - public UserInfo updateUser(UUID userId, UserUpdateRequest request, + public UserResultDto updateUser(UUID userId, UserUpdateRequest request, Optional image) { validateUserExist(request.newUsername()); validateEmailExist(request.newEmail()); @@ -145,7 +138,7 @@ public UserInfo updateUser(UUID userId, UserUpdateRequest request, userStatusRepository.save(status); userRepository.save(findUser); - return UserMapper.toUserInfo(findUser, status); + return UserMapper.toUserResultDto(findUser); } @Override diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java index fad1e5734..af352b2a9 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java @@ -3,8 +3,7 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserDtoWithStatus; -import com.sprint.mission.discodeit.user.dto.UserInfo; +import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import java.util.List; import java.util.Optional; @@ -12,17 +11,17 @@ public interface UserService { - UserInfo createUser(UserCreateRequest userInfo, Optional image); + UserResultDto createUser(UserCreateRequest userInfo, Optional image); - UserDtoWithStatus findUser(UUID userId); + UserResultDto findUser(UUID userId); - List findAll(); + List findAll(); List findAllWithUserDTO(); - List findAllByChannelId(UUID channelId); + List findAllByChannelId(UUID channelId); - UserInfo updateUser(UUID userId, UserUpdateRequest updateInfo, + UserResultDto updateUser(UUID userId, UserUpdateRequest updateInfo, Optional image); void deleteUser(UUID userId); diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java index 0c03a2f6e..44349c868 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java @@ -5,6 +5,8 @@ public record UserStatusDto( UUID id, + Instant createdAt, + Instant updatedAt, UUID userId, Instant lastActiveAt, boolean online diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java index 74fb8db85..d0e4eea99 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java @@ -11,6 +11,8 @@ private UserStatusMapper() { public static UserStatusDto toUserStatusInfo(UserStatus userStatus) { return new UserStatusDto( userStatus.getId(), + userStatus.getCreatedAt(), + userStatus.getUpdatedAt(), userStatus.getUserId(), userStatus.getLastActiveAt(), userStatus.isOnline() diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index 4a5c21327..af8bdeb90 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -69,8 +69,9 @@ public UserStatusDto updateUserStatusByUserId(UUID userId) { return UserStatusMapper.toUserStatusInfo(userStatus); } - public UserStatusDto update(UUID userStatusId, UserStatusUpdateRequest request) { - UserStatus userStatus = userStatusRepository.findById(userStatusId) + public UserStatusDto update(UUID userId, UserStatusUpdateRequest request) { + + UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(request.newLastActiveAt()); userStatusRepository.save(userStatus); From c56c7e823d17d558f216dd9c7919f33124a672ee Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Tue, 24 Feb 2026 14:22:24 +0900 Subject: [PATCH 16/48] =?UTF-8?q?refactor:=20=EB=AC=B8=EC=84=9C=EC=97=90?= =?UTF-8?q?=20=EB=A7=9E=EA=B2=8C=20=EC=9D=91=EB=8B=B5=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../channel/controller/ChannelController.java | 4 ++-- .../exception/GlobalExceptionHandler.java | 16 ++++++++-------- .../message/controller/MessageController.java | 5 +++-- .../controller/ReadStatusController.java | 2 +- .../user/controller/UserController.java | 5 +++-- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index c99605a2b..5e59cd644 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -49,7 +49,7 @@ public class ChannelController { public ResponseEntity create_3( @RequestBody PublicChannelCreateRequest channelInfo ) { - return ResponseEntity.ok(channelService.createPublicChannel(channelInfo)); + return ResponseEntity.status(201).body(channelService.createPublicChannel(channelInfo)); } @Operation(summary = "Private Channel 생성") @@ -66,7 +66,7 @@ public ResponseEntity create_3( public ResponseEntity create_4( @RequestBody PrivateChannelCreateRequest channelInfo ) { - return ResponseEntity.ok(channelService.createPrivateChannel(channelInfo)); + return ResponseEntity.status(201).body(channelService.createPrivateChannel(channelInfo)); } @RequestMapping(value = "/{channelId}", method = RequestMethod.GET) diff --git a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java index 502e1fed0..90869c34d 100644 --- a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java @@ -27,7 +27,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(EmailDuplicationException.class) public ResponseEntity handle(EmailDuplicationException e) { ErrorResponse response = new ErrorResponse("EMAIL_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(UserNotFoundException.class) @@ -39,7 +39,7 @@ public ResponseEntity handle(UserNotFoundException e) { @ExceptionHandler(UserDuplicationException.class) public ResponseEntity handle(UserDuplicationException e) { ErrorResponse response = new ErrorResponse("USER_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(UserStatusNotFoundException.class) @@ -51,13 +51,13 @@ public ResponseEntity handle(UserStatusNotFoundException e) { @ExceptionHandler(UserStatusDuplicationException.class) public ResponseEntity handle(UserStatusDuplicationException e) { ErrorResponse response = new ErrorResponse("USER_STATUS_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(AuthenticationFailedException.class) public ResponseEntity handle(AuthenticationFailedException e) { ErrorResponse response = new ErrorResponse("AUTHENTICATION_FAILED", e.getMessage()); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(ChannelNotFoundException.class) @@ -69,19 +69,19 @@ public ResponseEntity handle(ChannelNotFoundException e) { @ExceptionHandler(ChannelDuplicationException.class) public ResponseEntity handle(ChannelDuplicationException e) { ErrorResponse response = new ErrorResponse("CHANNEL_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(ChannelUpdateNotAllowedException.class) public ResponseEntity handle(ChannelUpdateNotAllowedException e) { ErrorResponse response = new ErrorResponse("CHANNEL_UPDATE_NOT_ALLOWED", e.getMessage()); - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(AlreadyJoinedException.class) public ResponseEntity handle(AlreadyJoinedException e) { ErrorResponse response = new ErrorResponse("ALREADY_JOINED", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(MessageNotFoundException.class) @@ -99,7 +99,7 @@ public ResponseEntity handle(ReadStatusNotFoundException e) { @ExceptionHandler(ReadStatusDuplicationException.class) public ResponseEntity handle(ReadStatusDuplicationException e) { ErrorResponse response = new ErrorResponse("READ_STATUS_DUPLICATION_ERROR", e.getMessage()); - return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(BinaryContentNotFoundException.class) diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index ffa7dfec1..872ec0624 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -81,8 +81,9 @@ public ResponseEntity create_2( }) .toList()) .orElse(new ArrayList<>()); - return ResponseEntity.ok( - messageService.createMessage(messageCreateRequest, attachmentRequests)); + return ResponseEntity.status(201).body( + messageService.createMessage(messageCreateRequest, attachmentRequests) + ); } @RequestMapping(value = "/{messageId}", method = RequestMethod.GET) diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index 0410a47b9..36aae6658 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -54,7 +54,7 @@ public class ReadStatusController { @RequestMapping(method = RequestMethod.POST, consumes = "application/json") public ResponseEntity create_1( @RequestBody ReadStatusCreateRequest statusInfo) { - return ResponseEntity.ok(readStatusService.createReadStatus(statusInfo)); + return ResponseEntity.status(201).body(readStatusService.createReadStatus(statusInfo)); } @RequestMapping(value = "/{statusId}/updated-at", method = RequestMethod.PATCH) diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index cdcd21fc8..a2f2cf822 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -90,8 +90,9 @@ public ResponseEntity create( @Parameter() @RequestPart UserCreateRequest userCreateRequest, @Parameter(description = "User 프로필 이미지") @RequestPart(required = false) MultipartFile profile ) { - return ResponseEntity.ok( - userService.createUser(userCreateRequest, resolveProfileFile(profile))); + return ResponseEntity.status(201).body( + userService.createUser(userCreateRequest, resolveProfileFile(profile)) + ); } @Operation(summary = "User 삭제") From a4a81a1360f6b0238c4f38f8441290b1a5f6dcfb Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Tue, 24 Feb 2026 16:10:03 +0900 Subject: [PATCH 17/48] =?UTF-8?q?feat:=20=EC=A0=95=EC=A0=81=20=EB=A6=AC?= =?UTF-8?q?=EC=86=8C=EC=8A=A4,=20=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=94?= =?UTF-8?q?=EB=93=9C=20=ED=86=B5=ED=95=A9(=EC=8B=AC=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/static/assets/index-CRrRqFH4.js | 956 ++++++++++++++++++ .../static/assets/index-kQJbKSsj.css | 1 + src/main/resources/static/favicon.ico | Bin 0 -> 1588 bytes src/main/resources/static/index.html | 26 + src/main/resources/static/script.js | 72 -- src/main/resources/static/styles.css | 80 -- src/main/resources/static/user-list.html | 18 - 7 files changed, 983 insertions(+), 170 deletions(-) create mode 100644 src/main/resources/static/assets/index-CRrRqFH4.js create mode 100644 src/main/resources/static/assets/index-kQJbKSsj.css create mode 100644 src/main/resources/static/favicon.ico create mode 100644 src/main/resources/static/index.html delete mode 100644 src/main/resources/static/script.js delete mode 100644 src/main/resources/static/styles.css delete mode 100644 src/main/resources/static/user-list.html diff --git a/src/main/resources/static/assets/index-CRrRqFH4.js b/src/main/resources/static/assets/index-CRrRqFH4.js new file mode 100644 index 000000000..ffeaa39b4 --- /dev/null +++ b/src/main/resources/static/assets/index-CRrRqFH4.js @@ -0,0 +1,956 @@ +(function(){const i=document.createElement("link").relList;if(i&&i.supports&&i.supports("modulepreload"))return;for(const c of document.querySelectorAll('link[rel="modulepreload"]'))u(c);new MutationObserver(c=>{for(const d of c)if(d.type==="childList")for(const p of d.addedNodes)p.tagName==="LINK"&&p.rel==="modulepreload"&&u(p)}).observe(document,{childList:!0,subtree:!0});function s(c){const d={};return c.integrity&&(d.integrity=c.integrity),c.referrerPolicy&&(d.referrerPolicy=c.referrerPolicy),c.crossOrigin==="use-credentials"?d.credentials="include":c.crossOrigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function u(c){if(c.ep)return;c.ep=!0;const d=s(c);fetch(c.href,d)}})();function Qm(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}var lu={exports:{}},ho={},uu={exports:{}},fe={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Wf;function qm(){if(Wf)return fe;Wf=1;var r=Symbol.for("react.element"),i=Symbol.for("react.portal"),s=Symbol.for("react.fragment"),u=Symbol.for("react.strict_mode"),c=Symbol.for("react.profiler"),d=Symbol.for("react.provider"),p=Symbol.for("react.context"),m=Symbol.for("react.forward_ref"),v=Symbol.for("react.suspense"),x=Symbol.for("react.memo"),E=Symbol.for("react.lazy"),j=Symbol.iterator;function O(S){return S===null||typeof S!="object"?null:(S=j&&S[j]||S["@@iterator"],typeof S=="function"?S:null)}var P={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},I=Object.assign,R={};function L(S,D,oe){this.props=S,this.context=D,this.refs=R,this.updater=oe||P}L.prototype.isReactComponent={},L.prototype.setState=function(S,D){if(typeof S!="object"&&typeof S!="function"&&S!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,S,D,"setState")},L.prototype.forceUpdate=function(S){this.updater.enqueueForceUpdate(this,S,"forceUpdate")};function V(){}V.prototype=L.prototype;function F(S,D,oe){this.props=S,this.context=D,this.refs=R,this.updater=oe||P}var W=F.prototype=new V;W.constructor=F,I(W,L.prototype),W.isPureReactComponent=!0;var K=Array.isArray,$=Object.prototype.hasOwnProperty,T={current:null},H={key:!0,ref:!0,__self:!0,__source:!0};function se(S,D,oe){var le,de={},ce=null,ve=null;if(D!=null)for(le in D.ref!==void 0&&(ve=D.ref),D.key!==void 0&&(ce=""+D.key),D)$.call(D,le)&&!H.hasOwnProperty(le)&&(de[le]=D[le]);var pe=arguments.length-2;if(pe===1)de.children=oe;else if(1>>1,D=Q[S];if(0>>1;Sc(de,q))cec(ve,de)?(Q[S]=ve,Q[ce]=q,S=ce):(Q[S]=de,Q[le]=q,S=le);else if(cec(ve,q))Q[S]=ve,Q[ce]=q,S=ce;else break e}}return ee}function c(Q,ee){var q=Q.sortIndex-ee.sortIndex;return q!==0?q:Q.id-ee.id}if(typeof performance=="object"&&typeof performance.now=="function"){var d=performance;r.unstable_now=function(){return d.now()}}else{var p=Date,m=p.now();r.unstable_now=function(){return p.now()-m}}var v=[],x=[],E=1,j=null,O=3,P=!1,I=!1,R=!1,L=typeof setTimeout=="function"?setTimeout:null,V=typeof clearTimeout=="function"?clearTimeout:null,F=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function W(Q){for(var ee=s(x);ee!==null;){if(ee.callback===null)u(x);else if(ee.startTime<=Q)u(x),ee.sortIndex=ee.expirationTime,i(v,ee);else break;ee=s(x)}}function K(Q){if(R=!1,W(Q),!I)if(s(v)!==null)I=!0,We($);else{var ee=s(x);ee!==null&&Se(K,ee.startTime-Q)}}function $(Q,ee){I=!1,R&&(R=!1,V(se),se=-1),P=!0;var q=O;try{for(W(ee),j=s(v);j!==null&&(!(j.expirationTime>ee)||Q&&!qt());){var S=j.callback;if(typeof S=="function"){j.callback=null,O=j.priorityLevel;var D=S(j.expirationTime<=ee);ee=r.unstable_now(),typeof D=="function"?j.callback=D:j===s(v)&&u(v),W(ee)}else u(v);j=s(v)}if(j!==null)var oe=!0;else{var le=s(x);le!==null&&Se(K,le.startTime-ee),oe=!1}return oe}finally{j=null,O=q,P=!1}}var T=!1,H=null,se=-1,Ve=5,At=-1;function qt(){return!(r.unstable_now()-AtQ||125S?(Q.sortIndex=q,i(x,Q),s(v)===null&&Q===s(x)&&(R?(V(se),se=-1):R=!0,Se(K,q-S))):(Q.sortIndex=D,i(v,Q),I||P||(I=!0,We($))),Q},r.unstable_shouldYield=qt,r.unstable_wrapCallback=function(Q){var ee=O;return function(){var q=O;O=ee;try{return Q.apply(this,arguments)}finally{O=q}}}}(fu)),fu}var Yf;function Km(){return Yf||(Yf=1,cu.exports=Ym()),cu.exports}/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Kf;function Xm(){if(Kf)return st;Kf=1;var r=Bu(),i=Km();function s(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),v=Object.prototype.hasOwnProperty,x=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,E={},j={};function O(e){return v.call(j,e)?!0:v.call(E,e)?!1:x.test(e)?j[e]=!0:(E[e]=!0,!1)}function P(e,t,n,o){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return o?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function I(e,t,n,o){if(t===null||typeof t>"u"||P(e,t,n,o))return!0;if(o)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function R(e,t,n,o,l,a,f){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=o,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=f}var L={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){L[e]=new R(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];L[t]=new R(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){L[e]=new R(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){L[e]=new R(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){L[e]=new R(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){L[e]=new R(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){L[e]=new R(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){L[e]=new R(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){L[e]=new R(e,5,!1,e.toLowerCase(),null,!1,!1)});var V=/[\-:]([a-z])/g;function F(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(V,F);L[t]=new R(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(V,F);L[t]=new R(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(V,F);L[t]=new R(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){L[e]=new R(e,1,!1,e.toLowerCase(),null,!1,!1)}),L.xlinkHref=new R("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){L[e]=new R(e,1,!1,e.toLowerCase(),null,!0,!0)});function W(e,t,n,o){var l=L.hasOwnProperty(t)?L[t]:null;(l!==null?l.type!==0:o||!(2h||l[f]!==a[h]){var y=` +`+l[f].replace(" at new "," at ");return e.displayName&&y.includes("")&&(y=y.replace("",e.displayName)),y}while(1<=f&&0<=h);break}}}finally{oe=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?D(e):""}function de(e){switch(e.tag){case 5:return D(e.type);case 16:return D("Lazy");case 13:return D("Suspense");case 19:return D("SuspenseList");case 0:case 2:case 15:return e=le(e.type,!1),e;case 11:return e=le(e.type.render,!1),e;case 1:return e=le(e.type,!0),e;default:return""}}function ce(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case H:return"Fragment";case T:return"Portal";case Ve:return"Profiler";case se:return"StrictMode";case Je:return"Suspense";case at:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case qt:return(e.displayName||"Context")+".Consumer";case At:return(e._context.displayName||"Context")+".Provider";case gt:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case yt:return t=e.displayName||null,t!==null?t:ce(e.type)||"Memo";case We:t=e._payload,e=e._init;try{return ce(e(t))}catch{}}return null}function ve(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return ce(t);case 8:return t===se?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function pe(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ge(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Be(e){var t=ge(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),o=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(f){o=""+f,a.call(this,f)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return o},setValue:function(f){o=""+f},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function bt(e){e._valueTracker||(e._valueTracker=Be(e))}function Rt(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),o="";return e&&(o=ge(e)?e.checked?"true":"false":e.value),e=o,e!==n?(t.setValue(e),!0):!1}function Ao(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function hs(e,t){var n=t.checked;return q({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Ku(e,t){var n=t.defaultValue==null?"":t.defaultValue,o=t.checked!=null?t.checked:t.defaultChecked;n=pe(t.value!=null?t.value:n),e._wrapperState={initialChecked:o,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Xu(e,t){t=t.checked,t!=null&&W(e,"checked",t,!1)}function ms(e,t){Xu(e,t);var n=pe(t.value),o=t.type;if(n!=null)o==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(o==="submit"||o==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?gs(e,t.type,n):t.hasOwnProperty("defaultValue")&&gs(e,t.type,pe(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Ju(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var o=t.type;if(!(o!=="submit"&&o!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function gs(e,t,n){(t!=="number"||Ao(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var jr=Array.isArray;function Gn(e,t,n,o){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Ro.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Ir(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var _r={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Kp=["Webkit","ms","Moz","O"];Object.keys(_r).forEach(function(e){Kp.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),_r[t]=_r[e]})});function oa(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||_r.hasOwnProperty(e)&&_r[e]?(""+t).trim():t+"px"}function ia(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var o=n.indexOf("--")===0,l=oa(n,t[n],o);n==="float"&&(n="cssFloat"),o?e.setProperty(n,l):e[n]=l}}var Xp=q({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function ws(e,t){if(t){if(Xp[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(s(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(s(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(s(61))}if(t.style!=null&&typeof t.style!="object")throw Error(s(62))}}function xs(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Ss=null;function ks(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Es=null,Yn=null,Kn=null;function sa(e){if(e=Jr(e)){if(typeof Es!="function")throw Error(s(280));var t=e.stateNode;t&&(t=Yo(t),Es(e.stateNode,e.type,t))}}function la(e){Yn?Kn?Kn.push(e):Kn=[e]:Yn=e}function ua(){if(Yn){var e=Yn,t=Kn;if(Kn=Yn=null,sa(e),t)for(e=0;e>>=0,e===0?32:31-(uh(e)/ah|0)|0}var No=64,Oo=4194304;function Lr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function To(e,t){var n=e.pendingLanes;if(n===0)return 0;var o=0,l=e.suspendedLanes,a=e.pingedLanes,f=n&268435455;if(f!==0){var h=f&~l;h!==0?o=Lr(h):(a&=f,a!==0&&(o=Lr(a)))}else f=n&~l,f!==0?o=Lr(f):a!==0&&(o=Lr(a));if(o===0)return 0;if(t!==0&&t!==o&&!(t&l)&&(l=o&-o,a=t&-t,l>=a||l===16&&(a&4194240)!==0))return t;if(o&4&&(o|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=o;0n;n++)t.push(e);return t}function Dr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Pt(t),e[t]=n}function ph(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var o=e.eventTimes;for(e=e.expirationTimes;0=Vr),za=" ",Ma=!1;function Ua(e,t){switch(e){case"keyup":return $h.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Fa(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Zn=!1;function Vh(e,t){switch(e){case"compositionend":return Fa(t);case"keypress":return t.which!==32?null:(Ma=!0,za);case"textInput":return e=t.data,e===za&&Ma?null:e;default:return null}}function Wh(e,t){if(Zn)return e==="compositionend"||!$s&&Ua(e,t)?(e=_a(),Uo=Ds=an=null,Zn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=o}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=qa(n)}}function Ga(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ga(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ya(){for(var e=window,t=Ao();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ao(e.document)}return t}function Ws(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Zh(e){var t=Ya(),n=e.focusedElem,o=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Ga(n.ownerDocument.documentElement,n)){if(o!==null&&Ws(n)){if(t=o.start,e=o.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,a=Math.min(o.start,l);o=o.end===void 0?a:Math.min(o.end,l),!e.extend&&a>o&&(l=o,o=a,a=l),l=ba(n,a);var f=ba(n,o);l&&f&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==f.node||e.focusOffset!==f.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),a>o?(e.addRange(t),e.extend(f.node,f.offset)):(t.setEnd(f.node,f.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,er=null,Qs=null,br=null,qs=!1;function Ka(e,t,n){var o=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;qs||er==null||er!==Ao(o)||(o=er,"selectionStart"in o&&Ws(o)?o={start:o.selectionStart,end:o.selectionEnd}:(o=(o.ownerDocument&&o.ownerDocument.defaultView||window).getSelection(),o={anchorNode:o.anchorNode,anchorOffset:o.anchorOffset,focusNode:o.focusNode,focusOffset:o.focusOffset}),br&&qr(br,o)||(br=o,o=qo(Qs,"onSelect"),0ir||(e.current=ol[ir],ol[ir]=null,ir--)}function ke(e,t){ir++,ol[ir]=e.current,e.current=t}var pn={},Qe=dn(pn),tt=dn(!1),Pn=pn;function sr(e,t){var n=e.type.contextTypes;if(!n)return pn;var o=e.stateNode;if(o&&o.__reactInternalMemoizedUnmaskedChildContext===t)return o.__reactInternalMemoizedMaskedChildContext;var l={},a;for(a in n)l[a]=t[a];return o&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function nt(e){return e=e.childContextTypes,e!=null}function Ko(){Ce(tt),Ce(Qe)}function fc(e,t,n){if(Qe.current!==pn)throw Error(s(168));ke(Qe,t),ke(tt,n)}function dc(e,t,n){var o=e.stateNode;if(t=t.childContextTypes,typeof o.getChildContext!="function")return n;o=o.getChildContext();for(var l in o)if(!(l in t))throw Error(s(108,ve(e)||"Unknown",l));return q({},n,o)}function Xo(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||pn,Pn=Qe.current,ke(Qe,e),ke(tt,tt.current),!0}function pc(e,t,n){var o=e.stateNode;if(!o)throw Error(s(169));n?(e=dc(e,t,Pn),o.__reactInternalMemoizedMergedChildContext=e,Ce(tt),Ce(Qe),ke(Qe,e)):Ce(tt),ke(tt,n)}var Yt=null,Jo=!1,il=!1;function hc(e){Yt===null?Yt=[e]:Yt.push(e)}function fm(e){Jo=!0,hc(e)}function hn(){if(!il&&Yt!==null){il=!0;var e=0,t=xe;try{var n=Yt;for(xe=1;e>=f,l-=f,Kt=1<<32-Pt(t)+l|n<re?(Ue=ne,ne=null):Ue=ne.sibling;var ye=z(k,ne,C[re],B);if(ye===null){ne===null&&(ne=Ue);break}e&&ne&&ye.alternate===null&&t(k,ne),w=a(ye,w,re),te===null?Z=ye:te.sibling=ye,te=ye,ne=Ue}if(re===C.length)return n(k,ne),Re&&In(k,re),Z;if(ne===null){for(;rere?(Ue=ne,ne=null):Ue=ne.sibling;var En=z(k,ne,ye.value,B);if(En===null){ne===null&&(ne=Ue);break}e&&ne&&En.alternate===null&&t(k,ne),w=a(En,w,re),te===null?Z=En:te.sibling=En,te=En,ne=Ue}if(ye.done)return n(k,ne),Re&&In(k,re),Z;if(ne===null){for(;!ye.done;re++,ye=C.next())ye=U(k,ye.value,B),ye!==null&&(w=a(ye,w,re),te===null?Z=ye:te.sibling=ye,te=ye);return Re&&In(k,re),Z}for(ne=o(k,ne);!ye.done;re++,ye=C.next())ye=b(ne,k,re,ye.value,B),ye!==null&&(e&&ye.alternate!==null&&ne.delete(ye.key===null?re:ye.key),w=a(ye,w,re),te===null?Z=ye:te.sibling=ye,te=ye);return e&&ne.forEach(function(Wm){return t(k,Wm)}),Re&&In(k,re),Z}function Ne(k,w,C,B){if(typeof C=="object"&&C!==null&&C.type===H&&C.key===null&&(C=C.props.children),typeof C=="object"&&C!==null){switch(C.$$typeof){case $:e:{for(var Z=C.key,te=w;te!==null;){if(te.key===Z){if(Z=C.type,Z===H){if(te.tag===7){n(k,te.sibling),w=l(te,C.props.children),w.return=k,k=w;break e}}else if(te.elementType===Z||typeof Z=="object"&&Z!==null&&Z.$$typeof===We&&xc(Z)===te.type){n(k,te.sibling),w=l(te,C.props),w.ref=Zr(k,te,C),w.return=k,k=w;break e}n(k,te);break}else t(k,te);te=te.sibling}C.type===H?(w=Mn(C.props.children,k.mode,B,C.key),w.return=k,k=w):(B=Ri(C.type,C.key,C.props,null,k.mode,B),B.ref=Zr(k,w,C),B.return=k,k=B)}return f(k);case T:e:{for(te=C.key;w!==null;){if(w.key===te)if(w.tag===4&&w.stateNode.containerInfo===C.containerInfo&&w.stateNode.implementation===C.implementation){n(k,w.sibling),w=l(w,C.children||[]),w.return=k,k=w;break e}else{n(k,w);break}else t(k,w);w=w.sibling}w=nu(C,k.mode,B),w.return=k,k=w}return f(k);case We:return te=C._init,Ne(k,w,te(C._payload),B)}if(jr(C))return Y(k,w,C,B);if(ee(C))return X(k,w,C,B);ni(k,C)}return typeof C=="string"&&C!==""||typeof C=="number"?(C=""+C,w!==null&&w.tag===6?(n(k,w.sibling),w=l(w,C),w.return=k,k=w):(n(k,w),w=tu(C,k.mode,B),w.return=k,k=w),f(k)):n(k,w)}return Ne}var cr=Sc(!0),kc=Sc(!1),ri=dn(null),oi=null,fr=null,fl=null;function dl(){fl=fr=oi=null}function pl(e){var t=ri.current;Ce(ri),e._currentValue=t}function hl(e,t,n){for(;e!==null;){var o=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,o!==null&&(o.childLanes|=t)):o!==null&&(o.childLanes&t)!==t&&(o.childLanes|=t),e===n)break;e=e.return}}function dr(e,t){oi=e,fl=fr=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(rt=!0),e.firstContext=null)}function xt(e){var t=e._currentValue;if(fl!==e)if(e={context:e,memoizedValue:t,next:null},fr===null){if(oi===null)throw Error(s(308));fr=e,oi.dependencies={lanes:0,firstContext:e}}else fr=fr.next=e;return t}var _n=null;function ml(e){_n===null?_n=[e]:_n.push(e)}function Ec(e,t,n,o){var l=t.interleaved;return l===null?(n.next=n,ml(t)):(n.next=l.next,l.next=n),t.interleaved=n,Jt(e,o)}function Jt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var mn=!1;function gl(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Cc(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Zt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function gn(e,t,n){var o=e.updateQueue;if(o===null)return null;if(o=o.shared,me&2){var l=o.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),o.pending=t,Jt(e,n)}return l=o.interleaved,l===null?(t.next=t,ml(o)):(t.next=l.next,l.next=t),o.interleaved=t,Jt(e,n)}function ii(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var o=t.lanes;o&=e.pendingLanes,n|=o,t.lanes=n,_s(e,n)}}function Ac(e,t){var n=e.updateQueue,o=e.alternate;if(o!==null&&(o=o.updateQueue,n===o)){var l=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var f={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};a===null?l=a=f:a=a.next=f,n=n.next}while(n!==null);a===null?l=a=t:a=a.next=t}else l=a=t;n={baseState:o.baseState,firstBaseUpdate:l,lastBaseUpdate:a,shared:o.shared,effects:o.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function si(e,t,n,o){var l=e.updateQueue;mn=!1;var a=l.firstBaseUpdate,f=l.lastBaseUpdate,h=l.shared.pending;if(h!==null){l.shared.pending=null;var y=h,A=y.next;y.next=null,f===null?a=A:f.next=A,f=y;var M=e.alternate;M!==null&&(M=M.updateQueue,h=M.lastBaseUpdate,h!==f&&(h===null?M.firstBaseUpdate=A:h.next=A,M.lastBaseUpdate=y))}if(a!==null){var U=l.baseState;f=0,M=A=y=null,h=a;do{var z=h.lane,b=h.eventTime;if((o&z)===z){M!==null&&(M=M.next={eventTime:b,lane:0,tag:h.tag,payload:h.payload,callback:h.callback,next:null});e:{var Y=e,X=h;switch(z=t,b=n,X.tag){case 1:if(Y=X.payload,typeof Y=="function"){U=Y.call(b,U,z);break e}U=Y;break e;case 3:Y.flags=Y.flags&-65537|128;case 0:if(Y=X.payload,z=typeof Y=="function"?Y.call(b,U,z):Y,z==null)break e;U=q({},U,z);break e;case 2:mn=!0}}h.callback!==null&&h.lane!==0&&(e.flags|=64,z=l.effects,z===null?l.effects=[h]:z.push(h))}else b={eventTime:b,lane:z,tag:h.tag,payload:h.payload,callback:h.callback,next:null},M===null?(A=M=b,y=U):M=M.next=b,f|=z;if(h=h.next,h===null){if(h=l.shared.pending,h===null)break;z=h,h=z.next,z.next=null,l.lastBaseUpdate=z,l.shared.pending=null}}while(!0);if(M===null&&(y=U),l.baseState=y,l.firstBaseUpdate=A,l.lastBaseUpdate=M,t=l.shared.interleaved,t!==null){l=t;do f|=l.lane,l=l.next;while(l!==t)}else a===null&&(l.shared.lanes=0);Tn|=f,e.lanes=f,e.memoizedState=U}}function Rc(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var o=Sl.transition;Sl.transition={};try{e(!1),t()}finally{xe=n,Sl.transition=o}}function Qc(){return St().memoizedState}function mm(e,t,n){var o=xn(e);if(n={lane:o,action:n,hasEagerState:!1,eagerState:null,next:null},qc(e))bc(t,n);else if(n=Ec(e,t,n,o),n!==null){var l=et();Tt(n,e,o,l),Gc(n,t,o)}}function gm(e,t,n){var o=xn(e),l={lane:o,action:n,hasEagerState:!1,eagerState:null,next:null};if(qc(e))bc(t,l);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var f=t.lastRenderedState,h=a(f,n);if(l.hasEagerState=!0,l.eagerState=h,jt(h,f)){var y=t.interleaved;y===null?(l.next=l,ml(t)):(l.next=y.next,y.next=l),t.interleaved=l;return}}catch{}finally{}n=Ec(e,t,l,o),n!==null&&(l=et(),Tt(n,e,o,l),Gc(n,t,o))}}function qc(e){var t=e.alternate;return e===je||t!==null&&t===je}function bc(e,t){ro=ai=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Gc(e,t,n){if(n&4194240){var o=t.lanes;o&=e.pendingLanes,n|=o,t.lanes=n,_s(e,n)}}var di={readContext:xt,useCallback:qe,useContext:qe,useEffect:qe,useImperativeHandle:qe,useInsertionEffect:qe,useLayoutEffect:qe,useMemo:qe,useReducer:qe,useRef:qe,useState:qe,useDebugValue:qe,useDeferredValue:qe,useTransition:qe,useMutableSource:qe,useSyncExternalStore:qe,useId:qe,unstable_isNewReconciler:!1},ym={readContext:xt,useCallback:function(e,t){return Bt().memoizedState=[e,t===void 0?null:t],e},useContext:xt,useEffect:Mc,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,ci(4194308,4,Bc.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ci(4194308,4,e,t)},useInsertionEffect:function(e,t){return ci(4,2,e,t)},useMemo:function(e,t){var n=Bt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var o=Bt();return t=n!==void 0?n(t):t,o.memoizedState=o.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},o.queue=e,e=e.dispatch=mm.bind(null,je,e),[o.memoizedState,e]},useRef:function(e){var t=Bt();return e={current:e},t.memoizedState=e},useState:Dc,useDebugValue:jl,useDeferredValue:function(e){return Bt().memoizedState=e},useTransition:function(){var e=Dc(!1),t=e[0];return e=hm.bind(null,e[1]),Bt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var o=je,l=Bt();if(Re){if(n===void 0)throw Error(s(407));n=n()}else{if(n=t(),Me===null)throw Error(s(349));On&30||_c(o,t,n)}l.memoizedState=n;var a={value:n,getSnapshot:t};return l.queue=a,Mc(Oc.bind(null,o,a,e),[e]),o.flags|=2048,so(9,Nc.bind(null,o,a,n,t),void 0,null),n},useId:function(){var e=Bt(),t=Me.identifierPrefix;if(Re){var n=Xt,o=Kt;n=(o&~(1<<32-Pt(o)-1)).toString(32)+n,t=":"+t+"R"+n,n=oo++,0<\/script>",e=e.removeChild(e.firstChild)):typeof o.is=="string"?e=f.createElement(n,{is:o.is}):(e=f.createElement(n),n==="select"&&(f=e,o.multiple?f.multiple=!0:o.size&&(f.size=o.size))):e=f.createElementNS(e,n),e[Ut]=t,e[Xr]=o,mf(e,t,!1,!1),t.stateNode=e;e:{switch(f=xs(n,o),n){case"dialog":Ee("cancel",e),Ee("close",e),l=o;break;case"iframe":case"object":case"embed":Ee("load",e),l=o;break;case"video":case"audio":for(l=0;lyr&&(t.flags|=128,o=!0,lo(a,!1),t.lanes=4194304)}else{if(!o)if(e=li(f),e!==null){if(t.flags|=128,o=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),lo(a,!0),a.tail===null&&a.tailMode==="hidden"&&!f.alternate&&!Re)return be(t),null}else 2*_e()-a.renderingStartTime>yr&&n!==1073741824&&(t.flags|=128,o=!0,lo(a,!1),t.lanes=4194304);a.isBackwards?(f.sibling=t.child,t.child=f):(n=a.last,n!==null?n.sibling=f:t.child=f,a.last=f)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=_e(),t.sibling=null,n=Pe.current,ke(Pe,o?n&1|2:n&1),t):(be(t),null);case 22:case 23:return Jl(),o=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==o&&(t.flags|=8192),o&&t.mode&1?pt&1073741824&&(be(t),t.subtreeFlags&6&&(t.flags|=8192)):be(t),null;case 24:return null;case 25:return null}throw Error(s(156,t.tag))}function Am(e,t){switch(ll(t),t.tag){case 1:return nt(t.type)&&Ko(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return pr(),Ce(tt),Ce(Qe),xl(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return vl(t),null;case 13:if(Ce(Pe),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(s(340));ar()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Ce(Pe),null;case 4:return pr(),null;case 10:return pl(t.type._context),null;case 22:case 23:return Jl(),null;case 24:return null;default:return null}}var gi=!1,Ge=!1,Rm=typeof WeakSet=="function"?WeakSet:Set,G=null;function mr(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(o){Ie(e,t,o)}else n.current=null}function Bl(e,t,n){try{n()}catch(o){Ie(e,t,o)}}var vf=!1;function Pm(e,t){if(Js=zo,e=Ya(),Ws(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var o=n.getSelection&&n.getSelection();if(o&&o.rangeCount!==0){n=o.anchorNode;var l=o.anchorOffset,a=o.focusNode;o=o.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break e}var f=0,h=-1,y=-1,A=0,M=0,U=e,z=null;t:for(;;){for(var b;U!==n||l!==0&&U.nodeType!==3||(h=f+l),U!==a||o!==0&&U.nodeType!==3||(y=f+o),U.nodeType===3&&(f+=U.nodeValue.length),(b=U.firstChild)!==null;)z=U,U=b;for(;;){if(U===e)break t;if(z===n&&++A===l&&(h=f),z===a&&++M===o&&(y=f),(b=U.nextSibling)!==null)break;U=z,z=U.parentNode}U=b}n=h===-1||y===-1?null:{start:h,end:y}}else n=null}n=n||{start:0,end:0}}else n=null;for(Zs={focusedElem:e,selectionRange:n},zo=!1,G=t;G!==null;)if(t=G,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,G=e;else for(;G!==null;){t=G;try{var Y=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(Y!==null){var X=Y.memoizedProps,Ne=Y.memoizedState,k=t.stateNode,w=k.getSnapshotBeforeUpdate(t.elementType===t.type?X:_t(t.type,X),Ne);k.__reactInternalSnapshotBeforeUpdate=w}break;case 3:var C=t.stateNode.containerInfo;C.nodeType===1?C.textContent="":C.nodeType===9&&C.documentElement&&C.removeChild(C.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(s(163))}}catch(B){Ie(t,t.return,B)}if(e=t.sibling,e!==null){e.return=t.return,G=e;break}G=t.return}return Y=vf,vf=!1,Y}function uo(e,t,n){var o=t.updateQueue;if(o=o!==null?o.lastEffect:null,o!==null){var l=o=o.next;do{if((l.tag&e)===e){var a=l.destroy;l.destroy=void 0,a!==void 0&&Bl(t,n,a)}l=l.next}while(l!==o)}}function yi(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var o=n.create;n.destroy=o()}n=n.next}while(n!==t)}}function $l(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function wf(e){var t=e.alternate;t!==null&&(e.alternate=null,wf(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ut],delete t[Xr],delete t[rl],delete t[am],delete t[cm])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function xf(e){return e.tag===5||e.tag===3||e.tag===4}function Sf(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||xf(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Hl(e,t,n){var o=e.tag;if(o===5||o===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Go));else if(o!==4&&(e=e.child,e!==null))for(Hl(e,t,n),e=e.sibling;e!==null;)Hl(e,t,n),e=e.sibling}function Vl(e,t,n){var o=e.tag;if(o===5||o===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(o!==4&&(e=e.child,e!==null))for(Vl(e,t,n),e=e.sibling;e!==null;)Vl(e,t,n),e=e.sibling}var $e=null,Nt=!1;function yn(e,t,n){for(n=n.child;n!==null;)kf(e,t,n),n=n.sibling}function kf(e,t,n){if(Mt&&typeof Mt.onCommitFiberUnmount=="function")try{Mt.onCommitFiberUnmount(_o,n)}catch{}switch(n.tag){case 5:Ge||mr(n,t);case 6:var o=$e,l=Nt;$e=null,yn(e,t,n),$e=o,Nt=l,$e!==null&&(Nt?(e=$e,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):$e.removeChild(n.stateNode));break;case 18:$e!==null&&(Nt?(e=$e,n=n.stateNode,e.nodeType===8?nl(e.parentNode,n):e.nodeType===1&&nl(e,n),Br(e)):nl($e,n.stateNode));break;case 4:o=$e,l=Nt,$e=n.stateNode.containerInfo,Nt=!0,yn(e,t,n),$e=o,Nt=l;break;case 0:case 11:case 14:case 15:if(!Ge&&(o=n.updateQueue,o!==null&&(o=o.lastEffect,o!==null))){l=o=o.next;do{var a=l,f=a.destroy;a=a.tag,f!==void 0&&(a&2||a&4)&&Bl(n,t,f),l=l.next}while(l!==o)}yn(e,t,n);break;case 1:if(!Ge&&(mr(n,t),o=n.stateNode,typeof o.componentWillUnmount=="function"))try{o.props=n.memoizedProps,o.state=n.memoizedState,o.componentWillUnmount()}catch(h){Ie(n,t,h)}yn(e,t,n);break;case 21:yn(e,t,n);break;case 22:n.mode&1?(Ge=(o=Ge)||n.memoizedState!==null,yn(e,t,n),Ge=o):yn(e,t,n);break;default:yn(e,t,n)}}function Ef(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Rm),t.forEach(function(o){var l=zm.bind(null,e,o);n.has(o)||(n.add(o),o.then(l,l))})}}function Ot(e,t){var n=t.deletions;if(n!==null)for(var o=0;ol&&(l=f),o&=~a}if(o=l,o=_e()-o,o=(120>o?120:480>o?480:1080>o?1080:1920>o?1920:3e3>o?3e3:4320>o?4320:1960*Im(o/1960))-o,10e?16:e,wn===null)var o=!1;else{if(e=wn,wn=null,ki=0,me&6)throw Error(s(331));var l=me;for(me|=4,G=e.current;G!==null;){var a=G,f=a.child;if(G.flags&16){var h=a.deletions;if(h!==null){for(var y=0;y_e()-ql?Dn(e,0):Ql|=n),it(e,t)}function zf(e,t){t===0&&(e.mode&1?(t=Oo,Oo<<=1,!(Oo&130023424)&&(Oo=4194304)):t=1);var n=et();e=Jt(e,t),e!==null&&(Dr(e,t,n),it(e,n))}function Dm(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),zf(e,n)}function zm(e,t){var n=0;switch(e.tag){case 13:var o=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:o=e.stateNode;break;default:throw Error(s(314))}o!==null&&o.delete(t),zf(e,n)}var Mf;Mf=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||tt.current)rt=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return rt=!1,Em(e,t,n);rt=!!(e.flags&131072)}else rt=!1,Re&&t.flags&1048576&&mc(t,ei,t.index);switch(t.lanes=0,t.tag){case 2:var o=t.type;mi(e,t),e=t.pendingProps;var l=sr(t,Qe.current);dr(t,n),l=El(null,t,o,e,l,n);var a=Cl();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,nt(o)?(a=!0,Xo(t)):a=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,gl(t),l.updater=pi,t.stateNode=l,l._reactInternals=t,_l(t,o,e,n),t=Ll(null,t,o,!0,a,n)):(t.tag=0,Re&&a&&sl(t),Ze(null,t,l,n),t=t.child),t;case 16:o=t.elementType;e:{switch(mi(e,t),e=t.pendingProps,l=o._init,o=l(o._payload),t.type=o,l=t.tag=Um(o),e=_t(o,e),l){case 0:t=Tl(null,t,o,e,n);break e;case 1:t=af(null,t,o,e,n);break e;case 11:t=rf(null,t,o,e,n);break e;case 14:t=of(null,t,o,_t(o.type,e),n);break e}throw Error(s(306,o,""))}return t;case 0:return o=t.type,l=t.pendingProps,l=t.elementType===o?l:_t(o,l),Tl(e,t,o,l,n);case 1:return o=t.type,l=t.pendingProps,l=t.elementType===o?l:_t(o,l),af(e,t,o,l,n);case 3:e:{if(cf(t),e===null)throw Error(s(387));o=t.pendingProps,a=t.memoizedState,l=a.element,Cc(e,t),si(t,o,null,n);var f=t.memoizedState;if(o=f.element,a.isDehydrated)if(a={element:o,isDehydrated:!1,cache:f.cache,pendingSuspenseBoundaries:f.pendingSuspenseBoundaries,transitions:f.transitions},t.updateQueue.baseState=a,t.memoizedState=a,t.flags&256){l=hr(Error(s(423)),t),t=ff(e,t,o,n,l);break e}else if(o!==l){l=hr(Error(s(424)),t),t=ff(e,t,o,n,l);break e}else for(dt=fn(t.stateNode.containerInfo.firstChild),ft=t,Re=!0,It=null,n=kc(t,null,o,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(ar(),o===l){t=en(e,t,n);break e}Ze(e,t,o,n)}t=t.child}return t;case 5:return Pc(t),e===null&&al(t),o=t.type,l=t.pendingProps,a=e!==null?e.memoizedProps:null,f=l.children,el(o,l)?f=null:a!==null&&el(o,a)&&(t.flags|=32),uf(e,t),Ze(e,t,f,n),t.child;case 6:return e===null&&al(t),null;case 13:return df(e,t,n);case 4:return yl(t,t.stateNode.containerInfo),o=t.pendingProps,e===null?t.child=cr(t,null,o,n):Ze(e,t,o,n),t.child;case 11:return o=t.type,l=t.pendingProps,l=t.elementType===o?l:_t(o,l),rf(e,t,o,l,n);case 7:return Ze(e,t,t.pendingProps,n),t.child;case 8:return Ze(e,t,t.pendingProps.children,n),t.child;case 12:return Ze(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(o=t.type._context,l=t.pendingProps,a=t.memoizedProps,f=l.value,ke(ri,o._currentValue),o._currentValue=f,a!==null)if(jt(a.value,f)){if(a.children===l.children&&!tt.current){t=en(e,t,n);break e}}else for(a=t.child,a!==null&&(a.return=t);a!==null;){var h=a.dependencies;if(h!==null){f=a.child;for(var y=h.firstContext;y!==null;){if(y.context===o){if(a.tag===1){y=Zt(-1,n&-n),y.tag=2;var A=a.updateQueue;if(A!==null){A=A.shared;var M=A.pending;M===null?y.next=y:(y.next=M.next,M.next=y),A.pending=y}}a.lanes|=n,y=a.alternate,y!==null&&(y.lanes|=n),hl(a.return,n,t),h.lanes|=n;break}y=y.next}}else if(a.tag===10)f=a.type===t.type?null:a.child;else if(a.tag===18){if(f=a.return,f===null)throw Error(s(341));f.lanes|=n,h=f.alternate,h!==null&&(h.lanes|=n),hl(f,n,t),f=a.sibling}else f=a.child;if(f!==null)f.return=a;else for(f=a;f!==null;){if(f===t){f=null;break}if(a=f.sibling,a!==null){a.return=f.return,f=a;break}f=f.return}a=f}Ze(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,o=t.pendingProps.children,dr(t,n),l=xt(l),o=o(l),t.flags|=1,Ze(e,t,o,n),t.child;case 14:return o=t.type,l=_t(o,t.pendingProps),l=_t(o.type,l),of(e,t,o,l,n);case 15:return sf(e,t,t.type,t.pendingProps,n);case 17:return o=t.type,l=t.pendingProps,l=t.elementType===o?l:_t(o,l),mi(e,t),t.tag=1,nt(o)?(e=!0,Xo(t)):e=!1,dr(t,n),Kc(t,o,l),_l(t,o,l,n),Ll(null,t,o,!0,e,n);case 19:return hf(e,t,n);case 22:return lf(e,t,n)}throw Error(s(156,t.tag))};function Uf(e,t){return ga(e,t)}function Mm(e,t,n,o){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=o,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Et(e,t,n,o){return new Mm(e,t,n,o)}function eu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Um(e){if(typeof e=="function")return eu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===gt)return 11;if(e===yt)return 14}return 2}function kn(e,t){var n=e.alternate;return n===null?(n=Et(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Ri(e,t,n,o,l,a){var f=2;if(o=e,typeof e=="function")eu(e)&&(f=1);else if(typeof e=="string")f=5;else e:switch(e){case H:return Mn(n.children,l,a,t);case se:f=8,l|=8;break;case Ve:return e=Et(12,n,t,l|2),e.elementType=Ve,e.lanes=a,e;case Je:return e=Et(13,n,t,l),e.elementType=Je,e.lanes=a,e;case at:return e=Et(19,n,t,l),e.elementType=at,e.lanes=a,e;case Se:return Pi(n,l,a,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case At:f=10;break e;case qt:f=9;break e;case gt:f=11;break e;case yt:f=14;break e;case We:f=16,o=null;break e}throw Error(s(130,e==null?e:typeof e,""))}return t=Et(f,n,t,l),t.elementType=e,t.type=o,t.lanes=a,t}function Mn(e,t,n,o){return e=Et(7,e,o,t),e.lanes=n,e}function Pi(e,t,n,o){return e=Et(22,e,o,t),e.elementType=Se,e.lanes=n,e.stateNode={isHidden:!1},e}function tu(e,t,n){return e=Et(6,e,null,t),e.lanes=n,e}function nu(e,t,n){return t=Et(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Fm(e,t,n,o,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Is(0),this.expirationTimes=Is(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Is(0),this.identifierPrefix=o,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function ru(e,t,n,o,l,a,f,h,y){return e=new Fm(e,t,n,h,y),t===1?(t=1,a===!0&&(t|=8)):t=0,a=Et(3,null,null,t),e.current=a,a.stateNode=e,a.memoizedState={element:o,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},gl(a),e}function Bm(e,t,n){var o=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(r)}catch(i){console.error(i)}}return r(),au.exports=Xm(),au.exports}var Jf;function Zm(){if(Jf)return Li;Jf=1;var r=Jm();return Li.createRoot=r.createRoot,Li.hydrateRoot=r.hydrateRoot,Li}var eg=Zm(),Ke=function(){return Ke=Object.assign||function(i){for(var s,u=1,c=arguments.length;u0?Fe(Ar,--Ct):0,kr--,Te===10&&(kr=1,rs--),Te}function Lt(){return Te=Ct2||Au(Te)>3?"":" "}function cg(r,i){for(;--i&&Lt()&&!(Te<48||Te>102||Te>57&&Te<65||Te>70&&Te<97););return is(r,Hi()+(i<6&&$n()==32&&Lt()==32))}function Ru(r){for(;Lt();)switch(Te){case r:return Ct;case 34:case 39:r!==34&&r!==39&&Ru(Te);break;case 40:r===41&&Ru(r);break;case 92:Lt();break}return Ct}function fg(r,i){for(;Lt()&&r+Te!==57;)if(r+Te===84&&$n()===47)break;return"/*"+is(i,Ct-1)+"*"+Hu(r===47?r:Lt())}function dg(r){for(;!Au($n());)Lt();return is(r,Ct)}function pg(r){return ug(Vi("",null,null,null,[""],r=lg(r),0,[0],r))}function Vi(r,i,s,u,c,d,p,m,v){for(var x=0,E=0,j=p,O=0,P=0,I=0,R=1,L=1,V=1,F=0,W="",K=c,$=d,T=u,H=W;L;)switch(I=F,F=Lt()){case 40:if(I!=108&&Fe(H,j-1)==58){$i(H+=ae(du(F),"&","&\f"),"&\f",Kd(x?m[x-1]:0))!=-1&&(V=-1);break}case 34:case 39:case 91:H+=du(F);break;case 9:case 10:case 13:case 32:H+=ag(I);break;case 92:H+=cg(Hi()-1,7);continue;case 47:switch($n()){case 42:case 47:go(hg(fg(Lt(),Hi()),i,s,v),v);break;default:H+="/"}break;case 123*R:m[x++]=Vt(H)*V;case 125*R:case 59:case 0:switch(F){case 0:case 125:L=0;case 59+E:V==-1&&(H=ae(H,/\f/g,"")),P>0&&Vt(H)-j&&go(P>32?td(H+";",u,s,j-1,v):td(ae(H," ","")+";",u,s,j-2,v),v);break;case 59:H+=";";default:if(go(T=ed(H,i,s,x,E,c,m,W,K=[],$=[],j,d),d),F===123)if(E===0)Vi(H,i,T,T,K,d,j,m,$);else switch(O===99&&Fe(H,3)===110?100:O){case 100:case 108:case 109:case 115:Vi(r,T,T,u&&go(ed(r,T,T,0,0,c,m,W,c,K=[],j,$),$),c,$,j,m,u?K:$);break;default:Vi(H,T,T,T,[""],$,0,m,$)}}x=E=P=0,R=V=1,W=H="",j=p;break;case 58:j=1+Vt(H),P=I;default:if(R<1){if(F==123)--R;else if(F==125&&R++==0&&sg()==125)continue}switch(H+=Hu(F),F*R){case 38:V=E>0?1:(H+="\f",-1);break;case 44:m[x++]=(Vt(H)-1)*V,V=1;break;case 64:$n()===45&&(H+=du(Lt())),O=$n(),E=j=Vt(W=H+=dg(Hi())),F++;break;case 45:I===45&&Vt(H)==2&&(R=0)}}return d}function ed(r,i,s,u,c,d,p,m,v,x,E,j){for(var O=c-1,P=c===0?d:[""],I=Jd(P),R=0,L=0,V=0;R0?P[F]+" "+W:ae(W,/&\f/g,P[F])))&&(v[V++]=K);return os(r,i,s,c===0?ns:m,v,x,E,j)}function hg(r,i,s,u){return os(r,i,s,Gd,Hu(ig()),Sr(r,2,-2),0,u)}function td(r,i,s,u,c){return os(r,i,s,$u,Sr(r,0,u),Sr(r,u+1,-1),u,c)}function ep(r,i,s){switch(rg(r,i)){case 5103:return we+"print-"+r+r;case 5737:case 4201:case 3177:case 3433:case 1641:case 4457:case 2921:case 5572:case 6356:case 5844:case 3191:case 6645:case 3005:case 6391:case 5879:case 5623:case 6135:case 4599:case 4855:case 4215:case 6389:case 5109:case 5365:case 5621:case 3829:return we+r+r;case 4789:return wo+r+r;case 5349:case 4246:case 4810:case 6968:case 2756:return we+r+wo+r+Ae+r+r;case 5936:switch(Fe(r,i+11)){case 114:return we+r+Ae+ae(r,/[svh]\w+-[tblr]{2}/,"tb")+r;case 108:return we+r+Ae+ae(r,/[svh]\w+-[tblr]{2}/,"tb-rl")+r;case 45:return we+r+Ae+ae(r,/[svh]\w+-[tblr]{2}/,"lr")+r}case 6828:case 4268:case 2903:return we+r+Ae+r+r;case 6165:return we+r+Ae+"flex-"+r+r;case 5187:return we+r+ae(r,/(\w+).+(:[^]+)/,we+"box-$1$2"+Ae+"flex-$1$2")+r;case 5443:return we+r+Ae+"flex-item-"+ae(r,/flex-|-self/g,"")+(nn(r,/flex-|baseline/)?"":Ae+"grid-row-"+ae(r,/flex-|-self/g,""))+r;case 4675:return we+r+Ae+"flex-line-pack"+ae(r,/align-content|flex-|-self/g,"")+r;case 5548:return we+r+Ae+ae(r,"shrink","negative")+r;case 5292:return we+r+Ae+ae(r,"basis","preferred-size")+r;case 6060:return we+"box-"+ae(r,"-grow","")+we+r+Ae+ae(r,"grow","positive")+r;case 4554:return we+ae(r,/([^-])(transform)/g,"$1"+we+"$2")+r;case 6187:return ae(ae(ae(r,/(zoom-|grab)/,we+"$1"),/(image-set)/,we+"$1"),r,"")+r;case 5495:case 3959:return ae(r,/(image-set\([^]*)/,we+"$1$`$1");case 4968:return ae(ae(r,/(.+:)(flex-)?(.*)/,we+"box-pack:$3"+Ae+"flex-pack:$3"),/s.+-b[^;]+/,"justify")+we+r+r;case 4200:if(!nn(r,/flex-|baseline/))return Ae+"grid-column-align"+Sr(r,i)+r;break;case 2592:case 3360:return Ae+ae(r,"template-","")+r;case 4384:case 3616:return s&&s.some(function(u,c){return i=c,nn(u.props,/grid-\w+-end/)})?~$i(r+(s=s[i].value),"span",0)?r:Ae+ae(r,"-start","")+r+Ae+"grid-row-span:"+(~$i(s,"span",0)?nn(s,/\d+/):+nn(s,/\d+/)-+nn(r,/\d+/))+";":Ae+ae(r,"-start","")+r;case 4896:case 4128:return s&&s.some(function(u){return nn(u.props,/grid-\w+-start/)})?r:Ae+ae(ae(r,"-end","-span"),"span ","")+r;case 4095:case 3583:case 4068:case 2532:return ae(r,/(.+)-inline(.+)/,we+"$1$2")+r;case 8116:case 7059:case 5753:case 5535:case 5445:case 5701:case 4933:case 4677:case 5533:case 5789:case 5021:case 4765:if(Vt(r)-1-i>6)switch(Fe(r,i+1)){case 109:if(Fe(r,i+4)!==45)break;case 102:return ae(r,/(.+:)(.+)-([^]+)/,"$1"+we+"$2-$3$1"+wo+(Fe(r,i+3)==108?"$3":"$2-$3"))+r;case 115:return~$i(r,"stretch",0)?ep(ae(r,"stretch","fill-available"),i,s)+r:r}break;case 5152:case 5920:return ae(r,/(.+?):(\d+)(\s*\/\s*(span)?\s*(\d+))?(.*)/,function(u,c,d,p,m,v,x){return Ae+c+":"+d+x+(p?Ae+c+"-span:"+(m?v:+v-+d)+x:"")+r});case 4949:if(Fe(r,i+6)===121)return ae(r,":",":"+we)+r;break;case 6444:switch(Fe(r,Fe(r,14)===45?18:11)){case 120:return ae(r,/(.+:)([^;\s!]+)(;|(\s+)?!.+)?/,"$1"+we+(Fe(r,14)===45?"inline-":"")+"box$3$1"+we+"$2$3$1"+Ae+"$2box$3")+r;case 100:return ae(r,":",":"+Ae)+r}break;case 5719:case 2647:case 2135:case 3927:case 2391:return ae(r,"scroll-","scroll-snap-")+r}return r}function Ki(r,i){for(var s="",u=0;u-1&&!r.return)switch(r.type){case $u:r.return=ep(r.value,r.length,s);return;case Yd:return Ki([Cn(r,{value:ae(r.value,"@","@"+we)})],u);case ns:if(r.length)return og(s=r.props,function(c){switch(nn(c,u=/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":wr(Cn(r,{props:[ae(c,/:(read-\w+)/,":"+wo+"$1")]})),wr(Cn(r,{props:[c]})),Cu(r,{props:Zf(s,u)});break;case"::placeholder":wr(Cn(r,{props:[ae(c,/:(plac\w+)/,":"+we+"input-$1")]})),wr(Cn(r,{props:[ae(c,/:(plac\w+)/,":"+wo+"$1")]})),wr(Cn(r,{props:[ae(c,/:(plac\w+)/,Ae+"input-$1")]})),wr(Cn(r,{props:[c]})),Cu(r,{props:Zf(s,u)});break}return""})}}var wg={animationIterationCount:1,aspectRatio:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},ht={},Er=typeof process<"u"&&ht!==void 0&&(ht.REACT_APP_SC_ATTR||ht.SC_ATTR)||"data-styled",tp="active",np="data-styled-version",ss="6.1.14",Vu=`/*!sc*/ +`,Xi=typeof window<"u"&&"HTMLElement"in window,xg=!!(typeof SC_DISABLE_SPEEDY=="boolean"?SC_DISABLE_SPEEDY:typeof process<"u"&&ht!==void 0&&ht.REACT_APP_SC_DISABLE_SPEEDY!==void 0&&ht.REACT_APP_SC_DISABLE_SPEEDY!==""?ht.REACT_APP_SC_DISABLE_SPEEDY!=="false"&&ht.REACT_APP_SC_DISABLE_SPEEDY:typeof process<"u"&&ht!==void 0&&ht.SC_DISABLE_SPEEDY!==void 0&&ht.SC_DISABLE_SPEEDY!==""&&ht.SC_DISABLE_SPEEDY!=="false"&&ht.SC_DISABLE_SPEEDY),ls=Object.freeze([]),Cr=Object.freeze({});function Sg(r,i,s){return s===void 0&&(s=Cr),r.theme!==s.theme&&r.theme||i||s.theme}var rp=new Set(["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","track","u","ul","use","var","video","wbr","circle","clipPath","defs","ellipse","foreignObject","g","image","line","linearGradient","marker","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","tspan"]),kg=/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~-]+/g,Eg=/(^-|-$)/g;function nd(r){return r.replace(kg,"-").replace(Eg,"")}var Cg=/(a)(d)/gi,Di=52,rd=function(r){return String.fromCharCode(r+(r>25?39:97))};function Pu(r){var i,s="";for(i=Math.abs(r);i>Di;i=i/Di|0)s=rd(i%Di)+s;return(rd(i%Di)+s).replace(Cg,"$1-$2")}var pu,op=5381,xr=function(r,i){for(var s=i.length;s;)r=33*r^i.charCodeAt(--s);return r},ip=function(r){return xr(op,r)};function Ag(r){return Pu(ip(r)>>>0)}function Rg(r){return r.displayName||r.name||"Component"}function hu(r){return typeof r=="string"&&!0}var sp=typeof Symbol=="function"&&Symbol.for,lp=sp?Symbol.for("react.memo"):60115,Pg=sp?Symbol.for("react.forward_ref"):60112,jg={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},Ig={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},up={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},_g=((pu={})[Pg]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},pu[lp]=up,pu);function od(r){return("type"in(i=r)&&i.type.$$typeof)===lp?up:"$$typeof"in r?_g[r.$$typeof]:jg;var i}var Ng=Object.defineProperty,Og=Object.getOwnPropertyNames,id=Object.getOwnPropertySymbols,Tg=Object.getOwnPropertyDescriptor,Lg=Object.getPrototypeOf,sd=Object.prototype;function ap(r,i,s){if(typeof i!="string"){if(sd){var u=Lg(i);u&&u!==sd&&ap(r,u,s)}var c=Og(i);id&&(c=c.concat(id(i)));for(var d=od(r),p=od(i),m=0;m0?" Args: ".concat(i.join(", ")):""))}var Dg=function(){function r(i){this.groupSizes=new Uint32Array(512),this.length=512,this.tag=i}return r.prototype.indexOfGroup=function(i){for(var s=0,u=0;u=this.groupSizes.length){for(var u=this.groupSizes,c=u.length,d=c;i>=d;)if((d<<=1)<0)throw Qn(16,"".concat(i));this.groupSizes=new Uint32Array(d),this.groupSizes.set(u),this.length=d;for(var p=c;p=this.length||this.groupSizes[i]===0)return s;for(var u=this.groupSizes[i],c=this.indexOfGroup(i),d=c+u,p=c;p=0){var u=document.createTextNode(s);return this.element.insertBefore(u,this.nodes[i]||null),this.length++,!0}return!1},r.prototype.deleteRule=function(i){this.element.removeChild(this.nodes[i]),this.length--},r.prototype.getRule=function(i){return i0&&(L+="".concat(V,","))}),v+="".concat(I).concat(R,'{content:"').concat(L,'"}').concat(Vu)},E=0;E0?".".concat(i):O},E=v.slice();E.push(function(O){O.type===ns&&O.value.includes("&")&&(O.props[0]=O.props[0].replace(qg,s).replace(u,x))}),p.prefix&&E.push(vg),E.push(mg);var j=function(O,P,I,R){P===void 0&&(P=""),I===void 0&&(I=""),R===void 0&&(R="&"),i=R,s=P,u=new RegExp("\\".concat(s,"\\b"),"g");var L=O.replace(bg,""),V=pg(I||P?"".concat(I," ").concat(P," { ").concat(L," }"):L);p.namespace&&(V=dp(V,p.namespace));var F=[];return Ki(V,gg(E.concat(yg(function(W){return F.push(W)})))),F};return j.hash=v.length?v.reduce(function(O,P){return P.name||Qn(15),xr(O,P.name)},op).toString():"",j}var Yg=new fp,Iu=Gg(),pp=rn.createContext({shouldForwardProp:void 0,styleSheet:Yg,stylis:Iu});pp.Consumer;rn.createContext(void 0);function cd(){return ue.useContext(pp)}var Kg=function(){function r(i,s){var u=this;this.inject=function(c,d){d===void 0&&(d=Iu);var p=u.name+d.hash;c.hasNameForId(u.id,p)||c.insertRules(u.id,p,d(u.rules,p,"@keyframes"))},this.name=i,this.id="sc-keyframes-".concat(i),this.rules=s,Qu(this,function(){throw Qn(12,String(u.name))})}return r.prototype.getName=function(i){return i===void 0&&(i=Iu),this.name+i.hash},r}(),Xg=function(r){return r>="A"&&r<="Z"};function fd(r){for(var i="",s=0;s>>0);if(!s.hasNameForId(this.componentId,p)){var m=u(d,".".concat(p),void 0,this.componentId);s.insertRules(this.componentId,p,m)}c=Un(c,p),this.staticRulesId=p}else{for(var v=xr(this.baseHash,u.hash),x="",E=0;E>>0);s.hasNameForId(this.componentId,P)||s.insertRules(this.componentId,P,u(x,".".concat(P),void 0,this.componentId)),c=Un(c,P)}}return c},r}(),Zi=rn.createContext(void 0);Zi.Consumer;function ty(r){var i=rn.useContext(Zi),s=ue.useMemo(function(){return function(u,c){if(!u)throw Qn(14);if(Wn(u)){var d=u(c);return d}if(Array.isArray(u)||typeof u!="object")throw Qn(8);return c?Ke(Ke({},c),u):u}(r.theme,i)},[r.theme,i]);return r.children?rn.createElement(Zi.Provider,{value:s},r.children):null}var mu={};function ny(r,i,s){var u=Wu(r),c=r,d=!hu(r),p=i.attrs,m=p===void 0?ls:p,v=i.componentId,x=v===void 0?function(K,$){var T=typeof K!="string"?"sc":nd(K);mu[T]=(mu[T]||0)+1;var H="".concat(T,"-").concat(Ag(ss+T+mu[T]));return $?"".concat($,"-").concat(H):H}(i.displayName,i.parentComponentId):v,E=i.displayName,j=E===void 0?function(K){return hu(K)?"styled.".concat(K):"Styled(".concat(Rg(K),")")}(r):E,O=i.displayName&&i.componentId?"".concat(nd(i.displayName),"-").concat(i.componentId):i.componentId||x,P=u&&c.attrs?c.attrs.concat(m).filter(Boolean):m,I=i.shouldForwardProp;if(u&&c.shouldForwardProp){var R=c.shouldForwardProp;if(i.shouldForwardProp){var L=i.shouldForwardProp;I=function(K,$){return R(K,$)&&L(K,$)}}else I=R}var V=new ey(s,O,u?c.componentStyle:void 0);function F(K,$){return function(T,H,se){var Ve=T.attrs,At=T.componentStyle,qt=T.defaultProps,gt=T.foldedComponentIds,Je=T.styledComponentId,at=T.target,yt=rn.useContext(Zi),We=cd(),Se=T.shouldForwardProp||We.shouldForwardProp,Q=Sg(H,yt,qt)||Cr,ee=function(de,ce,ve){for(var pe,ge=Ke(Ke({},ce),{className:void 0,theme:ve}),Be=0;Ber.$hasUnread?r.theme.colors.text.primary:r.theme.colors.text.muted}; + font-weight: ${r=>r.$hasUnread?"600":"normal"}; + cursor: pointer; + background: ${r=>r.$isActive?r.theme.colors.background.hover:"transparent"}; + border-radius: 4px; + + &:hover { + background: ${r=>r.theme.colors.background.hover}; + color: ${r=>r.theme.colors.text.primary}; + } +`,hd=N.div` + margin-bottom: 8px; +`,Nu=N.div` + padding: 8px 16px; + display: flex; + align-items: center; + color: ${J.colors.text.muted}; + text-transform: uppercase; + font-size: 12px; + font-weight: 600; + cursor: pointer; + user-select: none; + + & > span:nth-child(2) { + flex: 1; + margin-right: auto; + } + + &:hover { + color: ${J.colors.text.primary}; + } +`,md=N.span` + margin-right: 4px; + font-size: 10px; + transition: transform 0.2s; + transform: rotate(${r=>r.$folded?"-90deg":"0deg"}); +`,gd=N.div` + display: ${r=>r.$folded?"none":"block"}; +`,yd=N(yp)` + height: ${r=>r.hasSubtext?"42px":"34px"}; +`,ly=N.div` + position: relative; + width: 32px; + height: 32px; + margin: 0 8px; + flex-shrink: 0; + min-width: 40px; + + img { + width: 32px; + height: 32px; + border-radius: 50%; + } +`,vd=N.div` + font-size: 16px; + line-height: 18px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: ${r=>r.$isActive||r.$hasUnread?r.theme.colors.text.primary:r.theme.colors.text.muted}; + font-weight: ${r=>r.$hasUnread?"600":"normal"}; +`,vp=N.div` + position: absolute; + bottom: 0; + right: 0; + width: 10px; + height: 10px; + border-radius: 50%; + background: ${r=>r.$online?J.colors.status.online:J.colors.status.offline}; + border: 2px solid ${J.colors.background.secondary}; + transform: translate(20%, 20%); +`;N(vp)` + border-color: ${J.colors.background.primary}; +`;const wd=N.button` + background: none; + border: none; + color: ${J.colors.text.muted}; + font-size: 18px; + padding: 0; + cursor: pointer; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.2s, color 0.2s; + + ${Nu}:hover & { + opacity: 1; + } + + &:hover { + color: ${J.colors.text.primary}; + } +`,uy=N.div` + width: 40px; + min-width: 40px; + height: 24px; + margin: 0 8px; + flex-shrink: 0; + position: relative; +`,ay=N.div` + font-size: 12px; + line-height: 13px; + color: ${J.colors.text.muted}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`,xd=N.div` + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; + gap: 2px; +`,cy=N.img` + width: 24px; + height: 24px; + border-radius: 50%; + border: 2px solid ${J.colors.background.secondary}; + position: absolute; +`;function fy(){return g.jsx(sy,{children:"채널 목록"})}const Sd=r=>{let i;const s=new Set,u=(x,E)=>{const j=typeof x=="function"?x(i):x;if(!Object.is(j,i)){const O=i;i=E??(typeof j!="object"||j===null)?j:Object.assign({},i,j),s.forEach(P=>P(i,O))}},c=()=>i,m={setState:u,getState:c,getInitialState:()=>v,subscribe:x=>(s.add(x),()=>s.delete(x))},v=i=r(u,c,m);return m},dy=r=>r?Sd(r):Sd,py=r=>r;function hy(r,i=py){const s=rn.useSyncExternalStore(r.subscribe,()=>i(r.getState()),()=>i(r.getInitialState()));return rn.useDebugValue(s),s}const kd=r=>{const i=dy(r),s=u=>hy(i,u);return Object.assign(s,i),s},bn=r=>r?kd(r):kd;function wp(r,i){return function(){return r.apply(i,arguments)}}const{toString:my}=Object.prototype,{getPrototypeOf:qu}=Object,us=(r=>i=>{const s=my.call(i);return r[s]||(r[s]=s.slice(8,-1).toLowerCase())})(Object.create(null)),zt=r=>(r=r.toLowerCase(),i=>us(i)===r),as=r=>i=>typeof i===r,{isArray:Rr}=Array,ko=as("undefined");function gy(r){return r!==null&&!ko(r)&&r.constructor!==null&&!ko(r.constructor)&&mt(r.constructor.isBuffer)&&r.constructor.isBuffer(r)}const xp=zt("ArrayBuffer");function yy(r){let i;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?i=ArrayBuffer.isView(r):i=r&&r.buffer&&xp(r.buffer),i}const vy=as("string"),mt=as("function"),Sp=as("number"),cs=r=>r!==null&&typeof r=="object",wy=r=>r===!0||r===!1,qi=r=>{if(us(r)!=="object")return!1;const i=qu(r);return(i===null||i===Object.prototype||Object.getPrototypeOf(i)===null)&&!(Symbol.toStringTag in r)&&!(Symbol.iterator in r)},xy=zt("Date"),Sy=zt("File"),ky=zt("Blob"),Ey=zt("FileList"),Cy=r=>cs(r)&&mt(r.pipe),Ay=r=>{let i;return r&&(typeof FormData=="function"&&r instanceof FormData||mt(r.append)&&((i=us(r))==="formdata"||i==="object"&&mt(r.toString)&&r.toString()==="[object FormData]"))},Ry=zt("URLSearchParams"),[Py,jy,Iy,_y]=["ReadableStream","Request","Response","Headers"].map(zt),Ny=r=>r.trim?r.trim():r.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function Eo(r,i,{allOwnKeys:s=!1}={}){if(r===null||typeof r>"u")return;let u,c;if(typeof r!="object"&&(r=[r]),Rr(r))for(u=0,c=r.length;u0;)if(c=s[u],i===c.toLowerCase())return c;return null}const Fn=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,Ep=r=>!ko(r)&&r!==Fn;function Ou(){const{caseless:r}=Ep(this)&&this||{},i={},s=(u,c)=>{const d=r&&kp(i,c)||c;qi(i[d])&&qi(u)?i[d]=Ou(i[d],u):qi(u)?i[d]=Ou({},u):Rr(u)?i[d]=u.slice():i[d]=u};for(let u=0,c=arguments.length;u(Eo(i,(c,d)=>{s&&mt(c)?r[d]=wp(c,s):r[d]=c},{allOwnKeys:u}),r),Ty=r=>(r.charCodeAt(0)===65279&&(r=r.slice(1)),r),Ly=(r,i,s,u)=>{r.prototype=Object.create(i.prototype,u),r.prototype.constructor=r,Object.defineProperty(r,"super",{value:i.prototype}),s&&Object.assign(r.prototype,s)},Dy=(r,i,s,u)=>{let c,d,p;const m={};if(i=i||{},r==null)return i;do{for(c=Object.getOwnPropertyNames(r),d=c.length;d-- >0;)p=c[d],(!u||u(p,r,i))&&!m[p]&&(i[p]=r[p],m[p]=!0);r=s!==!1&&qu(r)}while(r&&(!s||s(r,i))&&r!==Object.prototype);return i},zy=(r,i,s)=>{r=String(r),(s===void 0||s>r.length)&&(s=r.length),s-=i.length;const u=r.indexOf(i,s);return u!==-1&&u===s},My=r=>{if(!r)return null;if(Rr(r))return r;let i=r.length;if(!Sp(i))return null;const s=new Array(i);for(;i-- >0;)s[i]=r[i];return s},Uy=(r=>i=>r&&i instanceof r)(typeof Uint8Array<"u"&&qu(Uint8Array)),Fy=(r,i)=>{const u=(r&&r[Symbol.iterator]).call(r);let c;for(;(c=u.next())&&!c.done;){const d=c.value;i.call(r,d[0],d[1])}},By=(r,i)=>{let s;const u=[];for(;(s=r.exec(i))!==null;)u.push(s);return u},$y=zt("HTMLFormElement"),Hy=r=>r.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(s,u,c){return u.toUpperCase()+c}),Ed=(({hasOwnProperty:r})=>(i,s)=>r.call(i,s))(Object.prototype),Vy=zt("RegExp"),Cp=(r,i)=>{const s=Object.getOwnPropertyDescriptors(r),u={};Eo(s,(c,d)=>{let p;(p=i(c,d,r))!==!1&&(u[d]=p||c)}),Object.defineProperties(r,u)},Wy=r=>{Cp(r,(i,s)=>{if(mt(r)&&["arguments","caller","callee"].indexOf(s)!==-1)return!1;const u=r[s];if(mt(u)){if(i.enumerable=!1,"writable"in i){i.writable=!1;return}i.set||(i.set=()=>{throw Error("Can not rewrite read-only method '"+s+"'")})}})},Qy=(r,i)=>{const s={},u=c=>{c.forEach(d=>{s[d]=!0})};return Rr(r)?u(r):u(String(r).split(i)),s},qy=()=>{},by=(r,i)=>r!=null&&Number.isFinite(r=+r)?r:i,gu="abcdefghijklmnopqrstuvwxyz",Cd="0123456789",Ap={DIGIT:Cd,ALPHA:gu,ALPHA_DIGIT:gu+gu.toUpperCase()+Cd},Gy=(r=16,i=Ap.ALPHA_DIGIT)=>{let s="";const{length:u}=i;for(;r--;)s+=i[Math.random()*u|0];return s};function Yy(r){return!!(r&&mt(r.append)&&r[Symbol.toStringTag]==="FormData"&&r[Symbol.iterator])}const Ky=r=>{const i=new Array(10),s=(u,c)=>{if(cs(u)){if(i.indexOf(u)>=0)return;if(!("toJSON"in u)){i[c]=u;const d=Rr(u)?[]:{};return Eo(u,(p,m)=>{const v=s(p,c+1);!ko(v)&&(d[m]=v)}),i[c]=void 0,d}}return u};return s(r,0)},Xy=zt("AsyncFunction"),Jy=r=>r&&(cs(r)||mt(r))&&mt(r.then)&&mt(r.catch),Rp=((r,i)=>r?setImmediate:i?((s,u)=>(Fn.addEventListener("message",({source:c,data:d})=>{c===Fn&&d===s&&u.length&&u.shift()()},!1),c=>{u.push(c),Fn.postMessage(s,"*")}))(`axios@${Math.random()}`,[]):s=>setTimeout(s))(typeof setImmediate=="function",mt(Fn.postMessage)),Zy=typeof queueMicrotask<"u"?queueMicrotask.bind(Fn):typeof process<"u"&&process.nextTick||Rp,_={isArray:Rr,isArrayBuffer:xp,isBuffer:gy,isFormData:Ay,isArrayBufferView:yy,isString:vy,isNumber:Sp,isBoolean:wy,isObject:cs,isPlainObject:qi,isReadableStream:Py,isRequest:jy,isResponse:Iy,isHeaders:_y,isUndefined:ko,isDate:xy,isFile:Sy,isBlob:ky,isRegExp:Vy,isFunction:mt,isStream:Cy,isURLSearchParams:Ry,isTypedArray:Uy,isFileList:Ey,forEach:Eo,merge:Ou,extend:Oy,trim:Ny,stripBOM:Ty,inherits:Ly,toFlatObject:Dy,kindOf:us,kindOfTest:zt,endsWith:zy,toArray:My,forEachEntry:Fy,matchAll:By,isHTMLForm:$y,hasOwnProperty:Ed,hasOwnProp:Ed,reduceDescriptors:Cp,freezeMethods:Wy,toObjectSet:Qy,toCamelCase:Hy,noop:qy,toFiniteNumber:by,findKey:kp,global:Fn,isContextDefined:Ep,ALPHABET:Ap,generateString:Gy,isSpecCompliantForm:Yy,toJSONObject:Ky,isAsyncFn:Xy,isThenable:Jy,setImmediate:Rp,asap:Zy};function ie(r,i,s,u,c){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=r,this.name="AxiosError",i&&(this.code=i),s&&(this.config=s),u&&(this.request=u),c&&(this.response=c,this.status=c.status?c.status:null)}_.inherits(ie,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:_.toJSONObject(this.config),code:this.code,status:this.status}}});const Pp=ie.prototype,jp={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(r=>{jp[r]={value:r}});Object.defineProperties(ie,jp);Object.defineProperty(Pp,"isAxiosError",{value:!0});ie.from=(r,i,s,u,c,d)=>{const p=Object.create(Pp);return _.toFlatObject(r,p,function(v){return v!==Error.prototype},m=>m!=="isAxiosError"),ie.call(p,r.message,i,s,u,c),p.cause=r,p.name=r.name,d&&Object.assign(p,d),p};const e0=null;function Tu(r){return _.isPlainObject(r)||_.isArray(r)}function Ip(r){return _.endsWith(r,"[]")?r.slice(0,-2):r}function Ad(r,i,s){return r?r.concat(i).map(function(c,d){return c=Ip(c),!s&&d?"["+c+"]":c}).join(s?".":""):i}function t0(r){return _.isArray(r)&&!r.some(Tu)}const n0=_.toFlatObject(_,{},null,function(i){return/^is[A-Z]/.test(i)});function fs(r,i,s){if(!_.isObject(r))throw new TypeError("target must be an object");i=i||new FormData,s=_.toFlatObject(s,{metaTokens:!0,dots:!1,indexes:!1},!1,function(R,L){return!_.isUndefined(L[R])});const u=s.metaTokens,c=s.visitor||E,d=s.dots,p=s.indexes,v=(s.Blob||typeof Blob<"u"&&Blob)&&_.isSpecCompliantForm(i);if(!_.isFunction(c))throw new TypeError("visitor must be a function");function x(I){if(I===null)return"";if(_.isDate(I))return I.toISOString();if(!v&&_.isBlob(I))throw new ie("Blob is not supported. Use a Buffer instead.");return _.isArrayBuffer(I)||_.isTypedArray(I)?v&&typeof Blob=="function"?new Blob([I]):Buffer.from(I):I}function E(I,R,L){let V=I;if(I&&!L&&typeof I=="object"){if(_.endsWith(R,"{}"))R=u?R:R.slice(0,-2),I=JSON.stringify(I);else if(_.isArray(I)&&t0(I)||(_.isFileList(I)||_.endsWith(R,"[]"))&&(V=_.toArray(I)))return R=Ip(R),V.forEach(function(W,K){!(_.isUndefined(W)||W===null)&&i.append(p===!0?Ad([R],K,d):p===null?R:R+"[]",x(W))}),!1}return Tu(I)?!0:(i.append(Ad(L,R,d),x(I)),!1)}const j=[],O=Object.assign(n0,{defaultVisitor:E,convertValue:x,isVisitable:Tu});function P(I,R){if(!_.isUndefined(I)){if(j.indexOf(I)!==-1)throw Error("Circular reference detected in "+R.join("."));j.push(I),_.forEach(I,function(V,F){(!(_.isUndefined(V)||V===null)&&c.call(i,V,_.isString(F)?F.trim():F,R,O))===!0&&P(V,R?R.concat(F):[F])}),j.pop()}}if(!_.isObject(r))throw new TypeError("data must be an object");return P(r),i}function Rd(r){const i={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(r).replace(/[!'()~]|%20|%00/g,function(u){return i[u]})}function bu(r,i){this._pairs=[],r&&fs(r,this,i)}const _p=bu.prototype;_p.append=function(i,s){this._pairs.push([i,s])};_p.toString=function(i){const s=i?function(u){return i.call(this,u,Rd)}:Rd;return this._pairs.map(function(c){return s(c[0])+"="+s(c[1])},"").join("&")};function r0(r){return encodeURIComponent(r).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function Np(r,i,s){if(!i)return r;const u=s&&s.encode||r0;_.isFunction(s)&&(s={serialize:s});const c=s&&s.serialize;let d;if(c?d=c(i,s):d=_.isURLSearchParams(i)?i.toString():new bu(i,s).toString(u),d){const p=r.indexOf("#");p!==-1&&(r=r.slice(0,p)),r+=(r.indexOf("?")===-1?"?":"&")+d}return r}class Pd{constructor(){this.handlers=[]}use(i,s,u){return this.handlers.push({fulfilled:i,rejected:s,synchronous:u?u.synchronous:!1,runWhen:u?u.runWhen:null}),this.handlers.length-1}eject(i){this.handlers[i]&&(this.handlers[i]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(i){_.forEach(this.handlers,function(u){u!==null&&i(u)})}}const Op={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},o0=typeof URLSearchParams<"u"?URLSearchParams:bu,i0=typeof FormData<"u"?FormData:null,s0=typeof Blob<"u"?Blob:null,l0={isBrowser:!0,classes:{URLSearchParams:o0,FormData:i0,Blob:s0},protocols:["http","https","file","blob","url","data"]},Gu=typeof window<"u"&&typeof document<"u",Lu=typeof navigator=="object"&&navigator||void 0,u0=Gu&&(!Lu||["ReactNative","NativeScript","NS"].indexOf(Lu.product)<0),a0=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",c0=Gu&&window.location.href||"http://localhost",f0=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:Gu,hasStandardBrowserEnv:u0,hasStandardBrowserWebWorkerEnv:a0,navigator:Lu,origin:c0},Symbol.toStringTag,{value:"Module"})),Ye={...f0,...l0};function d0(r,i){return fs(r,new Ye.classes.URLSearchParams,Object.assign({visitor:function(s,u,c,d){return Ye.isNode&&_.isBuffer(s)?(this.append(u,s.toString("base64")),!1):d.defaultVisitor.apply(this,arguments)}},i))}function p0(r){return _.matchAll(/\w+|\[(\w*)]/g,r).map(i=>i[0]==="[]"?"":i[1]||i[0])}function h0(r){const i={},s=Object.keys(r);let u;const c=s.length;let d;for(u=0;u=s.length;return p=!p&&_.isArray(c)?c.length:p,v?(_.hasOwnProp(c,p)?c[p]=[c[p],u]:c[p]=u,!m):((!c[p]||!_.isObject(c[p]))&&(c[p]=[]),i(s,u,c[p],d)&&_.isArray(c[p])&&(c[p]=h0(c[p])),!m)}if(_.isFormData(r)&&_.isFunction(r.entries)){const s={};return _.forEachEntry(r,(u,c)=>{i(p0(u),c,s,0)}),s}return null}function m0(r,i,s){if(_.isString(r))try{return(i||JSON.parse)(r),_.trim(r)}catch(u){if(u.name!=="SyntaxError")throw u}return(0,JSON.stringify)(r)}const Co={transitional:Op,adapter:["xhr","http","fetch"],transformRequest:[function(i,s){const u=s.getContentType()||"",c=u.indexOf("application/json")>-1,d=_.isObject(i);if(d&&_.isHTMLForm(i)&&(i=new FormData(i)),_.isFormData(i))return c?JSON.stringify(Tp(i)):i;if(_.isArrayBuffer(i)||_.isBuffer(i)||_.isStream(i)||_.isFile(i)||_.isBlob(i)||_.isReadableStream(i))return i;if(_.isArrayBufferView(i))return i.buffer;if(_.isURLSearchParams(i))return s.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),i.toString();let m;if(d){if(u.indexOf("application/x-www-form-urlencoded")>-1)return d0(i,this.formSerializer).toString();if((m=_.isFileList(i))||u.indexOf("multipart/form-data")>-1){const v=this.env&&this.env.FormData;return fs(m?{"files[]":i}:i,v&&new v,this.formSerializer)}}return d||c?(s.setContentType("application/json",!1),m0(i)):i}],transformResponse:[function(i){const s=this.transitional||Co.transitional,u=s&&s.forcedJSONParsing,c=this.responseType==="json";if(_.isResponse(i)||_.isReadableStream(i))return i;if(i&&_.isString(i)&&(u&&!this.responseType||c)){const p=!(s&&s.silentJSONParsing)&&c;try{return JSON.parse(i)}catch(m){if(p)throw m.name==="SyntaxError"?ie.from(m,ie.ERR_BAD_RESPONSE,this,null,this.response):m}}return i}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:Ye.classes.FormData,Blob:Ye.classes.Blob},validateStatus:function(i){return i>=200&&i<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};_.forEach(["delete","get","head","post","put","patch"],r=>{Co.headers[r]={}});const g0=_.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),y0=r=>{const i={};let s,u,c;return r&&r.split(` +`).forEach(function(p){c=p.indexOf(":"),s=p.substring(0,c).trim().toLowerCase(),u=p.substring(c+1).trim(),!(!s||i[s]&&g0[s])&&(s==="set-cookie"?i[s]?i[s].push(u):i[s]=[u]:i[s]=i[s]?i[s]+", "+u:u)}),i},jd=Symbol("internals");function mo(r){return r&&String(r).trim().toLowerCase()}function bi(r){return r===!1||r==null?r:_.isArray(r)?r.map(bi):String(r)}function v0(r){const i=Object.create(null),s=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let u;for(;u=s.exec(r);)i[u[1]]=u[2];return i}const w0=r=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(r.trim());function yu(r,i,s,u,c){if(_.isFunction(u))return u.call(this,i,s);if(c&&(i=s),!!_.isString(i)){if(_.isString(u))return i.indexOf(u)!==-1;if(_.isRegExp(u))return u.test(i)}}function x0(r){return r.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(i,s,u)=>s.toUpperCase()+u)}function S0(r,i){const s=_.toCamelCase(" "+i);["get","set","has"].forEach(u=>{Object.defineProperty(r,u+s,{value:function(c,d,p){return this[u].call(this,i,c,d,p)},configurable:!0})})}class lt{constructor(i){i&&this.set(i)}set(i,s,u){const c=this;function d(m,v,x){const E=mo(v);if(!E)throw new Error("header name must be a non-empty string");const j=_.findKey(c,E);(!j||c[j]===void 0||x===!0||x===void 0&&c[j]!==!1)&&(c[j||v]=bi(m))}const p=(m,v)=>_.forEach(m,(x,E)=>d(x,E,v));if(_.isPlainObject(i)||i instanceof this.constructor)p(i,s);else if(_.isString(i)&&(i=i.trim())&&!w0(i))p(y0(i),s);else if(_.isHeaders(i))for(const[m,v]of i.entries())d(v,m,u);else i!=null&&d(s,i,u);return this}get(i,s){if(i=mo(i),i){const u=_.findKey(this,i);if(u){const c=this[u];if(!s)return c;if(s===!0)return v0(c);if(_.isFunction(s))return s.call(this,c,u);if(_.isRegExp(s))return s.exec(c);throw new TypeError("parser must be boolean|regexp|function")}}}has(i,s){if(i=mo(i),i){const u=_.findKey(this,i);return!!(u&&this[u]!==void 0&&(!s||yu(this,this[u],u,s)))}return!1}delete(i,s){const u=this;let c=!1;function d(p){if(p=mo(p),p){const m=_.findKey(u,p);m&&(!s||yu(u,u[m],m,s))&&(delete u[m],c=!0)}}return _.isArray(i)?i.forEach(d):d(i),c}clear(i){const s=Object.keys(this);let u=s.length,c=!1;for(;u--;){const d=s[u];(!i||yu(this,this[d],d,i,!0))&&(delete this[d],c=!0)}return c}normalize(i){const s=this,u={};return _.forEach(this,(c,d)=>{const p=_.findKey(u,d);if(p){s[p]=bi(c),delete s[d];return}const m=i?x0(d):String(d).trim();m!==d&&delete s[d],s[m]=bi(c),u[m]=!0}),this}concat(...i){return this.constructor.concat(this,...i)}toJSON(i){const s=Object.create(null);return _.forEach(this,(u,c)=>{u!=null&&u!==!1&&(s[c]=i&&_.isArray(u)?u.join(", "):u)}),s}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([i,s])=>i+": "+s).join(` +`)}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(i){return i instanceof this?i:new this(i)}static concat(i,...s){const u=new this(i);return s.forEach(c=>u.set(c)),u}static accessor(i){const u=(this[jd]=this[jd]={accessors:{}}).accessors,c=this.prototype;function d(p){const m=mo(p);u[m]||(S0(c,p),u[m]=!0)}return _.isArray(i)?i.forEach(d):d(i),this}}lt.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);_.reduceDescriptors(lt.prototype,({value:r},i)=>{let s=i[0].toUpperCase()+i.slice(1);return{get:()=>r,set(u){this[s]=u}}});_.freezeMethods(lt);function vu(r,i){const s=this||Co,u=i||s,c=lt.from(u.headers);let d=u.data;return _.forEach(r,function(m){d=m.call(s,d,c.normalize(),i?i.status:void 0)}),c.normalize(),d}function Lp(r){return!!(r&&r.__CANCEL__)}function Pr(r,i,s){ie.call(this,r??"canceled",ie.ERR_CANCELED,i,s),this.name="CanceledError"}_.inherits(Pr,ie,{__CANCEL__:!0});function Dp(r,i,s){const u=s.config.validateStatus;!s.status||!u||u(s.status)?r(s):i(new ie("Request failed with status code "+s.status,[ie.ERR_BAD_REQUEST,ie.ERR_BAD_RESPONSE][Math.floor(s.status/100)-4],s.config,s.request,s))}function k0(r){const i=/^([-+\w]{1,25})(:?\/\/|:)/.exec(r);return i&&i[1]||""}function E0(r,i){r=r||10;const s=new Array(r),u=new Array(r);let c=0,d=0,p;return i=i!==void 0?i:1e3,function(v){const x=Date.now(),E=u[d];p||(p=x),s[c]=v,u[c]=x;let j=d,O=0;for(;j!==c;)O+=s[j++],j=j%r;if(c=(c+1)%r,c===d&&(d=(d+1)%r),x-p{s=E,c=null,d&&(clearTimeout(d),d=null),r.apply(null,x)};return[(...x)=>{const E=Date.now(),j=E-s;j>=u?p(x,E):(c=x,d||(d=setTimeout(()=>{d=null,p(c)},u-j)))},()=>c&&p(c)]}const es=(r,i,s=3)=>{let u=0;const c=E0(50,250);return C0(d=>{const p=d.loaded,m=d.lengthComputable?d.total:void 0,v=p-u,x=c(v),E=p<=m;u=p;const j={loaded:p,total:m,progress:m?p/m:void 0,bytes:v,rate:x||void 0,estimated:x&&m&&E?(m-p)/x:void 0,event:d,lengthComputable:m!=null,[i?"download":"upload"]:!0};r(j)},s)},Id=(r,i)=>{const s=r!=null;return[u=>i[0]({lengthComputable:s,total:r,loaded:u}),i[1]]},_d=r=>(...i)=>_.asap(()=>r(...i)),A0=Ye.hasStandardBrowserEnv?((r,i)=>s=>(s=new URL(s,Ye.origin),r.protocol===s.protocol&&r.host===s.host&&(i||r.port===s.port)))(new URL(Ye.origin),Ye.navigator&&/(msie|trident)/i.test(Ye.navigator.userAgent)):()=>!0,R0=Ye.hasStandardBrowserEnv?{write(r,i,s,u,c,d){const p=[r+"="+encodeURIComponent(i)];_.isNumber(s)&&p.push("expires="+new Date(s).toGMTString()),_.isString(u)&&p.push("path="+u),_.isString(c)&&p.push("domain="+c),d===!0&&p.push("secure"),document.cookie=p.join("; ")},read(r){const i=document.cookie.match(new RegExp("(^|;\\s*)("+r+")=([^;]*)"));return i?decodeURIComponent(i[3]):null},remove(r){this.write(r,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function P0(r){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(r)}function j0(r,i){return i?r.replace(/\/?\/$/,"")+"/"+i.replace(/^\/+/,""):r}function zp(r,i){return r&&!P0(i)?j0(r,i):i}const Nd=r=>r instanceof lt?{...r}:r;function qn(r,i){i=i||{};const s={};function u(x,E,j,O){return _.isPlainObject(x)&&_.isPlainObject(E)?_.merge.call({caseless:O},x,E):_.isPlainObject(E)?_.merge({},E):_.isArray(E)?E.slice():E}function c(x,E,j,O){if(_.isUndefined(E)){if(!_.isUndefined(x))return u(void 0,x,j,O)}else return u(x,E,j,O)}function d(x,E){if(!_.isUndefined(E))return u(void 0,E)}function p(x,E){if(_.isUndefined(E)){if(!_.isUndefined(x))return u(void 0,x)}else return u(void 0,E)}function m(x,E,j){if(j in i)return u(x,E);if(j in r)return u(void 0,x)}const v={url:d,method:d,data:d,baseURL:p,transformRequest:p,transformResponse:p,paramsSerializer:p,timeout:p,timeoutMessage:p,withCredentials:p,withXSRFToken:p,adapter:p,responseType:p,xsrfCookieName:p,xsrfHeaderName:p,onUploadProgress:p,onDownloadProgress:p,decompress:p,maxContentLength:p,maxBodyLength:p,beforeRedirect:p,transport:p,httpAgent:p,httpsAgent:p,cancelToken:p,socketPath:p,responseEncoding:p,validateStatus:m,headers:(x,E,j)=>c(Nd(x),Nd(E),j,!0)};return _.forEach(Object.keys(Object.assign({},r,i)),function(E){const j=v[E]||c,O=j(r[E],i[E],E);_.isUndefined(O)&&j!==m||(s[E]=O)}),s}const Mp=r=>{const i=qn({},r);let{data:s,withXSRFToken:u,xsrfHeaderName:c,xsrfCookieName:d,headers:p,auth:m}=i;i.headers=p=lt.from(p),i.url=Np(zp(i.baseURL,i.url),r.params,r.paramsSerializer),m&&p.set("Authorization","Basic "+btoa((m.username||"")+":"+(m.password?unescape(encodeURIComponent(m.password)):"")));let v;if(_.isFormData(s)){if(Ye.hasStandardBrowserEnv||Ye.hasStandardBrowserWebWorkerEnv)p.setContentType(void 0);else if((v=p.getContentType())!==!1){const[x,...E]=v?v.split(";").map(j=>j.trim()).filter(Boolean):[];p.setContentType([x||"multipart/form-data",...E].join("; "))}}if(Ye.hasStandardBrowserEnv&&(u&&_.isFunction(u)&&(u=u(i)),u||u!==!1&&A0(i.url))){const x=c&&d&&R0.read(d);x&&p.set(c,x)}return i},I0=typeof XMLHttpRequest<"u",_0=I0&&function(r){return new Promise(function(s,u){const c=Mp(r);let d=c.data;const p=lt.from(c.headers).normalize();let{responseType:m,onUploadProgress:v,onDownloadProgress:x}=c,E,j,O,P,I;function R(){P&&P(),I&&I(),c.cancelToken&&c.cancelToken.unsubscribe(E),c.signal&&c.signal.removeEventListener("abort",E)}let L=new XMLHttpRequest;L.open(c.method.toUpperCase(),c.url,!0),L.timeout=c.timeout;function V(){if(!L)return;const W=lt.from("getAllResponseHeaders"in L&&L.getAllResponseHeaders()),$={data:!m||m==="text"||m==="json"?L.responseText:L.response,status:L.status,statusText:L.statusText,headers:W,config:r,request:L};Dp(function(H){s(H),R()},function(H){u(H),R()},$),L=null}"onloadend"in L?L.onloadend=V:L.onreadystatechange=function(){!L||L.readyState!==4||L.status===0&&!(L.responseURL&&L.responseURL.indexOf("file:")===0)||setTimeout(V)},L.onabort=function(){L&&(u(new ie("Request aborted",ie.ECONNABORTED,r,L)),L=null)},L.onerror=function(){u(new ie("Network Error",ie.ERR_NETWORK,r,L)),L=null},L.ontimeout=function(){let K=c.timeout?"timeout of "+c.timeout+"ms exceeded":"timeout exceeded";const $=c.transitional||Op;c.timeoutErrorMessage&&(K=c.timeoutErrorMessage),u(new ie(K,$.clarifyTimeoutError?ie.ETIMEDOUT:ie.ECONNABORTED,r,L)),L=null},d===void 0&&p.setContentType(null),"setRequestHeader"in L&&_.forEach(p.toJSON(),function(K,$){L.setRequestHeader($,K)}),_.isUndefined(c.withCredentials)||(L.withCredentials=!!c.withCredentials),m&&m!=="json"&&(L.responseType=c.responseType),x&&([O,I]=es(x,!0),L.addEventListener("progress",O)),v&&L.upload&&([j,P]=es(v),L.upload.addEventListener("progress",j),L.upload.addEventListener("loadend",P)),(c.cancelToken||c.signal)&&(E=W=>{L&&(u(!W||W.type?new Pr(null,r,L):W),L.abort(),L=null)},c.cancelToken&&c.cancelToken.subscribe(E),c.signal&&(c.signal.aborted?E():c.signal.addEventListener("abort",E)));const F=k0(c.url);if(F&&Ye.protocols.indexOf(F)===-1){u(new ie("Unsupported protocol "+F+":",ie.ERR_BAD_REQUEST,r));return}L.send(d||null)})},N0=(r,i)=>{const{length:s}=r=r?r.filter(Boolean):[];if(i||s){let u=new AbortController,c;const d=function(x){if(!c){c=!0,m();const E=x instanceof Error?x:this.reason;u.abort(E instanceof ie?E:new Pr(E instanceof Error?E.message:E))}};let p=i&&setTimeout(()=>{p=null,d(new ie(`timeout ${i} of ms exceeded`,ie.ETIMEDOUT))},i);const m=()=>{r&&(p&&clearTimeout(p),p=null,r.forEach(x=>{x.unsubscribe?x.unsubscribe(d):x.removeEventListener("abort",d)}),r=null)};r.forEach(x=>x.addEventListener("abort",d));const{signal:v}=u;return v.unsubscribe=()=>_.asap(m),v}},O0=function*(r,i){let s=r.byteLength;if(s{const c=T0(r,i);let d=0,p,m=v=>{p||(p=!0,u&&u(v))};return new ReadableStream({async pull(v){try{const{done:x,value:E}=await c.next();if(x){m(),v.close();return}let j=E.byteLength;if(s){let O=d+=j;s(O)}v.enqueue(new Uint8Array(E))}catch(x){throw m(x),x}},cancel(v){return m(v),c.return()}},{highWaterMark:2})},ds=typeof fetch=="function"&&typeof Request=="function"&&typeof Response=="function",Up=ds&&typeof ReadableStream=="function",D0=ds&&(typeof TextEncoder=="function"?(r=>i=>r.encode(i))(new TextEncoder):async r=>new Uint8Array(await new Response(r).arrayBuffer())),Fp=(r,...i)=>{try{return!!r(...i)}catch{return!1}},z0=Up&&Fp(()=>{let r=!1;const i=new Request(Ye.origin,{body:new ReadableStream,method:"POST",get duplex(){return r=!0,"half"}}).headers.has("Content-Type");return r&&!i}),Td=64*1024,Du=Up&&Fp(()=>_.isReadableStream(new Response("").body)),ts={stream:Du&&(r=>r.body)};ds&&(r=>{["text","arrayBuffer","blob","formData","stream"].forEach(i=>{!ts[i]&&(ts[i]=_.isFunction(r[i])?s=>s[i]():(s,u)=>{throw new ie(`Response type '${i}' is not supported`,ie.ERR_NOT_SUPPORT,u)})})})(new Response);const M0=async r=>{if(r==null)return 0;if(_.isBlob(r))return r.size;if(_.isSpecCompliantForm(r))return(await new Request(Ye.origin,{method:"POST",body:r}).arrayBuffer()).byteLength;if(_.isArrayBufferView(r)||_.isArrayBuffer(r))return r.byteLength;if(_.isURLSearchParams(r)&&(r=r+""),_.isString(r))return(await D0(r)).byteLength},U0=async(r,i)=>{const s=_.toFiniteNumber(r.getContentLength());return s??M0(i)},F0=ds&&(async r=>{let{url:i,method:s,data:u,signal:c,cancelToken:d,timeout:p,onDownloadProgress:m,onUploadProgress:v,responseType:x,headers:E,withCredentials:j="same-origin",fetchOptions:O}=Mp(r);x=x?(x+"").toLowerCase():"text";let P=N0([c,d&&d.toAbortSignal()],p),I;const R=P&&P.unsubscribe&&(()=>{P.unsubscribe()});let L;try{if(v&&z0&&s!=="get"&&s!=="head"&&(L=await U0(E,u))!==0){let $=new Request(i,{method:"POST",body:u,duplex:"half"}),T;if(_.isFormData(u)&&(T=$.headers.get("content-type"))&&E.setContentType(T),$.body){const[H,se]=Id(L,es(_d(v)));u=Od($.body,Td,H,se)}}_.isString(j)||(j=j?"include":"omit");const V="credentials"in Request.prototype;I=new Request(i,{...O,signal:P,method:s.toUpperCase(),headers:E.normalize().toJSON(),body:u,duplex:"half",credentials:V?j:void 0});let F=await fetch(I);const W=Du&&(x==="stream"||x==="response");if(Du&&(m||W&&R)){const $={};["status","statusText","headers"].forEach(Ve=>{$[Ve]=F[Ve]});const T=_.toFiniteNumber(F.headers.get("content-length")),[H,se]=m&&Id(T,es(_d(m),!0))||[];F=new Response(Od(F.body,Td,H,()=>{se&&se(),R&&R()}),$)}x=x||"text";let K=await ts[_.findKey(ts,x)||"text"](F,r);return!W&&R&&R(),await new Promise(($,T)=>{Dp($,T,{data:K,headers:lt.from(F.headers),status:F.status,statusText:F.statusText,config:r,request:I})})}catch(V){throw R&&R(),V&&V.name==="TypeError"&&/fetch/i.test(V.message)?Object.assign(new ie("Network Error",ie.ERR_NETWORK,r,I),{cause:V.cause||V}):ie.from(V,V&&V.code,r,I)}}),zu={http:e0,xhr:_0,fetch:F0};_.forEach(zu,(r,i)=>{if(r){try{Object.defineProperty(r,"name",{value:i})}catch{}Object.defineProperty(r,"adapterName",{value:i})}});const Ld=r=>`- ${r}`,B0=r=>_.isFunction(r)||r===null||r===!1,Bp={getAdapter:r=>{r=_.isArray(r)?r:[r];const{length:i}=r;let s,u;const c={};for(let d=0;d`adapter ${m} `+(v===!1?"is not supported by the environment":"is not available in the build"));let p=i?d.length>1?`since : +`+d.map(Ld).join(` +`):" "+Ld(d[0]):"as no adapter specified";throw new ie("There is no suitable adapter to dispatch the request "+p,"ERR_NOT_SUPPORT")}return u},adapters:zu};function wu(r){if(r.cancelToken&&r.cancelToken.throwIfRequested(),r.signal&&r.signal.aborted)throw new Pr(null,r)}function Dd(r){return wu(r),r.headers=lt.from(r.headers),r.data=vu.call(r,r.transformRequest),["post","put","patch"].indexOf(r.method)!==-1&&r.headers.setContentType("application/x-www-form-urlencoded",!1),Bp.getAdapter(r.adapter||Co.adapter)(r).then(function(u){return wu(r),u.data=vu.call(r,r.transformResponse,u),u.headers=lt.from(u.headers),u},function(u){return Lp(u)||(wu(r),u&&u.response&&(u.response.data=vu.call(r,r.transformResponse,u.response),u.response.headers=lt.from(u.response.headers))),Promise.reject(u)})}const $p="1.7.9",ps={};["object","boolean","number","function","string","symbol"].forEach((r,i)=>{ps[r]=function(u){return typeof u===r||"a"+(i<1?"n ":" ")+r}});const zd={};ps.transitional=function(i,s,u){function c(d,p){return"[Axios v"+$p+"] Transitional option '"+d+"'"+p+(u?". "+u:"")}return(d,p,m)=>{if(i===!1)throw new ie(c(p," has been removed"+(s?" in "+s:"")),ie.ERR_DEPRECATED);return s&&!zd[p]&&(zd[p]=!0,console.warn(c(p," has been deprecated since v"+s+" and will be removed in the near future"))),i?i(d,p,m):!0}};ps.spelling=function(i){return(s,u)=>(console.warn(`${u} is likely a misspelling of ${i}`),!0)};function $0(r,i,s){if(typeof r!="object")throw new ie("options must be an object",ie.ERR_BAD_OPTION_VALUE);const u=Object.keys(r);let c=u.length;for(;c-- >0;){const d=u[c],p=i[d];if(p){const m=r[d],v=m===void 0||p(m,d,r);if(v!==!0)throw new ie("option "+d+" must be "+v,ie.ERR_BAD_OPTION_VALUE);continue}if(s!==!0)throw new ie("Unknown option "+d,ie.ERR_BAD_OPTION)}}const Gi={assertOptions:$0,validators:ps},Ht=Gi.validators;class Vn{constructor(i){this.defaults=i,this.interceptors={request:new Pd,response:new Pd}}async request(i,s){try{return await this._request(i,s)}catch(u){if(u instanceof Error){let c={};Error.captureStackTrace?Error.captureStackTrace(c):c=new Error;const d=c.stack?c.stack.replace(/^.+\n/,""):"";try{u.stack?d&&!String(u.stack).endsWith(d.replace(/^.+\n.+\n/,""))&&(u.stack+=` +`+d):u.stack=d}catch{}}throw u}}_request(i,s){typeof i=="string"?(s=s||{},s.url=i):s=i||{},s=qn(this.defaults,s);const{transitional:u,paramsSerializer:c,headers:d}=s;u!==void 0&&Gi.assertOptions(u,{silentJSONParsing:Ht.transitional(Ht.boolean),forcedJSONParsing:Ht.transitional(Ht.boolean),clarifyTimeoutError:Ht.transitional(Ht.boolean)},!1),c!=null&&(_.isFunction(c)?s.paramsSerializer={serialize:c}:Gi.assertOptions(c,{encode:Ht.function,serialize:Ht.function},!0)),Gi.assertOptions(s,{baseUrl:Ht.spelling("baseURL"),withXsrfToken:Ht.spelling("withXSRFToken")},!0),s.method=(s.method||this.defaults.method||"get").toLowerCase();let p=d&&_.merge(d.common,d[s.method]);d&&_.forEach(["delete","get","head","post","put","patch","common"],I=>{delete d[I]}),s.headers=lt.concat(p,d);const m=[];let v=!0;this.interceptors.request.forEach(function(R){typeof R.runWhen=="function"&&R.runWhen(s)===!1||(v=v&&R.synchronous,m.unshift(R.fulfilled,R.rejected))});const x=[];this.interceptors.response.forEach(function(R){x.push(R.fulfilled,R.rejected)});let E,j=0,O;if(!v){const I=[Dd.bind(this),void 0];for(I.unshift.apply(I,m),I.push.apply(I,x),O=I.length,E=Promise.resolve(s);j{if(!u._listeners)return;let d=u._listeners.length;for(;d-- >0;)u._listeners[d](c);u._listeners=null}),this.promise.then=c=>{let d;const p=new Promise(m=>{u.subscribe(m),d=m}).then(c);return p.cancel=function(){u.unsubscribe(d)},p},i(function(d,p,m){u.reason||(u.reason=new Pr(d,p,m),s(u.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(i){if(this.reason){i(this.reason);return}this._listeners?this._listeners.push(i):this._listeners=[i]}unsubscribe(i){if(!this._listeners)return;const s=this._listeners.indexOf(i);s!==-1&&this._listeners.splice(s,1)}toAbortSignal(){const i=new AbortController,s=u=>{i.abort(u)};return this.subscribe(s),i.signal.unsubscribe=()=>this.unsubscribe(s),i.signal}static source(){let i;return{token:new Yu(function(c){i=c}),cancel:i}}}function H0(r){return function(s){return r.apply(null,s)}}function V0(r){return _.isObject(r)&&r.isAxiosError===!0}const Mu={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Mu).forEach(([r,i])=>{Mu[i]=r});function Hp(r){const i=new Vn(r),s=wp(Vn.prototype.request,i);return _.extend(s,Vn.prototype,i,{allOwnKeys:!0}),_.extend(s,i,null,{allOwnKeys:!0}),s.create=function(c){return Hp(qn(r,c))},s}const he=Hp(Co);he.Axios=Vn;he.CanceledError=Pr;he.CancelToken=Yu;he.isCancel=Lp;he.VERSION=$p;he.toFormData=fs;he.AxiosError=ie;he.Cancel=he.CanceledError;he.all=function(i){return Promise.all(i)};he.spread=H0;he.isAxiosError=V0;he.mergeConfig=qn;he.AxiosHeaders=lt;he.formToJSON=r=>Tp(_.isHTMLForm(r)?new FormData(r):r);he.getAdapter=Bp.getAdapter;he.HttpStatusCode=Mu;he.default=he;const Xe={apiBaseUrl:"/api"},Dt=bn(r=>({users:[],fetchUsers:async()=>{try{const i=await he.get(`${Xe.apiBaseUrl}/users`);r({users:i.data})}catch(i){console.error("사용자 목록 조회 실패:",i)}},updateUserStatus:async i=>{try{await he.patch(`${Xe.apiBaseUrl}/users/${i}/userStatus`,{newLastActiveAt:new Date().toISOString()})}catch(s){console.error("사용자 상태 업데이트 실패:",s)}}})),Wt=bn(r=>({profileImages:{},fetchProfileImage:async i=>{try{const s=await he.get(`${Xe.apiBaseUrl}/binaryContents/${i}`),u=s.data.bytes,d=`data:${s.data.contentType};base64,${u}`;return r(p=>({profileImages:{...p.profileImages,[i]:d}})),d}catch(s){return console.error("프로필 이미지 로딩 실패:",s),null}}}));function Vp(r,i){let s;try{s=r()}catch{return}return{getItem:c=>{var d;const p=v=>v===null?null:JSON.parse(v,void 0),m=(d=s.getItem(c))!=null?d:null;return m instanceof Promise?m.then(p):p(m)},setItem:(c,d)=>s.setItem(c,JSON.stringify(d,void 0)),removeItem:c=>s.removeItem(c)}}const Uu=r=>i=>{try{const s=r(i);return s instanceof Promise?s:{then(u){return Uu(u)(s)},catch(u){return this}}}catch(s){return{then(u){return this},catch(u){return Uu(u)(s)}}}},W0=(r,i)=>(s,u,c)=>{let d={storage:Vp(()=>localStorage),partialize:R=>R,version:0,merge:(R,L)=>({...L,...R}),...i},p=!1;const m=new Set,v=new Set;let x=d.storage;if(!x)return r((...R)=>{console.warn(`[zustand persist middleware] Unable to update item '${d.name}', the given storage is currently unavailable.`),s(...R)},u,c);const E=()=>{const R=d.partialize({...u()});return x.setItem(d.name,{state:R,version:d.version})},j=c.setState;c.setState=(R,L)=>{j(R,L),E()};const O=r((...R)=>{s(...R),E()},u,c);c.getInitialState=()=>O;let P;const I=()=>{var R,L;if(!x)return;p=!1,m.forEach(F=>{var W;return F((W=u())!=null?W:O)});const V=((L=d.onRehydrateStorage)==null?void 0:L.call(d,(R=u())!=null?R:O))||void 0;return Uu(x.getItem.bind(x))(d.name).then(F=>{if(F)if(typeof F.version=="number"&&F.version!==d.version){if(d.migrate){const W=d.migrate(F.state,F.version);return W instanceof Promise?W.then(K=>[!0,K]):[!0,W]}console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return[!1,F.state];return[!1,void 0]}).then(F=>{var W;const[K,$]=F;if(P=d.merge($,(W=u())!=null?W:O),s(P,!0),K)return E()}).then(()=>{V==null||V(P,void 0),P=u(),p=!0,v.forEach(F=>F(P))}).catch(F=>{V==null||V(void 0,F)})};return c.persist={setOptions:R=>{d={...d,...R},R.storage&&(x=R.storage)},clearStorage:()=>{x==null||x.removeItem(d.name)},getOptions:()=>d,rehydrate:()=>I(),hasHydrated:()=>p,onHydrate:R=>(m.add(R),()=>{m.delete(R)}),onFinishHydration:R=>(v.add(R),()=>{v.delete(R)})},d.skipHydration||I(),P||O},Q0=W0,ut=bn(Q0(r=>({currentUserId:null,setCurrentUser:i=>r({currentUserId:i.id}),logout:()=>{const i=ut.getState().currentUserId;i&&Dt.getState().updateUserStatus(i),r({currentUserId:null})},updateUser:async(i,s)=>{try{const u=await he.patch(`${Xe.apiBaseUrl}/users/${i}`,s,{headers:{"Content-Type":"multipart/form-data"}});return await Dt.getState().fetchUsers(),u.data}catch(u){throw console.error("사용자 정보 수정 실패:",u),u}}}),{name:"user-storage",storage:Vp(()=>sessionStorage)})),Qt="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAw2SURBVHgB7d3PT1XpHcfxBy5g6hipSMolGViACThxJDbVRZ2FXejKlf9h/4GmC1fTRdkwC8fE0JgyJuICFkCjEA04GeZe6P0cPC0698I95zzPc57v5f1K6DSto3A8n/v9nufXGfrr338+dgBMGnYAzCLAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwbcTDvyuWh//33w1/1dexwMRBgYxTW5vVh9/vxYTcxPpR9jY0OffZrdt8fu82ttlvfbLv9j4R5kBHgxCmcE1eH3NfTDTc7PfxZte3lJNgjbmlxxK3+1HKrr1oOg4kAJ0pVdnG+4ZqTw7+psEUoxF91Qv/Di1+db/q+ZpvD7g+T6gb04XLyv6mF3//osuqvTmDn3RGdQCAEOCG6+W/ONdzNTnCrhPZLN2Yb2T99hVhdwOLcSOf37f7hknUN4yedgLoGeb3Rdv/qdAIE2S8CnIDzAuGDQrzXeTZee1OtndaHy9LCSOHvU3++vv693nLPX9LS+0KAa6QQLC2o4sb5a1A7rYGtMqPU+l7v3hpx85+qeVnfdH7W2c7z/Pcrh1RjD5gHromq2JOHY9HCK2Ojzk1dL1fhH90fqxzenDoO/X79DMjhbAQ4Mg1OPXl4KauGodrls6j6FaXKq+dZn/IQ13ENBgkBjiRvQR99V2/lmZos9lc+PxOuxdd1uL3gp6pfVDwDR6Ab9cG9Me9VLAZ1CiHpmXhz6yibakJxVODAZpoN9/iBzfCq+sboFkJ/SAwyrlxAujE1WJWSIiO/sYKlxSpTnbEBqnBxVOBA9LybWnjloM8An6ysitc1NCe5FcvgqgVw/85o1OmhItY32n39uqnJuC3/FAEuhavmmcLra77UN7XP2322qRNX494aqvgojqvmUcrhFa1+6tdXkae6tMiEhR3FEWBPNOCTcni1rZCli4OHAHuQ4mjzaewJHlxMI1Wked5Uw7v99ijbwqd/FnVQQ7WmQyiOAFegZ7a736ZzCU820h+7nbfHbnO7XSq4p3+vmHbfMwdcBgGuoO4dNQrZxtaR+08nqNueT73Y2D7qTIW5aLRXGcUR4JL03FtHeBXa9Y2jyhX2PHudiqg/K9ZuoY3t/uan8TkCXIKCG/u5V2Fae9N2a+vtKO2tjqfVnxfj5zw5O4sWugwCXIJa51hiB/e0tfVWdkZX6CrMCHl5BLigWDt0RCc6rrxo1XZQu6rw6qt2tq47FD0G9Lu8E79FgAvIWucIO3QU2B9ftpK4sVWFZ5rDQTYbqHUOcdztRcJCjgLUToauvrqpny4fJlWVlp/5P4BOH1IcbFcdAe6Tght6h5FeiaLwpnZTq5VW2HzN1eYfUoS3OgLcp9sL4cOrkKT6YrI8dFUHnDQYR3j94Rm4D9kLxQLuV009vKdpXbXae00vFdm8UWVZJ3ojwH3QcS+hnn1VifSMaemVoPqeVzqDT6rG2oivQS5dH33l70ZS262w7n04yhae8MrTMAhwH0KNPFsfyNH3vd+pxkwD1Ydn4HOodQ5VfTXHyrMgqiDA55ibCbNJX1VLc6xAFQT4HCEGr9Q6s3wQPhDgM4RqnzWVQusMHwjwGTS66puCS/WFLwT4DCHOKia88IkA96BjTkOcVbzDQgZ4RIB7CBFejTzz7AufCHAPWn3lGwse4BsB7uGa5wqcLS3k7XvwjAD3cOWy84pnX4RAgHvw/QzMLhyEQIC7CLF4Y4+DyxEAAe4iRIB3PzD6DP8IcBejnncPagCL/bAIgQB34fsc5P2PtM8IgwBHcMjJqQiEAHfBm+JhBQGO4IDlkwiEAHdx2PIbuFhv+MPFQ4C7ODx0Xo2OOiAIAhwBz9QIhQB34XvOlhYaoRDgLg5+dl7pcACqMEIgwF2EWDV1bZwAwz8C3IVOzfAd4omrXGr4x13Vg++jb6YmudTwj7uqh733fgOsM6YZzIJvBLiH3Q/+NyDMB3pNCy4u3k7Yw+57/wNZM9PDbu2NGwjqJiauDrmvpxufXiv6+f+v63fw8SjrZDgLLBwC3INO0NBAls+2V220jurZNXw6h8K6ODfibsye/UjQnNR/nnQcGk/IX/DNsbp+EeAetAVQVaQ56fe5dXGu4X54YTPASwsj7uZ8o/CHmkJ/Y7aRfb3eaBNkj3gGPsNOgNZPN7G1RR36fh8/uJS96LxqR6Kf/9H9MRa2eEKAz7C5FaZS3l6w0/goaArchMeFKPkHwrVxbr+quIJn0LNqiFZPVSjEmx98U7UNVS016PWXe6NU4ooI8DnWN8O8DuX+H0eTnxdeWgjb7uv3/vMd9lpWQYDPEep9Rrp5by+kOy+s7+/mfPhWXyPzFrqRVHHlzpFPgYTwTScg87NphjhmZdTgGMohwH1YexPupdx3b40mN5ij6tuMuHabKlweV60PGo0OdTB7ioM5WjEWW5PNHqVw1fq09ibcu33zqZpUQjzTjN/Ws1urHK5an9bWW0Ffj5JSiOv4HiaYEy6Fq9YnLa1cfRWuCku+wOHmXL2DOnUEmGOHyiHABagKh17Dqxv57rcj7k+3RpKfJ0b9CHBBKy/ivOhIU0yPH4xdqD3EV37HB1ZRBLignc6c8MZW2FY6p5ZSK7b0bNyMOM3CTiE7CHAJz1+2or7vV1Msj74by4IcoyKHOMygH4fhptsHFgEuQRXqx5fx7zYFWRX5ycNL2UqpUFV5512cDuNLvAS9ONawlaQ10jpSJsZ64S+d3iCvm3777XGntW9nx9fsfqh+JK5+Nq0Qi43WvTgCXMHqq5abma53g75Gqmen9fX/alz1CBtNmenfj7k6yvIxQ3Wiha5AN/r3K4fJtX55hVarvVTy8AB9OMV0GGdwf+AQ4IpU4f75LN27Tzt9HtwbKzynrNF2zXvHsvOWClwGAfZAN18dg1r9UnuthSFF6WeK1doS4HIIsCeqVrHbziLUUpdZornc6S5iDC5p8A3FEWCPVn9KO8RlTpVUeJ8u/xLsUAPR780UUjkE2LOUQ6x11jPN4n/l+WDdaqDznEOdO3YREOAAFOJUn4mrTA3p51KQNU/sM8g8/5bHPHAgeibWAND9O2mdtlF147yCm2/o0IeBXlyuAwDKfjDotBMWcJRHBQ5IlUUVa1Bv0O1squnkVSllvd5kAXQVBDiwfBAo5pyqFbo2od5+cVEQ4Ag0CKRnYrWedVfjlLqBlEfsrSDAEWnwJx8Eqsve+zQCrA+SOq/DoCDAkeWDQE+X63k23txKIzRUXz8IcE00Qv23f/wSta3Odim9q/+Zc6Pz3Ev19YNppJrpRtaXXrGinUMhp5zUvqfg+Uu2HvlCgBORB1nzqYtzDTc77ffoHC3CSGEAS4N5zPv6Q4ATo7lVfV253MoWXegMrKob6xWaFKax9PzNdJpfBDhRqlL7n6qy2mqFWeuY9QaDfttsfRCoXd1NYOS5rnPEBh0BNuB0mGVifOgk1Ncb2VJGbVLIdxnp12qqaHO7HXQHURH6ngZ5RVqdCLBBqqj62jCwiknbBJefEd5QCDCCUWgV3hRa+EFFgBEEbXMcBBjeabR55UWLUzYiIMDwRoHVK1iZKoqHAMMLqm49CDAqyxefID42MwCGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhv0XZkN9IbEGbp4AAAAASUVORK5CYII=";function Md({channel:r,isActive:i,onClick:s,hasUnread:u}){const c=ut(x=>x.currentUserId),d=Dt(x=>x.users),p=Wt(x=>x.profileImages);if(r.type==="PUBLIC")return g.jsxs(yp,{$isActive:i,onClick:s,$hasUnread:u,children:["# ",r.name]});const m=r.participantIds.map(x=>d.find(E=>E.id===x)).filter(Boolean);if(m.length>2){const x=m.filter(E=>E.id!==c).map(E=>E.username).join(", ");return g.jsxs(yd,{$isActive:i,onClick:s,children:[g.jsx(uy,{children:m.filter(E=>E.id!==c).slice(0,2).map((E,j)=>g.jsx(cy,{src:E.profileId?p[E.profileId]:Qt,style:{position:"absolute",left:j*16,zIndex:2-j}},E.id))}),g.jsxs(xd,{children:[g.jsx(vd,{$hasUnread:u,children:x}),g.jsxs(ay,{children:["멤버 ",m.length,"명"]})]})]})}const v=m.filter(x=>x.id!==c)[0];return g.jsxs(yd,{$isActive:i,onClick:s,children:[g.jsxs(ly,{children:[g.jsx("img",{src:v.profileId?p[v.profileId]:Qt,alt:"profile"}),g.jsx(vp,{$online:v.online})]}),g.jsx(xd,{children:g.jsx(vd,{$hasUnread:u,children:v.username})})]})}function q0({isOpen:r,onClose:i,user:s,onSubmit:u}){const[c,d]=ue.useState(s.username),[p,m]=ue.useState(s.email),[v,x]=ue.useState(""),[E,j]=ue.useState(null),[O,P]=ue.useState(""),[I,R]=ue.useState(null),L=Wt(T=>T.profileImages),V=Wt(T=>T.fetchProfileImage),F=ut(T=>T.logout);ue.useEffect(()=>{s.profileId&&!L[s.profileId]&&V(s.profileId)},[s.profileId,L,V]);const W=()=>{d(s.username),m(s.email),x(""),j(null),R(null),P(""),i()},K=T=>{const H=T.target.files[0];if(H){j(H);const se=new FileReader;se.onloadend=()=>{R(se.result)},se.readAsDataURL(H)}},$=async T=>{T.preventDefault(),P("");try{const H=new FormData,se={};c!==s.username&&(se.newUsername=c),p!==s.email&&(se.newEmail=p),v&&(se.newPassword=v),(Object.keys(se).length>0||E)&&(H.append("userUpdateRequest",new Blob([JSON.stringify(se)],{type:"application/json"})),E&&H.append("profile",E),await u(H)),i()}catch{P("사용자 정보 수정에 실패했습니다.")}};return r?g.jsx(b0,{children:g.jsxs(G0,{children:[g.jsx("h2",{children:"프로필 수정"}),g.jsxs("form",{onSubmit:$,children:[g.jsxs(Mi,{children:[g.jsx(Ui,{children:"프로필 이미지"}),g.jsxs(K0,{children:[g.jsx(X0,{src:I||L[s.profileId]||Qt,alt:"profile"}),g.jsx(J0,{type:"file",accept:"image/*",onChange:K,id:"profile-image"}),g.jsx(Z0,{htmlFor:"profile-image",children:"이미지 변경"})]})]}),g.jsxs(Mi,{children:[g.jsxs(Ui,{children:["사용자명 ",g.jsx(Fd,{children:"*"})]}),g.jsx(xu,{type:"text",value:c,onChange:T=>d(T.target.value),required:!0})]}),g.jsxs(Mi,{children:[g.jsxs(Ui,{children:["이메일 ",g.jsx(Fd,{children:"*"})]}),g.jsx(xu,{type:"email",value:p,onChange:T=>m(T.target.value),required:!0})]}),g.jsxs(Mi,{children:[g.jsx(Ui,{children:"새 비밀번호"}),g.jsx(xu,{type:"password",placeholder:"변경하지 않으려면 비워두세요",value:v,onChange:T=>x(T.target.value)})]}),O&&g.jsx(Y0,{children:O}),g.jsxs(ev,{children:[g.jsx(Ud,{type:"button",onClick:W,$secondary:!0,children:"취소"}),g.jsx(Ud,{type:"submit",children:"저장"})]})]}),g.jsx(tv,{onClick:F,children:"로그아웃"})]})}):null}const b0=N.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +`,G0=N.div` + background: ${({theme:r})=>r.colors.background.secondary}; + padding: 32px; + border-radius: 5px; + width: 100%; + max-width: 480px; + + h2 { + color: ${({theme:r})=>r.colors.text.primary}; + margin-bottom: 24px; + text-align: center; + font-size: 24px; + } +`,xu=N.input` + width: 100%; + padding: 10px; + margin-bottom: 10px; + border: none; + border-radius: 4px; + background: ${({theme:r})=>r.colors.background.input}; + color: ${({theme:r})=>r.colors.text.primary}; + + &::placeholder { + color: ${({theme:r})=>r.colors.text.muted}; + } + + &:focus { + outline: none; + box-shadow: 0 0 0 2px ${({theme:r})=>r.colors.brand.primary}; + } +`,Ud=N.button` + width: 100%; + padding: 10px; + border: none; + border-radius: 4px; + background: ${({$secondary:r,theme:i})=>r?"transparent":i.colors.brand.primary}; + color: ${({theme:r})=>r.colors.text.primary}; + cursor: pointer; + font-weight: 500; + + &:hover { + background: ${({$secondary:r,theme:i})=>r?i.colors.background.hover:i.colors.brand.hover}; + } +`,Y0=N.div` + color: ${({theme:r})=>r.colors.status.error}; + font-size: 14px; + margin-bottom: 10px; +`,K0=N.div` + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 20px; +`,X0=N.img` + width: 100px; + height: 100px; + border-radius: 50%; + margin-bottom: 10px; + object-fit: cover; +`,J0=N.input` + display: none; +`,Z0=N.label` + color: ${({theme:r})=>r.colors.brand.primary}; + cursor: pointer; + font-size: 14px; + + &:hover { + text-decoration: underline; + } +`,ev=N.div` + display: flex; + gap: 10px; + margin-top: 20px; +`,tv=N.button` + width: 100%; + padding: 10px; + margin-top: 16px; + border: none; + border-radius: 4px; + background: transparent; + color: ${({theme:r})=>r.colors.status.error}; + cursor: pointer; + font-weight: 500; + + &:hover { + background: ${({theme:r})=>r.colors.status.error}20; + } +`,Mi=N.div` + margin-bottom: 20px; +`,Ui=N.label` + display: block; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 12px; + font-weight: 700; + margin-bottom: 8px; +`,Fd=N.span` + color: ${({theme:r})=>r.colors.status.error}; +`,Wp=N.div` + position: absolute; + bottom: -3px; + right: -3px; + width: 16px; + height: 16px; + border-radius: 50%; + background: ${r=>r.$online?J.colors.status.online:J.colors.status.offline}; + border: 4px solid ${r=>r.$background||J.colors.background.secondary}; +`,nv=N.div` + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.5rem 0.75rem; + background-color: ${({theme:r})=>r.colors.background.tertiary}; + width: 100%; + height: 52px; +`,rv=N.div` + position: relative; + width: 32px; + height: 32px; + flex-shrink: 0; +`,ov=N.img` + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; +`,iv=N.div` + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; +`,sv=N.div` + font-weight: 500; + color: ${({theme:r})=>r.colors.text.primary}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 0.875rem; + line-height: 1.2; +`,lv=N.div` + font-size: 0.75rem; + color: ${({theme:r})=>r.colors.text.secondary}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.2; +`,uv=N.div` + display: flex; + align-items: center; + flex-shrink: 0; +`,av=N.button` + background: none; + border: none; + padding: 0.25rem; + cursor: pointer; + color: ${({theme:r})=>r.colors.text.secondary}; + font-size: 18px; + + &:hover { + color: ${({theme:r})=>r.colors.text.primary}; + } +`;function cv({user:r}){const[i,s]=ue.useState(!1);ut(m=>m.logout);const u=ut(m=>m.updateUser),c=Wt(m=>m.profileImages),d=Wt(m=>m.fetchProfileImage);ue.useEffect(()=>{r.profileId&&!c[r.profileId]&&d(r.profileId)},[r.profileId,c,d]);const p=async m=>{await u(r.id,m)};return g.jsxs(g.Fragment,{children:[g.jsxs(nv,{children:[g.jsxs(rv,{children:[g.jsx(ov,{src:c[r.profileId]||Qt}),g.jsx(Wp,{$online:r.online,$background:J.colors.background.tertiary})]}),g.jsxs(iv,{children:[g.jsx(sv,{children:r.username}),g.jsx(lv,{children:r.email})]}),g.jsx(uv,{children:g.jsx(av,{onClick:()=>s(!0),children:"⚙️"})})]}),g.jsx(q0,{isOpen:i,onClose:()=>s(!1),user:r,onSubmit:p})]})}const fv=N.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.85); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +`,dv=N.div` + background: ${J.colors.background.primary}; + border-radius: 4px; + width: 440px; + max-width: 90%; +`,pv=N.div` + padding: 16px; + display: flex; + justify-content: space-between; + align-items: center; +`,hv=N.h2` + color: ${J.colors.text.primary}; + font-size: 20px; + font-weight: 600; + margin: 0; +`,mv=N.div` + padding: 0 16px 16px; +`,gv=N.form` + display: flex; + flex-direction: column; + gap: 16px; +`,Su=N.div` + display: flex; + flex-direction: column; + gap: 8px; +`,ku=N.label` + color: ${J.colors.text.primary}; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; +`,yv=N.p` + color: ${J.colors.text.muted}; + font-size: 14px; + margin: -4px 0 0; +`,Fu=N.input` + padding: 10px; + background: ${J.colors.background.tertiary}; + border: none; + border-radius: 3px; + color: ${J.colors.text.primary}; + font-size: 16px; + + &:focus { + outline: none; + box-shadow: 0 0 0 2px ${J.colors.status.online}; + } + + &::placeholder { + color: ${J.colors.text.muted}; + } +`,vv=N.button` + margin-top: 8px; + padding: 12px; + background: ${J.colors.status.online}; + color: white; + border: none; + border-radius: 3px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: #3ca374; + } +`,wv=N.button` + background: none; + border: none; + color: ${J.colors.text.muted}; + font-size: 24px; + cursor: pointer; + padding: 4px; + line-height: 1; + + &:hover { + color: ${J.colors.text.primary}; + } +`,xv=N(Fu)` + margin-bottom: 8px; +`,Sv=N.div` + max-height: 300px; + overflow-y: auto; + background: ${J.colors.background.tertiary}; + border-radius: 4px; +`,kv=N.div` + display: flex; + align-items: center; + padding: 8px 12px; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: ${J.colors.background.hover}; + } + + & + & { + border-top: 1px solid ${J.colors.border.primary}; + } +`,Ev=N.input` + margin-right: 12px; + width: 16px; + height: 16px; + cursor: pointer; +`,Bd=N.img` + width: 32px; + height: 32px; + border-radius: 50%; + margin-right: 12px; +`,Cv=N.div` + flex: 1; + min-width: 0; +`,Av=N.div` + color: ${J.colors.text.primary}; + font-size: 14px; + font-weight: 500; +`,Rv=N.div` + color: ${J.colors.text.muted}; + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`,Pv=N.div` + padding: 16px; + text-align: center; + color: ${J.colors.text.muted}; +`,jv=N.div` + color: ${J.colors.status.error}; + font-size: 14px; + padding: 8px 0; + text-align: center; + background-color: ${({theme:r})=>r.colors.background.tertiary}; + border-radius: 4px; + margin-bottom: 8px; +`,Bn=bn((r,i)=>({channels:[],pollingInterval:null,fetchChannels:async s=>{try{const u=await he.get(`${Xe.apiBaseUrl}/channels`,{params:{userId:s}});return r({channels:u.data}),u.data}catch(u){console.error("채널 목록 조회 실패:",u)}},startPolling:s=>{i().pollingInterval&&clearInterval(i().pollingInterval);const u=setInterval(()=>{i().fetchChannels(s)},3e3);r({pollingInterval:u})},stopPolling:()=>{i().pollingInterval&&(clearInterval(i().pollingInterval),r({pollingInterval:null}))},createPublicChannel:async s=>{try{const u=await he.post(`${Xe.apiBaseUrl}/channels/public`,s),c={...u.data,participantIds:[],lastMessageAt:u.data.createdAt};return r(d=>({channels:[...d.channels,c]})),c}catch(u){throw console.error("공개 채널 생성 실패:",u),u}},createPrivateChannel:async s=>{try{const u=await he.post(`${Xe.apiBaseUrl}/channels/private`,{participantIds:s}),c={...u.data,participantIds:s,lastMessageAt:u.data.createdAt};return r(d=>({channels:[...d.channels,c]})),c}catch(u){throw console.error("비공개 채널 생성 실패:",u),u}}}));function Iv({isOpen:r,type:i,onClose:s,onCreateSuccess:u}){const[c,d]=ue.useState({name:"",description:""}),[p,m]=ue.useState(""),[v,x]=ue.useState([]),[E,j]=ue.useState(""),O=Dt($=>$.users),P=Wt($=>$.profileImages),I=ut($=>$.currentUserId),R=ue.useMemo(()=>O.filter($=>$.id!==I).filter($=>$.username.toLowerCase().includes(p.toLowerCase())||$.email.toLowerCase().includes(p.toLowerCase())),[p,O]),L=Bn($=>$.createPublicChannel),V=Bn($=>$.createPrivateChannel),F=$=>{const{name:T,value:H}=$.target;d(se=>({...se,[T]:H}))},W=$=>{x(T=>T.includes($)?T.filter(H=>H!==$):[...T,$])},K=async $=>{var T,H;$.preventDefault(),j("");try{if(i==="PUBLIC"){if(!c.name.trim()){j("채널 이름을 입력해주세요.");return}await L({name:c.name,description:c.description})}else{if(v.length===0){j("대화 상대를 선택해주세요.");return}const se=[...v,I];await V(se)}u()}catch(se){console.error("채널 생성 실패:",se),j(((H=(T=se.response)==null?void 0:T.data)==null?void 0:H.message)||"채널 생성에 실패했습니다. 다시 시도해주세요.")}};return r?g.jsx(fv,{onClick:s,children:g.jsxs(dv,{onClick:$=>$.stopPropagation(),children:[g.jsxs(pv,{children:[g.jsx(hv,{children:i==="PUBLIC"?"채널 만들기":"개인 메시지 시작하기"}),g.jsx(wv,{onClick:s,children:"×"})]}),g.jsx(mv,{children:g.jsxs(gv,{onSubmit:K,children:[E&&g.jsx(jv,{children:E}),i==="PUBLIC"?g.jsxs(g.Fragment,{children:[g.jsxs(Su,{children:[g.jsx(ku,{children:"채널 이름"}),g.jsx(Fu,{name:"name",value:c.name,onChange:F,placeholder:"새로운-채널",required:!0})]}),g.jsxs(Su,{children:[g.jsx(ku,{children:"채널 설명"}),g.jsx(yv,{children:"이 채널의 주제를 설명해주세요."}),g.jsx(Fu,{name:"description",value:c.description,onChange:F,placeholder:"채널 설명을 입력하세요"})]})]}):g.jsxs(Su,{children:[g.jsx(ku,{children:"사용자 검색"}),g.jsx(xv,{type:"text",value:p,onChange:$=>m($.target.value),placeholder:"사용자명 또는 이메일로 검색"}),g.jsx(Sv,{children:R.length>0?R.map($=>g.jsxs(kv,{children:[g.jsx(Ev,{type:"checkbox",checked:v.includes($.id),onChange:()=>W($.id)}),$.profileId?g.jsx(Bd,{src:P[$.profileId]}):g.jsx(Bd,{src:Qt}),g.jsxs(Cv,{children:[g.jsx(Av,{children:$.username}),g.jsx(Rv,{children:$.email})]})]},$.id)):g.jsx(Pv,{children:"검색 결과가 없습니다."})})]}),g.jsx(vv,{type:"submit",children:i==="PUBLIC"?"채널 만들기":"대화 시작하기"})]})})]})}):null}const yo=bn((r,i)=>({readStatuses:{},fetchReadStatuses:async()=>{try{const s=ut.getState().currentUserId;if(!s)return;const c=(await he.get(`${Xe.apiBaseUrl}/readStatuses`,{params:{userId:s}})).data.reduce((d,p)=>(d[p.channelId]={id:p.id,lastReadAt:p.lastReadAt},d),{});r({readStatuses:c})}catch(s){console.error("읽음 상태 조회 실패:",s)}},updateReadStatus:async s=>{try{const u=ut.getState().currentUserId;if(!u)return;const c=i().readStatuses[s];let d;c?d=await he.patch(`${Xe.apiBaseUrl}/readStatuses/${c.id}`,{newLastReadAt:new Date().toISOString()}):d=await he.post(`${Xe.apiBaseUrl}/readStatuses`,{userId:u,channelId:s,lastReadAt:new Date().toISOString()}),r(p=>({readStatuses:{...p.readStatuses,[s]:{id:d.data.id,lastReadAt:d.data.lastReadAt}}}))}catch(u){console.error("읽음 상태 업데이트 실패:",u)}},hasUnreadMessages:(s,u)=>{const c=i().readStatuses[s],d=c==null?void 0:c.lastReadAt;return!d||new Date(u)>new Date(d)}}));function _v({currentUser:r,activeChannel:i,onChannelSelect:s}){var K,$;const[u,c]=ue.useState({PUBLIC:!1,PRIVATE:!1}),[d,p]=ue.useState({isOpen:!1,type:null}),m=Bn(T=>T.channels),v=Bn(T=>T.fetchChannels),x=Bn(T=>T.startPolling),E=Bn(T=>T.stopPolling);yo(T=>T.readStatuses);const j=yo(T=>T.fetchReadStatuses),O=yo(T=>T.updateReadStatus),P=yo(T=>T.hasUnreadMessages);ue.useEffect(()=>{if(r)return v(r.id),j(),x(r.id),()=>{E()}},[r,v,j,x,E]);const I=T=>{c(H=>({...H,[T]:!H[T]}))},R=(T,H)=>{H.stopPropagation(),p({isOpen:!0,type:T})},L=()=>{p({isOpen:!1,type:null})},V=async T=>{try{await v(r.id),L()}catch(H){console.error("채널 생성 실패:",H)}},F=T=>{s(T),O(T.id)},W=m.reduce((T,H)=>(T[H.type]||(T[H.type]=[]),T[H.type].push(H),T),{});return g.jsxs(oy,{children:[g.jsx(fy,{}),g.jsxs(iy,{children:[g.jsxs(hd,{children:[g.jsxs(Nu,{onClick:()=>I("PUBLIC"),children:[g.jsx(md,{$folded:u.PUBLIC,children:"▼"}),g.jsx("span",{children:"일반 채널"}),g.jsx(wd,{onClick:T=>R("PUBLIC",T),children:"+"})]}),g.jsx(gd,{$folded:u.PUBLIC,children:(K=W.PUBLIC)==null?void 0:K.map(T=>g.jsx(Md,{channel:T,isActive:(i==null?void 0:i.id)===T.id,hasUnread:P(T.id,T.lastMessageAt),onClick:()=>F(T)},T.id))})]}),g.jsxs(hd,{children:[g.jsxs(Nu,{onClick:()=>I("PRIVATE"),children:[g.jsx(md,{$folded:u.PRIVATE,children:"▼"}),g.jsx("span",{children:"개인 메시지"}),g.jsx(wd,{onClick:T=>R("PRIVATE",T),children:"+"})]}),g.jsx(gd,{$folded:u.PRIVATE,children:($=W.PRIVATE)==null?void 0:$.map(T=>g.jsx(Md,{channel:T,isActive:(i==null?void 0:i.id)===T.id,hasUnread:P(T.id,T.lastMessageAt),onClick:()=>F(T)},T.id))})]})]}),g.jsx(Nv,{children:g.jsx(cv,{user:r})}),g.jsx(Iv,{isOpen:d.isOpen,type:d.type,onClose:L,onCreateSuccess:V})]})}const Nv=N.div` + margin-top: auto; + border-top: 1px solid ${({theme:r})=>r.colors.border.primary}; + background-color: ${({theme:r})=>r.colors.background.tertiary}; +`,Ov=N.div` + flex: 1; + display: flex; + flex-direction: column; + background: ${({theme:r})=>r.colors.background.primary}; +`,Tv=N.div` + display: flex; + flex-direction: column; + height: 100%; + background: ${({theme:r})=>r.colors.background.primary}; +`,Lv=N(Tv)` + justify-content: center; + align-items: center; + flex: 1; + padding: 0 20px; +`,Dv=N.div` + text-align: center; + max-width: 400px; + padding: 20px; + margin-bottom: 80px; +`,zv=N.div` + font-size: 48px; + margin-bottom: 16px; + animation: wave 2s infinite; + transform-origin: 70% 70%; + + @keyframes wave { + 0% { transform: rotate(0deg); } + 10% { transform: rotate(14deg); } + 20% { transform: rotate(-8deg); } + 30% { transform: rotate(14deg); } + 40% { transform: rotate(-4deg); } + 50% { transform: rotate(10deg); } + 60% { transform: rotate(0deg); } + 100% { transform: rotate(0deg); } + } +`,Mv=N.h2` + color: ${({theme:r})=>r.colors.text.primary}; + font-size: 28px; + font-weight: 700; + margin-bottom: 16px; +`,Uv=N.p` + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 16px; + line-height: 1.6; + word-break: keep-all; +`,$d=N.div` + height: 48px; + padding: 0 16px; + background: ${J.colors.background.primary}; + border-bottom: 1px solid ${J.colors.border.primary}; + display: flex; + align-items: center; +`,Hd=N.div` + display: flex; + align-items: center; + gap: 8px; + height: 100%; +`,Fv=N.div` + display: flex; + align-items: center; + gap: 12px; + height: 100%; +`,Bv=N.div` + position: relative; + width: 24px; + height: 24px; + flex-shrink: 0; +`,Vd=N.img` + width: 24px; + height: 24px; + border-radius: 50%; +`,$v=N.div` + position: relative; + width: 40px; + height: 24px; + flex-shrink: 0; +`,Hv=N.div` + position: absolute; + bottom: -2px; + right: -2px; + width: 14px; + height: 14px; + border-radius: 50%; + background: ${r=>r.online?J.colors.status.online:J.colors.status.offline}; + border: 3px solid ${J.colors.background.secondary}; +`,Vv=N(Hv)` + border-color: ${J.colors.background.primary}; + bottom: -3px; + right: -3px; +`,Wv=N.div` + font-size: 12px; + color: ${J.colors.text.muted}; + line-height: 13px; +`,Wd=N.div` + font-weight: bold; + color: ${J.colors.text.primary}; + line-height: 20px; + font-size: 16px; +`,Qv=N.div` + flex: 1; + display: flex; + flex-direction: column-reverse; + overflow-y: auto; +`,qv=N.div` + padding: 16px; + display: flex; + flex-direction: column; +`,bv=N.div` + margin-bottom: 16px; + display: flex; + align-items: flex-start; +`,Gv=N.div` + position: relative; + margin-right: 16px; + flex-shrink: 0; +`,Yv=N.img` + width: 40px; + height: 40px; + border-radius: 50%; +`,Kv=N.div` + display: flex; + align-items: center; + margin-bottom: 4px; +`,Xv=N.span` + font-weight: bold; + color: ${J.colors.text.primary}; + margin-right: 8px; +`,Jv=N.span` + font-size: 0.75rem; + color: ${J.colors.text.muted}; +`,Zv=N.div` + color: ${J.colors.text.secondary}; + margin-top: 4px; +`,e1=N.form` + display: flex; + align-items: center; + gap: 8px; + padding: 16px; + background: ${({theme:r})=>r.colors.background.secondary}; +`,t1=N.textarea` + flex: 1; + padding: 12px; + background: ${({theme:r})=>r.colors.background.tertiary}; + border: none; + border-radius: 4px; + color: ${({theme:r})=>r.colors.text.primary}; + font-size: 14px; + resize: none; + min-height: 44px; + max-height: 144px; + + &:focus { + outline: none; + } + + &::placeholder { + color: ${({theme:r})=>r.colors.text.muted}; + } +`,n1=N.button` + background: none; + border: none; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 24px; + cursor: pointer; + padding: 4px 8px; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + color: ${({theme:r})=>r.colors.text.primary}; + } +`;N.div` + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: ${J.colors.text.muted}; + font-size: 16px; + font-weight: 500; + padding: 20px; + text-align: center; +`;const Qd=N.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 8px; + width: 100%; +`,r1=N.a` + display: block; + border-radius: 4px; + overflow: hidden; + max-width: 300px; + + img { + width: 100%; + height: auto; + display: block; + } +`,o1=N.a` + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background: ${({theme:r})=>r.colors.background.tertiary}; + border-radius: 8px; + text-decoration: none; + width: fit-content; + + &:hover { + background: ${({theme:r})=>r.colors.background.hover}; + } +`,i1=N.div` + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + font-size: 40px; + color: #0B93F6; +`,s1=N.div` + display: flex; + flex-direction: column; + gap: 2px; +`,l1=N.span` + font-size: 14px; + color: #0B93F6; + font-weight: 500; +`,u1=N.span` + font-size: 13px; + color: ${({theme:r})=>r.colors.text.muted}; +`,a1=N.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 8px 0; +`,Qp=N.div` + position: relative; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: ${({theme:r})=>r.colors.background.tertiary}; + border-radius: 4px; + max-width: 300px; +`,c1=N(Qp)` + padding: 0; + overflow: hidden; + width: 200px; + height: 120px; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } +`,f1=N.div` + color: #0B93F6; + font-size: 20px; +`,d1=N.div` + font-size: 13px; + color: ${({theme:r})=>r.colors.text.primary}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`,qd=N.button` + position: absolute; + top: -6px; + right: -6px; + width: 20px; + height: 20px; + border-radius: 50%; + background: ${({theme:r})=>r.colors.background.secondary}; + border: none; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 16px; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + padding: 0; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + + &:hover { + color: ${({theme:r})=>r.colors.text.primary}; + } +`,vo=bn((r,i)=>({messages:[],pollingIntervals:{},lastMessageId:null,fetchMessages:async s=>{try{const u=await he.get(`${Xe.apiBaseUrl}/messages`,{params:{channelId:s}}),c=u.data[u.data.length-1],d=(c==null?void 0:c.id)!==i().lastMessageId;return r({messages:u.data,lastMessageId:c==null?void 0:c.id}),d}catch(u){return console.error("메시지 목록 조회 실패:",u),!1}},startPolling:s=>{const u=i();u.pollingIntervals[s]&&clearTimeout(u.pollingIntervals[s]);let c=300;const d=3e3;r(m=>({pollingIntervals:{...m.pollingIntervals,[s]:!0}}));const p=async()=>{const m=i();if(!m.pollingIntervals[s])return;if(await m.fetchMessages(s)?c=300:c=Math.min(c*1.5,d),i().pollingIntervals[s]){const x=setTimeout(p,c);r(E=>({pollingIntervals:{...E.pollingIntervals,[s]:x}}))}};p()},stopPolling:s=>{const{pollingIntervals:u}=i();if(u[s]){const c=u[s];typeof c=="number"&&clearTimeout(c),r(d=>{const p={...d.pollingIntervals};return delete p[s],{pollingIntervals:p}})}},createMessage:async s=>{try{const u=new FormData;u.append("messageCreateRequest",new Blob([JSON.stringify({content:s.content,channelId:s.channelId,authorId:s.authorId})],{type:"application/json"})),s.attachments&&s.attachments.forEach(p=>{u.append("attachments",p)});const c=await he.post(`${Xe.apiBaseUrl}/messages`,u,{headers:{"Content-Type":"multipart/form-data"}}),d=yo.getState().updateReadStatus;return await d(s.channelId),r(p=>({messages:[...p.messages,c.data]})),c.data}catch(u){throw console.error("메시지 생성 실패:",u),u}}})),p1=bn((r,i)=>({attachments:{},fetchAttachment:async s=>{if(i().attachments[s])return i().attachments[s];try{const u=await he.get(`${Xe.apiBaseUrl}/binaryContents/${s}`),{bytes:c,contentType:d,fileName:p,size:m}=u.data,x={url:`data:${d};base64,${c}`,contentType:d,originalName:p,size:m};return r(E=>({attachments:{...E.attachments,[s]:x}})),x}catch(u){return console.error("첨부파일 정보 조회 실패:",u),null}}})),h1=r=>r<1024?r+" B":r<1024*1024?(r/1024).toFixed(2)+" KB":r<1024*1024*1024?(r/(1024*1024)).toFixed(2)+" MB":(r/(1024*1024*1024)).toFixed(2)+" GB";function m1({channel:r}){const i=vo(P=>P.messages),s=vo(P=>P.fetchMessages),u=vo(P=>P.startPolling),c=vo(P=>P.stopPolling),d=Wt(P=>P.profileImages),p=Dt(P=>P.users),{attachments:m,fetchAttachment:v}=p1();ue.useEffect(()=>{if(r!=null&&r.id)return s(r.id),u(r.id),()=>{c(r.id)}},[r==null?void 0:r.id,s,u,c]),ue.useEffect(()=>{i.forEach(P=>{var I;(I=P.attachmentIds)==null||I.forEach(R=>{m[R]||v(R)})})},[i,m,v]);const x=async(P,I)=>{try{const R=await he.get(`${Xe.apiBaseUrl}/binaryContents/${P}`,{responseType:"blob"}),L=new Blob([R.data],{type:R.headers["content-type"]}),V=window.URL.createObjectURL(L),F=document.createElement("a");F.href=V,F.download=I,F.style.display="none",document.body.appendChild(F);try{const K=await(await window.showSaveFilePicker({suggestedName:I,types:[{description:"Files",accept:{"*/*":[".txt",".pdf",".doc",".docx",".xls",".xlsx",".jpg",".jpeg",".png",".gif"]}}]})).createWritable();await K.write(L),await K.close()}catch(W){W.name!=="AbortError"&&F.click()}document.body.removeChild(F),window.URL.revokeObjectURL(V)}catch(R){console.error("파일 다운로드 실패:",R)}},E=P=>P!=null&&P.length?P.map(I=>{const R=m[I];return R?R.contentType.startsWith("image/")?g.jsx(Qd,{children:g.jsx(r1,{href:"#",onClick:V=>{V.preventDefault(),x(I,R.originalName)},children:g.jsx("img",{src:R.url,alt:R.originalName})})},I):g.jsx(Qd,{children:g.jsxs(o1,{href:"#",onClick:V=>{V.preventDefault(),x(I,R.originalName)},children:[g.jsx(i1,{children:g.jsxs("svg",{width:"40",height:"40",viewBox:"0 0 40 40",fill:"none",children:[g.jsx("path",{d:"M8 3C8 1.89543 8.89543 1 10 1H22L32 11V37C32 38.1046 31.1046 39 30 39H10C8.89543 39 8 38.1046 8 37V3Z",fill:"#0B93F6",fillOpacity:"0.1"}),g.jsx("path",{d:"M22 1L32 11H24C22.8954 11 22 10.1046 22 9V1Z",fill:"#0B93F6",fillOpacity:"0.3"}),g.jsx("path",{d:"M13 19H27M13 25H27M13 31H27",stroke:"#0B93F6",strokeWidth:"2",strokeLinecap:"round"})]})}),g.jsxs(s1,{children:[g.jsx(l1,{children:R.originalName}),g.jsx(u1,{children:h1(R.size)})]})]})},I):null}):null,j=P=>new Date(P).toLocaleTimeString(),O=[...i].sort((P,I)=>P.createdAt.localeCompare(I.createdAt));return g.jsx(Qv,{children:g.jsx(qv,{children:O.map(P=>{const I=p.find(R=>R.id===P.authorId);return g.jsxs(bv,{children:[g.jsx(Gv,{children:g.jsx(Yv,{src:I&&I.profileId?d[I.profileId]:Qt,alt:I&&I.username||"알 수 없음"})}),g.jsxs("div",{children:[g.jsxs(Kv,{children:[g.jsx(Xv,{children:I&&I.username||"알 수 없음"}),g.jsx(Jv,{children:j(P.createdAt)})]}),g.jsx(Zv,{children:P.content}),E(P.attachmentIds)]})]},P.id)})})})}function g1({channel:r}){const[i,s]=ue.useState(""),[u,c]=ue.useState([]),d=vo(O=>O.createMessage),p=ut(O=>O.currentUserId),m=async O=>{if(O.preventDefault(),!(!i.trim()&&u.length===0))try{await d({content:i.trim(),channelId:r.id,authorId:p,attachments:u}),s(""),c([])}catch(P){console.error("메시지 전송 실패:",P)}},v=O=>{const P=Array.from(O.target.files);c(I=>[...I,...P]),O.target.value=""},x=O=>{c(P=>P.filter((I,R)=>R!==O))},E=O=>{O.key==="Enter"&&!O.shiftKey&&(O.preventDefault(),m(O))},j=(O,P)=>O.type.startsWith("image/")?g.jsxs(c1,{children:[g.jsx("img",{src:URL.createObjectURL(O),alt:O.name}),g.jsx(qd,{onClick:()=>x(P),children:"×"})]},P):g.jsxs(Qp,{children:[g.jsx(f1,{children:"📎"}),g.jsx(d1,{children:O.name}),g.jsx(qd,{onClick:()=>x(P),children:"×"})]},P);return ue.useEffect(()=>()=>{u.forEach(O=>{O.type.startsWith("image/")&&URL.revokeObjectURL(O)})},[u]),r?g.jsxs(g.Fragment,{children:[u.length>0&&g.jsx(a1,{children:u.map((O,P)=>j(O,P))}),g.jsxs(e1,{onSubmit:m,children:[g.jsxs(n1,{as:"label",children:["+",g.jsx("input",{type:"file",multiple:!0,onChange:v,style:{display:"none"}})]}),g.jsx(t1,{value:i,onChange:O=>s(O.target.value),onKeyPress:E,placeholder:r.type==="PUBLIC"?`#${r.name}에 메시지 보내기`:"메시지 보내기"})]})]}):null}function y1({channel:r}){const i=ut(v=>v.currentUserId),s=Dt(v=>v.users),u=Wt(v=>v.profileImages);if(!r)return null;if(r.type==="PUBLIC")return g.jsx($d,{children:g.jsx(Hd,{children:g.jsxs(Wd,{children:["# ",r.name]})})});const c=r.participantIds.map(v=>s.find(x=>x.id===v)).filter(Boolean),d=c.filter(v=>v.id!==i),p=c.length>2,m=c.filter(v=>v.id!==i).map(v=>v.username).join(", ");return g.jsx($d,{children:g.jsx(Hd,{children:g.jsxs(Fv,{children:[p?g.jsx($v,{children:d.slice(0,2).map((v,x)=>g.jsx(Vd,{src:v.profileId?u[v.profileId]:Qt,style:{position:"absolute",left:x*16,zIndex:2-x}},v.id))}):g.jsxs(Bv,{children:[g.jsx(Vd,{src:d[0].profileId?u[d[0].profileId]:Qt}),g.jsx(Vv,{online:d[0].online})]}),g.jsxs("div",{children:[g.jsx(Wd,{children:m}),p&&g.jsxs(Wv,{children:["멤버 ",c.length,"명"]})]})]})})})}function v1({channel:r}){return r?g.jsxs(Ov,{children:[g.jsx(y1,{channel:r}),g.jsx(m1,{channel:r}),g.jsx(g1,{channel:r})]}):g.jsx(Lv,{children:g.jsxs(Dv,{children:[g.jsx(zv,{children:"👋"}),g.jsx(Mv,{children:"채널을 선택해주세요"}),g.jsxs(Uv,{children:["왼쪽의 채널 목록에서 채널을 선택하여",g.jsx("br",{}),"대화를 시작하세요."]})]})})}const w1=N.div` + width: 240px; + background: ${J.colors.background.secondary}; + border-left: 1px solid ${J.colors.border.primary}; +`,x1=N.div` + padding: 16px; + font-size: 14px; + font-weight: bold; + color: ${J.colors.text.muted}; + text-transform: uppercase; +`,S1=N.div` + padding: 8px 16px; + display: flex; + align-items: center; + color: ${J.colors.text.muted}; +`,k1=N.div` + position: relative; + width: 32px; + height: 32px; + margin-right: 12px; +`,bd=N.img` + width: 100%; + height: 100%; + border-radius: 50%; +`,E1=N.div` + display: flex; + align-items: center; +`;N.div` + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 8px; + background: ${r=>J.colors.status[r.status]}; +`;function C1({member:r}){const i=Wt(u=>u.profileImages),s=Wt(u=>u.fetchProfileImage);return ue.useEffect(()=>{r.profileId&&!i[r.profileId]&&s(r.profileId)},[r.profileId,i,s]),g.jsxs(S1,{children:[g.jsxs(k1,{children:[i[r.profileId]?g.jsx(bd,{src:i[r.profileId]}):g.jsx(bd,{src:Qt}),g.jsx(Wp,{$online:r.online})]}),g.jsx(E1,{children:r.username})]})}function A1(){const r=Dt(c=>c.users),i=Dt(c=>c.fetchUsers),s=ut(c=>c.currentUserId);ue.useEffect(()=>{i()},[i]);const u=[...r].sort((c,d)=>c.id===s?-1:d.id===s?1:c.online&&!d.online?-1:!c.online&&d.online?1:c.username.localeCompare(d.username));return g.jsxs(w1,{children:[g.jsxs(x1,{children:["멤버 목록 - ",r.length]}),u.map(c=>g.jsx(C1,{member:c},c.id))]})}const qp=N.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +`,bp=N.div` + background: ${J.colors.background.primary}; + padding: 32px; + border-radius: 8px; + width: 440px; + + h2 { + color: ${J.colors.text.primary}; + margin-bottom: 24px; + font-size: 24px; + font-weight: bold; + } + + form { + display: flex; + flex-direction: column; + gap: 16px; + } +`,xo=N.input` + width: 100%; + padding: 10px; + border-radius: 4px; + background: ${J.colors.background.input}; + border: none; + color: ${J.colors.text.primary}; + font-size: 16px; + + &::placeholder { + color: ${J.colors.text.muted}; + } + + &:focus { + outline: none; + } +`,Gp=N.button` + width: 100%; + padding: 12px; + border-radius: 4px; + background: ${J.colors.brand.primary}; + color: white; + font-size: 16px; + font-weight: 500; + border: none; + cursor: pointer; + transition: background-color 0.2s; + + &:hover { + background: ${J.colors.brand.hover}; + } +`,Yp=N.div` + color: ${J.colors.status.error}; + font-size: 14px; + text-align: center; +`,R1=N.p` + text-align: center; + margin-top: 16px; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 14px; +`,P1=N.span` + color: ${({theme:r})=>r.colors.brand.primary}; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +`;function j1({isOpen:r,onClose:i}){const[s,u]=ue.useState(""),[c,d]=ue.useState(""),[p,m]=ue.useState(""),[v,x]=ue.useState(null),[E,j]=ue.useState(null),[O,P]=ue.useState(""),I=ut(V=>V.setCurrentUser),R=V=>{const F=V.target.files[0];if(F){x(F);const W=new FileReader;W.onloadend=()=>{j(W.result)},W.readAsDataURL(F)}},L=async V=>{V.preventDefault(),P("");try{const F=new FormData;F.append("userCreateRequest",new Blob([JSON.stringify({email:s,username:c,password:p})],{type:"application/json"})),v&&F.append("profile",v);const W=await he.post(`${Xe.apiBaseUrl}/users`,F);I(W.data),i()}catch{P("회원가입에 실패했습니다.")}};return r?g.jsx(qp,{children:g.jsxs(bp,{children:[g.jsx("h2",{children:"계정 만들기"}),g.jsxs("form",{onSubmit:L,children:[g.jsxs(Fi,{children:[g.jsxs(Bi,{children:["이메일 ",g.jsx(Eu,{children:"*"})]}),g.jsx(xo,{type:"email",value:s,onChange:V=>u(V.target.value),required:!0})]}),g.jsxs(Fi,{children:[g.jsxs(Bi,{children:["사용자명 ",g.jsx(Eu,{children:"*"})]}),g.jsx(xo,{type:"text",value:c,onChange:V=>d(V.target.value),required:!0})]}),g.jsxs(Fi,{children:[g.jsxs(Bi,{children:["비밀번호 ",g.jsx(Eu,{children:"*"})]}),g.jsx(xo,{type:"password",value:p,onChange:V=>m(V.target.value),required:!0})]}),g.jsxs(Fi,{children:[g.jsx(Bi,{children:"프로필 이미지"}),g.jsxs(I1,{children:[g.jsx(_1,{src:E||Qt,alt:"profile"}),g.jsx(N1,{type:"file",accept:"image/*",onChange:R,id:"profile-image"}),g.jsx(O1,{htmlFor:"profile-image",children:"이미지 변경"})]})]}),O&&g.jsx(Yp,{children:O}),g.jsx(Gp,{type:"submit",children:"계속하기"}),g.jsx(L1,{onClick:i,children:"이미 계정이 있으신가요?"})]})]})}):null}const Fi=N.div` + margin-bottom: 20px; +`,Bi=N.label` + display: block; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 12px; + font-weight: 700; + margin-bottom: 8px; +`,Eu=N.span` + color: ${({theme:r})=>r.colors.status.error}; +`,I1=N.div` + display: flex; + flex-direction: column; + align-items: center; + margin: 10px 0; +`,_1=N.img` + width: 80px; + height: 80px; + border-radius: 50%; + margin-bottom: 10px; + object-fit: cover; +`,N1=N.input` + display: none; +`,O1=N.label` + color: ${({theme:r})=>r.colors.brand.primary}; + cursor: pointer; + font-size: 14px; + + &:hover { + text-decoration: underline; + } +`;N.p` + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 12px; + margin-top: 16px; + text-align: center; +`;const T1=N.span` + color: ${({theme:r})=>r.colors.brand.primary}; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +`,L1=N(T1)` + display: block; + text-align: center; + margin-top: 16px; +`,D1=({isOpen:r,onClose:i})=>{const[s,u]=ue.useState(""),[c,d]=ue.useState(""),[p,m]=ue.useState(""),[v,x]=ue.useState(!1),E=ut(P=>P.setCurrentUser),{fetchUsers:j}=Dt(),O=async()=>{var P;try{const I=await he.post(`${Xe.apiBaseUrl}/auth/login`,{username:s,password:c});I.status===200&&(await j(),E(I.data),m(""),i())}catch(I){console.error("로그인 에러:",I),((P=I.response)==null?void 0:P.status)===401?m("아이디 또는 비밀번호가 올바르지 않습니다."):m("로그인에 실패했습니다.")}};return r?g.jsxs(g.Fragment,{children:[g.jsx(qp,{children:g.jsxs(bp,{children:[g.jsx("h2",{children:"돌아오신 것을 환영해요!"}),g.jsxs("form",{onSubmit:P=>{P.preventDefault(),O()},children:[g.jsx(xo,{type:"text",placeholder:"사용자 이름",value:s,onChange:P=>u(P.target.value)}),g.jsx(xo,{type:"password",placeholder:"비밀번호",value:c,onChange:P=>d(P.target.value)}),p&&g.jsx(Yp,{children:p}),g.jsx(Gp,{type:"submit",children:"로그인"})]}),g.jsxs(R1,{children:["계정이 필요한가요? ",g.jsx(P1,{onClick:()=>x(!0),children:"가입하기"})]})]})}),g.jsx(j1,{isOpen:v,onClose:()=>x(!1)})]}):null};function z1(){const r=ut(v=>v.currentUserId),i=Dt(v=>v.users),{fetchUsers:s,updateUserStatus:u}=Dt(),[c,d]=ue.useState(null),p=Bn(v=>v.channels),m=r?i.find(v=>v.id===r):null;return ue.useEffect(()=>{let v;if(r){s(),u(r),v=setInterval(()=>{u(r)},3e4);const x=setInterval(()=>{s()},6e4);return()=>{clearInterval(v),clearInterval(x)}}},[r,s,u]),g.jsx(ty,{theme:J,children:m?g.jsxs(M1,{children:[g.jsx(_v,{channels:p,currentUser:m,activeChannel:c,onChannelSelect:d}),g.jsx(v1,{channel:c}),g.jsx(A1,{})]}):g.jsx(D1,{isOpen:!0,onClose:()=>{}})})}const M1=N.div` + display: flex; + height: 100vh; + width: 100vw; + position: relative; +`;eg.createRoot(document.getElementById("root")).render(g.jsx(ue.StrictMode,{children:g.jsx(z1,{})})); diff --git a/src/main/resources/static/assets/index-kQJbKSsj.css b/src/main/resources/static/assets/index-kQJbKSsj.css new file mode 100644 index 000000000..096eb4112 --- /dev/null +++ b/src/main/resources/static/assets/index-kQJbKSsj.css @@ -0,0 +1 @@ +:root{font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}h1{font-size:3.2em;line-height:1.1}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}} diff --git a/src/main/resources/static/favicon.ico b/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..479bed6a3da0a8dbdd08a51d81b30e4d4fabae89 GIT binary patch literal 1588 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyacIC_6YK2V5m}KU}$JzVE6?TYIwoGP-?)y@G60U!Dv>Mu*Du8ycRt4Yw>0&$ytddU zdTHwA$vlU)7;*ZQn^d>r9eiw}SEV3v&DP3PpZVm?c2D=&D? zJg+7dT;x9cg;(mDqrovi2QemjySudY+_R1aaySb-B8!2p69!>MhFNnYfC{QST^vI! zPM@6=9?WDY()wLtM|S>=KoQ44K~Zk4us5=<8xs!eeY>~&=ly4!jD%AXj+wvro>aU~ zrMO$=?`j4U&ZyW$Je*!Zo0>H2RZVqmn^V&mZ(9Dkv!~|IuDF1RBN|EPJE zX3ok)rzF<3&vZKWEj4ag73&t}uJvVk^<~M;*V0n54#8@&v!WGjE_hAaeAZEF z$~V4aF>{^dUc7o%=f8f9m%*2vzjfI@vJ2Z97)VU5x-s2*r@e{H>FEn3A3Dr3G&8U| z)>wFiQO&|Yl6}UkXAQ>%q$jNWac-tTL*)AEyto|onkmnmcJLf?71w_<>4WODmBMxF zwGM7``txcQgT`x>(tH-DrT2Kg=4LzpNv>|+a@TgYDZ`5^$KJVb`K=%k^tRpoxP|4? zwXb!O5~dXYKYt*j(YSx+#_rP{TNcK=40T|)+k3s|?t||EQTgwGgs{E0Y+(QPL&Wx4 zMP23By&sn`zn7oCQQLp%-(Axm|M=5-u;TlFiTn5B^PWnb%fAPV8r2flh?11Vl2ohY zqEsNoU}Ruqple{LYiJr`U}|M-Vr62aZD3$!V6dZTmJ5o8-29Zxv`X9>PU+TH>UWRL)v7?M$%n`C9>lAm0fo0?Z*WfcHaTFhX${Qqu! zG&Nv5t*kOqGt)Cl7z{0q_!){?fojB&%z>&2&rB)F04ce=Mv()kL=s7fZ)R?4No7GQ z1K3si1$pWAo5K9i%<&BYs$wuSHMcY{Gc&O;(${(hEL0izk<1CstV(4taB`Zm$nFhL zDhx>~G{}=7Ei)$-=zaa%ypo*!bp5o%vdrZCykdPs#ORw@rkW)uCz=~4Cz={1nkQNs oC7PHSBpVtgnwc6|q*&+yb?5=zccWrGsMu%lboFyt=akR{0N~++#sB~S literal 0 HcmV?d00001 diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 000000000..66e849757 --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,26 @@ + + + + + + Discodeit + + + + + +
+ + diff --git a/src/main/resources/static/script.js b/src/main/resources/static/script.js deleted file mode 100644 index 241352cb5..000000000 --- a/src/main/resources/static/script.js +++ /dev/null @@ -1,72 +0,0 @@ -// API endpoints -const API_BASE_URL = '/api'; -const ENDPOINTS = { - USERS: `${API_BASE_URL}/user/findAll`, - BINARY_CONTENT: `${API_BASE_URL}/binaryContent/find` -}; - -// Initialize the application -document.addEventListener('DOMContentLoaded', () => { - fetchAndRenderUsers(); -}); - -// Fetch users from the API -async function fetchAndRenderUsers() { - try { - const response = await fetch(ENDPOINTS.USERS); - if (!response.ok) { - throw new Error('Failed to fetch users'); - } - const users = await response.json(); - renderUserList(users); - } catch (error) { - console.error('Error fetching users:', error); - } -} - -// Fetch user profile image -async function fetchUserProfile(profileId) { - try { - const response = await fetch( - `${ENDPOINTS.BINARY_CONTENT}?binaryContentId=${profileId}`); - if (!response.ok) { - throw new Error('Failed to fetch profile'); - } - const profile = await response.json(); - - // Convert base64 encoded bytes to data URL - return `data:${profile.contentType};base64,${profile.bytes}`; - } catch (error) { - console.error('Error fetching profile:', error); - return '/default-avatar.png'; // Fallback to default avatar - } -} - -// Render user list -async function renderUserList(users) { - const userListElement = document.getElementById('userList'); - userListElement.innerHTML = ''; // Clear existing content - - for (const user of users) { - const userElement = document.createElement('div'); - userElement.className = 'user-item'; - - // Get profile image URL - const profileUrl = user.profileId ? - await fetchUserProfile(user.profileId) : - '/default-avatar.png'; - - userElement.innerHTML = ` - ${user.username} - -
- ${user.online ? '온라인' : '오프라인'} -
- `; - - userListElement.appendChild(userElement); - } -} \ No newline at end of file diff --git a/src/main/resources/static/styles.css b/src/main/resources/static/styles.css deleted file mode 100644 index 14f6172a5..000000000 --- a/src/main/resources/static/styles.css +++ /dev/null @@ -1,80 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: Arial, sans-serif; - background-color: #f5f5f5; -} - -.container { - max-width: 800px; - margin: 0 auto; - padding: 20px; -} - -h1 { - text-align: center; - margin-bottom: 30px; - color: #333; -} - -.user-list { - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -.user-item { - display: flex; - align-items: center; - padding: 20px; - border-bottom: 1px solid #eee; -} - -.user-item:last-child { - border-bottom: none; -} - -.user-avatar { - width: 60px; - height: 60px; - border-radius: 50%; - margin-right: 20px; - object-fit: cover; -} - -.user-info { - flex-grow: 1; -} - -.user-name { - font-size: 18px; - font-weight: bold; - color: #333; - margin-bottom: 5px; -} - -.user-email { - font-size: 14px; - color: #666; -} - -.status-badge { - padding: 6px 12px; - border-radius: 20px; - font-size: 14px; - font-weight: bold; -} - -.online { - background-color: #4CAF50; - color: white; -} - -.offline { - background-color: #9e9e9e; - color: white; -} \ No newline at end of file diff --git a/src/main/resources/static/user-list.html b/src/main/resources/static/user-list.html deleted file mode 100644 index 622884144..000000000 --- a/src/main/resources/static/user-list.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - 사용자 목록 - - - -
-

사용자 목록

-
- -
-
- - - \ No newline at end of file From 60ee132a9f9a9c5d865d5fbd020f307f9011614b Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Tue, 24 Feb 2026 16:10:55 +0900 Subject: [PATCH 18/48] =?UTF-8?q?refactor:=20mapper=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95,=20import?= =?UTF-8?q?=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../binarycontent/mapper/BinaryContentMapper.java | 2 +- .../binarycontent/service/BinaryContentService.java | 8 ++++---- .../channel/controller/ChannelController.java | 2 +- .../discodeit/channel/mapper/ChannelMapper.java | 4 ++-- .../channel/service/BasicChannelService.java | 2 +- .../discodeit/channel/service/ChannelService.java | 2 +- .../discodeit/message/mapper/MessageMapper.java | 6 +++--- .../message/service/BasicMessageService.java | 8 ++++---- .../readstatus/mapper/ReadStatusMapper.java | 4 ++-- .../readstatus/service/ReadStatusService.java | 12 ++++++------ 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java index 11f8cec92..f6370931c 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java @@ -8,7 +8,7 @@ public class BinaryContentMapper { private BinaryContentMapper() { } - public static BinaryContentDto toBinaryContentInfo(BinaryContent binaryContent) { + public static BinaryContentDto toBinaryContentDto(BinaryContent binaryContent) { return new BinaryContentDto( binaryContent.getId(), binaryContent.getCreatedAt(), diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index 26268787d..17f6e5e16 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -24,13 +24,13 @@ public BinaryContentDto createBinaryContent(BinaryContentCreateRequest contentIn contentInfo.contentType(), bytes); contentRepository.save(content); - return BinaryContentMapper.toBinaryContentInfo(content); + return BinaryContentMapper.toBinaryContentDto(content); } public BinaryContentDto findBinaryContent(UUID contentId) { BinaryContent content = contentRepository.findById(contentId) .orElseThrow(BinaryContentNotFoundException::new); - return BinaryContentMapper.toBinaryContentInfo(content); + return BinaryContentMapper.toBinaryContentDto(content); } public BinaryContent findBinaryContentEntity(UUID contentId) { @@ -41,7 +41,7 @@ public BinaryContent findBinaryContentEntity(UUID contentId) { public List findAll() { return contentRepository.findAll() .stream() - .map(BinaryContentMapper::toBinaryContentInfo) + .map(BinaryContentMapper::toBinaryContentDto) .toList(); } @@ -49,7 +49,7 @@ public List findAllByIdIn(BinaryContentsRequest request) { return contentRepository.findAll() .stream() .filter(content -> request.ids().contains(content.getId())) - .map(BinaryContentMapper::toBinaryContentInfo) + .map(BinaryContentMapper::toBinaryContentDto) .toList(); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index 5e59cd644..25c831ca1 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -1,10 +1,10 @@ package com.sprint.mission.discodeit.channel.controller; import com.sprint.mission.discodeit.channel.dto.ChannelDto; +import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; -import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.service.ChannelService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index 22aa1a078..af0f9b9a6 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -55,7 +55,7 @@ public static ChannelResultDto toChannelResultDto(Channel channel) { } } - public static PrivateChannelCreateRequest toPrivateChannelCreateInfo(Channel channel) { + public static PrivateChannelCreateRequest toPrivateChannelCreateRequest(Channel channel) { return new PrivateChannelCreateRequest(channel.getUserIds()); } @@ -67,7 +67,7 @@ public static Channel toChannel(ChannelDto channelDto) { ); } - public static Channel toChannel(PrivateChannelCreateRequest channelInfo) { + public static Channel toChannel(PrivateChannelCreateRequest request) { return new Channel( null, ChannelType.PRIVATE, diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index f8da94066..d3b05cc1b 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -1,10 +1,10 @@ package com.sprint.mission.discodeit.channel.service; import com.sprint.mission.discodeit.channel.dto.ChannelDto; +import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; -import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelDuplicationException; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java index bb9acc185..6d26a823e 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java @@ -1,10 +1,10 @@ package com.sprint.mission.discodeit.channel.service; import com.sprint.mission.discodeit.channel.dto.ChannelDto; +import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; -import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import java.util.List; import java.util.UUID; diff --git a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java index cc433a05f..a5d803a83 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java @@ -12,7 +12,7 @@ public final class MessageMapper { private MessageMapper() { } - public static MessageDto toMessageInfo(Message message) { + public static MessageDto toMessageDto(Message message) { return new MessageDto( message.getId(), message.getCreatedAt(), @@ -24,7 +24,7 @@ public static MessageDto toMessageInfo(Message message) { ); } - public static MessageCreateRequest toMessageCreateInfo( + public static MessageCreateRequest toMessageCreateRequest( String content, UUID authorId, UUID channelId, @@ -37,7 +37,7 @@ public static MessageCreateRequest toMessageCreateInfo( ); } - public static MessageUpdateRequest toMessageUpdateInfo( + public static MessageUpdateRequest toMessageUpdateRequest( String content ) { return new MessageUpdateRequest( diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index b71372c00..2c3aaf5dc 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -62,7 +62,7 @@ public MessageDto createMessage(MessageCreateRequest createInfo, userRepository.save(author); channelRepository.save(findChannel); messageRepository.save(message); - return MessageMapper.toMessageInfo(message); + return MessageMapper.toMessageDto(message); } @Override @@ -70,7 +70,7 @@ public MessageDto findMessage(UUID messageId) { Message message = messageRepository.findById(messageId) .orElseThrow(MessageNotFoundException::new); - return MessageMapper.toMessageInfo(message); + return MessageMapper.toMessageDto(message); } @Override @@ -97,7 +97,7 @@ public MessageDto updateMessage(UUID messageId, MessageUpdateRequest messageInfo .ifPresent(findMessage::update); messageRepository.save(findMessage); - return MessageMapper.toMessageInfo(findMessage); + return MessageMapper.toMessageDto(findMessage); } @Override @@ -122,7 +122,7 @@ public void deleteMessage(UUID messageId) { private List toMessageInfoList(List messages) { return messages.stream() - .map(MessageMapper::toMessageInfo) + .map(MessageMapper::toMessageDto) .toList(); } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java index 0939a2d63..c7f3cee5d 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java @@ -9,7 +9,7 @@ public class ReadStatusMapper { private ReadStatusMapper() { } - public static ReadStatusDto toReadStatusInfo(ReadStatus readStatus) { + public static ReadStatusDto toReadStatusDto(ReadStatus readStatus) { return new ReadStatusDto( readStatus.getId(), readStatus.getCreatedAt(), @@ -20,7 +20,7 @@ public static ReadStatusDto toReadStatusInfo(ReadStatus readStatus) { ); } - public static ReadStatusCreateRequest toReadStatusCreateInfo(ReadStatus readStatus) { + public static ReadStatusCreateRequest toReadStatusCreateRequest(ReadStatus readStatus) { return new ReadStatusCreateRequest( readStatus.getUserId(), readStatus.getChannelId(), diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index c7687075d..b29827b7a 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -40,26 +40,26 @@ public ReadStatusDto createReadStatus(ReadStatusCreateRequest statusInfo) { ReadStatus readStatus = new ReadStatus(channel.getId(), user.getId()); readStatusRepository.save(readStatus); - return ReadStatusMapper.toReadStatusInfo(readStatus); + return ReadStatusMapper.toReadStatusDto(readStatus); } public ReadStatusDto find(UUID statusId) { ReadStatus readStatus = readStatusRepository.findById(statusId) .orElseThrow(ReadStatusNotFoundException::new); - return ReadStatusMapper.toReadStatusInfo(readStatus); + return ReadStatusMapper.toReadStatusDto(readStatus); } public List findAllByUserId(UUID userId) { return readStatusRepository.findAllByUserId(userId) .stream() - .map(ReadStatusMapper::toReadStatusInfo) + .map(ReadStatusMapper::toReadStatusDto) .toList(); } public List findAll() { return readStatusRepository.findAll() .stream() - .map(ReadStatusMapper::toReadStatusInfo) + .map(ReadStatusMapper::toReadStatusDto) .toList(); } @@ -68,7 +68,7 @@ public ReadStatusDto updateReadStatus(UUID statusId) { .orElseThrow(ReadStatusNotFoundException::new); readStatus.updateLastReadAt(); readStatusRepository.save(readStatus); - return ReadStatusMapper.toReadStatusInfo(readStatus); + return ReadStatusMapper.toReadStatusDto(readStatus); } public ReadStatusDto updateReadStatus(UUID readStatusId, ReadStatusUpdateRequest request) { @@ -76,7 +76,7 @@ public ReadStatusDto updateReadStatus(UUID readStatusId, ReadStatusUpdateRequest .orElseThrow(ReadStatusNotFoundException::new); readStatus.update(request.newLastReadAt()); readStatusRepository.save(readStatus); - return ReadStatusMapper.toReadStatusInfo(readStatus); + return ReadStatusMapper.toReadStatusDto(readStatus); } public void deleteReadStatus(UUID statusId) { From 73c3028bc8b77945e5979503f29c75eabcf8fc6f Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Tue, 24 Feb 2026 17:06:41 +0900 Subject: [PATCH 19/48] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=8B=9C=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20NPE=20=EB=B0=9C=EC=83=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 --- .../mission/discodeit/user/controller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index a2f2cf822..d620d9adf 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -177,7 +177,7 @@ public ResponseEntity getUserStatus(@PathVariable UUID userId) { } private Optional resolveProfileFile(MultipartFile profileFile) { - if (profileFile.isEmpty()) { + if (profileFile == null || profileFile.isEmpty()) { return Optional.empty(); } else { try { From b678a4c8b1aa0bfdc2be3334517596c8ae63b856 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Tue, 24 Feb 2026 17:35:34 +0900 Subject: [PATCH 20/48] =?UTF-8?q?build:=20railway=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20plain.jar=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=B0=A9=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 433f0f649..682c11d7c 100644 --- a/build.gradle +++ b/build.gradle @@ -36,3 +36,7 @@ dependencies { tasks.named('test') { useJUnitPlatform() } + +jar { + enabled = false +} From bab7596a98af05d3c618aa923fab392369f3c4dc Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 09:07:59 +0900 Subject: [PATCH 21/48] =?UTF-8?q?feat:=20DB=20=EC=84=B8=ED=8C=85=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 --- build.gradle | 4 +++- src/main/resources/application.yaml | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 682c11d7c..ecd3d8998 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { group = 'com.sprint.mission' version = '0.0.1-SNAPSHOT' -description = 'Sprint Mission 3' +description = 'Sprint Mission 6' java { toolchain { @@ -27,6 +27,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.14' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.postgresql:postgresql:42.7.3' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 8eacd95a3..094fa42e4 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,6 +1,18 @@ spring: application: name: discodeit + datasource: + url: jdbc:postgresql://localhost:5432/discodeit + username: discodeit_user + password: discodeit1234 + driver-class-name: org.postgresql.Driver + jpa: + hibernate: + ddl-auto: none + show-sql: true + properties: + hibernate: + format_sql: true springdoc: api-docs: From e24d8f756f8b91e0268144b9a8e5f0d83e4b7786 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 09:19:00 +0900 Subject: [PATCH 22/48] =?UTF-8?q?fix:=20ReadStatus=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=20id=EA=B0=80=20=EB=B0=94=EB=80=8C=EC=96=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=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 --- .../mission/discodeit/readstatus/service/ReadStatusService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index b29827b7a..4d5566900 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -38,7 +38,7 @@ public ReadStatusDto createReadStatus(ReadStatusCreateRequest statusInfo) { throw new ReadStatusDuplicationException(); } - ReadStatus readStatus = new ReadStatus(channel.getId(), user.getId()); + ReadStatus readStatus = new ReadStatus(user.getId(), channel.getId()); readStatusRepository.save(readStatus); return ReadStatusMapper.toReadStatusDto(readStatus); } From d0e6a1fcb3c806d0db9278c9cd651696a21d03f4 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 09:22:54 +0900 Subject: [PATCH 23/48] =?UTF-8?q?fix:=20MessageController=EC=9D=98=20?= =?UTF-8?q?=EC=B2=A8=EB=B6=80=ED=8C=8C=EC=9D=BC=20=EC=98=88=EC=99=B8=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 --- .../discodeit/message/controller/MessageController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index 872ec0624..6a0d6ab46 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -1,6 +1,7 @@ package com.sprint.mission.discodeit.message.controller; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; +import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentFileProcessingException; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageDto; @@ -76,7 +77,7 @@ public ResponseEntity create_2( file.getBytes() ); } catch (IOException e) { - throw new BinaryContentNotFoundException(); + throw new BinaryContentFileProcessingException(); } }) .toList()) From bf1236d21fa9242b86171481bfb62ab167b62aec Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 09:37:26 +0900 Subject: [PATCH 24/48] =?UTF-8?q?fix:=20=EC=A4=91=EB=B3=B5=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=EC=97=90=20null=20=EC=B2=B4=ED=81=AC=20=EB=A1=9C?= =?UTF-8?q?=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 --- .../discodeit/channel/service/BasicChannelService.java | 3 ++- .../mission/discodeit/user/service/BasicUserService.java | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index d3b05cc1b..4fc74f839 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -90,7 +90,8 @@ public ChannelResultDto updateChannel(UUID channelId, if (findChannel.getType() == ChannelType.PRIVATE) { throw new ChannelUpdateNotAllowedException(); } - validateChannelExist(channelInfo.newName()); + Optional.ofNullable(channelInfo.newName()) + .ifPresent(this::validateChannelExist); Optional.ofNullable(channelInfo.newName()) .ifPresent(findChannel::updateChannelName); diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index 2343004ed..09515de7c 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -103,8 +103,10 @@ public List findAllByChannelId(UUID channelId) { @Override public UserResultDto updateUser(UUID userId, UserUpdateRequest request, Optional image) { - validateUserExist(request.newUsername()); - validateEmailExist(request.newEmail()); + Optional.ofNullable(request.newUsername()) + .ifPresent(this::validateUserExist); + Optional.ofNullable(request.newEmail()) + .ifPresent(this::validateEmailExist); User findUser = userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); Optional.ofNullable(request.newUsername()) From ef2bb49cd7bbd093a00dab8fb83d721504d653b6 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 09:59:54 +0900 Subject: [PATCH 25/48] =?UTF-8?q?refactor:=20@RequestMapping=EC=9D=84=20?= =?UTF-8?q?=EB=8B=A8=EC=B6=95=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=EC=9C=BC=EB=A1=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/BinaryContentController.java | 6 +++--- .../channel/controller/ChannelController.java | 17 ++++++++++------- .../message/controller/MessageController.java | 15 +++++++++------ .../controller/ReadStatusController.java | 16 +++++++++------- .../user/controller/AuthController.java | 4 ++-- .../user/controller/UserController.java | 19 +++++++++++-------- 6 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java index d080613e7..339710933 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java @@ -17,9 +17,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +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.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -48,7 +48,7 @@ public class BinaryContentController { ) ) }) - @RequestMapping(value = "/{binaryContentId}", method = RequestMethod.GET) + @GetMapping(value = "/{binaryContentId}") public ResponseEntity find( @Parameter(description = "조회할 첨부 파일 ID") @PathVariable UUID binaryContentId ) { @@ -65,7 +65,7 @@ public ResponseEntity find( ) ) }) - @RequestMapping(method = RequestMethod.GET) + @GetMapping public ResponseEntity> findAllByIdIn( @Parameter(description = "조회할 첨부 파일 ID 목록", array = @ArraySchema(schema = @Schema(implementation = UUID.class)) diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index 25c831ca1..c185949e0 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -20,10 +20,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +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.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -45,7 +48,7 @@ public class ChannelController { ) ) }) - @RequestMapping(value = "/public", method = RequestMethod.POST, consumes = "application/json") + @PostMapping(value = "/public", consumes = "application/json") public ResponseEntity create_3( @RequestBody PublicChannelCreateRequest channelInfo ) { @@ -62,14 +65,14 @@ public ResponseEntity create_3( ) ) }) - @RequestMapping(value = "/private", method = RequestMethod.POST, consumes = "application/json") + @PostMapping(value = "/private", consumes = "application/json") public ResponseEntity create_4( @RequestBody PrivateChannelCreateRequest channelInfo ) { return ResponseEntity.status(201).body(channelService.createPrivateChannel(channelInfo)); } - @RequestMapping(value = "/{channelId}", method = RequestMethod.GET) + @GetMapping(value = "/{channelId}") public ResponseEntity getChannel(@PathVariable UUID channelId) { return ResponseEntity.ok(channelService.findChannel(channelId)); } @@ -84,7 +87,7 @@ public ResponseEntity getChannel(@PathVariable UUID channelId) { ) ) }) - @RequestMapping(method = RequestMethod.GET) + @GetMapping public ResponseEntity> findAll_1( @Parameter(description = "조회할 User ID") @RequestParam UUID userId ) { @@ -115,7 +118,7 @@ public ResponseEntity> findAll_1( ) ) }) - @RequestMapping(value = "/{channelId}", method = RequestMethod.PATCH, consumes = "application/json") + @PatchMapping(value = "/{channelId}", consumes = "application/json") public ResponseEntity update_3( @Parameter(description = "수정할 Channel ID") @PathVariable UUID channelId, @RequestBody PublicChannelUpdateRequest channelInfo @@ -134,7 +137,7 @@ public ResponseEntity update_3( ), @ApiResponse(responseCode = "204", description = "Channel이 성공적으로 삭제됨") }) - @RequestMapping(value = "/{channelId}", method = RequestMethod.DELETE) + @DeleteMapping(value = "/{channelId}") public ResponseEntity delete_2( @Parameter(description = "삭제할 Channel ID") @PathVariable UUID channelId ) { diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index 6a0d6ab46..3ad6e0a05 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -24,10 +24,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +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.RequestMethod; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -63,7 +66,7 @@ public class MessageController { ) ) }) - @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity create_2( @RequestPart MessageCreateRequest messageCreateRequest, @Parameter(description = "Message 첨부 파일들") @RequestPart(required = false) List attachments) { @@ -87,12 +90,12 @@ public ResponseEntity create_2( ); } - @RequestMapping(value = "/{messageId}", method = RequestMethod.GET) + @GetMapping(value = "/{messageId}") public ResponseEntity getMessage(@PathVariable UUID messageId) { return ResponseEntity.ok(messageService.findMessage(messageId)); } - @RequestMapping(value = "/all", method = RequestMethod.GET) + @GetMapping(value = "/all") public ResponseEntity> getAllMessages() { return ResponseEntity.ok(messageService.findAll()); } @@ -114,7 +117,7 @@ public ResponseEntity> getAllMessages() { ) ) }) - @RequestMapping(value = "/{messageId}", method = RequestMethod.PATCH) + @PatchMapping(value = "/{messageId}") public ResponseEntity update_2( @Parameter(description = "수정할 Message ID") @PathVariable UUID messageId, @RequestBody MessageUpdateRequest messageInfo @@ -133,7 +136,7 @@ public ResponseEntity update_2( ) ) }) - @RequestMapping(value = "/{messageId}", method = RequestMethod.DELETE) + @DeleteMapping(value = "/{messageId}") public ResponseEntity delete_1( @Parameter(description = "삭제할 Message ID") @PathVariable UUID messageId ) { diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java index 36aae6658..325578c82 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/controller/ReadStatusController.java @@ -18,10 +18,12 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +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.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -51,13 +53,13 @@ public class ReadStatusController { schema = @Schema(implementation = ReadStatusDto.class)) ) }) - @RequestMapping(method = RequestMethod.POST, consumes = "application/json") + @PostMapping(consumes = "application/json") public ResponseEntity create_1( @RequestBody ReadStatusCreateRequest statusInfo) { return ResponseEntity.status(201).body(readStatusService.createReadStatus(statusInfo)); } - @RequestMapping(value = "/{statusId}/updated-at", method = RequestMethod.PATCH) + @PatchMapping(value = "/{statusId}/updated-at") public ResponseEntity updateReadStatus(@PathVariable UUID statusId) { readStatusService.updateReadStatus(statusId); return ResponseEntity.noContent().build(); @@ -78,7 +80,7 @@ public ResponseEntity updateReadStatus(@PathVariable UUID statusId) { ) ) }) - @RequestMapping(value = "/{readStatusId}", method = RequestMethod.PATCH, consumes = "application/json") + @PatchMapping(value = "/{readStatusId}", consumes = "application/json") public ResponseEntity update_1( @Parameter(description = "수정할 읽음 상태 ID") @PathVariable UUID readStatusId, @RequestBody ReadStatusUpdateRequest request) { @@ -93,18 +95,18 @@ public ResponseEntity update_1( array = @ArraySchema(schema = @Schema(implementation = ReadStatusDto.class))) ) }) - @RequestMapping(method = RequestMethod.GET) + @GetMapping public ResponseEntity> findAllByUserId( @Parameter(description = "조회할 User ID") @RequestParam UUID userId) { return ResponseEntity.ok(readStatusService.findAllByUserId(userId)); } - @RequestMapping(value = "/all", method = RequestMethod.GET) + @GetMapping(value = "/all") public ResponseEntity> getAllReadStatuses() { return ResponseEntity.ok(readStatusService.findAll()); } - @RequestMapping(value = "/{statusId}", method = RequestMethod.GET) + @GetMapping(value = "/{statusId}") public ResponseEntity getReadStatus(@PathVariable UUID statusId) { return ResponseEntity.ok(readStatusService.find(statusId)); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java index 1a623fc66..b2703956d 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java @@ -13,9 +13,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +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.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @@ -47,7 +47,7 @@ public class AuthController { ) ) }) - @RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "application/json") + @PostMapping(value = "/login", consumes = "application/json") public ResponseEntity login(@RequestBody LoginRequest loginInfo) { return ResponseEntity.ok(authService.login(loginInfo)); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index d620d9adf..8db3afec1 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -28,10 +28,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +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.RequestMethod; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -45,7 +48,7 @@ public class UserController { private final UserService userService; private final UserStatusService userStatusService; - @RequestMapping(value = "/{userId}", method = RequestMethod.GET) + @GetMapping(value = "/{userId}") public ResponseEntity getUser(@PathVariable UUID userId) { return ResponseEntity.ok(userService.findUser(userId)); } @@ -59,7 +62,7 @@ public ResponseEntity getUser(@PathVariable UUID userId) { ) ) }) - @RequestMapping(method = RequestMethod.GET) + @GetMapping public ResponseEntity> findAll() { return ResponseEntity.ok(userService.findAll()); } @@ -85,7 +88,7 @@ public ResponseEntity> findAll() { ) ) }) - @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity create( @Parameter() @RequestPart UserCreateRequest userCreateRequest, @Parameter(description = "User 프로필 이미지") @RequestPart(required = false) MultipartFile profile @@ -102,7 +105,7 @@ public ResponseEntity create( content = @Content(examples = @ExampleObject("User with id {id} not found")) ) }) - @RequestMapping(value = "/{userId}", method = RequestMethod.DELETE) + @DeleteMapping(value = "/{userId}") public ResponseEntity delete( @Parameter(description = "삭제할 User ID") @PathVariable UUID userId ) { @@ -137,7 +140,7 @@ public ResponseEntity delete( ) ) }) - @RequestMapping(value = "/{userId}", method = RequestMethod.PATCH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PatchMapping(value = "/{userId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity update( @Parameter(description = "수정할 User ID") @PathVariable UUID userId, @RequestPart UserUpdateRequest userUpdateRequest, @@ -163,7 +166,7 @@ public ResponseEntity update( ) ) }) - @RequestMapping(value = "/{userId}/userStatus", method = RequestMethod.PATCH) + @PatchMapping(value = "/{userId}/userStatus") public ResponseEntity updateUserStatusByUserId( @Parameter(description = "상태를 변경할 User ID") @PathVariable UUID userId, @RequestBody UserStatusUpdateRequest request @@ -171,7 +174,7 @@ public ResponseEntity updateUserStatusByUserId( return ResponseEntity.ok(userStatusService.update(userId, request)); } - @RequestMapping(value = "/{userId}/userStatus", method = RequestMethod.GET) + @GetMapping(value = "/{userId}/userStatus") public ResponseEntity getUserStatus(@PathVariable UUID userId) { return ResponseEntity.ok(userStatusService.findUserStatusByUserId(userId)); } From 0cc6c46c795d9f6cc82eff0b61f18c638d2c67bd Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 13:28:48 +0900 Subject: [PATCH 26/48] =?UTF-8?q?feat:=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=8A=A4=ED=82=A4=EB=A7=88=20=ED=8C=8C=EC=9D=BC=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 --- src/main/resources/schema.sql | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/resources/schema.sql diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 000000000..0e30d670c --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,70 @@ +CREATE TABLE users +( + id UUID PRIMARY KEY, + created_at timestamptz NOT NULL, + updated_at timestamptz, + username varchar(50) unique NOT NULL, + email varchar(100) unique NOT NULL, + password varchar(60) NOT NULL, + profile_id UUID unique +); +CREATE TABLE channels +( + id UUID PRIMARY KEY, + created_at timestamptz NOT NULL, + updated_at timestamptz, + name varchar(100), + description varchar(500), + type varchar(10) check (type in ('PUBLIC', 'PRIVATE')) +); +CREATE TABLE messages +( + id UUID PRIMARY KEY, + created_at timestamptz NOT NULL, + updated_at timestamptz, + content TEXT, + channel_id UUID NOT NULL, + author_id UUID, + FOREIGN KEY (channel_id) REFERENCES channels (id) ON DELETE CASCADE, + FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE SET NULL +); +CREATE TABLE user_statuses +( + id UUID PRIMARY KEY, + created_at timestamptz NOT NULL, + updated_at timestamptz, + user_id UUID unique NOT NULL, + last_active_at timestamptz NOT NULL, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE +); +CREATE TABLE read_statuses +( + id UUID PRIMARY KEY, + created_at timestamptz NOT NULL, + updated_at timestamptz, + user_id UUID NOT NULL, + channel_id UUID NOT NULL, + last_read_at timestamptz NOT NULL, + UNIQUE (user_id, channel_id), + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, + FOREIGN KEY (channel_id) REFERENCES channels (id) ON DELETE CASCADE +); +CREATE TABLE binary_contents +( + id UUID PRIMARY KEY, + created_at timestamptz NOT NULL, + filename varchar(255) NOT NULL, + size bigint NOT NULL, + content_type varchar(100) NOT NULL, + bytes bytea NOT NULL +); +CREATE TABLE message_attachments +( + message_id UUID NOT NULL, + attachment_id UUID NOT NULL, + FOREIGN KEY (message_id) REFERENCES messages (id) ON DELETE CASCADE, + FOREIGN KEY (attachment_id) REFERENCES binary_contents (id) ON DELETE CASCADE +); +ALTER TABLE users + ADD CONSTRAINT fk_users_binary_contents FOREIGN KEY (profile_id) + REFERENCES binary_contents (id) ON DELETE SET NULL; \ No newline at end of file From 3e164f423f9c09c198aaab897ba321a95a4c436b Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 14:06:39 +0900 Subject: [PATCH 27/48] =?UTF-8?q?refactor:=20BaseEntity,=20BaseUpdatableEn?= =?UTF-8?q?tity=20=EA=B5=AC=EC=A1=B0=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 --- .../BaseEntity.java} | 12 +++---- .../discodeit/base/BaseUpdatableEntity.java | 33 +++++++++++++++++++ .../binarycontent/entity/BinaryContent.java | 10 ++---- .../discodeit/channel/dto/ChannelDto.java | 2 +- .../channel/dto/ChannelResultDto.java | 2 +- .../discodeit/channel/entity/Channel.java | 6 ++-- .../entity}/ChannelType.java | 2 +- .../channel/mapper/ChannelMapper.java | 2 +- .../repository/FileChannelRepository.java | 2 +- .../repository/JCFChannelRepository.java | 2 +- .../channel/service/BasicChannelService.java | 2 +- .../discodeit/message/entity/Message.java | 5 ++- .../readstatus/entity/ReadStatus.java | 5 ++- .../mission/discodeit/user/entity/User.java | 5 ++- .../userstatus/entity/UserStatus.java | 5 ++- 15 files changed, 56 insertions(+), 39 deletions(-) rename src/main/java/com/sprint/mission/discodeit/{common/CommonEntity.java => base/BaseEntity.java} (60%) create mode 100644 src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java rename src/main/java/com/sprint/mission/discodeit/{common => channel/entity}/ChannelType.java (89%) diff --git a/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java b/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java similarity index 60% rename from src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java rename to src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java index 0c51aa6f4..356ed7f2a 100644 --- a/src/main/java/com/sprint/mission/discodeit/common/CommonEntity.java +++ b/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java @@ -1,22 +1,18 @@ -package com.sprint.mission.discodeit.common; +package com.sprint.mission.discodeit.base; -import java.io.Serializable; import java.time.Instant; import java.util.UUID; import lombok.Getter; @Getter -public abstract class CommonEntity implements Serializable { +public abstract class BaseEntity { - private static final long serialVersionUID = 1L; protected final UUID id; protected Instant createdAt; - protected Instant updatedAt; - public CommonEntity() { + public BaseEntity() { this.id = UUID.randomUUID(); this.createdAt = Instant.now(); - this.updatedAt = this.createdAt; } @Override @@ -30,7 +26,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - return id.equals(((CommonEntity) obj).id); + return id.equals(((BaseEntity) obj).id); } @Override diff --git a/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java b/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java new file mode 100644 index 000000000..55d1508af --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java @@ -0,0 +1,33 @@ +package com.sprint.mission.discodeit.base; + +import java.time.Instant; +import lombok.Getter; + +@Getter +public abstract class BaseUpdatableEntity extends BaseEntity { + + protected Instant updatedAt; + + public BaseUpdatableEntity() { + this.updatedAt = this.createdAt; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return id.equals(((BaseUpdatableEntity) obj).id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java index f750eb3b3..b996a9fd1 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java @@ -1,25 +1,19 @@ package com.sprint.mission.discodeit.binarycontent.entity; +import com.sprint.mission.discodeit.base.BaseEntity; import java.io.Serializable; -import java.time.Instant; import java.util.Arrays; -import java.util.UUID; import lombok.Getter; @Getter -public class BinaryContent implements Serializable { +public class BinaryContent extends BaseEntity implements Serializable { - private static final long serialVersionUID = 1L; - private final UUID id; - private final Instant createdAt; private final String fileName; private final Long size; private final String contentType; private final byte[] bytes; public BinaryContent(String fileName, Long size, String contentType, byte[] bytes) { - this.id = UUID.randomUUID(); - this.createdAt = Instant.now(); this.fileName = fileName; this.size = size; this.contentType = contentType; diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java index dcf43e609..24c7bd88b 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.channel.dto; -import com.sprint.mission.discodeit.common.ChannelType; +import com.sprint.mission.discodeit.channel.entity.ChannelType; import java.time.Instant; import java.util.List; import java.util.UUID; diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java index 5c699d070..348663064 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.channel.dto; -import com.sprint.mission.discodeit.common.ChannelType; +import com.sprint.mission.discodeit.channel.entity.ChannelType; import java.time.Instant; import java.util.UUID; diff --git a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java index 4b55a91b6..a9e37806c 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java @@ -1,7 +1,6 @@ package com.sprint.mission.discodeit.channel.entity; -import com.sprint.mission.discodeit.common.ChannelType; -import com.sprint.mission.discodeit.common.CommonEntity; +import com.sprint.mission.discodeit.base.BaseUpdatableEntity; import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -9,9 +8,8 @@ import lombok.Getter; @Getter -public class Channel extends CommonEntity { +public class Channel extends BaseUpdatableEntity { - private static final long serialVersionUID = 1L; private final List messageIds = new ArrayList<>(); private final List userIds = new ArrayList<>(); private String name; diff --git a/src/main/java/com/sprint/mission/discodeit/common/ChannelType.java b/src/main/java/com/sprint/mission/discodeit/channel/entity/ChannelType.java similarity index 89% rename from src/main/java/com/sprint/mission/discodeit/common/ChannelType.java rename to src/main/java/com/sprint/mission/discodeit/channel/entity/ChannelType.java index 2a04204c7..6d150de16 100644 --- a/src/main/java/com/sprint/mission/discodeit/common/ChannelType.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/entity/ChannelType.java @@ -1,4 +1,4 @@ -package com.sprint.mission.discodeit.common; +package com.sprint.mission.discodeit.channel.entity; public enum ChannelType { PUBLIC(0), PRIVATE(1); diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index af0f9b9a6..5f539da05 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -4,7 +4,7 @@ import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.entity.Channel; -import com.sprint.mission.discodeit.common.ChannelType; +import com.sprint.mission.discodeit.channel.entity.ChannelType; import java.time.Instant; public final class ChannelMapper { diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java index 2b8b9711c..1c0015d48 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.channel.repository; import com.sprint.mission.discodeit.channel.entity.Channel; -import com.sprint.mission.discodeit.common.ChannelType; +import com.sprint.mission.discodeit.channel.entity.ChannelType; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java index e5024cfe2..283dae6d9 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.channel.repository; import com.sprint.mission.discodeit.channel.entity.Channel; -import com.sprint.mission.discodeit.common.ChannelType; +import com.sprint.mission.discodeit.channel.entity.ChannelType; import java.util.ArrayList; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 4fc74f839..63a50d4bb 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -11,7 +11,7 @@ import com.sprint.mission.discodeit.channel.exception.ChannelUpdateNotAllowedException; import com.sprint.mission.discodeit.channel.mapper.ChannelMapper; import com.sprint.mission.discodeit.channel.repository.ChannelRepository; -import com.sprint.mission.discodeit.common.ChannelType; +import com.sprint.mission.discodeit.channel.entity.ChannelType; import com.sprint.mission.discodeit.message.entity.Message; import com.sprint.mission.discodeit.message.repository.MessageRepository; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; diff --git a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java index 1c11227fd..1e0c62977 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java +++ b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java @@ -1,15 +1,14 @@ package com.sprint.mission.discodeit.message.entity; -import com.sprint.mission.discodeit.common.CommonEntity; +import com.sprint.mission.discodeit.base.BaseUpdatableEntity; import java.time.Instant; import java.util.List; import java.util.UUID; import lombok.Getter; @Getter -public class Message extends CommonEntity { +public class Message extends BaseUpdatableEntity { - private static final long serialVersionUID = 1L; private final UUID authorId; private final UUID channelId; private final List attachmentIds; diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java index 8ab1d1667..1cd17f744 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java @@ -1,14 +1,13 @@ package com.sprint.mission.discodeit.readstatus.entity; -import com.sprint.mission.discodeit.common.CommonEntity; +import com.sprint.mission.discodeit.base.BaseUpdatableEntity; import java.time.Instant; import java.util.UUID; import lombok.Getter; @Getter -public class ReadStatus extends CommonEntity { +public class ReadStatus extends BaseUpdatableEntity { - private static final long serialVersionUID = 1L; private final UUID userId; private final UUID channelId; private Instant lastReadAt; diff --git a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java index 1bfeee0c2..a7711ac14 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java +++ b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.user.entity; -import com.sprint.mission.discodeit.common.CommonEntity; +import com.sprint.mission.discodeit.base.BaseUpdatableEntity; import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -9,9 +9,8 @@ import lombok.Setter; @Getter -public class User extends CommonEntity { +public class User extends BaseUpdatableEntity { - private static final long serialVersionUID = 1L; private final List channelIds = new ArrayList<>(); private final List messageIds = new ArrayList<>(); private String username; diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java index fc46c8ef6..d9118e172 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java @@ -1,14 +1,13 @@ package com.sprint.mission.discodeit.userstatus.entity; -import com.sprint.mission.discodeit.common.CommonEntity; +import com.sprint.mission.discodeit.base.BaseUpdatableEntity; import java.time.Instant; import java.util.UUID; import lombok.Getter; @Getter -public class UserStatus extends CommonEntity { +public class UserStatus extends BaseUpdatableEntity { - private static final long serialVersionUID = 1L; private final UUID userId; private final int loginLimitSeconds = 60 * 5; private Instant lastActiveAt; From 7d0cbe5e2567fdd4b966351c4517a630ae135dde Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 15:15:34 +0900 Subject: [PATCH 28/48] =?UTF-8?q?refactor:=20=EC=83=9D=EC=84=B1,=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=8B=9C=EA=B0=84=EC=9D=84=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=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 --- .../java/com/sprint/mission/discodeit/base/BaseEntity.java | 3 ++- .../sprint/mission/discodeit/base/BaseUpdatableEntity.java | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java b/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java index 356ed7f2a..ca12feb3a 100644 --- a/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java +++ b/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java @@ -3,16 +3,17 @@ import java.time.Instant; import java.util.UUID; import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; @Getter public abstract class BaseEntity { protected final UUID id; + @CreatedDate protected Instant createdAt; public BaseEntity() { this.id = UUID.randomUUID(); - this.createdAt = Instant.now(); } @Override diff --git a/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java b/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java index 55d1508af..634665fd3 100644 --- a/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java +++ b/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java @@ -2,16 +2,14 @@ import java.time.Instant; import lombok.Getter; +import org.springframework.data.annotation.LastModifiedDate; @Getter public abstract class BaseUpdatableEntity extends BaseEntity { + @LastModifiedDate protected Instant updatedAt; - public BaseUpdatableEntity() { - this.updatedAt = this.createdAt; - } - @Override public boolean equals(Object obj) { if (this == obj) { From 765b44bc432055522d9b0b3c67008462326ba3c0 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 16:07:42 +0900 Subject: [PATCH 29/48] =?UTF-8?q?feat:=20Base=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20Jpa=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mission/discodeit/base/BaseEntity.java | 22 ++++++++++++++----- .../discodeit/base/BaseUpdatableEntity.java | 4 ++++ .../mission/discodeit/config/JpaConfig.java | 10 +++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/sprint/mission/discodeit/config/JpaConfig.java diff --git a/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java b/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java index ca12feb3a..e1a9d5482 100644 --- a/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java +++ b/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java @@ -1,21 +1,33 @@ package com.sprint.mission.discodeit.base; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; import java.time.Instant; import java.util.UUID; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class BaseEntity { - protected final UUID id; + @Id + @GeneratedValue(strategy = GenerationType.UUID) + protected UUID id; + @CreatedDate + @Column(nullable = false, updatable = false) protected Instant createdAt; - public BaseEntity() { - this.id = UUID.randomUUID(); - } - @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java b/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java index 634665fd3..6f23cbb6f 100644 --- a/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java +++ b/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java @@ -1,10 +1,14 @@ package com.sprint.mission.discodeit.base; +import jakarta.persistence.MappedSuperclass; import java.time.Instant; import lombok.Getter; +import lombok.NoArgsConstructor; import org.springframework.data.annotation.LastModifiedDate; @Getter +@MappedSuperclass +@NoArgsConstructor public abstract class BaseUpdatableEntity extends BaseEntity { @LastModifiedDate diff --git a/src/main/java/com/sprint/mission/discodeit/config/JpaConfig.java b/src/main/java/com/sprint/mission/discodeit/config/JpaConfig.java new file mode 100644 index 000000000..51d42ca32 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/config/JpaConfig.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing +public class JpaConfig { + +} From f018a2dcfff01fe14331c5d83980063956024b4b Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 19:00:07 +0900 Subject: [PATCH 30/48] =?UTF-8?q?feat:=20schema.sql=EC=97=90=20channel=20t?= =?UTF-8?q?ype=20not=20null=20=EB=88=84=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/schema.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 0e30d670c..3ac9dcfa8 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -11,11 +11,11 @@ CREATE TABLE users CREATE TABLE channels ( id UUID PRIMARY KEY, - created_at timestamptz NOT NULL, + created_at timestamptz NOT NULL, updated_at timestamptz, name varchar(100), description varchar(500), - type varchar(10) check (type in ('PUBLIC', 'PRIVATE')) + type varchar(10) check (type in ('PUBLIC', 'PRIVATE')) NOT NULL ); CREATE TABLE messages ( From 065d2291a64524729eb7bb23a3318682997af151 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Wed, 4 Mar 2026 19:02:24 +0900 Subject: [PATCH 31/48] =?UTF-8?q?feat:=20entity=EC=97=90=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EB=A7=A4=ED=95=91=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mission/discodeit/base/BaseEntity.java | 2 +- .../discodeit/base/BaseUpdatableEntity.java | 8 +-- .../binarycontent/entity/BinaryContent.java | 44 +++++++------- .../discodeit/channel/entity/Channel.java | 51 ++++++---------- .../discodeit/message/entity/Message.java | 55 ++++++++++++----- .../readstatus/entity/ReadStatus.java | 30 ++++++++-- .../mission/discodeit/user/entity/User.java | 59 ++++++++----------- .../userstatus/entity/UserStatus.java | 26 ++++++-- 8 files changed, 151 insertions(+), 124 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java b/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java index e1a9d5482..7dd7125dc 100644 --- a/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java +++ b/src/main/java/com/sprint/mission/discodeit/base/BaseEntity.java @@ -15,9 +15,9 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) @MappedSuperclass @EntityListeners(AuditingEntityListener.class) -@NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class BaseEntity { @Id diff --git a/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java b/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java index 6f23cbb6f..54ea31964 100644 --- a/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java +++ b/src/main/java/com/sprint/mission/discodeit/base/BaseUpdatableEntity.java @@ -2,13 +2,14 @@ import jakarta.persistence.MappedSuperclass; import java.time.Instant; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.data.annotation.LastModifiedDate; @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) @MappedSuperclass -@NoArgsConstructor public abstract class BaseUpdatableEntity extends BaseEntity { @LastModifiedDate @@ -27,9 +28,4 @@ public boolean equals(Object obj) { } return id.equals(((BaseUpdatableEntity) obj).id); } - - @Override - public int hashCode() { - return id.hashCode(); - } } \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java index b996a9fd1..c49479e34 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java @@ -1,17 +1,34 @@ package com.sprint.mission.discodeit.binarycontent.entity; import com.sprint.mission.discodeit.base.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Lob; +import jakarta.persistence.Table; import java.io.Serializable; import java.util.Arrays; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "binary_contents") public class BinaryContent extends BaseEntity implements Serializable { - private final String fileName; - private final Long size; - private final String contentType; - private final byte[] bytes; + @Column(nullable = false) + private String fileName; + + @Column(nullable = false) + private Long size; + + @Column(nullable = false, length = 100) + private String contentType; + + @Lob + @Column(nullable = false) + private byte[] bytes; public BinaryContent(String fileName, Long size, String contentType, byte[] bytes) { this.fileName = fileName; @@ -23,23 +40,4 @@ public BinaryContent(String fileName, Long size, String contentType, byte[] byte public byte[] getBytes() { return Arrays.copyOf(bytes, bytes.length); } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - return id.equals(((BinaryContent) obj).id); - } - - @Override - public int hashCode() { - return id.hashCode(); - } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java index a9e37806c..4fcd4d995 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java @@ -1,19 +1,30 @@ package com.sprint.mission.discodeit.channel.entity; import com.sprint.mission.discodeit.base.BaseUpdatableEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "channels") public class Channel extends BaseUpdatableEntity { - private final List messageIds = new ArrayList<>(); - private final List userIds = new ArrayList<>(); - private String name; + @Enumerated(EnumType.STRING) + @Column(nullable = false) private ChannelType type; + + @Column(length = 100) + private String name; + + @Column(length = 500) private String description; public Channel(String name, ChannelType type, String description) { @@ -22,14 +33,6 @@ public Channel(String name, ChannelType type, String description) { this.description = description; } - public List getMessageIds() { - return List.copyOf(messageIds); - } - - public List getUserIds() { - return List.copyOf(userIds); - } - public void updateChannelName(String channelName) { this.name = channelName; this.updatedAt = Instant.now(); @@ -44,24 +47,4 @@ public void updateDescription(String description) { this.description = description; this.updatedAt = Instant.now(); } - - public void addMessageId(UUID messageId) { - messageIds.add(messageId); - this.updatedAt = Instant.now(); - } - - public void removeMessageId(UUID messageId) { - messageIds.remove(messageId); - this.updatedAt = Instant.now(); - } - - public void addUserId(UUID userId) { - userIds.add(userId); - this.updatedAt = Instant.now(); - } - - public void removeUserId(UUID userId) { - userIds.remove(userId); - this.updatedAt = Instant.now(); - } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java index 1e0c62977..e3385e3f4 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java +++ b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java @@ -1,24 +1,51 @@ package com.sprint.mission.discodeit.message.entity; import com.sprint.mission.discodeit.base.BaseUpdatableEntity; +import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; +import com.sprint.mission.discodeit.channel.entity.Channel; +import com.sprint.mission.discodeit.user.entity.User; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.Lob; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; import java.time.Instant; import java.util.List; -import java.util.UUID; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "messages") public class Message extends BaseUpdatableEntity { - private final UUID authorId; - private final UUID channelId; - private final List attachmentIds; + @Lob private String content; - public Message(String content, UUID authorId, UUID channel, List attachmentIds) { + @ManyToOne + @JoinColumn(name = "channel_id", nullable = false) + private Channel channel; + + @ManyToOne + @JoinColumn(name = "author_id") + private User author; + + @OneToMany + @JoinTable(name = "message_attachments", + joinColumns = @JoinColumn(name = "message_id"), + inverseJoinColumns = @JoinColumn(name = "attachment_id") + ) + private List attachments; + + public Message(String content, Channel channel, User author, List attachments) { this.content = content; - this.authorId = authorId; - this.channelId = channel; - this.attachmentIds = attachmentIds; + this.channel = channel; + this.author = author; + this.attachments = attachments; } public void update(String newContent) { @@ -26,15 +53,15 @@ public void update(String newContent) { this.updatedAt = Instant.now(); } - public List getAttachmentIds() { - return List.copyOf(attachmentIds); + public List getAttachmentIds() { + return List.copyOf(attachments); } - public void addAttachmentId(UUID attachmentId) { - attachmentIds.add(attachmentId); + public void addAttachmentId(BinaryContent attachment) { + attachments.add(attachment); } - public void removeAttachmentId(UUID attachmentId) { - attachmentIds.remove(attachmentId); + public void removeAttachmentId(BinaryContent attachment) { + attachments.remove(attachment); } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java index 1cd17f744..3d19bd77b 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java @@ -1,20 +1,38 @@ package com.sprint.mission.discodeit.readstatus.entity; import com.sprint.mission.discodeit.base.BaseUpdatableEntity; +import com.sprint.mission.discodeit.channel.entity.Channel; +import com.sprint.mission.discodeit.user.entity.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import java.time.Instant; -import java.util.UUID; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "read_statuses") public class ReadStatus extends BaseUpdatableEntity { - private final UUID userId; - private final UUID channelId; + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "channel_id", nullable = false) + private Channel channel; + + @Column(nullable = false) private Instant lastReadAt; - public ReadStatus(UUID userId, UUID channelId) { - this.userId = userId; - this.channelId = channelId; + public ReadStatus(User user, Channel channel) { + this.user = user; + this.channel = channel; this.lastReadAt = Instant.now(); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java index a7711ac14..04b8d9a78 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java +++ b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java @@ -1,23 +1,40 @@ package com.sprint.mission.discodeit.user.entity; import com.sprint.mission.discodeit.base.BaseUpdatableEntity; +import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; +import com.sprint.mission.discodeit.userstatus.entity.UserStatus; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Getter +@NoArgsConstructor +@Entity +@Table(name = "users") public class User extends BaseUpdatableEntity { - private final List channelIds = new ArrayList<>(); - private final List messageIds = new ArrayList<>(); + @Column(unique = true, nullable = false, length = 50) private String username; + + @Column(nullable = false, length = 60) private String password; + + @Column(unique = true, nullable = false, length = 100) private String email; + + @OneToOne + @JoinColumn(name = "profile_id", unique = true) + private BinaryContent profile; + @Setter - private UUID profileId; + @OneToOne(mappedBy = "user") + private UserStatus userStatus; public User(String username, String password, String email) { this.username = username; @@ -25,14 +42,6 @@ public User(String username, String password, String email) { this.email = email; } - public List getChannelIds() { - return List.copyOf(channelIds); - } - - public List getMessageIds() { - return List.copyOf(messageIds); - } - public void updateUserName(String username) { this.username = username; this.updatedAt = Instant.now(); @@ -48,27 +57,7 @@ public void updateEmail(String email) { this.updatedAt = Instant.now(); } - public void addChannelId(UUID channelId) { - channelIds.add(channelId); - this.updatedAt = Instant.now(); - } - - public void removeChannelId(UUID channelId) { - channelIds.remove(channelId); - this.updatedAt = Instant.now(); - } - - public void addMessageId(UUID messageId) { - messageIds.add(messageId); - this.updatedAt = Instant.now(); - } - - public void removeMessageId(UUID messageId) { - messageIds.remove(messageId); - this.updatedAt = Instant.now(); - } - public boolean isProfileImageUploaded() { - return profileId != null; + return profile != null; } } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java index d9118e172..622f2b8bf 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java @@ -1,19 +1,30 @@ package com.sprint.mission.discodeit.userstatus.entity; import com.sprint.mission.discodeit.base.BaseUpdatableEntity; +import com.sprint.mission.discodeit.user.entity.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; import java.time.Instant; -import java.util.UUID; import lombok.Getter; @Getter +@Entity +@Table(name = "user_statuses") public class UserStatus extends BaseUpdatableEntity { - private final UUID userId; - private final int loginLimitSeconds = 60 * 5; + @OneToOne + @JoinColumn(name = "user_id") + private User user; + + @Column(nullable = false) private Instant lastActiveAt; - public UserStatus(UUID userId) { - this.userId = userId; + private final int loginLimitSeconds = 60 * 5; + + public UserStatus() { lastActiveAt = Instant.now(); } @@ -25,6 +36,11 @@ public void update(Instant newLastActiveAt) { lastActiveAt = newLastActiveAt; } + public void setUser(User user) { + this.user = user; + user.setUserStatus(this); + } + public boolean isOnline() { return lastActiveAt.isAfter(Instant.now().minusSeconds(loginLimitSeconds)); } From a99e62d4c59567387b6d523d7aee253a01d45898 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Thu, 5 Mar 2026 09:42:07 +0900 Subject: [PATCH 32/48] =?UTF-8?q?refactor:=20BinaryContent=EC=97=90?= =?UTF-8?q?=EC=84=9C=20Serializable=20=EC=A0=9C=EA=B1=B0=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mission/discodeit/binarycontent/entity/BinaryContent.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java index c49479e34..c6352f984 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java @@ -5,7 +5,6 @@ import jakarta.persistence.Entity; import jakarta.persistence.Lob; import jakarta.persistence.Table; -import java.io.Serializable; import java.util.Arrays; import lombok.AccessLevel; import lombok.Getter; @@ -15,7 +14,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @Table(name = "binary_contents") -public class BinaryContent extends BaseEntity implements Serializable { +public class BinaryContent extends BaseEntity { @Column(nullable = false) private String fileName; From 3b920873b4e36378d03e8aa75443630053c70957 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Thu, 5 Mar 2026 11:09:15 +0900 Subject: [PATCH 33/48] =?UTF-8?q?feat:=20entity=EC=97=90=20=EC=98=81?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EC=A0=84=EC=9D=B4,=20orphanRemoval=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sprint/mission/discodeit/message/entity/Message.java | 5 +++-- .../mission/discodeit/readstatus/entity/ReadStatus.java | 4 ++-- .../java/com/sprint/mission/discodeit/user/entity/User.java | 5 +++-- .../mission/discodeit/userstatus/entity/UserStatus.java | 3 ++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java index e3385e3f4..d44f9c357 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java +++ b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java @@ -4,6 +4,7 @@ import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.user.entity.User; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; @@ -26,7 +27,7 @@ public class Message extends BaseUpdatableEntity { @Lob private String content; - @ManyToOne + @ManyToOne(optional = false) @JoinColumn(name = "channel_id", nullable = false) private Channel channel; @@ -34,7 +35,7 @@ public class Message extends BaseUpdatableEntity { @JoinColumn(name = "author_id") private User author; - @OneToMany + @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true) @JoinTable(name = "message_attachments", joinColumns = @JoinColumn(name = "message_id"), inverseJoinColumns = @JoinColumn(name = "attachment_id") diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java index 3d19bd77b..74d7edfa0 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java @@ -19,11 +19,11 @@ @Table(name = "read_statuses") public class ReadStatus extends BaseUpdatableEntity { - @ManyToOne + @ManyToOne(optional = false) @JoinColumn(name = "user_id", nullable = false) private User user; - @ManyToOne + @ManyToOne(optional = false) @JoinColumn(name = "channel_id", nullable = false) private Channel channel; diff --git a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java index 04b8d9a78..3cf5d222f 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java +++ b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java @@ -3,6 +3,7 @@ import com.sprint.mission.discodeit.base.BaseUpdatableEntity; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; @@ -28,12 +29,12 @@ public class User extends BaseUpdatableEntity { @Column(unique = true, nullable = false, length = 100) private String email; - @OneToOne + @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true) @JoinColumn(name = "profile_id", unique = true) private BinaryContent profile; @Setter - @OneToOne(mappedBy = "user") + @OneToOne(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) private UserStatus userStatus; public User(String username, String password, String email) { diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java index 622f2b8bf..d07480fe5 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java @@ -2,6 +2,7 @@ import com.sprint.mission.discodeit.base.BaseUpdatableEntity; import com.sprint.mission.discodeit.user.entity.User; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; @@ -15,7 +16,7 @@ @Table(name = "user_statuses") public class UserStatus extends BaseUpdatableEntity { - @OneToOne + @OneToOne(optional = false) @JoinColumn(name = "user_id") private User user; From 37acc042105f8565201290d6d557a42d91d44d61 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Thu, 5 Mar 2026 18:54:25 +0900 Subject: [PATCH 34/48] =?UTF-8?q?feat:=20repository=EC=97=90=20JpaReposito?= =?UTF-8?q?ry=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/BinaryContentRepository.java | 12 +- .../FileBinaryContentRepository.java | 104 -------------- .../JCFBinaryContentRepository.java | 42 ------ .../channel/repository/ChannelRepository.java | 17 +-- .../repository/FileChannelRepository.java | 124 ----------------- .../repository/JCFChannelRepository.java | 61 -------- .../repository/FileMessageRepository.java | 120 ---------------- .../repository/JCFMessageRepository.java | 56 -------- .../message/repository/MessageRepository.java | 13 +- .../repository/FileReadStatusRepository.java | 126 ----------------- .../repository/JCFReadStatusRepository.java | 63 --------- .../repository/ReadStatusRepository.java | 13 +- .../user/repository/FileUserRepository.java | 130 ------------------ .../user/repository/JCFUserRepository.java | 65 --------- .../user/repository/UserRepository.java | 18 +-- .../repository/FileUserStatusRepository.java | 117 ---------------- .../repository/JCFUserStatusRepository.java | 54 -------- .../repository/UserStatusRepository.java | 14 +- src/main/resources/application.yaml | 7 +- 19 files changed, 23 insertions(+), 1133 deletions(-) delete mode 100644 src/main/java/com/sprint/mission/discodeit/binarycontent/repository/FileBinaryContentRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/binarycontent/repository/JCFBinaryContentRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/readstatus/repository/FileReadStatusRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/readstatus/repository/JCFReadStatusRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/userstatus/repository/FileUserStatusRepository.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/userstatus/repository/JCFUserStatusRepository.java diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/BinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/BinaryContentRepository.java index d1021f946..05a6e8720 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/BinaryContentRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/BinaryContentRepository.java @@ -1,17 +1,9 @@ package com.sprint.mission.discodeit.binarycontent.repository; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; -import java.util.List; -import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; -public interface BinaryContentRepository { +public interface BinaryContentRepository extends JpaRepository { - Optional findById(UUID id); - - List findAll(); - - void save(BinaryContent binaryContent); - - void deleteById(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/FileBinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/FileBinaryContentRepository.java deleted file mode 100644 index f42bd81c1..000000000 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/FileBinaryContentRepository.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.sprint.mission.discodeit.binarycontent.repository; - -import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") -public class FileBinaryContentRepository implements BinaryContentRepository { - - private final Path binaryContentPath; - - public FileBinaryContentRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.binaryContentPath = Paths.get(rootPath, "binary-contents"); - } - - @Override - public Optional findById(UUID id) { - Path binaryContentPath = getBinaryContentPath(id); - return Optional.ofNullable(read(binaryContentPath)); - } - - @Override - public List findAll() { - if (Files.exists(binaryContentPath)) { - try { - return Files.list(binaryContentPath) - .map(this::read) - .toList(); - } catch (IOException e) { - throw new RuntimeException("BinaryContent 파일 목록을 불러오는데 실패했습니다."); - } - } else { - return List.of(); - } - } - - @Override - public void save(BinaryContent binaryContent) { - Path binaryContentPath = getBinaryContentPath(binaryContent.getId()); - write(binaryContent, binaryContentPath); - } - - @Override - public void deleteById(UUID id) { - Path binaryContentPath = getBinaryContentPath(id); - try { - Files.delete(binaryContentPath); - } catch (IOException e) { - throw new RuntimeException("BinaryContent 파일을 삭제하는데 실패했습니다."); - } - } - - private BinaryContent read(Path path) { - if (!Files.exists(path)) { - throw new IllegalStateException("해당 파일이 존재하지 않습니다."); - } - - try (FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return (BinaryContent) ois.readObject(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("파일을 BinaryContent로 변환할 수 없습니다."); - } catch (IOException e) { - throw new RuntimeException("BinaryContent 파일이나 경로를 불러오는데 실패했습니다."); - } - } - - private void write(BinaryContent binaryContent, Path path) { - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(binaryContent); - } catch (IOException e) { - throw new RuntimeException("BinaryContent를 파일로 저장하는데 실패했습니다."); - } - } - - - private Path getBinaryContentPath(UUID statusId) { - try { - Files.createDirectories(binaryContentPath); - } catch (IOException e) { - throw new IllegalStateException("binary-contents 경로를 만드는데 실패했습니다."); - } - - return binaryContentPath.resolve(statusId.toString() + ".ser"); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/JCFBinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/JCFBinaryContentRepository.java deleted file mode 100644 index 2b3d4ae9f..000000000 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/repository/JCFBinaryContentRepository.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.sprint.mission.discodeit.binarycontent.repository; - -import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) -public class JCFBinaryContentRepository implements BinaryContentRepository { - - private final List data = new ArrayList<>(); - - @Override - public Optional findById(UUID id) { - return data.stream() - .filter(bc -> bc.getId().equals(id)) - .findFirst(); - } - - @Override - public List findAll() { - return List.copyOf(data); - } - - @Override - public void save(BinaryContent binaryContent) { - if (data.contains(binaryContent)) { - data.set(data.indexOf(binaryContent), binaryContent); - } else { - data.add(binaryContent); - } - } - - @Override - public void deleteById(UUID id) { - data.removeIf(bc -> bc.getId().equals(id)); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/ChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/ChannelRepository.java index af3b0c9dd..0adc947be 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/ChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/repository/ChannelRepository.java @@ -1,21 +1,10 @@ package com.sprint.mission.discodeit.channel.repository; import com.sprint.mission.discodeit.channel.entity.Channel; -import java.util.List; -import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; -public interface ChannelRepository { +public interface ChannelRepository extends JpaRepository { - Optional findById(UUID channelId); - - Optional findByName(String channelName); - - List findAll(); - - List findAllByUserId(UUID userId); - - void save(Channel channel); - - void deleteById(UUID channelId); + boolean existsByName(String name); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java deleted file mode 100644 index 1c0015d48..000000000 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/FileChannelRepository.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.sprint.mission.discodeit.channel.repository; - -import com.sprint.mission.discodeit.channel.entity.Channel; -import com.sprint.mission.discodeit.channel.entity.ChannelType; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") -public class FileChannelRepository implements ChannelRepository { - - private final Path channelPath; - - public FileChannelRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.channelPath = Paths.get(rootPath, "channels"); - } - - @Override - public Optional findById(UUID channelId) { - Path channelPath = getChannelPath(channelId); - - if (!Files.exists(channelPath)) { - return Optional.empty(); - } - - try (FileInputStream fis = new FileInputStream(channelPath.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return Optional.ofNullable((Channel) ois.readObject()); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("채널을 가져오는데 실패했습니다."); - } - } - - @Override - public Optional findByName(String channelName) { - return findAll().stream() - .filter( - c -> c.getType() == ChannelType.PUBLIC && c.getName().equals(channelName)) - .findFirst(); - } - - @Override - public List findAll() { - if (Files.exists(channelPath)) { - try { - List channels = Files.list(channelPath) - .map(path -> { - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - Channel channel = (Channel) ois.readObject(); - return channel; - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("모든 채널을 가져오는데 실패했습니다."); - } - }) - .toList(); - return channels; - } catch (IOException e) { - throw new RuntimeException("모든 채널을 가져오는데 실패했습니다."); - } - } else { - return new ArrayList<>(); - } - } - - @Override - public List findAllByUserId(UUID userId) { - return findAll().stream() - .filter(c -> c.getUserIds() - .stream() - .anyMatch(findUserId -> findUserId.equals(userId))) - .toList(); - } - - @Override - public void save(Channel channel) { - Path channelPath = getChannelPath(channel.getId()); - try ( - FileOutputStream fos = new FileOutputStream(channelPath.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(channel); - } catch (IOException e) { - throw new RuntimeException("채널을 저장하는데 실패했습니다."); - } - } - - @Override - public void deleteById(UUID channelId) { - Path channelPath = getChannelPath(channelId); - try { - Files.delete(channelPath); - } catch (IOException e) { - throw new RuntimeException("채널을 삭제하는데 실패했습니다."); - } - } - - private Path getChannelPath(UUID channelId) { - try { - Files.createDirectories(channelPath); - } catch (IOException e) { - throw new IllegalStateException("channels 경로를 만드는데 실패했습니다."); - } - - return channelPath.resolve(channelId.toString() + ".ser"); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java deleted file mode 100644 index 283dae6d9..000000000 --- a/src/main/java/com/sprint/mission/discodeit/channel/repository/JCFChannelRepository.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.sprint.mission.discodeit.channel.repository; - -import com.sprint.mission.discodeit.channel.entity.Channel; -import com.sprint.mission.discodeit.channel.entity.ChannelType; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) -public class JCFChannelRepository implements ChannelRepository { - - private final List data = new ArrayList<>(); - - @Override - public Optional findById(UUID channelId) { - return data.stream() - .filter(c -> c.getId().equals(channelId)) - .findFirst(); - } - - @Override - public Optional findByName(String channelName) { - return data.stream() - .filter(c -> c.getType() == ChannelType.PUBLIC - && c.getName().equals(channelName)) - .findFirst(); - } - - @Override - public List findAll() { - return List.copyOf(data); - } - - @Override - public List findAllByUserId(UUID userId) { - return data.stream() - .filter(c -> c.getUserIds() - .stream() - .anyMatch(findUserId -> findUserId.equals(userId))) - .toList(); - - } - - @Override - public void save(Channel channel) { - if (data.contains(channel)) { - data.set(data.indexOf(channel), channel); - } else { - data.add(channel); - } - } - - @Override - public void deleteById(UUID channelId) { - data.removeIf(c -> c.getId().equals(channelId)); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java deleted file mode 100644 index f03518900..000000000 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/FileMessageRepository.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.sprint.mission.discodeit.message.repository; - -import com.sprint.mission.discodeit.message.entity.Message; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") -public class FileMessageRepository implements MessageRepository { - - private final Path messagePath; - - public FileMessageRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.messagePath = Paths.get(rootPath, "messages"); - } - - @Override - public Optional findById(UUID messageId) { - Path messagePath = getMessagePath(messageId); - - if (!Files.exists(messagePath)) { - return Optional.empty(); - } - - try (FileInputStream fis = new FileInputStream(messagePath.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return Optional.ofNullable((Message) ois.readObject()); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("메세지를 가져오는데 실패했습니다."); - } - } - - @Override - public List findAll() { - if (Files.exists(messagePath)) { - try { - List messages = Files.list(messagePath) - .map(path -> { - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - Message message = (Message) ois.readObject(); - return message; - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("모든 메세지를 가져오는데 실패했습니다."); - } - }) - .toList(); - return messages; - } catch (IOException e) { - throw new RuntimeException("모든 메세지를 가져오는데 실패했습니다."); - } - } else { - return new ArrayList<>(); - } - } - - @Override - public List findAllByUserId(UUID userId) { - return findAll().stream() - .filter(m -> m.getAuthorId().equals(userId)) - .toList(); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return findAll().stream() - .filter(m -> m.getChannelId().equals(channelId)) - .toList(); - } - - @Override - public void save(Message message) { - Path messagePath = getMessagePath(message.getId()); - try ( - FileOutputStream fos = new FileOutputStream(messagePath.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(message); - } catch (IOException e) { - throw new RuntimeException("메세지를 저장하는데 실패했습니다."); - } - } - - @Override - public void deleteById(UUID messageId) { - Path messagePath = getMessagePath(messageId); - try { - Files.delete(messagePath); - } catch (IOException e) { - throw new RuntimeException("메세지를 삭제하는데 실패했습니다."); - } - } - - private Path getMessagePath(UUID messageId) { - try { - Files.createDirectories(messagePath); - } catch (IOException e) { - throw new IllegalStateException("messages 경로를 만드는데 실패했습니다."); - } - - return messagePath.resolve(messageId.toString() + ".ser"); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java deleted file mode 100644 index 3e4e12241..000000000 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/JCFMessageRepository.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.sprint.mission.discodeit.message.repository; - -import com.sprint.mission.discodeit.message.entity.Message; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) -public class JCFMessageRepository implements MessageRepository { - - private final List data = new ArrayList<>(); - - @Override - public Optional findById(UUID messageId) { - return data.stream() - .filter(m -> m.getId().equals(messageId)) - .findFirst(); - } - - @Override - public List findAll() { - return List.copyOf(data); - } - - @Override - public List findAllByUserId(UUID userId) { - return data.stream() - .filter(m -> m.getAuthorId().equals(userId)) - .toList(); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return data.stream() - .filter(m -> m.getChannelId().equals(channelId)) - .toList(); - } - - @Override - public void save(Message message) { - if (data.contains(message)) { - data.set(data.indexOf(message), message); - } else { - data.add(message); - } - } - - @Override - public void deleteById(UUID messageId) { - data.removeIf(m -> m.getId().equals(messageId)); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java index befaf22f2..bff14b0c2 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java @@ -4,18 +4,13 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; -public interface MessageRepository { +public interface MessageRepository extends JpaRepository { - Optional findById(UUID messageId); - - List findAll(); - - List findAllByUserId(UUID userId); + List findAllByAuthorId(UUID authorId); List findAllByChannelId(UUID channelId); - void save(Message message); - - void deleteById(UUID messageId); + Optional findFirstByChannel_IdOrderByCreatedAtAsc(UUID channelId); } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/FileReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/FileReadStatusRepository.java deleted file mode 100644 index fe248c8ba..000000000 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/FileReadStatusRepository.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.sprint.mission.discodeit.readstatus.repository; - -import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") -public class FileReadStatusRepository implements ReadStatusRepository { - - private final Path readStatusPath; - - public FileReadStatusRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.readStatusPath = Paths.get(rootPath, "read-statuses"); - } - - @Override - public Optional findById(UUID id) { - Path readStatusPath = getReadStatusPath(id); - return Optional.ofNullable(read(readStatusPath)); - } - - @Override - public Optional findByUserIdAndChannelId(UUID userId, UUID channelId) { - return findAll().stream() - .filter(rs -> rs.getUserId().equals(userId) && rs.getChannelId().equals(channelId)) - .findFirst(); - } - - @Override - public List findAll() { - if (Files.exists(readStatusPath)) { - try { - return Files.list(readStatusPath) - .map(this::read) - .toList(); - } catch (IOException e) { - throw new RuntimeException("ReadStatus 파일 목록을 불러오는데 실패했습니다."); - } - } else { - return List.of(); - } - } - - @Override - public List findAllByUserId(UUID userId) { - return findAll().stream() - .filter(rs -> rs.getUserId().equals(userId)) - .toList(); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return findAll().stream() - .filter(rs -> rs.getChannelId().equals(channelId)) - .toList(); - } - - @Override - public void save(ReadStatus readStatus) { - Path readStatusPath = getReadStatusPath(readStatus.getId()); - write(readStatus, readStatusPath); - - } - - @Override - public void deleteById(UUID id) { - Path readStatusPath = getReadStatusPath(id); - try { - Files.delete(readStatusPath); - } catch (IOException e) { - throw new RuntimeException("ReadStatus 파일을 삭제하는데 실패했습니다."); - } - } - - private ReadStatus read(Path path) { - if (!Files.exists(path)) { - throw new IllegalStateException("해당 파일이 존재하지 않습니다."); - } - - try (FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return (ReadStatus) ois.readObject(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("파일을 ReadStatus로 변환할 수 없습니다."); - } catch (IOException e) { - throw new RuntimeException("ReadStatus 파일이나 경로를 불러오는데 실패했습니다."); - } - } - - private void write(ReadStatus readStatus, Path path) { - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(readStatus); - } catch (IOException e) { - throw new RuntimeException("ReadStatus를 파일로 저장하는데 실패했습니다."); - } - } - - - private Path getReadStatusPath(UUID statusId) { - try { - Files.createDirectories(readStatusPath); - } catch (IOException e) { - throw new IllegalStateException("read-statuses 경로를 만드는데 실패했습니다."); - } - - return readStatusPath.resolve(statusId.toString() + ".ser"); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/JCFReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/JCFReadStatusRepository.java deleted file mode 100644 index 9f12006cb..000000000 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/JCFReadStatusRepository.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.sprint.mission.discodeit.readstatus.repository; - -import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) -public class JCFReadStatusRepository implements ReadStatusRepository { - - private final List data = new ArrayList<>(); - - @Override - public Optional findById(UUID id) { - return data.stream() - .filter(rs -> rs.getId().equals(id)) - .findFirst(); - } - - @Override - public Optional findByUserIdAndChannelId(UUID userId, UUID channelId) { - return data.stream() - .filter(rs -> rs.getUserId().equals(userId) && rs.getChannelId().equals(channelId)) - .findFirst(); - } - - @Override - public List findAll() { - return List.copyOf(data); - } - - @Override - public List findAllByUserId(UUID userId) { - return data.stream() - .filter(rs -> rs.getUserId().equals(userId)) - .toList(); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return data.stream() - .filter(rs -> rs.getChannelId().equals(channelId)) - .toList(); - } - - @Override - public void save(ReadStatus readStatus) { - if (data.contains(readStatus)) { - data.set(data.indexOf(readStatus), readStatus); - } else { - data.add(readStatus); - } - } - - @Override - public void deleteById(UUID id) { - data.removeIf(rs -> rs.getId().equals(id)); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/ReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/ReadStatusRepository.java index cf0cd5ec0..c182625be 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/repository/ReadStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/repository/ReadStatusRepository.java @@ -4,20 +4,15 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; -public interface ReadStatusRepository { - - Optional findById(UUID id); +public interface ReadStatusRepository extends JpaRepository { Optional findByUserIdAndChannelId(UUID userId, UUID channelId); - List findAll(); - - List findAllByUserId(UUID userId); - List findAllByChannelId(UUID channelId); - void save(ReadStatus readStatus); + List findAllByUserId(UUID userId); - void deleteById(UUID id); + Boolean existsByUserIdAndChannelId(UUID userId, UUID channelId); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java b/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java deleted file mode 100644 index 3c652eb39..000000000 --- a/src/main/java/com/sprint/mission/discodeit/user/repository/FileUserRepository.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.sprint.mission.discodeit.user.repository; - -import com.sprint.mission.discodeit.user.entity.User; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") -public class FileUserRepository implements UserRepository { - - private final Path userPath; - - public FileUserRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - this.userPath = Paths.get(rootPath, "users"); - } - - @Override - public Optional findById(UUID userId) { - Path userPath = getUserPath(userId); - if (!Files.exists(userPath)) { - return Optional.empty(); - } - - try ( - FileInputStream fis = new FileInputStream(userPath.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - return Optional.ofNullable((User) ois.readObject()); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("유저를 가져오는데 실패했습니다."); - } - } - - @Override - public Optional findByName(String userName) { - return findAll().stream() - .filter(u -> u.getUsername().equals(userName)) - .findFirst(); - } - - @Override - public Optional findByEmail(String email) { - return findAll().stream() - .filter(u -> u.getEmail().equals(email)) - .findFirst(); - } - - @Override - public List findAll() { - if (Files.exists(userPath)) { - try { - List users = Files.list(userPath) - .map(path -> { - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - User user = (User) ois.readObject(); - return user; - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException("모든 유저를 가져오는데 실패했습니다."); - } - }) - .toList(); - return users; - } catch (IOException e) { - throw new RuntimeException("모든 유저를 가져오는데 실패했습니다."); - } - } else { - return new ArrayList<>(); - } - } - - @Override - public List findAllByChannelId(UUID channelId) { - return findAll().stream() - .filter(u -> u.getChannelIds() - .stream() - .anyMatch(findChannelId -> findChannelId.equals(channelId))) - .toList(); - } - - @Override - public void save(User user) { - Path userPath = getUserPath(user.getId()); - try ( - FileOutputStream fos = new FileOutputStream(userPath.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(user); - } catch (IOException e) { - throw new RuntimeException("유저를 저장하는데 실패했습니다."); - } - } - - @Override - public void deleteById(UUID userId) { - Path userPath = getUserPath(userId); - try { - Files.deleteIfExists(userPath); - } catch (IOException e) { - throw new RuntimeException("유저를 삭제하는데 실패했습니다."); - } - } - - private Path getUserPath(UUID userId) { - try { - Files.createDirectories(userPath); - } catch (IOException e) { - throw new IllegalStateException("users 경로를 만드는데 실패했습니다."); - } - - return userPath.resolve(userId.toString() + ".ser"); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java b/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java deleted file mode 100644 index 832d85e73..000000000 --- a/src/main/java/com/sprint/mission/discodeit/user/repository/JCFUserRepository.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.sprint.mission.discodeit.user.repository; - -import com.sprint.mission.discodeit.user.entity.User; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) -public class JCFUserRepository implements UserRepository { - - private final List data = new ArrayList<>(); - - @Override - public Optional findById(UUID userId) { - return data.stream() - .filter(u -> u.getId().equals(userId)) - .findFirst(); - } - - @Override - public Optional findByName(String userName) { - return data.stream() - .filter(u -> u.getUsername().equals(userName)) - .findFirst(); - } - - @Override - public List findAll() { - return List.copyOf(data); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return data.stream() - .filter(u -> u.getChannelIds() - .stream() - .anyMatch(findChannelId -> findChannelId.equals(channelId))) - .toList(); - } - - @Override - public Optional findByEmail(String email) { - return data.stream() - .filter(u -> u.getEmail().equals(email)) - .findFirst(); - } - - @Override - public void save(User user) { - if (data.contains(user)) { - data.set(data.indexOf(user), user); - } else { - data.add(user); - } - } - - @Override - public void deleteById(UUID userId) { - data.removeIf(u -> u.getId().equals(userId)); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/user/repository/UserRepository.java b/src/main/java/com/sprint/mission/discodeit/user/repository/UserRepository.java index 639c3c69e..0479e8097 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/repository/UserRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/user/repository/UserRepository.java @@ -1,23 +1,15 @@ package com.sprint.mission.discodeit.user.repository; import com.sprint.mission.discodeit.user.entity.User; -import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; -public interface UserRepository { +public interface UserRepository extends JpaRepository { - Optional findById(UUID userId); + Optional findByUsername(String username); - Optional findByName(String userName); + boolean existsUserByUsername(String username); - Optional findByEmail(String email); - - List findAll(); - - List findAllByChannelId(UUID channelId); - - void save(User user); - - void deleteById(UUID userId); + boolean existsByEmail(String email); } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/FileUserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/FileUserStatusRepository.java deleted file mode 100644 index af1e08500..000000000 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/FileUserStatusRepository.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.sprint.mission.discodeit.userstatus.repository; - -import com.sprint.mission.discodeit.userstatus.entity.UserStatus; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Optional; -import java.util.UUID; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "file") -public class FileUserStatusRepository implements UserStatusRepository { - - private final Path userStatusPath; - - public FileUserStatusRepository( - @Value("${discodeit.repository.file-directory:data}") String rootPath - ) { - userStatusPath = Paths.get(rootPath, "user-statuses"); - } - - @Override - public Optional findById(UUID id) { - Path userStatusPath = getUserStatusPath(id); - return Optional.ofNullable(read(userStatusPath)); - } - - @Override - public Optional findByUserId(UUID userId) { - return findAll().stream() - .filter(us -> us.getUserId().equals(userId)) - .findFirst(); - } - - @Override - public List findAll() { - if (Files.exists(userStatusPath)) { - try { - return Files.list(userStatusPath) - .map(this::read) - .toList(); - } catch (IOException e) { - throw new RuntimeException("UserStatus 파일 목록을 불러오는데 실패했습니다."); - } - } else { - return List.of(); - } - } - - @Override - public void save(UserStatus userStatus) { - Path userStatusPath = getUserStatusPath(userStatus.getId()); - write(userStatus, userStatusPath); - } - - @Override - public void deleteById(UUID id) { - Path userStatusPath = getUserStatusPath(id); - try { - Files.delete(userStatusPath); - } catch (IOException e) { - throw new RuntimeException("UserStatus 파일을 삭제하는데 실패했습니다."); - } - } - - @Override - public void deleteByUserId(UUID userId) { - UserStatus userStatus = findByUserId(userId) - .orElseThrow(() -> new NoSuchElementException("해당 사용자를 찾을 수 없습니다.")); - deleteById(userStatus.getId()); - } - - private UserStatus read(Path path) { - if (!Files.exists(path)) { - throw new IllegalStateException("해당 파일이 존재하지 않습니다."); - } - - try (FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis)) { - return (UserStatus) ois.readObject(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("파일을 UserStatus로 변환할 수 없습니다."); - } catch (IOException e) { - throw new RuntimeException("UserStatus 파일이나 경로를 불러오는데 실패했습니다."); - } - } - - private void write(UserStatus userStatus, Path path) { - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(userStatus); - } catch (IOException e) { - throw new RuntimeException("UserStatus를 파일로 저장하는데 실패했습니다."); - } - } - - private Path getUserStatusPath(UUID userId) { - try { - Files.createDirectories(userStatusPath); - } catch (IOException e) { - throw new IllegalStateException("user-statuses 경로를 만드는데 실패했습니다."); - } - return userStatusPath.resolve(userId.toString() + ".ser"); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/JCFUserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/JCFUserStatusRepository.java deleted file mode 100644 index 3ebbd42bb..000000000 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/JCFUserStatusRepository.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.sprint.mission.discodeit.userstatus.repository; - -import com.sprint.mission.discodeit.userstatus.entity.UserStatus; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Repository; - -@Repository -@ConditionalOnProperty(prefix = "discodeit.repository", name = "type", havingValue = "jcf", matchIfMissing = true) -public class JCFUserStatusRepository implements UserStatusRepository { - - private final List data = new ArrayList<>(); - - @Override - public Optional findById(UUID id) { - return data.stream() - .filter(us -> us.getId().equals(id)) - .findFirst(); - } - - @Override - public Optional findByUserId(UUID userId) { - return data.stream() - .filter(us -> us.getUserId().equals(userId)) - .findFirst(); - } - - @Override - public List findAll() { - return List.copyOf(data); - } - - @Override - public void save(UserStatus userStatus) { - if (data.contains(userStatus)) { - data.set(data.indexOf(userStatus), userStatus); - } else { - data.add(userStatus); - } - } - - @Override - public void deleteById(UUID id) { - data.removeIf(us -> us.getId().equals(id)); - } - - @Override - public void deleteByUserId(UUID userId) { - data.removeIf(us -> us.getUserId().equals(userId)); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/UserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/UserStatusRepository.java index 95c5c4e4b..c8deda9f7 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/repository/UserStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/repository/UserStatusRepository.java @@ -1,21 +1,15 @@ package com.sprint.mission.discodeit.userstatus.repository; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; -import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; -public interface UserStatusRepository { - - Optional findById(UUID id); +public interface UserStatusRepository extends JpaRepository { Optional findByUserId(UUID userId); - List findAll(); - - void save(UserStatus userStatus); - - void deleteById(UUID id); - void deleteByUserId(UUID userId); + + boolean existsByUserId(UUID userId); } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 094fa42e4..0722d9a51 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -18,9 +18,4 @@ springdoc: api-docs: enabled: true swagger-ui: - enabled: true - -discodeit: - repository: - type: jcf #jcf | file - file-directory: ".discodeit" \ No newline at end of file + enabled: true \ No newline at end of file From f101c1440b37b151ce1e66675a2ec1b3553730e2 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Thu, 5 Mar 2026 18:59:39 +0900 Subject: [PATCH 35/48] =?UTF-8?q?refactor:=20JpaRepository=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4,=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=93=B1?= =?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 --- .../binarycontent/entity/BinaryContent.java | 4 +- .../channel/mapper/ChannelMapper.java | 13 ++-- .../channel/service/BasicChannelService.java | 72 +++++++------------ .../message/controller/MessageController.java | 1 - .../discodeit/message/entity/Message.java | 4 +- .../message/mapper/MessageMapper.java | 8 +-- .../message/service/BasicMessageService.java | 48 +++++-------- .../readstatus/mapper/ReadStatusMapper.java | 15 ++-- .../readstatus/service/ReadStatusService.java | 9 ++- .../mission/discodeit/user/entity/User.java | 1 + .../discodeit/user/mapper/UserMapper.java | 4 +- .../discodeit/user/service/AuthService.java | 2 +- .../user/service/BasicUserService.java | 42 ++++------- .../userstatus/entity/UserStatus.java | 3 +- .../userstatus/mapper/UserStatusMapper.java | 2 +- .../userstatus/service/UserStatusService.java | 11 +-- 16 files changed, 92 insertions(+), 147 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java index c6352f984..7d758ea51 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java @@ -3,7 +3,6 @@ import com.sprint.mission.discodeit.base.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.Lob; import jakarta.persistence.Table; import java.util.Arrays; import lombok.AccessLevel; @@ -25,8 +24,7 @@ public class BinaryContent extends BaseEntity { @Column(nullable = false, length = 100) private String contentType; - @Lob - @Column(nullable = false) + @Column(nullable = false, columnDefinition = "bytea") private byte[] bytes; public BinaryContent(String fileName, Long size, String contentType, byte[] bytes) { diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index 5f539da05..dbd562e53 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -6,13 +6,16 @@ import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.entity.ChannelType; import java.time.Instant; +import java.util.List; +import java.util.UUID; public final class ChannelMapper { private ChannelMapper() { } - public static ChannelDto toChannelDto(Channel channel, Instant lastMessageTime) { + public static ChannelDto toChannelDto(Channel channel, Instant lastMessageTime, + List participantIds) { if (channel.getType() == ChannelType.PRIVATE) { return new ChannelDto( channel.getId(), @@ -20,7 +23,7 @@ public static ChannelDto toChannelDto(Channel channel, Instant lastMessageTime) channel.getType(), null, lastMessageTime, - channel.getUserIds()); + null); } else { return new ChannelDto( channel.getId(), @@ -28,7 +31,7 @@ public static ChannelDto toChannelDto(Channel channel, Instant lastMessageTime) channel.getType(), channel.getDescription(), null, - channel.getUserIds() + participantIds ); } } @@ -55,10 +58,6 @@ public static ChannelResultDto toChannelResultDto(Channel channel) { } } - public static PrivateChannelCreateRequest toPrivateChannelCreateRequest(Channel channel) { - return new PrivateChannelCreateRequest(channel.getUserIds()); - } - public static Channel toChannel(ChannelDto channelDto) { return new Channel( channelDto.name(), diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 63a50d4bb..9f1423486 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -1,26 +1,26 @@ package com.sprint.mission.discodeit.channel.service; +import com.sprint.mission.discodeit.base.BaseEntity; import com.sprint.mission.discodeit.channel.dto.ChannelDto; import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; import com.sprint.mission.discodeit.channel.entity.Channel; +import com.sprint.mission.discodeit.channel.entity.ChannelType; import com.sprint.mission.discodeit.channel.exception.ChannelDuplicationException; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; import com.sprint.mission.discodeit.channel.exception.ChannelUpdateNotAllowedException; import com.sprint.mission.discodeit.channel.mapper.ChannelMapper; import com.sprint.mission.discodeit.channel.repository.ChannelRepository; -import com.sprint.mission.discodeit.channel.entity.ChannelType; -import com.sprint.mission.discodeit.message.entity.Message; import com.sprint.mission.discodeit.message.repository.MessageRepository; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; +import com.sprint.mission.discodeit.readstatus.exception.ReadStatusNotFoundException; import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; import java.time.Instant; -import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -55,8 +55,8 @@ public ChannelResultDto createPrivateChannel(PrivateChannelCreateRequest channel public ChannelDto findChannel(UUID channelId) { Channel channel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); - - return ChannelMapper.toChannelDto(channel, getLastMessageTime(channelId)); + return ChannelMapper.toChannelDto(channel, getLastMessageTime(channelId), + getParticipantIds(channelId)); } @Override @@ -64,7 +64,8 @@ public List findAll() { return channelRepository.findAll() .stream() .map(channel -> - ChannelMapper.toChannelDto(channel, getLastMessageTime(channel.getId())) + ChannelMapper.toChannelDto(channel, getLastMessageTime(channel.getId()), + getParticipantIds(channel.getId())) ) .toList(); } @@ -74,10 +75,10 @@ public List findAllByUserId(UUID userId) { return channelRepository.findAll() .stream() .filter(channel -> channel.getType() == ChannelType.PUBLIC - || (channel.getType() == ChannelType.PRIVATE && channel.getUserIds() - .contains(userId))) + || readStatusRepository.existsByUserIdAndChannelId(userId, channel.getId())) .map(channel -> - ChannelMapper.toChannelDto(channel, getLastMessageTime(channel.getId())) + ChannelMapper.toChannelDto(channel, getLastMessageTime(channel.getId()), + getParticipantIds(channel.getId())) ) .toList(); } @@ -107,17 +108,6 @@ public ChannelResultDto updateChannel(UUID channelId, public void deleteChannel(UUID channelId) { channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); - userRepository.findAllByChannelId(channelId).forEach(user -> { - user.removeChannelId(channelId); - userRepository.save(user); - }); - - messageRepository.findAllByChannelId(channelId).forEach(message -> - messageRepository.deleteById(message.getId())); - - readStatusRepository.findAllByChannelId(channelId).forEach(readStatus -> - readStatusRepository.deleteById(readStatus.getId())); - channelRepository.deleteById(channelId); } @@ -128,47 +118,37 @@ public void joinChannel(UUID channelId, UUID userId) { User user = userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); - channel.addUserId(userId); - user.addChannelId(channelId); - - channelRepository.save(channel); - userRepository.save(user); - readStatusRepository.save(new ReadStatus(userId, channelId)); + readStatusRepository.save(new ReadStatus(user, channel)); } @Override public void leaveChannel(UUID channelId, UUID userId) { - Channel channel = channelRepository.findById(channelId) + channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); - User user = userRepository.findById(userId) + userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); ReadStatus findReadStatus = readStatusRepository.findByUserIdAndChannelId(userId, channelId) - .orElseThrow(() -> new IllegalStateException("유저가 채널에 가입되어 있지 않습니다.")); - - channel.removeUserId(userId); - user.removeChannelId(channelId); + .orElseThrow(ReadStatusNotFoundException::new); - channelRepository.save(channel); - userRepository.save(user); readStatusRepository.deleteById(findReadStatus.getId()); } - private void validateChannelExist(String channelName) { - if (channelRepository.findByName(channelName).isPresent()) { + private void validateChannelExist(String name) { + if (channelRepository.existsByName(name)) { throw new ChannelDuplicationException(); } } private Instant getLastMessageTime(UUID channelId) { - List messages = messageRepository.findAllByChannelId(channelId); - if (messages.isEmpty()) { - return null; - } else { - return messages - .stream() - .max(Comparator.comparing(Message::getUpdatedAt)) - .orElseThrow(() -> new IllegalStateException("메세지가 존재하지 않습니다.")) - .getUpdatedAt(); - } + return messageRepository.findFirstByChannel_IdOrderByCreatedAtAsc(channelId) + .map(BaseEntity::getCreatedAt) + .orElse(null); + } + + private List getParticipantIds(UUID channelId) { + return readStatusRepository.findAllByChannelId(channelId) + .stream() + .map(readStatus -> readStatus.getUser().getId()) + .toList(); } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index 3ad6e0a05..066e64f0b 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -2,7 +2,6 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentFileProcessingException; -import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; diff --git a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java index d44f9c357..f0c3ab349 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java +++ b/src/main/java/com/sprint/mission/discodeit/message/entity/Message.java @@ -5,10 +5,10 @@ import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.user.entity.User; import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; -import jakarta.persistence.Lob; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; @@ -24,7 +24,7 @@ @Table(name = "messages") public class Message extends BaseUpdatableEntity { - @Lob + @Column(columnDefinition = "TEXT") private String content; @ManyToOne(optional = false) diff --git a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java index a5d803a83..1b0e1e901 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java @@ -12,15 +12,15 @@ public final class MessageMapper { private MessageMapper() { } - public static MessageDto toMessageDto(Message message) { + public static MessageDto toMessageDto(Message message, List attachmentIds) { return new MessageDto( message.getId(), message.getCreatedAt(), message.getUpdatedAt(), message.getContent(), - message.getAuthorId(), - message.getChannelId(), - message.getAttachmentIds() + message.getAuthor() != null ? message.getAuthor().getId() : null, + message.getChannel().getId(), + attachmentIds ); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 2c3aaf5dc..468d2b2cb 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -40,7 +40,7 @@ public MessageDto createMessage(MessageCreateRequest createInfo, Channel findChannel = channelRepository.findById(createInfo.channelId()) .orElseThrow(ChannelNotFoundException::new); - List attachmentIds = binaryContentCreateRequests.stream() + List attachments = binaryContentCreateRequests.stream() .map(request -> { BinaryContent binaryContent = new BinaryContent( request.fileName(), @@ -49,20 +49,16 @@ public MessageDto createMessage(MessageCreateRequest createInfo, request.bytes() ); contentRepository.save(binaryContent); - return binaryContent.getId(); + return binaryContent; }) .toList(); - Message message = new Message(createInfo.content(), author.getId(), findChannel.getId(), - attachmentIds); - - author.addMessageId(message.getId()); - findChannel.addMessageId(message.getId()); + Message message = new Message(createInfo.content(), findChannel, author, attachments); userRepository.save(author); channelRepository.save(findChannel); messageRepository.save(message); - return MessageMapper.toMessageDto(message); + return MessageMapper.toMessageDto(message, getAttachmentIds(message)); } @Override @@ -70,22 +66,22 @@ public MessageDto findMessage(UUID messageId) { Message message = messageRepository.findById(messageId) .orElseThrow(MessageNotFoundException::new); - return MessageMapper.toMessageDto(message); + return MessageMapper.toMessageDto(message, getAttachmentIds(message)); } @Override public List findAll() { - return toMessageInfoList(messageRepository.findAll()); + return toMessageDtoList(messageRepository.findAll()); } @Override public List findAllByUserId(UUID userId) { - return toMessageInfoList(messageRepository.findAllByUserId(userId)); + return toMessageDtoList(messageRepository.findAllByAuthorId(userId)); } @Override public List findAllByChannelId(UUID channelId) { - return toMessageInfoList(messageRepository.findAllByChannelId(channelId)); + return toMessageDtoList(messageRepository.findAllByChannelId(channelId)); } @Override @@ -97,32 +93,24 @@ public MessageDto updateMessage(UUID messageId, MessageUpdateRequest messageInfo .ifPresent(findMessage::update); messageRepository.save(findMessage); - return MessageMapper.toMessageDto(findMessage); + return MessageMapper.toMessageDto(findMessage, getAttachmentIds(findMessage)); } @Override public void deleteMessage(UUID messageId) { - Message findMessage = messageRepository.findById(messageId) - .orElseThrow(MessageNotFoundException::new); - User findUser = userRepository.findById(findMessage.getAuthorId()) - .orElseThrow(UserNotFoundException::new); - Channel findChannel = channelRepository.findById(findMessage.getChannelId()) - .orElseThrow(ChannelNotFoundException::new); - - findMessage.getAttachmentIds() - .forEach(contentRepository::deleteById); - - findUser.removeMessageId(messageId); - findChannel.removeMessageId(messageId); - - userRepository.save(findUser); - channelRepository.save(findChannel); messageRepository.deleteById(messageId); } - private List toMessageInfoList(List messages) { + private List toMessageDtoList(List messages) { return messages.stream() - .map(MessageMapper::toMessageDto) + .map(m -> MessageMapper.toMessageDto(m, getAttachmentIds(m))) + .toList(); + } + + private List getAttachmentIds(Message message) { + return message.getAttachments() + .stream() + .map(BinaryContent::getId) .toList(); } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java index c7f3cee5d..7f7f96117 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java @@ -14,24 +14,17 @@ public static ReadStatusDto toReadStatusDto(ReadStatus readStatus) { readStatus.getId(), readStatus.getCreatedAt(), readStatus.getUpdatedAt(), - readStatus.getUserId(), - readStatus.getChannelId(), + readStatus.getUser().getId(), + readStatus.getChannel().getId(), readStatus.getLastReadAt() ); } public static ReadStatusCreateRequest toReadStatusCreateRequest(ReadStatus readStatus) { return new ReadStatusCreateRequest( - readStatus.getUserId(), - readStatus.getChannelId(), + readStatus.getUser().getId(), + readStatus.getChannel().getId(), readStatus.getLastReadAt() ); } - - public static ReadStatus toReadStatus(ReadStatusCreateRequest readStatusCreateRequest) { - return new ReadStatus( - readStatusCreateRequest.userId(), - readStatusCreateRequest.channelId() - ); - } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index 4d5566900..0467e55cb 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -33,12 +33,11 @@ public ReadStatusDto createReadStatus(ReadStatusCreateRequest statusInfo) { Channel channel = channelRepository.findById(statusInfo.channelId()) .orElseThrow(ChannelNotFoundException::new); - if (readStatusRepository.findByUserIdAndChannelId(user.getId(), channel.getId()) - .isPresent()) { + if (readStatusRepository.existsByUserIdAndChannelId(user.getId(), channel.getId())) { throw new ReadStatusDuplicationException(); } - ReadStatus readStatus = new ReadStatus(user.getId(), channel.getId()); + ReadStatus readStatus = new ReadStatus(user, channel); readStatusRepository.save(readStatus); return ReadStatusMapper.toReadStatusDto(readStatus); } @@ -63,8 +62,8 @@ public List findAll() { .toList(); } - public ReadStatusDto updateReadStatus(UUID statusId) { - ReadStatus readStatus = readStatusRepository.findById(statusId) + public ReadStatusDto updateReadStatus(UUID readStatusId) { + ReadStatus readStatus = readStatusRepository.findById(readStatusId) .orElseThrow(ReadStatusNotFoundException::new); readStatus.updateLastReadAt(); readStatusRepository.save(readStatus); diff --git a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java index 3cf5d222f..33523216b 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java +++ b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java @@ -29,6 +29,7 @@ public class User extends BaseUpdatableEntity { @Column(unique = true, nullable = false, length = 100) private String email; + @Setter @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true) @JoinColumn(name = "profile_id", unique = true) private BinaryContent profile; diff --git a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java index e5ae7819d..4f86fc17c 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java @@ -18,7 +18,7 @@ public static UserDto toUserDto(User user, UserStatus userStatus) { user.getUpdatedAt(), user.getUsername(), user.getEmail(), - user.getProfileId(), + user.getProfile().getId(), userStatus.isOnline() ); } @@ -30,7 +30,7 @@ public static UserResultDto toUserResultDto(User user) { user.getUpdatedAt(), user.getUsername(), user.getEmail(), - user.getProfileId() + user.getProfile() != null ? user.getProfile().getId() : null ); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java index 10b314667..4e1d451c0 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java @@ -17,7 +17,7 @@ public class AuthService { private final UserRepository userRepository; public UserResultDto login(LoginRequest loginRequest) { - User findUser = userRepository.findByName(loginRequest.username()) + User findUser = userRepository.findByUsername(loginRequest.username()) .orElseThrow(UserNotFoundException::new); if (!findUser.getPassword().equals(loginRequest.password())) { throw new AuthenticationFailedException(); diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index 09515de7c..ed475cb52 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -4,6 +4,7 @@ import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; import com.sprint.mission.discodeit.channel.repository.ChannelRepository; +import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.dto.UserResultDto; @@ -29,34 +30,30 @@ public class BasicUserService implements UserService { private final UserRepository userRepository; private final ChannelRepository channelRepository; + private final ReadStatusRepository readStatusRepository; private final BinaryContentRepository contentRepository; private final UserStatusRepository userStatusRepository; @Override public UserResultDto createUser(UserCreateRequest userInfo, Optional image) { - // 유저 이름 & 이메일 검증 + validateUserExist(userInfo.username()); validateEmailExist(userInfo.email()); - // 유저 생성 -> mapper로 대체 가능 User user = new User(userInfo.username(), userInfo.password(), userInfo.email()); - // status 생성 - UserStatus status = new UserStatus(user.getId()); + UserStatus status = new UserStatus(); + status.setUser(user); - // profile image가 존재한다면 생성 if (image.isPresent()) { BinaryContentCreateRequest createInfo = image.get(); byte[] bytes = createInfo.bytes(); BinaryContent profileImage = new BinaryContent(createInfo.fileName(), (long) bytes.length, createInfo.contentType(), bytes); - user.setProfileId(profileImage.getId()); - contentRepository.save(profileImage); + user.setProfile(profileImage); } - // Repo 저장 - userStatusRepository.save(status); userRepository.save(user); return UserMapper.toUserResultDto(user); } @@ -95,7 +92,7 @@ public List findAllWithUserDTO() { public List findAllByChannelId(UUID channelId) { return userRepository.findAll() .stream() - .filter(user -> user.getChannelIds().contains(channelId)) + .filter(user -> readStatusRepository.existsByUserIdAndChannelId(user.getId(), channelId)) .map(UserMapper::toUserResultDto) .toList(); } @@ -116,28 +113,23 @@ public UserResultDto updateUser(UUID userId, UserUpdateRequest request, Optional.ofNullable(request.newEmail()) .ifPresent(findUser::updateEmail); - // profileId가 존재하면 업데이트 if (image.isPresent()) { if (findUser.isProfileImageUploaded()) { - contentRepository.deleteById(findUser.getProfileId()); + contentRepository.deleteById(findUser.getProfile().getId()); } BinaryContentCreateRequest createInfo = image.get(); byte[] bytes = createInfo.bytes(); BinaryContent profileImage = new BinaryContent(createInfo.fileName(), (long) bytes.length, createInfo.contentType(), bytes); - findUser.setProfileId(profileImage.getId()); - contentRepository.save(profileImage); + findUser.setProfile(profileImage); } - // statusRepo.findByUserId로 찾기 UserStatus status = userStatusRepository.findByUserId(findUser.getId()) .map(findStatus -> { findStatus.update(); return findStatus; }) .orElseThrow(UserStatusNotFoundException::new); - // status 업데이트 & save - userStatusRepository.save(status); userRepository.save(findUser); return UserMapper.toUserResultDto(findUser); @@ -145,27 +137,19 @@ public UserResultDto updateUser(UUID userId, UserUpdateRequest request, @Override public void deleteUser(UUID userId) { - User user = userRepository.findById(userId) + userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); - channelRepository.findAllByUserId(userId).forEach(channel -> { - channel.removeUserId(userId); - channelRepository.save(channel); - }); - if (user.isProfileImageUploaded()) { - contentRepository.deleteById(user.getProfileId()); - } - userStatusRepository.deleteByUserId(userId); userRepository.deleteById(userId); } - private void validateUserExist(String userName) { - if (userRepository.findByName(userName).isPresent()) { + private void validateUserExist(String username) { + if (userRepository.existsUserByUsername(username)) { throw new UserDuplicationException(); } } private void validateEmailExist(String email) { - if (userRepository.findByEmail(email).isPresent()) { + if (userRepository.existsByEmail(email)) { throw new EmailDuplicationException(); } } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java index d07480fe5..7e60d5a3e 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java @@ -2,12 +2,12 @@ import com.sprint.mission.discodeit.base.BaseUpdatableEntity; import com.sprint.mission.discodeit.user.entity.User; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; +import jakarta.persistence.Transient; import java.time.Instant; import lombok.Getter; @@ -23,6 +23,7 @@ public class UserStatus extends BaseUpdatableEntity { @Column(nullable = false) private Instant lastActiveAt; + @Transient private final int loginLimitSeconds = 60 * 5; public UserStatus() { diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java index d0e4eea99..dd262caad 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java @@ -13,7 +13,7 @@ public static UserStatusDto toUserStatusInfo(UserStatus userStatus) { userStatus.getId(), userStatus.getCreatedAt(), userStatus.getUpdatedAt(), - userStatus.getUserId(), + userStatus.getUser().getId(), userStatus.getLastActiveAt(), userStatus.isOnline() ); diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index af8bdeb90..d1410c108 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -1,5 +1,6 @@ package com.sprint.mission.discodeit.userstatus.service; +import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; import com.sprint.mission.discodeit.userstatus.dto.UserStatusCreateRequest; @@ -23,13 +24,15 @@ public class UserStatusService { private final UserRepository userRepository; public UserStatusDto createUserStatus(UserStatusCreateRequest statusInfo) { - userRepository.findById(statusInfo.userId()) + User findUser = userRepository.findById(statusInfo.userId()) .orElseThrow(UserNotFoundException::new); - if (userStatusRepository.findByUserId(statusInfo.userId()) - .isPresent()) { + if (userStatusRepository.existsByUserId(statusInfo.userId())) { throw new UserStatusDuplicationException(); } - UserStatus userStatus = new UserStatus(statusInfo.userId()); + + UserStatus userStatus = new UserStatus(); + userStatus.setUser(findUser); + userStatusRepository.save(userStatus); return UserStatusMapper.toUserStatusInfo(userStatus); } From 81651dfa8a1098f9e6b6d40a6cdb3a3377aab4ed Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 6 Mar 2026 11:12:58 +0900 Subject: [PATCH 36/48] =?UTF-8?q?refactor:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=A0=81=EC=9A=A9,=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EA=B0=90=EC=A7=80=20=EB=B0=98=EC=98=81=ED=95=9C=20update=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 --- .../service/BinaryContentService.java | 6 +++++- .../channel/service/BasicChannelService.java | 10 ++++++++-- .../message/service/BasicMessageService.java | 9 +++++---- .../readstatus/service/ReadStatusService.java | 8 ++++++-- .../discodeit/user/service/AuthService.java | 2 ++ .../discodeit/user/service/BasicUserService.java | 16 +++++++--------- .../userstatus/service/UserStatusService.java | 10 +++++++--- 7 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index 17f6e5e16..04b4b986e 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -11,13 +11,16 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service +@Transactional(readOnly = true) public class BinaryContentService { private final BinaryContentRepository contentRepository; + @Transactional public BinaryContentDto createBinaryContent(BinaryContentCreateRequest contentInfo) { byte[] bytes = contentInfo.bytes(); BinaryContent content = new BinaryContent(contentInfo.fileName(), (long) bytes.length, @@ -44,7 +47,7 @@ public List findAll() { .map(BinaryContentMapper::toBinaryContentDto) .toList(); } - + public List findAllByIdIn(BinaryContentsRequest request) { return contentRepository.findAll() .stream() @@ -53,6 +56,7 @@ public List findAllByIdIn(BinaryContentsRequest request) { .toList(); } + @Transactional public void deleteBinaryContent(UUID contentId) { contentRepository.deleteById(contentId); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 9f1423486..20d364b08 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -26,9 +26,11 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service +@Transactional(readOnly = true) public class BasicChannelService implements ChannelService { private final ChannelRepository channelRepository; @@ -36,6 +38,7 @@ public class BasicChannelService implements ChannelService { private final MessageRepository messageRepository; private final ReadStatusRepository readStatusRepository; + @Transactional public ChannelResultDto createPublicChannel(PublicChannelCreateRequest channelInfo) { validateChannelExist(channelInfo.name()); Channel channel = new Channel(channelInfo.name(), ChannelType.PUBLIC, @@ -44,6 +47,7 @@ public ChannelResultDto createPublicChannel(PublicChannelCreateRequest channelIn return ChannelMapper.toChannelResultDto(channel); } + @Transactional public ChannelResultDto createPrivateChannel(PrivateChannelCreateRequest channelInfo) { Channel channel = new Channel(null, ChannelType.PRIVATE, null); channelRepository.save(channel); @@ -83,6 +87,7 @@ public List findAllByUserId(UUID userId) { .toList(); } + @Transactional @Override public ChannelResultDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo) { @@ -99,11 +104,10 @@ public ChannelResultDto updateChannel(UUID channelId, Optional.ofNullable(channelInfo.newDescription()) .ifPresent(findChannel::updateDescription); - channelRepository.save(findChannel); - return ChannelMapper.toChannelResultDto(findChannel); } + @Transactional @Override public void deleteChannel(UUID channelId) { channelRepository.findById(channelId) @@ -111,6 +115,7 @@ public void deleteChannel(UUID channelId) { channelRepository.deleteById(channelId); } + @Transactional @Override public void joinChannel(UUID channelId, UUID userId) { Channel channel = channelRepository.findById(channelId) @@ -121,6 +126,7 @@ public void joinChannel(UUID channelId, UUID userId) { readStatusRepository.save(new ReadStatus(user, channel)); } + @Transactional @Override public void leaveChannel(UUID channelId, UUID userId) { channelRepository.findById(channelId) diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 468d2b2cb..9b4b53397 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -21,9 +21,11 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service +@Transactional(readOnly = true) public class BasicMessageService implements MessageService { private final MessageRepository messageRepository; @@ -31,6 +33,7 @@ public class BasicMessageService implements MessageService { private final ChannelRepository channelRepository; private final BinaryContentRepository contentRepository; + @Transactional @Override public MessageDto createMessage(MessageCreateRequest createInfo, List binaryContentCreateRequests) { @@ -54,9 +57,6 @@ public MessageDto createMessage(MessageCreateRequest createInfo, .toList(); Message message = new Message(createInfo.content(), findChannel, author, attachments); - - userRepository.save(author); - channelRepository.save(findChannel); messageRepository.save(message); return MessageMapper.toMessageDto(message, getAttachmentIds(message)); } @@ -84,6 +84,7 @@ public List findAllByChannelId(UUID channelId) { return toMessageDtoList(messageRepository.findAllByChannelId(channelId)); } + @Transactional @Override public MessageDto updateMessage(UUID messageId, MessageUpdateRequest messageInfo) { Message findMessage = messageRepository.findById(messageId) @@ -92,10 +93,10 @@ public MessageDto updateMessage(UUID messageId, MessageUpdateRequest messageInfo Optional.ofNullable(messageInfo.newContent()) .ifPresent(findMessage::update); - messageRepository.save(findMessage); return MessageMapper.toMessageDto(findMessage, getAttachmentIds(findMessage)); } + @Transactional @Override public void deleteMessage(UUID messageId) { messageRepository.deleteById(messageId); diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index 0467e55cb..16d10063d 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -18,15 +18,18 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service +@Transactional(readOnly = true) public class ReadStatusService { private final ReadStatusRepository readStatusRepository; private final UserRepository userRepository; private final ChannelRepository channelRepository; + @Transactional public ReadStatusDto createReadStatus(ReadStatusCreateRequest statusInfo) { User user = userRepository.findById(statusInfo.userId()) .orElseThrow(UserNotFoundException::new); @@ -62,22 +65,23 @@ public List findAll() { .toList(); } + @Transactional public ReadStatusDto updateReadStatus(UUID readStatusId) { ReadStatus readStatus = readStatusRepository.findById(readStatusId) .orElseThrow(ReadStatusNotFoundException::new); readStatus.updateLastReadAt(); - readStatusRepository.save(readStatus); return ReadStatusMapper.toReadStatusDto(readStatus); } + @Transactional public ReadStatusDto updateReadStatus(UUID readStatusId, ReadStatusUpdateRequest request) { ReadStatus readStatus = readStatusRepository.findById(readStatusId) .orElseThrow(ReadStatusNotFoundException::new); readStatus.update(request.newLastReadAt()); - readStatusRepository.save(readStatus); return ReadStatusMapper.toReadStatusDto(readStatus); } + @Transactional public void deleteReadStatus(UUID statusId) { readStatusRepository.deleteById(statusId); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java index 4e1d451c0..cf7232473 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java @@ -9,9 +9,11 @@ import com.sprint.mission.discodeit.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service +@Transactional(readOnly = true) public class AuthService { private final UserRepository userRepository; diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index ed475cb52..7b1c00c5d 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -3,7 +3,6 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; -import com.sprint.mission.discodeit.channel.repository.ChannelRepository; import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; @@ -23,17 +22,19 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service +@Transactional(readOnly = true) public class BasicUserService implements UserService { private final UserRepository userRepository; - private final ChannelRepository channelRepository; private final ReadStatusRepository readStatusRepository; private final BinaryContentRepository contentRepository; private final UserStatusRepository userStatusRepository; + @Transactional @Override public UserResultDto createUser(UserCreateRequest userInfo, Optional image) { @@ -97,6 +98,7 @@ public List findAllByChannelId(UUID channelId) { .toList(); } + @Transactional @Override public UserResultDto updateUser(UUID userId, UserUpdateRequest request, Optional image) { @@ -124,17 +126,13 @@ public UserResultDto updateUser(UUID userId, UserUpdateRequest request, findUser.setProfile(profileImage); } - UserStatus status = userStatusRepository.findByUserId(findUser.getId()) - .map(findStatus -> { - findStatus.update(); - return findStatus; - }) - .orElseThrow(UserStatusNotFoundException::new); + userStatusRepository.findByUserId(findUser.getId()) + .ifPresent(UserStatus::update); - userRepository.save(findUser); return UserMapper.toUserResultDto(findUser); } + @Transactional @Override public void deleteUser(UUID userId) { userRepository.findById(userId) diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index d1410c108..a6c4be70d 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -15,14 +15,17 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service +@Transactional(readOnly = true) public class UserStatusService { private final UserStatusRepository userStatusRepository; private final UserRepository userRepository; + @Transactional public UserStatusDto createUserStatus(UserStatusCreateRequest statusInfo) { User findUser = userRepository.findById(statusInfo.userId()) .orElseThrow(UserNotFoundException::new); @@ -56,31 +59,32 @@ public List findAll() { .toList(); } + @Transactional public UserStatusDto update(UUID userStatusId) { UserStatus userStatus = userStatusRepository.findById(userStatusId) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(); - userStatusRepository.save(userStatus); return UserStatusMapper.toUserStatusInfo(userStatus); } + @Transactional public UserStatusDto updateUserStatusByUserId(UUID userId) { UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(); - userStatusRepository.save(userStatus); return UserStatusMapper.toUserStatusInfo(userStatus); } + @Transactional public UserStatusDto update(UUID userId, UserStatusUpdateRequest request) { UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(request.newLastActiveAt()); - userStatusRepository.save(userStatus); return UserStatusMapper.toUserStatusInfo(userStatus); } + @Transactional void deleteUserStatus(UUID statusId) { userStatusRepository.deleteById(statusId); } From 9b75c0f89db5c22bd829f1c9fef6e77453f6aa99 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 6 Mar 2026 17:06:33 +0900 Subject: [PATCH 37/48] =?UTF-8?q?refactor:=20=EA=B8=B0=EC=A1=B4=20mapper?= =?UTF-8?q?=EC=97=90=20mapstruct=20=EC=A0=81=EC=9A=A9=20&=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=BD=94=EB=93=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 --- build.gradle | 2 + .../binarycontent/dto/BinaryContentDto.java | 2 - .../mapper/BinaryContentMapper.java | 20 ++--- .../service/BinaryContentService.java | 11 +-- .../channel/controller/ChannelController.java | 13 ++-- .../discodeit/channel/dto/ChannelDto.java | 7 +- .../channel/dto/ChannelResultDto.java | 16 ---- .../channel/mapper/ChannelMapper.java | 73 ++----------------- .../channel/service/BasicChannelService.java | 46 ++++++------ .../channel/service/ChannelService.java | 7 +- .../discodeit/message/dto/MessageDto.java | 6 +- .../message/mapper/MessageMapper.java | 47 ++---------- .../message/service/BasicMessageService.java | 16 ++-- .../readstatus/dto/ReadStatusDto.java | 2 - .../readstatus/mapper/ReadStatusMapper.java | 30 ++------ .../readstatus/service/ReadStatusService.java | 13 ++-- .../user/controller/AuthController.java | 6 +- .../user/controller/UserController.java | 11 ++- .../mission/discodeit/user/dto/UserDto.java | 6 +- .../discodeit/user/dto/UserResultDto.java | 15 ---- .../discodeit/user/mapper/UserMapper.java | 44 ++--------- .../discodeit/user/service/AuthService.java | 7 +- .../user/service/BasicUserService.java | 38 ++++------ .../discodeit/user/service/UserService.java | 9 +-- .../userstatus/dto/UserStatusDto.java | 5 +- .../userstatus/entity/UserStatus.java | 6 +- .../userstatus/mapper/UserStatusMapper.java | 22 ++---- .../userstatus/service/UserStatusService.java | 15 ++-- 28 files changed, 144 insertions(+), 351 deletions(-) delete mode 100644 src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/user/dto/UserResultDto.java diff --git a/build.gradle b/build.gradle index ecd3d8998..970dc5d82 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,8 @@ dependencies { implementation 'org.postgresql:postgresql:42.7.3' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + implementation 'org.mapstruct:mapstruct:1.6.3' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java index 56f91fb17..f3a4652ca 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java @@ -1,11 +1,9 @@ package com.sprint.mission.discodeit.binarycontent.dto; -import java.time.Instant; import java.util.UUID; public record BinaryContentDto( UUID id, - Instant createdAt, String fileName, Long size, String contentType, diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java index f6370931c..0b9a735cc 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java @@ -2,20 +2,10 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; +import org.mapstruct.Mapper; -public class BinaryContentMapper { +@Mapper(componentModel = "spring") +public interface BinaryContentMapper { - private BinaryContentMapper() { - } - - public static BinaryContentDto toBinaryContentDto(BinaryContent binaryContent) { - return new BinaryContentDto( - binaryContent.getId(), - binaryContent.getCreatedAt(), - binaryContent.getFileName(), - binaryContent.getSize(), - binaryContent.getContentType(), - binaryContent.getBytes() - ); - } -} + BinaryContentDto toDto(BinaryContent binaryContent); +} \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index 04b4b986e..c9970566a 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -19,6 +19,7 @@ public class BinaryContentService { private final BinaryContentRepository contentRepository; + private final BinaryContentMapper binaryContentMapper; @Transactional public BinaryContentDto createBinaryContent(BinaryContentCreateRequest contentInfo) { @@ -27,13 +28,13 @@ public BinaryContentDto createBinaryContent(BinaryContentCreateRequest contentIn contentInfo.contentType(), bytes); contentRepository.save(content); - return BinaryContentMapper.toBinaryContentDto(content); + return binaryContentMapper.toDto(content); } public BinaryContentDto findBinaryContent(UUID contentId) { BinaryContent content = contentRepository.findById(contentId) .orElseThrow(BinaryContentNotFoundException::new); - return BinaryContentMapper.toBinaryContentDto(content); + return binaryContentMapper.toDto(content); } public BinaryContent findBinaryContentEntity(UUID contentId) { @@ -44,15 +45,15 @@ public BinaryContent findBinaryContentEntity(UUID contentId) { public List findAll() { return contentRepository.findAll() .stream() - .map(BinaryContentMapper::toBinaryContentDto) + .map(binaryContentMapper::toDto) .toList(); } - + public List findAllByIdIn(BinaryContentsRequest request) { return contentRepository.findAll() .stream() .filter(content -> request.ids().contains(content.getId())) - .map(BinaryContentMapper::toBinaryContentDto) + .map(binaryContentMapper::toDto) .toList(); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java index c185949e0..fad2f0c94 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/controller/ChannelController.java @@ -1,7 +1,6 @@ package com.sprint.mission.discodeit.channel.controller; import com.sprint.mission.discodeit.channel.dto.ChannelDto; -import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; @@ -44,12 +43,12 @@ public class ChannelController { responseCode = "201", description = "Public Channel이 성공적으로 생성됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = ChannelResultDto.class) + schema = @Schema(implementation = ChannelDto.class) ) ) }) @PostMapping(value = "/public", consumes = "application/json") - public ResponseEntity create_3( + public ResponseEntity create_3( @RequestBody PublicChannelCreateRequest channelInfo ) { return ResponseEntity.status(201).body(channelService.createPublicChannel(channelInfo)); @@ -61,12 +60,12 @@ public ResponseEntity create_3( responseCode = "201", description = "Private Channel이 성공적으로 생성됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = ChannelResultDto.class) + schema = @Schema(implementation = ChannelDto.class) ) ) }) @PostMapping(value = "/private", consumes = "application/json") - public ResponseEntity create_4( + public ResponseEntity create_4( @RequestBody PrivateChannelCreateRequest channelInfo ) { return ResponseEntity.status(201).body(channelService.createPrivateChannel(channelInfo)); @@ -114,12 +113,12 @@ public ResponseEntity> findAll_1( responseCode = "200", description = "Channel 정보가 성공적으로 수정됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = ChannelResultDto.class) + schema = @Schema(implementation = ChannelDto.class) ) ) }) @PatchMapping(value = "/{channelId}", consumes = "application/json") - public ResponseEntity update_3( + public ResponseEntity update_3( @Parameter(description = "수정할 Channel ID") @PathVariable UUID channelId, @RequestBody PublicChannelUpdateRequest channelInfo ) { diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java index 24c7bd88b..f35edd884 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelDto.java @@ -1,17 +1,18 @@ package com.sprint.mission.discodeit.channel.dto; import com.sprint.mission.discodeit.channel.entity.ChannelType; +import com.sprint.mission.discodeit.user.dto.UserDto; import java.time.Instant; import java.util.List; import java.util.UUID; public record ChannelDto( UUID id, - String name, ChannelType type, + String name, String description, - Instant lastMessageAt, - List participantIds + List participants, + Instant lastMessageAt ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java b/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java deleted file mode 100644 index 348663064..000000000 --- a/src/main/java/com/sprint/mission/discodeit/channel/dto/ChannelResultDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.sprint.mission.discodeit.channel.dto; - -import com.sprint.mission.discodeit.channel.entity.ChannelType; -import java.time.Instant; -import java.util.UUID; - -public record ChannelResultDto( - UUID id, - Instant createdAt, - Instant updatedAt, - ChannelType type, - String name, - String description -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index dbd562e53..7509175be 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -1,76 +1,15 @@ package com.sprint.mission.discodeit.channel.mapper; import com.sprint.mission.discodeit.channel.dto.ChannelDto; -import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; -import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.entity.Channel; -import com.sprint.mission.discodeit.channel.entity.ChannelType; +import com.sprint.mission.discodeit.user.dto.UserDto; import java.time.Instant; import java.util.List; -import java.util.UUID; +import org.mapstruct.Mapper; -public final class ChannelMapper { +@Mapper(componentModel = "spring") +public interface ChannelMapper { - private ChannelMapper() { - } - - public static ChannelDto toChannelDto(Channel channel, Instant lastMessageTime, - List participantIds) { - if (channel.getType() == ChannelType.PRIVATE) { - return new ChannelDto( - channel.getId(), - null, - channel.getType(), - null, - lastMessageTime, - null); - } else { - return new ChannelDto( - channel.getId(), - channel.getName(), - channel.getType(), - channel.getDescription(), - null, - participantIds - ); - } - } - - public static ChannelResultDto toChannelResultDto(Channel channel) { - if (channel.getType() == ChannelType.PRIVATE) { - return new ChannelResultDto( - channel.getId(), - channel.getCreatedAt(), - channel.getUpdatedAt(), - channel.getType(), - null, - null - ); - } else { - return new ChannelResultDto( - channel.getId(), - channel.getCreatedAt(), - channel.getUpdatedAt(), - channel.getType(), - channel.getName(), - channel.getDescription() - ); - } - } - - public static Channel toChannel(ChannelDto channelDto) { - return new Channel( - channelDto.name(), - channelDto.type(), - channelDto.description() - ); - } - - public static Channel toChannel(PrivateChannelCreateRequest request) { - return new Channel( - null, - ChannelType.PRIVATE, - null - ); - } + ChannelDto toDto(Channel channel, Instant lastMessageAt, + List participants); } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 20d364b08..9596c1317 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -2,7 +2,6 @@ import com.sprint.mission.discodeit.base.BaseEntity; import com.sprint.mission.discodeit.channel.dto.ChannelDto; -import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; @@ -17,8 +16,10 @@ import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusNotFoundException; import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; +import com.sprint.mission.discodeit.user.mapper.UserMapper; import com.sprint.mission.discodeit.user.repository.UserRepository; import java.time.Instant; import java.util.List; @@ -35,42 +36,44 @@ public class BasicChannelService implements ChannelService { private final ChannelRepository channelRepository; private final UserRepository userRepository; - private final MessageRepository messageRepository; private final ReadStatusRepository readStatusRepository; + private final MessageRepository messageRepository; + private final UserMapper userMapper; + private final ChannelMapper channelMapper; @Transactional - public ChannelResultDto createPublicChannel(PublicChannelCreateRequest channelInfo) { + public ChannelDto createPublicChannel(PublicChannelCreateRequest channelInfo) { validateChannelExist(channelInfo.name()); Channel channel = new Channel(channelInfo.name(), ChannelType.PUBLIC, channelInfo.description()); channelRepository.save(channel); - return ChannelMapper.toChannelResultDto(channel); + return channelMapper.toDto(channel, + getLastMessageAt(channel.getId()), getParticipants(channel.getId())); } @Transactional - public ChannelResultDto createPrivateChannel(PrivateChannelCreateRequest channelInfo) { + public ChannelDto createPrivateChannel(PrivateChannelCreateRequest channelInfo) { Channel channel = new Channel(null, ChannelType.PRIVATE, null); channelRepository.save(channel); channelInfo.participantIds().forEach(userId -> joinChannel(channel.getId(), userId)); - return ChannelMapper.toChannelResultDto(channel); + return channelMapper.toDto(channel, + getLastMessageAt(channel.getId()), getParticipants(channel.getId())); } @Override public ChannelDto findChannel(UUID channelId) { Channel channel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); - return ChannelMapper.toChannelDto(channel, getLastMessageTime(channelId), - getParticipantIds(channelId)); + return channelMapper.toDto(channel, + getLastMessageAt(channel.getId()), getParticipants(channel.getId())); } @Override public List findAll() { return channelRepository.findAll() .stream() - .map(channel -> - ChannelMapper.toChannelDto(channel, getLastMessageTime(channel.getId()), - getParticipantIds(channel.getId())) - ) + .map(channel -> channelMapper.toDto(channel, + getLastMessageAt(channel.getId()), getParticipants(channel.getId()))) .toList(); } @@ -80,16 +83,14 @@ public List findAllByUserId(UUID userId) { .stream() .filter(channel -> channel.getType() == ChannelType.PUBLIC || readStatusRepository.existsByUserIdAndChannelId(userId, channel.getId())) - .map(channel -> - ChannelMapper.toChannelDto(channel, getLastMessageTime(channel.getId()), - getParticipantIds(channel.getId())) - ) + .map(channel -> channelMapper.toDto(channel, + getLastMessageAt(channel.getId()), getParticipants(channel.getId()))) .toList(); } @Transactional @Override - public ChannelResultDto updateChannel(UUID channelId, + public ChannelDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo) { Channel findChannel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); @@ -104,7 +105,8 @@ public ChannelResultDto updateChannel(UUID channelId, Optional.ofNullable(channelInfo.newDescription()) .ifPresent(findChannel::updateDescription); - return ChannelMapper.toChannelResultDto(findChannel); + return channelMapper.toDto(findChannel, + getLastMessageAt(findChannel.getId()), getParticipants(findChannel.getId())); } @Transactional @@ -145,16 +147,18 @@ private void validateChannelExist(String name) { } } - private Instant getLastMessageTime(UUID channelId) { + private Instant getLastMessageAt(UUID channelId) { return messageRepository.findFirstByChannel_IdOrderByCreatedAtAsc(channelId) .map(BaseEntity::getCreatedAt) .orElse(null); } - private List getParticipantIds(UUID channelId) { + private List getParticipants(UUID channelId) { return readStatusRepository.findAllByChannelId(channelId) .stream() - .map(readStatus -> readStatus.getUser().getId()) + .map(readStatus -> + userMapper.toDto(readStatus.getUser()) + ) .toList(); } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java index 6d26a823e..7b8db87fd 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java @@ -1,7 +1,6 @@ package com.sprint.mission.discodeit.channel.service; import com.sprint.mission.discodeit.channel.dto.ChannelDto; -import com.sprint.mission.discodeit.channel.dto.ChannelResultDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; @@ -10,9 +9,9 @@ public interface ChannelService { - ChannelResultDto createPublicChannel(PublicChannelCreateRequest channelInfo); + ChannelDto createPublicChannel(PublicChannelCreateRequest channelInfo); - ChannelResultDto createPrivateChannel(PrivateChannelCreateRequest channelInfo); + ChannelDto createPrivateChannel(PrivateChannelCreateRequest channelInfo); ChannelDto findChannel(UUID channelId); @@ -20,7 +19,7 @@ public interface ChannelService { List findAllByUserId(UUID userId); - ChannelResultDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo); + ChannelDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo); void deleteChannel(UUID channelId); diff --git a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java index 407490aa0..3b7ae7e55 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java +++ b/src/main/java/com/sprint/mission/discodeit/message/dto/MessageDto.java @@ -1,5 +1,7 @@ package com.sprint.mission.discodeit.message.dto; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; +import com.sprint.mission.discodeit.user.dto.UserDto; import java.time.Instant; import java.util.List; import java.util.UUID; @@ -9,9 +11,9 @@ public record MessageDto( Instant createdAt, Instant updatedAt, String content, - UUID authorId, UUID channelId, - List attachmentIds + UserDto author, + List attachments ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java index 1b0e1e901..753dfbd0d 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java @@ -1,47 +1,14 @@ package com.sprint.mission.discodeit.message.mapper; -import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageDto; -import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; import com.sprint.mission.discodeit.message.entity.Message; -import java.util.List; -import java.util.UUID; +import com.sprint.mission.discodeit.user.mapper.UserMapper; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; -public final class MessageMapper { +@Mapper(componentModel = "spring", uses = UserMapper.class) +public interface MessageMapper { - private MessageMapper() { - } - - public static MessageDto toMessageDto(Message message, List attachmentIds) { - return new MessageDto( - message.getId(), - message.getCreatedAt(), - message.getUpdatedAt(), - message.getContent(), - message.getAuthor() != null ? message.getAuthor().getId() : null, - message.getChannel().getId(), - attachmentIds - ); - } - - public static MessageCreateRequest toMessageCreateRequest( - String content, - UUID authorId, - UUID channelId, - List attachments - ) { - return new MessageCreateRequest( - content, - authorId, - channelId - ); - } - - public static MessageUpdateRequest toMessageUpdateRequest( - String content - ) { - return new MessageUpdateRequest( - content - ); - } + @Mapping(target = "channelId", source = "channel.id") + MessageDto toDto(Message message); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 9b4b53397..134326658 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -32,6 +32,7 @@ public class BasicMessageService implements MessageService { private final UserRepository userRepository; private final ChannelRepository channelRepository; private final BinaryContentRepository contentRepository; + private final MessageMapper messageMapper; @Transactional @Override @@ -58,7 +59,7 @@ public MessageDto createMessage(MessageCreateRequest createInfo, Message message = new Message(createInfo.content(), findChannel, author, attachments); messageRepository.save(message); - return MessageMapper.toMessageDto(message, getAttachmentIds(message)); + return messageMapper.toDto(message); } @Override @@ -66,7 +67,7 @@ public MessageDto findMessage(UUID messageId) { Message message = messageRepository.findById(messageId) .orElseThrow(MessageNotFoundException::new); - return MessageMapper.toMessageDto(message, getAttachmentIds(message)); + return messageMapper.toDto(message); } @Override @@ -93,7 +94,7 @@ public MessageDto updateMessage(UUID messageId, MessageUpdateRequest messageInfo Optional.ofNullable(messageInfo.newContent()) .ifPresent(findMessage::update); - return MessageMapper.toMessageDto(findMessage, getAttachmentIds(findMessage)); + return messageMapper.toDto(findMessage); } @Transactional @@ -104,14 +105,7 @@ public void deleteMessage(UUID messageId) { private List toMessageDtoList(List messages) { return messages.stream() - .map(m -> MessageMapper.toMessageDto(m, getAttachmentIds(m))) - .toList(); - } - - private List getAttachmentIds(Message message) { - return message.getAttachments() - .stream() - .map(BinaryContent::getId) + .map(messageMapper::toDto) .toList(); } } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java index 3615bfe73..1e09762c6 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/dto/ReadStatusDto.java @@ -5,8 +5,6 @@ public record ReadStatusDto( UUID id, - Instant createdAt, - Instant updatedAt, UUID userId, UUID channelId, Instant lastReadAt diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java index 7f7f96117..2b1c32289 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/mapper/ReadStatusMapper.java @@ -1,30 +1,14 @@ package com.sprint.mission.discodeit.readstatus.mapper; -import com.sprint.mission.discodeit.readstatus.dto.ReadStatusCreateRequest; import com.sprint.mission.discodeit.readstatus.dto.ReadStatusDto; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; -public class ReadStatusMapper { +@Mapper(componentModel = "spring") +public interface ReadStatusMapper { - private ReadStatusMapper() { - } - - public static ReadStatusDto toReadStatusDto(ReadStatus readStatus) { - return new ReadStatusDto( - readStatus.getId(), - readStatus.getCreatedAt(), - readStatus.getUpdatedAt(), - readStatus.getUser().getId(), - readStatus.getChannel().getId(), - readStatus.getLastReadAt() - ); - } - - public static ReadStatusCreateRequest toReadStatusCreateRequest(ReadStatus readStatus) { - return new ReadStatusCreateRequest( - readStatus.getUser().getId(), - readStatus.getChannel().getId(), - readStatus.getLastReadAt() - ); - } + @Mapping(target = "userId", source = "user.id") + @Mapping(target = "channelId", source = "channel.id") + ReadStatusDto toDto(ReadStatus readStatus); } diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index 16d10063d..1eb92fbd8 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -28,6 +28,7 @@ public class ReadStatusService { private final ReadStatusRepository readStatusRepository; private final UserRepository userRepository; private final ChannelRepository channelRepository; + private final ReadStatusMapper readStatusMapper; @Transactional public ReadStatusDto createReadStatus(ReadStatusCreateRequest statusInfo) { @@ -42,26 +43,26 @@ public ReadStatusDto createReadStatus(ReadStatusCreateRequest statusInfo) { ReadStatus readStatus = new ReadStatus(user, channel); readStatusRepository.save(readStatus); - return ReadStatusMapper.toReadStatusDto(readStatus); + return readStatusMapper.toDto(readStatus); } public ReadStatusDto find(UUID statusId) { ReadStatus readStatus = readStatusRepository.findById(statusId) .orElseThrow(ReadStatusNotFoundException::new); - return ReadStatusMapper.toReadStatusDto(readStatus); + return readStatusMapper.toDto(readStatus); } public List findAllByUserId(UUID userId) { return readStatusRepository.findAllByUserId(userId) .stream() - .map(ReadStatusMapper::toReadStatusDto) + .map(readStatusMapper::toDto) .toList(); } public List findAll() { return readStatusRepository.findAll() .stream() - .map(ReadStatusMapper::toReadStatusDto) + .map(readStatusMapper::toDto) .toList(); } @@ -70,7 +71,7 @@ public ReadStatusDto updateReadStatus(UUID readStatusId) { ReadStatus readStatus = readStatusRepository.findById(readStatusId) .orElseThrow(ReadStatusNotFoundException::new); readStatus.updateLastReadAt(); - return ReadStatusMapper.toReadStatusDto(readStatus); + return readStatusMapper.toDto(readStatus); } @Transactional @@ -78,7 +79,7 @@ public ReadStatusDto updateReadStatus(UUID readStatusId, ReadStatusUpdateRequest ReadStatus readStatus = readStatusRepository.findById(readStatusId) .orElseThrow(ReadStatusNotFoundException::new); readStatus.update(request.newLastReadAt()); - return ReadStatusMapper.toReadStatusDto(readStatus); + return readStatusMapper.toDto(readStatus); } @Transactional diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java index b2703956d..5ed65f8b1 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/AuthController.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.user.controller; import com.sprint.mission.discodeit.user.dto.LoginRequest; -import com.sprint.mission.discodeit.user.dto.UserResultDto; +import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.service.AuthService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -31,7 +31,7 @@ public class AuthController { @ApiResponse(responseCode = "200", description = "로그인 성공", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = UserResultDto.class) + schema = @Schema(implementation = UserDto.class) ) ), @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", @@ -48,7 +48,7 @@ public class AuthController { ) }) @PostMapping(value = "/login", consumes = "application/json") - public ResponseEntity login(@RequestBody LoginRequest loginInfo) { + public ResponseEntity login(@RequestBody LoginRequest loginInfo) { return ResponseEntity.ok(authService.login(loginInfo)); } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java index 8db3afec1..9f4bb51bc 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java +++ b/src/main/java/com/sprint/mission/discodeit/user/controller/UserController.java @@ -5,7 +5,6 @@ import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import com.sprint.mission.discodeit.user.service.UserService; import com.sprint.mission.discodeit.userstatus.dto.UserStatusDto; @@ -49,7 +48,7 @@ public class UserController { private final UserStatusService userStatusService; @GetMapping(value = "/{userId}") - public ResponseEntity getUser(@PathVariable UUID userId) { + public ResponseEntity getUser(@PathVariable UUID userId) { return ResponseEntity.ok(userService.findUser(userId)); } @@ -78,7 +77,7 @@ public ResponseEntity> findAll() { @ApiResponse(responseCode = "201", description = "User가 성공적으로 생성됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = UserResultDto.class) + schema = @Schema(implementation = UserDto.class) ) ), @ApiResponse(responseCode = "400", description = "같은 email 또는 username를 사용하는 User가 이미 존재함", @@ -89,7 +88,7 @@ public ResponseEntity> findAll() { ) }) @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity create( + public ResponseEntity create( @Parameter() @RequestPart UserCreateRequest userCreateRequest, @Parameter(description = "User 프로필 이미지") @RequestPart(required = false) MultipartFile profile ) { @@ -136,12 +135,12 @@ public ResponseEntity delete( @ApiResponse(responseCode = "200", description = "User 정보가 성공적으로 수정됨", content = @Content( mediaType = MediaType.ALL_VALUE, - schema = @Schema(implementation = UserResultDto.class) + schema = @Schema(implementation = UserDto.class) ) ) }) @PatchMapping(value = "/{userId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity update( + public ResponseEntity update( @Parameter(description = "수정할 User ID") @PathVariable UUID userId, @RequestPart UserUpdateRequest userUpdateRequest, @Parameter(description = "수정할 User 프로필 이미지") @RequestPart(required = false) MultipartFile profile diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserDto.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserDto.java index df2df97dc..b4cb852c0 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserDto.java +++ b/src/main/java/com/sprint/mission/discodeit/user/dto/UserDto.java @@ -1,15 +1,13 @@ package com.sprint.mission.discodeit.user.dto; -import java.time.Instant; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; import java.util.UUID; public record UserDto( UUID id, - Instant createdAt, - Instant updatedAt, String username, String email, - UUID profileId, + BinaryContentDto profile, Boolean online ) { diff --git a/src/main/java/com/sprint/mission/discodeit/user/dto/UserResultDto.java b/src/main/java/com/sprint/mission/discodeit/user/dto/UserResultDto.java deleted file mode 100644 index cadea7dc2..000000000 --- a/src/main/java/com/sprint/mission/discodeit/user/dto/UserResultDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.sprint.mission.discodeit.user.dto; - -import java.time.Instant; -import java.util.UUID; - -public record UserResultDto( - UUID id, - Instant createdAt, - Instant updatedAt, - String username, - String email, - UUID profileId -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java index 4f86fc17c..730a6e6ba 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java @@ -1,44 +1,14 @@ package com.sprint.mission.discodeit.user.mapper; -import com.sprint.mission.discodeit.user.dto.UserCreateRequest; +import com.sprint.mission.discodeit.binarycontent.mapper.BinaryContentMapper; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.entity.User; -import com.sprint.mission.discodeit.userstatus.entity.UserStatus; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; -public class UserMapper { +@Mapper(componentModel = "spring", uses = BinaryContentMapper.class) +public interface UserMapper { - private UserMapper() { - } - - public static UserDto toUserDto(User user, UserStatus userStatus) { - return new UserDto( - user.getId(), - user.getCreatedAt(), - user.getUpdatedAt(), - user.getUsername(), - user.getEmail(), - user.getProfile().getId(), - userStatus.isOnline() - ); - } - - public static UserResultDto toUserResultDto(User user) { - return new UserResultDto( - user.getId(), - user.getCreatedAt(), - user.getUpdatedAt(), - user.getUsername(), - user.getEmail(), - user.getProfile() != null ? user.getProfile().getId() : null - ); - } - - public static User toUser(UserCreateRequest userInfo) { - return new User( - userInfo.username(), - userInfo.password(), - userInfo.email() - ); - } + @Mapping(target = "online", expression = "java(user.getUserStatus().isOnline())") + UserDto toDto(User user); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java index cf7232473..9d8db61c5 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/AuthService.java @@ -1,7 +1,7 @@ package com.sprint.mission.discodeit.user.service; import com.sprint.mission.discodeit.user.dto.LoginRequest; -import com.sprint.mission.discodeit.user.dto.UserResultDto; +import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.AuthenticationFailedException; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; @@ -17,13 +17,14 @@ public class AuthService { private final UserRepository userRepository; + private final UserMapper userMapper; - public UserResultDto login(LoginRequest loginRequest) { + public UserDto login(LoginRequest loginRequest) { User findUser = userRepository.findByUsername(loginRequest.username()) .orElseThrow(UserNotFoundException::new); if (!findUser.getPassword().equals(loginRequest.password())) { throw new AuthenticationFailedException(); } - return UserMapper.toUserResultDto(findUser); + return userMapper.toDto(findUser); } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index 7b1c00c5d..ce0323b67 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -6,7 +6,6 @@ import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.EmailDuplicationException; @@ -15,7 +14,6 @@ import com.sprint.mission.discodeit.user.mapper.UserMapper; import com.sprint.mission.discodeit.user.repository.UserRepository; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; -import com.sprint.mission.discodeit.userstatus.exception.UserStatusNotFoundException; import com.sprint.mission.discodeit.userstatus.repository.UserStatusRepository; import java.util.List; import java.util.Optional; @@ -33,16 +31,18 @@ public class BasicUserService implements UserService { private final ReadStatusRepository readStatusRepository; private final BinaryContentRepository contentRepository; private final UserStatusRepository userStatusRepository; + private final UserMapper userMapper; @Transactional @Override - public UserResultDto createUser(UserCreateRequest userInfo, + public UserDto createUser(UserCreateRequest request, Optional image) { - validateUserExist(userInfo.username()); - validateEmailExist(userInfo.email()); + validateUserExist(request.username()); + validateEmailExist(request.email()); - User user = new User(userInfo.username(), userInfo.password(), userInfo.email()); + User user = new User(request.username(), request.password(), request.email()); + // User user = userMapper.toEntity(request) UserStatus status = new UserStatus(); status.setUser(user); @@ -56,51 +56,43 @@ public UserResultDto createUser(UserCreateRequest userInfo, } userRepository.save(user); - return UserMapper.toUserResultDto(user); + return userMapper.toDto(user); } @Override - public UserResultDto findUser(UUID userId) { + public UserDto findUser(UUID userId) { User user = userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); - return UserMapper.toUserResultDto(user); + return userMapper.toDto(user); } @Override public List findAll() { return userRepository.findAll() .stream() - .map(user -> { - UserStatus status = userStatusRepository.findByUserId(user.getId()) - .orElseThrow(UserStatusNotFoundException::new); - return UserMapper.toUserDto(user, status); - }) + .map(userMapper::toDto) .toList(); } public List findAllWithUserDTO() { return userRepository.findAll() .stream() - .map(user -> { - UserStatus status = userStatusRepository.findByUserId(user.getId()) - .orElseThrow(UserStatusNotFoundException::new); - return UserMapper.toUserDto(user, status); - }) + .map(userMapper::toDto) .toList(); } @Override - public List findAllByChannelId(UUID channelId) { + public List findAllByChannelId(UUID channelId) { return userRepository.findAll() .stream() .filter(user -> readStatusRepository.existsByUserIdAndChannelId(user.getId(), channelId)) - .map(UserMapper::toUserResultDto) + .map(userMapper::toDto) .toList(); } @Transactional @Override - public UserResultDto updateUser(UUID userId, UserUpdateRequest request, + public UserDto updateUser(UUID userId, UserUpdateRequest request, Optional image) { Optional.ofNullable(request.newUsername()) .ifPresent(this::validateUserExist); @@ -129,7 +121,7 @@ public UserResultDto updateUser(UUID userId, UserUpdateRequest request, userStatusRepository.findByUserId(findUser.getId()) .ifPresent(UserStatus::update); - return UserMapper.toUserResultDto(findUser); + return userMapper.toDto(findUser); } @Transactional diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java index af352b2a9..b6a11f271 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/UserService.java @@ -3,7 +3,6 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; -import com.sprint.mission.discodeit.user.dto.UserResultDto; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import java.util.List; import java.util.Optional; @@ -11,17 +10,17 @@ public interface UserService { - UserResultDto createUser(UserCreateRequest userInfo, Optional image); + UserDto createUser(UserCreateRequest userInfo, Optional image); - UserResultDto findUser(UUID userId); + UserDto findUser(UUID userId); List findAll(); List findAllWithUserDTO(); - List findAllByChannelId(UUID channelId); + List findAllByChannelId(UUID channelId); - UserResultDto updateUser(UUID userId, UserUpdateRequest updateInfo, + UserDto updateUser(UUID userId, UserUpdateRequest updateInfo, Optional image); void deleteUser(UUID userId); diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java index 44349c868..4f418d0ff 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/dto/UserStatusDto.java @@ -5,11 +5,8 @@ public record UserStatusDto( UUID id, - Instant createdAt, - Instant updatedAt, UUID userId, - Instant lastActiveAt, - boolean online + Instant lastActiveAt ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java index 7e60d5a3e..2d5556ced 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/entity/UserStatus.java @@ -16,16 +16,14 @@ @Table(name = "user_statuses") public class UserStatus extends BaseUpdatableEntity { + @Transient + private final int loginLimitSeconds = 60 * 5; @OneToOne(optional = false) @JoinColumn(name = "user_id") private User user; - @Column(nullable = false) private Instant lastActiveAt; - @Transient - private final int loginLimitSeconds = 60 * 5; - public UserStatus() { lastActiveAt = Instant.now(); } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java index dd262caad..ffe2f7f65 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/mapper/UserStatusMapper.java @@ -2,22 +2,12 @@ import com.sprint.mission.discodeit.userstatus.dto.UserStatusDto; import com.sprint.mission.discodeit.userstatus.entity.UserStatus; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; -public class UserStatusMapper { - - private UserStatusMapper() { - } - - public static UserStatusDto toUserStatusInfo(UserStatus userStatus) { - return new UserStatusDto( - userStatus.getId(), - userStatus.getCreatedAt(), - userStatus.getUpdatedAt(), - userStatus.getUser().getId(), - userStatus.getLastActiveAt(), - userStatus.isOnline() - ); - } - +@Mapper(componentModel = "spring") +public interface UserStatusMapper { + @Mapping(target = "userId", source = "user.id") + UserStatusDto toDto(UserStatus userStatus); } diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index a6c4be70d..1addaecc6 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -24,6 +24,7 @@ public class UserStatusService { private final UserStatusRepository userStatusRepository; private final UserRepository userRepository; + private final UserStatusMapper userStatusMapper; @Transactional public UserStatusDto createUserStatus(UserStatusCreateRequest statusInfo) { @@ -37,25 +38,25 @@ public UserStatusDto createUserStatus(UserStatusCreateRequest statusInfo) { userStatus.setUser(findUser); userStatusRepository.save(userStatus); - return UserStatusMapper.toUserStatusInfo(userStatus); + return userStatusMapper.toDto(userStatus); } public UserStatusDto findUserStatus(UUID statusId) { UserStatus userStatus = userStatusRepository.findById(statusId) .orElseThrow(UserStatusNotFoundException::new); - return UserStatusMapper.toUserStatusInfo(userStatus); + return userStatusMapper.toDto(userStatus); } public UserStatusDto findUserStatusByUserId(UUID userId) { UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(UserStatusNotFoundException::new); - return UserStatusMapper.toUserStatusInfo(userStatus); + return userStatusMapper.toDto(userStatus); } public List findAll() { return userStatusRepository.findAll() .stream() - .map(UserStatusMapper::toUserStatusInfo) + .map(userStatusMapper::toDto) .toList(); } @@ -64,7 +65,7 @@ public UserStatusDto update(UUID userStatusId) { UserStatus userStatus = userStatusRepository.findById(userStatusId) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(); - return UserStatusMapper.toUserStatusInfo(userStatus); + return userStatusMapper.toDto(userStatus); } @Transactional @@ -72,7 +73,7 @@ public UserStatusDto updateUserStatusByUserId(UUID userId) { UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(); - return UserStatusMapper.toUserStatusInfo(userStatus); + return userStatusMapper.toDto(userStatus); } @Transactional @@ -81,7 +82,7 @@ public UserStatusDto update(UUID userId, UserStatusUpdateRequest request) { UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(UserStatusNotFoundException::new); userStatus.update(request.newLastActiveAt()); - return UserStatusMapper.toUserStatusInfo(userStatus); + return userStatusMapper.toDto(userStatus); } @Transactional From 949e3c39010a3b01c39d4df6fd4b91fefc73606c Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 6 Mar 2026 17:19:38 +0900 Subject: [PATCH 38/48] =?UTF-8?q?refactor:=20service=EC=9D=98=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=9D=B4=EB=A6=84=20=EC=9D=BC?= =?UTF-8?q?=EA=B4=80=EC=84=B1=20=EC=9E=88=EA=B2=8C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/BinaryContentService.java | 8 +++---- .../channel/service/BasicChannelService.java | 20 ++++++++--------- .../message/service/BasicMessageService.java | 22 +++++++++---------- .../readstatus/service/ReadStatusService.java | 14 ++++++------ .../user/service/BasicUserService.java | 1 - .../userstatus/service/UserStatusService.java | 16 +++++++------- 6 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index c9970566a..1ce0b6dc4 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -22,10 +22,10 @@ public class BinaryContentService { private final BinaryContentMapper binaryContentMapper; @Transactional - public BinaryContentDto createBinaryContent(BinaryContentCreateRequest contentInfo) { - byte[] bytes = contentInfo.bytes(); - BinaryContent content = new BinaryContent(contentInfo.fileName(), (long) bytes.length, - contentInfo.contentType(), + public BinaryContentDto createBinaryContent(BinaryContentCreateRequest request) { + byte[] bytes = request.bytes(); + BinaryContent content = new BinaryContent(request.fileName(), (long) bytes.length, + request.contentType(), bytes); contentRepository.save(content); return binaryContentMapper.toDto(content); diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 9596c1317..8f22de321 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -42,20 +42,20 @@ public class BasicChannelService implements ChannelService { private final ChannelMapper channelMapper; @Transactional - public ChannelDto createPublicChannel(PublicChannelCreateRequest channelInfo) { - validateChannelExist(channelInfo.name()); - Channel channel = new Channel(channelInfo.name(), ChannelType.PUBLIC, - channelInfo.description()); + public ChannelDto createPublicChannel(PublicChannelCreateRequest request) { + validateChannelExist(request.name()); + Channel channel = new Channel(request.name(), ChannelType.PUBLIC, + request.description()); channelRepository.save(channel); return channelMapper.toDto(channel, getLastMessageAt(channel.getId()), getParticipants(channel.getId())); } @Transactional - public ChannelDto createPrivateChannel(PrivateChannelCreateRequest channelInfo) { + public ChannelDto createPrivateChannel(PrivateChannelCreateRequest request) { Channel channel = new Channel(null, ChannelType.PRIVATE, null); channelRepository.save(channel); - channelInfo.participantIds().forEach(userId -> joinChannel(channel.getId(), userId)); + request.participantIds().forEach(userId -> joinChannel(channel.getId(), userId)); return channelMapper.toDto(channel, getLastMessageAt(channel.getId()), getParticipants(channel.getId())); } @@ -91,18 +91,18 @@ public List findAllByUserId(UUID userId) { @Transactional @Override public ChannelDto updateChannel(UUID channelId, - PublicChannelUpdateRequest channelInfo) { + PublicChannelUpdateRequest request) { Channel findChannel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); if (findChannel.getType() == ChannelType.PRIVATE) { throw new ChannelUpdateNotAllowedException(); } - Optional.ofNullable(channelInfo.newName()) + Optional.ofNullable(request.newName()) .ifPresent(this::validateChannelExist); - Optional.ofNullable(channelInfo.newName()) + Optional.ofNullable(request.newName()) .ifPresent(findChannel::updateChannelName); - Optional.ofNullable(channelInfo.newDescription()) + Optional.ofNullable(request.newDescription()) .ifPresent(findChannel::updateDescription); return channelMapper.toDto(findChannel, diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 134326658..36b2d2b19 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -36,28 +36,28 @@ public class BasicMessageService implements MessageService { @Transactional @Override - public MessageDto createMessage(MessageCreateRequest createInfo, + public MessageDto createMessage(MessageCreateRequest request, List binaryContentCreateRequests) { - User author = userRepository.findById(createInfo.authorId()) + User author = userRepository.findById(request.authorId()) .orElseThrow(UserNotFoundException::new); - Channel findChannel = channelRepository.findById(createInfo.channelId()) + Channel findChannel = channelRepository.findById(request.channelId()) .orElseThrow(ChannelNotFoundException::new); List attachments = binaryContentCreateRequests.stream() - .map(request -> { + .map(contentRequest -> { BinaryContent binaryContent = new BinaryContent( - request.fileName(), - (long) request.bytes().length, - request.contentType(), - request.bytes() + contentRequest.fileName(), + (long) contentRequest.bytes().length, + contentRequest.contentType(), + contentRequest.bytes() ); contentRepository.save(binaryContent); return binaryContent; }) .toList(); - Message message = new Message(createInfo.content(), findChannel, author, attachments); + Message message = new Message(request.content(), findChannel, author, attachments); messageRepository.save(message); return messageMapper.toDto(message); } @@ -87,11 +87,11 @@ public List findAllByChannelId(UUID channelId) { @Transactional @Override - public MessageDto updateMessage(UUID messageId, MessageUpdateRequest messageInfo) { + public MessageDto updateMessage(UUID messageId, MessageUpdateRequest request) { Message findMessage = messageRepository.findById(messageId) .orElseThrow(MessageNotFoundException::new); - Optional.ofNullable(messageInfo.newContent()) + Optional.ofNullable(request.newContent()) .ifPresent(findMessage::update); return messageMapper.toDto(findMessage); diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index 1eb92fbd8..cad4fd9c7 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -31,10 +31,10 @@ public class ReadStatusService { private final ReadStatusMapper readStatusMapper; @Transactional - public ReadStatusDto createReadStatus(ReadStatusCreateRequest statusInfo) { - User user = userRepository.findById(statusInfo.userId()) + public ReadStatusDto createReadStatus(ReadStatusCreateRequest request) { + User user = userRepository.findById(request.userId()) .orElseThrow(UserNotFoundException::new); - Channel channel = channelRepository.findById(statusInfo.channelId()) + Channel channel = channelRepository.findById(request.channelId()) .orElseThrow(ChannelNotFoundException::new); if (readStatusRepository.existsByUserIdAndChannelId(user.getId(), channel.getId())) { @@ -46,8 +46,8 @@ public ReadStatusDto createReadStatus(ReadStatusCreateRequest statusInfo) { return readStatusMapper.toDto(readStatus); } - public ReadStatusDto find(UUID statusId) { - ReadStatus readStatus = readStatusRepository.findById(statusId) + public ReadStatusDto find(UUID reaStatusId) { + ReadStatus readStatus = readStatusRepository.findById(reaStatusId) .orElseThrow(ReadStatusNotFoundException::new); return readStatusMapper.toDto(readStatus); } @@ -83,7 +83,7 @@ public ReadStatusDto updateReadStatus(UUID readStatusId, ReadStatusUpdateRequest } @Transactional - public void deleteReadStatus(UUID statusId) { - readStatusRepository.deleteById(statusId); + public void deleteReadStatus(UUID reaStatusId) { + readStatusRepository.deleteById(reaStatusId); } } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index ce0323b67..c9edde08b 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -42,7 +42,6 @@ public UserDto createUser(UserCreateRequest request, validateEmailExist(request.email()); User user = new User(request.username(), request.password(), request.email()); - // User user = userMapper.toEntity(request) UserStatus status = new UserStatus(); status.setUser(user); diff --git a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java index 1addaecc6..bb094c698 100644 --- a/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/userstatus/service/UserStatusService.java @@ -27,10 +27,10 @@ public class UserStatusService { private final UserStatusMapper userStatusMapper; @Transactional - public UserStatusDto createUserStatus(UserStatusCreateRequest statusInfo) { - User findUser = userRepository.findById(statusInfo.userId()) + public UserStatusDto createUserStatus(UserStatusCreateRequest request) { + User findUser = userRepository.findById(request.userId()) .orElseThrow(UserNotFoundException::new); - if (userStatusRepository.existsByUserId(statusInfo.userId())) { + if (userStatusRepository.existsByUserId(request.userId())) { throw new UserStatusDuplicationException(); } @@ -41,8 +41,8 @@ public UserStatusDto createUserStatus(UserStatusCreateRequest statusInfo) { return userStatusMapper.toDto(userStatus); } - public UserStatusDto findUserStatus(UUID statusId) { - UserStatus userStatus = userStatusRepository.findById(statusId) + public UserStatusDto findUserStatus(UUID userStatusId) { + UserStatus userStatus = userStatusRepository.findById(userStatusId) .orElseThrow(UserStatusNotFoundException::new); return userStatusMapper.toDto(userStatus); } @@ -86,7 +86,7 @@ public UserStatusDto update(UUID userId, UserStatusUpdateRequest request) { } @Transactional - void deleteUserStatus(UUID statusId) { - userStatusRepository.deleteById(statusId); + void deleteUserStatus(UUID userStatusId) { + userStatusRepository.deleteById(userStatusId); } -} +} \ No newline at end of file From b9f0b7a963f840a7b64b2fd990e9c2662ca43e9d Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 6 Mar 2026 17:39:14 +0900 Subject: [PATCH 39/48] =?UTF-8?q?refactor:=20message=5Fattachments=20uniqu?= =?UTF-8?q?e=20=EC=B6=94=EA=B0=80,=20file=5Fname=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/schema.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 3ac9dcfa8..947c3ca23 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -53,7 +53,7 @@ CREATE TABLE binary_contents ( id UUID PRIMARY KEY, created_at timestamptz NOT NULL, - filename varchar(255) NOT NULL, + file_name varchar(255) NOT NULL, size bigint NOT NULL, content_type varchar(100) NOT NULL, bytes bytea NOT NULL @@ -62,6 +62,7 @@ CREATE TABLE message_attachments ( message_id UUID NOT NULL, attachment_id UUID NOT NULL, + UNIQUE (message_id, attachment_id), FOREIGN KEY (message_id) REFERENCES messages (id) ON DELETE CASCADE, FOREIGN KEY (attachment_id) REFERENCES binary_contents (id) ON DELETE CASCADE ); From 7e90bcfbb5fb525e635f8e5a4679c4a46f7fe62e Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 6 Mar 2026 18:27:30 +0900 Subject: [PATCH 40/48] =?UTF-8?q?refactor:=20ChannelDto=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=EC=9D=84=20mapper=EC=97=90=EC=84=9C=20=ED=95=98?= =?UTF-8?q?=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 --- .../channel/mapper/ChannelMapper.java | 38 ++++++++++++++++-- .../channel/service/BasicChannelService.java | 40 +++---------------- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index 7509175be..8917913cf 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -1,15 +1,47 @@ package com.sprint.mission.discodeit.channel.mapper; +import com.sprint.mission.discodeit.base.BaseEntity; import com.sprint.mission.discodeit.channel.dto.ChannelDto; import com.sprint.mission.discodeit.channel.entity.Channel; +import com.sprint.mission.discodeit.message.repository.MessageRepository; +import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; import com.sprint.mission.discodeit.user.dto.UserDto; +import com.sprint.mission.discodeit.user.mapper.UserMapper; import java.time.Instant; import java.util.List; +import java.util.UUID; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.beans.factory.annotation.Autowired; @Mapper(componentModel = "spring") -public interface ChannelMapper { +public abstract class ChannelMapper { - ChannelDto toDto(Channel channel, Instant lastMessageAt, - List participants); + @Autowired + protected MessageRepository messageRepository; + + @Autowired + protected ReadStatusRepository readStatusRepository; + + @Autowired + protected UserMapper userMapper; + + @Mapping(target = "lastMessageAt", expression = "java(getLastMessageAt(channel.getId()))") + @Mapping(target = "participants", expression = "java(getParticipants(channel.getId()))") + public abstract ChannelDto toDto(Channel channel); + + protected Instant getLastMessageAt(UUID channelId) { + return messageRepository.findFirstByChannel_IdOrderByCreatedAtAsc(channelId) + .map(BaseEntity::getCreatedAt) + .orElse(null); + } + + protected List getParticipants(UUID channelId) { + return readStatusRepository.findAllByChannelId(channelId) + .stream() + .map(readStatus -> + userMapper.toDto(readStatus.getUser()) + ) + .toList(); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 8f22de321..cbd27ccad 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -1,6 +1,5 @@ package com.sprint.mission.discodeit.channel.service; -import com.sprint.mission.discodeit.base.BaseEntity; import com.sprint.mission.discodeit.channel.dto.ChannelDto; import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; @@ -12,16 +11,12 @@ import com.sprint.mission.discodeit.channel.exception.ChannelUpdateNotAllowedException; import com.sprint.mission.discodeit.channel.mapper.ChannelMapper; import com.sprint.mission.discodeit.channel.repository.ChannelRepository; -import com.sprint.mission.discodeit.message.repository.MessageRepository; import com.sprint.mission.discodeit.readstatus.entity.ReadStatus; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusNotFoundException; import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; -import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; -import com.sprint.mission.discodeit.user.mapper.UserMapper; import com.sprint.mission.discodeit.user.repository.UserRepository; -import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -37,8 +32,6 @@ public class BasicChannelService implements ChannelService { private final ChannelRepository channelRepository; private final UserRepository userRepository; private final ReadStatusRepository readStatusRepository; - private final MessageRepository messageRepository; - private final UserMapper userMapper; private final ChannelMapper channelMapper; @Transactional @@ -47,8 +40,7 @@ public ChannelDto createPublicChannel(PublicChannelCreateRequest request) { Channel channel = new Channel(request.name(), ChannelType.PUBLIC, request.description()); channelRepository.save(channel); - return channelMapper.toDto(channel, - getLastMessageAt(channel.getId()), getParticipants(channel.getId())); + return channelMapper.toDto(channel); } @Transactional @@ -56,24 +48,21 @@ public ChannelDto createPrivateChannel(PrivateChannelCreateRequest request) { Channel channel = new Channel(null, ChannelType.PRIVATE, null); channelRepository.save(channel); request.participantIds().forEach(userId -> joinChannel(channel.getId(), userId)); - return channelMapper.toDto(channel, - getLastMessageAt(channel.getId()), getParticipants(channel.getId())); + return channelMapper.toDto(channel); } @Override public ChannelDto findChannel(UUID channelId) { Channel channel = channelRepository.findById(channelId) .orElseThrow(ChannelNotFoundException::new); - return channelMapper.toDto(channel, - getLastMessageAt(channel.getId()), getParticipants(channel.getId())); + return channelMapper.toDto(channel); } @Override public List findAll() { return channelRepository.findAll() .stream() - .map(channel -> channelMapper.toDto(channel, - getLastMessageAt(channel.getId()), getParticipants(channel.getId()))) + .map(channelMapper::toDto) .toList(); } @@ -83,8 +72,7 @@ public List findAllByUserId(UUID userId) { .stream() .filter(channel -> channel.getType() == ChannelType.PUBLIC || readStatusRepository.existsByUserIdAndChannelId(userId, channel.getId())) - .map(channel -> channelMapper.toDto(channel, - getLastMessageAt(channel.getId()), getParticipants(channel.getId()))) + .map(channelMapper::toDto) .toList(); } @@ -105,8 +93,7 @@ public ChannelDto updateChannel(UUID channelId, Optional.ofNullable(request.newDescription()) .ifPresent(findChannel::updateDescription); - return channelMapper.toDto(findChannel, - getLastMessageAt(findChannel.getId()), getParticipants(findChannel.getId())); + return channelMapper.toDto(findChannel); } @Transactional @@ -146,19 +133,4 @@ private void validateChannelExist(String name) { throw new ChannelDuplicationException(); } } - - private Instant getLastMessageAt(UUID channelId) { - return messageRepository.findFirstByChannel_IdOrderByCreatedAtAsc(channelId) - .map(BaseEntity::getCreatedAt) - .orElse(null); - } - - private List getParticipants(UUID channelId) { - return readStatusRepository.findAllByChannelId(channelId) - .stream() - .map(readStatus -> - userMapper.toDto(readStatus.getUser()) - ) - .toList(); - } } From 16a27e7abcfb9ae4aeddf89c59b3bebc2d9e36b0 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Fri, 6 Mar 2026 18:29:05 +0900 Subject: [PATCH 41/48] =?UTF-8?q?fix:=20lastMessageAt=EC=9D=B4=20=EC=98=A4?= =?UTF-8?q?=EB=9E=98=EB=90=9C=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EC=9D=84=20=EB=B0=98=ED=99=98=ED=96=88=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 --- .../sprint/mission/discodeit/channel/mapper/ChannelMapper.java | 2 +- .../mission/discodeit/message/repository/MessageRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index 8917913cf..e7ab481c7 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -31,7 +31,7 @@ public abstract class ChannelMapper { public abstract ChannelDto toDto(Channel channel); protected Instant getLastMessageAt(UUID channelId) { - return messageRepository.findFirstByChannel_IdOrderByCreatedAtAsc(channelId) + return messageRepository.findFirstByChannel_IdOrderByCreatedAtDesc(channelId) .map(BaseEntity::getCreatedAt) .orElse(null); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java index bff14b0c2..05273522b 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java @@ -12,5 +12,5 @@ public interface MessageRepository extends JpaRepository { List findAllByChannelId(UUID channelId); - Optional findFirstByChannel_IdOrderByCreatedAtAsc(UUID channelId); + Optional findFirstByChannel_IdOrderByCreatedAtDesc(UUID channelId); } From 3cdd7754dd40807d360cfb992aa13e7fa4ad88a4 Mon Sep 17 00:00:00 2001 From: JunSuHwang <85167454+JunSuHwang@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:30:20 +0900 Subject: [PATCH 42/48] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EB=A1=9C=EC=BB=AC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EB=B0=A9=EC=8B=9D=20=EA=B5=AC=ED=98=84=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BinaryContent 메타데이터 구조로 변경 - 파일 다운로드 엔드포인트 추가 - StorageException 적용 --- .gitignore | 1 + .../controller/BinaryContentController.java | 20 +++++ .../binarycontent/dto/BinaryContentDto.java | 3 +- .../binarycontent/entity/BinaryContent.java | 8 -- .../mapper/BinaryContentMapper.java | 5 ++ .../service/BinaryContentService.java | 13 +-- .../discodeit/channel/entity/Channel.java | 18 +---- .../channel/mapper/ChannelMapper.java | 21 +++++ .../channel/service/BasicChannelService.java | 14 +--- .../exception/GlobalExceptionHandler.java | 7 ++ .../message/service/BasicMessageService.java | 12 +-- .../storage/BinaryContentStorage.java | 15 ++++ .../storage/LocalBinaryContentStorage.java | 81 +++++++++++++++++++ .../storage/exception/StorageException.java | 10 +++ .../mission/discodeit/user/entity/User.java | 17 +--- .../discodeit/user/mapper/UserMapper.java | 17 ++++ .../user/service/BasicUserService.java | 29 +++---- src/main/resources/application.yaml | 8 +- src/main/resources/schema.sql | 4 +- 19 files changed, 217 insertions(+), 86 deletions(-) create mode 100644 src/main/java/com/sprint/mission/discodeit/storage/BinaryContentStorage.java create mode 100644 src/main/java/com/sprint/mission/discodeit/storage/LocalBinaryContentStorage.java create mode 100644 src/main/java/com/sprint/mission/discodeit/storage/exception/StorageException.java diff --git a/.gitignore b/.gitignore index f60dcb30b..4b6762788 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ bin/ ### Mac OS ### .DS_Store +/data/** diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java index 339710933..9d8fc6520 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/controller/BinaryContentController.java @@ -3,6 +3,7 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentsRequest; import com.sprint.mission.discodeit.binarycontent.service.BinaryContentService; +import com.sprint.mission.discodeit.storage.BinaryContentStorage; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -30,6 +31,7 @@ public class BinaryContentController { private final BinaryContentService binaryContentService; + private final BinaryContentStorage storage; @Operation(summary = "첨부 파일 조회") @ApiResponses(value = { @@ -75,4 +77,22 @@ public ResponseEntity> findAllByIdIn( BinaryContentsRequest request = new BinaryContentsRequest(binaryContentIds); return ResponseEntity.ok(binaryContentService.findAllByIdIn(request)); } + + @Operation(summary = "파일 다운로드") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", description = "파일 다운로드 성공", + content = @Content( + mediaType = MediaType.ALL_VALUE, + schema = @Schema(type = "string", format = "binary") + ) + ) + }) + @GetMapping("/{binaryContentId}/download") + public ResponseEntity download( + @Parameter(description = "다운로드할 파일 ID", required = true) @PathVariable UUID binaryContentId + ) { + BinaryContentDto binaryContent = binaryContentService.findBinaryContent(binaryContentId); + return storage.download(binaryContent); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java index f3a4652ca..d92de8810 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/dto/BinaryContentDto.java @@ -6,8 +6,7 @@ public record BinaryContentDto( UUID id, String fileName, Long size, - String contentType, - byte[] bytes + String contentType ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java index 7d758ea51..06ae75f0b 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/entity/BinaryContent.java @@ -24,17 +24,9 @@ public class BinaryContent extends BaseEntity { @Column(nullable = false, length = 100) private String contentType; - @Column(nullable = false, columnDefinition = "bytea") - private byte[] bytes; - public BinaryContent(String fileName, Long size, String contentType, byte[] bytes) { this.fileName = fileName; this.size = size; this.contentType = contentType; - this.bytes = bytes; - } - - public byte[] getBytes() { - return Arrays.copyOf(bytes, bytes.length); } } diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java index 0b9a735cc..bcafd01a7 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/mapper/BinaryContentMapper.java @@ -1,11 +1,16 @@ package com.sprint.mission.discodeit.binarycontent.mapper; +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; @Mapper(componentModel = "spring") public interface BinaryContentMapper { BinaryContentDto toDto(BinaryContent binaryContent); + + @Mapping(target = "size", expression = "java((long) request.bytes().length)") + BinaryContent toEntity(BinaryContentCreateRequest request); } \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java index 1ce0b6dc4..4c55fa7f6 100644 --- a/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/binarycontent/service/BinaryContentService.java @@ -7,6 +7,7 @@ import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentNotFoundException; import com.sprint.mission.discodeit.binarycontent.mapper.BinaryContentMapper; import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.storage.BinaryContentStorage; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -20,14 +21,13 @@ public class BinaryContentService { private final BinaryContentRepository contentRepository; private final BinaryContentMapper binaryContentMapper; + private final BinaryContentStorage storage; @Transactional public BinaryContentDto createBinaryContent(BinaryContentCreateRequest request) { - byte[] bytes = request.bytes(); - BinaryContent content = new BinaryContent(request.fileName(), (long) bytes.length, - request.contentType(), - bytes); + BinaryContent content = binaryContentMapper.toEntity(request); contentRepository.save(content); + storage.put(content.getId(), request.bytes()); return binaryContentMapper.toDto(content); } @@ -37,11 +37,6 @@ public BinaryContentDto findBinaryContent(UUID contentId) { return binaryContentMapper.toDto(content); } - public BinaryContent findBinaryContentEntity(UUID contentId) { - return contentRepository.findById(contentId) - .orElseThrow(BinaryContentNotFoundException::new); - } - public List findAll() { return contentRepository.findAll() .stream() diff --git a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java index 4fcd4d995..fa8b24dca 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/entity/Channel.java @@ -6,12 +6,13 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Table; -import java.time.Instant; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; @Getter +@Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @Table(name = "channels") @@ -32,19 +33,4 @@ public Channel(String name, ChannelType type, String description) { this.type = type; this.description = description; } - - public void updateChannelName(String channelName) { - this.name = channelName; - this.updatedAt = Instant.now(); - } - - public void updateChannelType(ChannelType channelType) { - this.type = channelType; - this.updatedAt = Instant.now(); - } - - public void updateDescription(String description) { - this.description = description; - this.updatedAt = Instant.now(); - } } diff --git a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java index e7ab481c7..a21ab87a4 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/mapper/ChannelMapper.java @@ -2,6 +2,9 @@ import com.sprint.mission.discodeit.base.BaseEntity; import com.sprint.mission.discodeit.channel.dto.ChannelDto; +import com.sprint.mission.discodeit.channel.dto.PrivateChannelCreateRequest; +import com.sprint.mission.discodeit.channel.dto.PublicChannelCreateRequest; +import com.sprint.mission.discodeit.channel.dto.PublicChannelUpdateRequest; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.message.repository.MessageRepository; import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; @@ -10,8 +13,11 @@ import java.time.Instant; import java.util.List; import java.util.UUID; +import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; import org.springframework.beans.factory.annotation.Autowired; @Mapper(componentModel = "spring") @@ -30,6 +36,21 @@ public abstract class ChannelMapper { @Mapping(target = "participants", expression = "java(getParticipants(channel.getId()))") public abstract ChannelDto toDto(Channel channel); + @Mapping(target = "type", constant = "PUBLIC") + public abstract Channel toEntity(PublicChannelCreateRequest request); + + @Mapping(target = "type", constant = "PRIVATE") + @Mapping(target = "name", ignore = true) + @Mapping(target = "description", ignore = true) + public abstract Channel toEntity(PrivateChannelCreateRequest request); + + @Mapping(target = "name", source = "request.newName") + @Mapping(target = "description", source = "request.newDescription") + @Mapping(target = "type", ignore = true) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + public abstract void update(PublicChannelUpdateRequest request, @MappingTarget Channel channel); + + protected Instant getLastMessageAt(UUID channelId) { return messageRepository.findFirstByChannel_IdOrderByCreatedAtDesc(channelId) .map(BaseEntity::getCreatedAt) diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index cbd27ccad..2e087d7f0 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -18,7 +18,6 @@ import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; import java.util.List; -import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -37,15 +36,14 @@ public class BasicChannelService implements ChannelService { @Transactional public ChannelDto createPublicChannel(PublicChannelCreateRequest request) { validateChannelExist(request.name()); - Channel channel = new Channel(request.name(), ChannelType.PUBLIC, - request.description()); + Channel channel = channelMapper.toEntity(request); channelRepository.save(channel); return channelMapper.toDto(channel); } @Transactional public ChannelDto createPrivateChannel(PrivateChannelCreateRequest request) { - Channel channel = new Channel(null, ChannelType.PRIVATE, null); + Channel channel = channelMapper.toEntity(request); channelRepository.save(channel); request.participantIds().forEach(userId -> joinChannel(channel.getId(), userId)); return channelMapper.toDto(channel); @@ -85,13 +83,7 @@ public ChannelDto updateChannel(UUID channelId, if (findChannel.getType() == ChannelType.PRIVATE) { throw new ChannelUpdateNotAllowedException(); } - Optional.ofNullable(request.newName()) - .ifPresent(this::validateChannelExist); - - Optional.ofNullable(request.newName()) - .ifPresent(findChannel::updateChannelName); - Optional.ofNullable(request.newDescription()) - .ifPresent(findChannel::updateDescription); + channelMapper.update(request, findChannel); return channelMapper.toDto(findChannel); } diff --git a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java index 90869c34d..a44ca2125 100644 --- a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java @@ -9,6 +9,7 @@ import com.sprint.mission.discodeit.message.exception.MessageNotFoundException; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusDuplicationException; import com.sprint.mission.discodeit.readstatus.exception.ReadStatusNotFoundException; +import com.sprint.mission.discodeit.storage.exception.StorageException; import com.sprint.mission.discodeit.user.exception.AuthenticationFailedException; import com.sprint.mission.discodeit.user.exception.EmailDuplicationException; import com.sprint.mission.discodeit.user.exception.UserDuplicationException; @@ -114,6 +115,12 @@ public ResponseEntity handle(BinaryContentFileProcessingException return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); } + @ExceptionHandler(StorageException.class) + public ResponseEntity handle(StorageException e) { + ErrorResponse response = new ErrorResponse("STORAGE_ERROR", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } + @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity handle(MethodArgumentTypeMismatchException e) { ErrorResponse response = new ErrorResponse("INVALID_PARAMETER_TYPE", "해당 파라미터가 유효하지 않습니다."); diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 36b2d2b19..74cc52241 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -2,6 +2,7 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; +import com.sprint.mission.discodeit.binarycontent.mapper.BinaryContentMapper; import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; @@ -13,6 +14,7 @@ import com.sprint.mission.discodeit.message.exception.MessageNotFoundException; import com.sprint.mission.discodeit.message.mapper.MessageMapper; import com.sprint.mission.discodeit.message.repository.MessageRepository; +import com.sprint.mission.discodeit.storage.BinaryContentStorage; import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; @@ -32,7 +34,9 @@ public class BasicMessageService implements MessageService { private final UserRepository userRepository; private final ChannelRepository channelRepository; private final BinaryContentRepository contentRepository; + private final BinaryContentStorage storage; private final MessageMapper messageMapper; + private final BinaryContentMapper binaryContentMapper; @Transactional @Override @@ -46,13 +50,9 @@ public MessageDto createMessage(MessageCreateRequest request, List attachments = binaryContentCreateRequests.stream() .map(contentRequest -> { - BinaryContent binaryContent = new BinaryContent( - contentRequest.fileName(), - (long) contentRequest.bytes().length, - contentRequest.contentType(), - contentRequest.bytes() - ); + BinaryContent binaryContent = binaryContentMapper.toEntity(contentRequest); contentRepository.save(binaryContent); + storage.put(binaryContent.getId(), contentRequest.bytes()); return binaryContent; }) .toList(); diff --git a/src/main/java/com/sprint/mission/discodeit/storage/BinaryContentStorage.java b/src/main/java/com/sprint/mission/discodeit/storage/BinaryContentStorage.java new file mode 100644 index 000000000..7b7d71727 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/storage/BinaryContentStorage.java @@ -0,0 +1,15 @@ +package com.sprint.mission.discodeit.storage; + +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; +import java.io.InputStream; +import java.util.UUID; +import org.springframework.http.ResponseEntity; + +public interface BinaryContentStorage { + + UUID put(UUID id, byte[] bytes); + + InputStream get(UUID id); + + ResponseEntity download(BinaryContentDto binaryContentDto); +} diff --git a/src/main/java/com/sprint/mission/discodeit/storage/LocalBinaryContentStorage.java b/src/main/java/com/sprint/mission/discodeit/storage/LocalBinaryContentStorage.java new file mode 100644 index 000000000..f7befd8ce --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/storage/LocalBinaryContentStorage.java @@ -0,0 +1,81 @@ +package com.sprint.mission.discodeit.storage; + +import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentDto; +import com.sprint.mission.discodeit.storage.exception.StorageException; +import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty(prefix = "discodeit.storage", name = "type", havingValue = "local") +public class LocalBinaryContentStorage implements BinaryContentStorage { + + private final Path root; + + public LocalBinaryContentStorage(@Value("${discodeit.local.root-path}") String root) { + this.root = Path.of(root); + } + + @PostConstruct + public void init() { + if (Files.notExists(root)) { + try { + Files.createDirectories(root); + } catch (IOException e) { + throw new StorageException("디렉터리 생성을 실패했습니다."); + } + } + } + + @Override + public UUID put(UUID id, byte[] bytes) { + Path path = resolvePath(id); + try { + Files.write(path, bytes); + } catch (IOException e) { + throw new StorageException("파일 쓰기에 실패했습니다."); + } + return id; + } + + @Override + public InputStream get(UUID id) { + Path path = resolvePath(id); + try { + return Files.newInputStream(path); + } catch (IOException e) { + throw new StorageException("파일 읽기에 실패했습니다."); + } + } + + @Override + public ResponseEntity download(BinaryContentDto binaryContentDto) { + Path path = resolvePath(binaryContentDto.id()); + try { + Resource resource = new InputStreamResource(Files.newInputStream(path)); + return ResponseEntity + .status(200) + .contentType(MediaType.valueOf(binaryContentDto.contentType())) + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + binaryContentDto.fileName() + "\"") + .body(resource); + } catch (IOException e) { + throw new StorageException("다운로드 파일 로드에 실패했습니다."); + } + } + + private Path resolvePath(UUID id) { + return root.resolve(id.toString()); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/storage/exception/StorageException.java b/src/main/java/com/sprint/mission/discodeit/storage/exception/StorageException.java new file mode 100644 index 000000000..04f6a75cb --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/storage/exception/StorageException.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.storage.exception; + +import com.sprint.mission.discodeit.exception.BusinessException; + +public class StorageException extends BusinessException { + + public StorageException(String message) { + super(message); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java index 33523216b..a32baa61b 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/entity/User.java +++ b/src/main/java/com/sprint/mission/discodeit/user/entity/User.java @@ -9,12 +9,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; -import java.time.Instant; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter +@Setter @NoArgsConstructor @Entity @Table(name = "users") @@ -44,21 +44,6 @@ public User(String username, String password, String email) { this.email = email; } - public void updateUserName(String username) { - this.username = username; - this.updatedAt = Instant.now(); - } - - public void updatePassword(String password) { - this.password = password; - this.updatedAt = Instant.now(); - } - - public void updateEmail(String email) { - this.email = email; - this.updatedAt = Instant.now(); - } - public boolean isProfileImageUploaded() { return profile != null; } diff --git a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java index 730a6e6ba..b6c6495af 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/user/mapper/UserMapper.java @@ -1,14 +1,31 @@ package com.sprint.mission.discodeit.user.mapper; import com.sprint.mission.discodeit.binarycontent.mapper.BinaryContentMapper; +import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; +import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; import com.sprint.mission.discodeit.user.entity.User; +import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; @Mapper(componentModel = "spring", uses = BinaryContentMapper.class) public interface UserMapper { @Mapping(target = "online", expression = "java(user.getUserStatus().isOnline())") UserDto toDto(User user); + + @Mapping(target = "userStatus", ignore = true) + @Mapping(target = "profile", ignore = true) + User toEntity(UserCreateRequest request); + + @Mapping(target = "username", source = "request.newUsername") + @Mapping(target = "password", source = "request.newPassword") + @Mapping(target = "email", source = "request.newEmail") + @Mapping(target = "userStatus", ignore = true) + @Mapping(target = "profile", ignore = true) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void update(UserUpdateRequest request, @MappingTarget User user); } diff --git a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java index c9edde08b..9b3542587 100644 --- a/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java +++ b/src/main/java/com/sprint/mission/discodeit/user/service/BasicUserService.java @@ -2,8 +2,10 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.entity.BinaryContent; +import com.sprint.mission.discodeit.binarycontent.mapper.BinaryContentMapper; import com.sprint.mission.discodeit.binarycontent.repository.BinaryContentRepository; import com.sprint.mission.discodeit.readstatus.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.storage.BinaryContentStorage; import com.sprint.mission.discodeit.user.dto.UserCreateRequest; import com.sprint.mission.discodeit.user.dto.UserDto; import com.sprint.mission.discodeit.user.dto.UserUpdateRequest; @@ -31,7 +33,9 @@ public class BasicUserService implements UserService { private final ReadStatusRepository readStatusRepository; private final BinaryContentRepository contentRepository; private final UserStatusRepository userStatusRepository; + private final BinaryContentStorage storage; private final UserMapper userMapper; + private final BinaryContentMapper binaryContentMapper; @Transactional @Override @@ -41,19 +45,17 @@ public UserDto createUser(UserCreateRequest request, validateUserExist(request.username()); validateEmailExist(request.email()); - User user = new User(request.username(), request.password(), request.email()); + User user = userMapper.toEntity(request); UserStatus status = new UserStatus(); status.setUser(user); if (image.isPresent()) { - BinaryContentCreateRequest createInfo = image.get(); - byte[] bytes = createInfo.bytes(); - BinaryContent profileImage = new BinaryContent(createInfo.fileName(), (long) bytes.length, - createInfo.contentType(), bytes); + BinaryContent profileImage = binaryContentMapper.toEntity(image.get()); user.setProfile(profileImage); + contentRepository.save(profileImage); + storage.put(profileImage.getId(), image.get().bytes()); } - userRepository.save(user); return userMapper.toDto(user); } @@ -97,24 +99,19 @@ public UserDto updateUser(UUID userId, UserUpdateRequest request, .ifPresent(this::validateUserExist); Optional.ofNullable(request.newEmail()) .ifPresent(this::validateEmailExist); + User findUser = userRepository.findById(userId) .orElseThrow(UserNotFoundException::new); - Optional.ofNullable(request.newUsername()) - .ifPresent(findUser::updateUserName); - Optional.ofNullable(request.newPassword()) - .ifPresent(findUser::updatePassword); - Optional.ofNullable(request.newEmail()) - .ifPresent(findUser::updateEmail); + userMapper.update(request, findUser); if (image.isPresent()) { if (findUser.isProfileImageUploaded()) { contentRepository.deleteById(findUser.getProfile().getId()); } - BinaryContentCreateRequest createInfo = image.get(); - byte[] bytes = createInfo.bytes(); - BinaryContent profileImage = new BinaryContent(createInfo.fileName(), (long) bytes.length, - createInfo.contentType(), bytes); + BinaryContent profileImage = binaryContentMapper.toEntity(image.get()); findUser.setProfile(profileImage); + contentRepository.save(profileImage); + storage.put(profileImage.getId(), image.get().bytes()); } userStatusRepository.findByUserId(findUser.getId()) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 0722d9a51..da411332e 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -18,4 +18,10 @@ springdoc: api-docs: enabled: true swagger-ui: - enabled: true \ No newline at end of file + enabled: true + +discodeit: + storage: + type: local + local: + root-path: "./data" \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 947c3ca23..72aa8d974 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -68,4 +68,6 @@ CREATE TABLE message_attachments ); ALTER TABLE users ADD CONSTRAINT fk_users_binary_contents FOREIGN KEY (profile_id) - REFERENCES binary_contents (id) ON DELETE SET NULL; \ No newline at end of file + REFERENCES binary_contents (id) ON DELETE SET NULL; +ALTER TABLE binary_contents + DROP COLUMN bytes; \ No newline at end of file From 9329273b9cc20629e7fee7e31ec7fc3387e04ba1 Mon Sep 17 00:00:00 2001 From: JunSuHwang <85167454+JunSuHwang@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:27:19 +0900 Subject: [PATCH 43/48] =?UTF-8?q?feat:=20message=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=97=90=20Pagination=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=20(#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit page 공통 응답, 요청 dto 추가 message 목록 조회에 Pagination 적용 ChannelMessageController를 MessageController에 병합 --- .../discodeit/global/dto/MyPageRequest.java | 10 ++++ .../discodeit/global/dto/PageResponse.java | 13 +++++ .../global/mapper/PageResponseMapper.java | 30 ++++++++++++ .../controller/ChannelMessageController.java | 47 ------------------- .../message/controller/MessageController.java | 26 ++++++++++ .../message/repository/MessageRepository.java | 4 +- .../message/service/BasicMessageService.java | 13 ++++- .../message/service/MessageService.java | 4 +- 8 files changed, 96 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/sprint/mission/discodeit/global/dto/MyPageRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/global/dto/PageResponse.java create mode 100644 src/main/java/com/sprint/mission/discodeit/global/mapper/PageResponseMapper.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java diff --git a/src/main/java/com/sprint/mission/discodeit/global/dto/MyPageRequest.java b/src/main/java/com/sprint/mission/discodeit/global/dto/MyPageRequest.java new file mode 100644 index 000000000..85fe01197 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/global/dto/MyPageRequest.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.global.dto; + +import org.springframework.data.domain.Pageable; + +public record MyPageRequest( + T t, + Pageable pageable +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/global/dto/PageResponse.java b/src/main/java/com/sprint/mission/discodeit/global/dto/PageResponse.java new file mode 100644 index 000000000..77455f8c0 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/global/dto/PageResponse.java @@ -0,0 +1,13 @@ +package com.sprint.mission.discodeit.global.dto; + +import java.util.List; + +public record PageResponse( + List content, + int number, + int size, + boolean hasNext, + Long totalElements +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/global/mapper/PageResponseMapper.java b/src/main/java/com/sprint/mission/discodeit/global/mapper/PageResponseMapper.java new file mode 100644 index 000000000..109649b42 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/global/mapper/PageResponseMapper.java @@ -0,0 +1,30 @@ +package com.sprint.mission.discodeit.global.mapper; + +import com.sprint.mission.discodeit.global.dto.PageResponse; +import org.mapstruct.Mapper; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; + +@Mapper(componentModel = "spring") +public interface PageResponseMapper { + + default PageResponse fromSlice(Slice slice) { + return new PageResponse<>( + slice.getContent(), + slice.getNumber(), + slice.getSize(), + slice.hasNext(), + null + ); + } + + default PageResponse fromPage(Page page) { + return new PageResponse<>( + page.getContent(), + page.getNumber(), + page.getSize(), + page.hasNext(), + page.getTotalElements() + ); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java deleted file mode 100644 index 3fa53a806..000000000 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/ChannelMessageController.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.sprint.mission.discodeit.message.controller; - -import com.sprint.mission.discodeit.message.dto.MessageDto; -import com.sprint.mission.discodeit.message.service.MessageService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/messages") -@Tag(name = "Message", description = "Message API") -public class ChannelMessageController { - - private final MessageService messageService; - - @Operation(summary = "Channel의 Message 목록 조회") - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", description = "Message 목록 조회 성공", - content = @Content( - mediaType = MediaType.ALL_VALUE, - array = @ArraySchema(schema = @Schema(implementation = MessageDto.class)) - ) - ) - }) - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> findAllByChannelId( - @Parameter(description = "조회할 Channel ID") @RequestParam UUID channelId - ) { - return ResponseEntity.ok(messageService.findAllByChannelId(channelId)); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index 066e64f0b..e66c23f0e 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -2,6 +2,8 @@ import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; import com.sprint.mission.discodeit.binarycontent.exception.BinaryContentFileProcessingException; +import com.sprint.mission.discodeit.global.dto.MyPageRequest; +import com.sprint.mission.discodeit.global.dto.PageResponse; import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; @@ -21,6 +23,12 @@ import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.web.PageableDefault; +import org.springframework.data.web.SortDefault; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -30,6 +38,7 @@ 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.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -142,4 +151,21 @@ public ResponseEntity delete_1( messageService.deleteMessage(messageId); return ResponseEntity.noContent().build(); } + + @Operation(summary = "Channel의 Message 목록 조회") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", description = "Message 목록 조회 성공", + content = @Content(mediaType = MediaType.ALL_VALUE) + ) + }) + @GetMapping + public ResponseEntity> findAllByChannelId( + @Parameter(description = "조회할 Channel ID") @RequestParam UUID channelId, + @ParameterObject @PageableDefault(size = 50, sort = + "createdAt", direction = Direction.DESC) Pageable pageable + ) { + MyPageRequest messagePagingRequest = new MyPageRequest<>(channelId, pageable); + return ResponseEntity.status(200).body(messageService.findAllByChannelId(messagePagingRequest)); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java index 05273522b..e205b5213 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java @@ -4,13 +4,15 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; public interface MessageRepository extends JpaRepository { List findAllByAuthorId(UUID authorId); - List findAllByChannelId(UUID channelId); + Slice findAllByChannelId(UUID channelId, Pageable pageable); Optional findFirstByChannel_IdOrderByCreatedAtDesc(UUID channelId); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 74cc52241..727bcb984 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -7,6 +7,9 @@ import com.sprint.mission.discodeit.channel.entity.Channel; import com.sprint.mission.discodeit.channel.exception.ChannelNotFoundException; import com.sprint.mission.discodeit.channel.repository.ChannelRepository; +import com.sprint.mission.discodeit.global.dto.MyPageRequest; +import com.sprint.mission.discodeit.global.dto.PageResponse; +import com.sprint.mission.discodeit.global.mapper.PageResponseMapper; import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; @@ -22,6 +25,7 @@ import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -37,6 +41,7 @@ public class BasicMessageService implements MessageService { private final BinaryContentStorage storage; private final MessageMapper messageMapper; private final BinaryContentMapper binaryContentMapper; + private final PageResponseMapper pageResponseMapper; @Transactional @Override @@ -81,8 +86,12 @@ public List findAllByUserId(UUID userId) { } @Override - public List findAllByChannelId(UUID channelId) { - return toMessageDtoList(messageRepository.findAllByChannelId(channelId)); + public PageResponse findAllByChannelId(MyPageRequest request) { + Slice slice = messageRepository.findAllByChannelId(request.t(), + request.pageable()); + Slice messageDtoSlice = slice.map(messageMapper::toDto); + return pageResponseMapper.fromSlice(messageDtoSlice); + } @Transactional diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java index 54c4e67b2..c9e17f504 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/MessageService.java @@ -1,6 +1,8 @@ package com.sprint.mission.discodeit.message.service; import com.sprint.mission.discodeit.binarycontent.dto.BinaryContentCreateRequest; +import com.sprint.mission.discodeit.global.dto.MyPageRequest; +import com.sprint.mission.discodeit.global.dto.PageResponse; import com.sprint.mission.discodeit.message.dto.MessageCreateRequest; import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.dto.MessageUpdateRequest; @@ -18,7 +20,7 @@ MessageDto createMessage(MessageCreateRequest createInfo, List findAllByUserId(UUID userId); - List findAllByChannelId(UUID channelId); + PageResponse findAllByChannelId(MyPageRequest request); MessageDto updateMessage(UUID messageId, MessageUpdateRequest messageInfo); From 2cf5ece27352b5cee9ee16d1904f76e0c65a6ff9 Mon Sep 17 00:00:00 2001 From: JunSuHwang Date: Tue, 10 Mar 2026 16:19:27 +0900 Subject: [PATCH 44/48] =?UTF-8?q?feat:=20=ED=98=B8=ED=99=98=20=EC=A0=95?= =?UTF-8?q?=EC=A0=81=20=EB=A6=AC=EC=86=8C=EC=8A=A4=EB=A1=9C=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/static/assets/index-CRrRqFH4.js | 956 ---------------- .../resources/static/assets/index-D8OMG6Bz.js | 1015 +++++++++++++++++ src/main/resources/static/index.html | 2 +- 3 files changed, 1016 insertions(+), 957 deletions(-) delete mode 100644 src/main/resources/static/assets/index-CRrRqFH4.js create mode 100644 src/main/resources/static/assets/index-D8OMG6Bz.js diff --git a/src/main/resources/static/assets/index-CRrRqFH4.js b/src/main/resources/static/assets/index-CRrRqFH4.js deleted file mode 100644 index ffeaa39b4..000000000 --- a/src/main/resources/static/assets/index-CRrRqFH4.js +++ /dev/null @@ -1,956 +0,0 @@ -(function(){const i=document.createElement("link").relList;if(i&&i.supports&&i.supports("modulepreload"))return;for(const c of document.querySelectorAll('link[rel="modulepreload"]'))u(c);new MutationObserver(c=>{for(const d of c)if(d.type==="childList")for(const p of d.addedNodes)p.tagName==="LINK"&&p.rel==="modulepreload"&&u(p)}).observe(document,{childList:!0,subtree:!0});function s(c){const d={};return c.integrity&&(d.integrity=c.integrity),c.referrerPolicy&&(d.referrerPolicy=c.referrerPolicy),c.crossOrigin==="use-credentials"?d.credentials="include":c.crossOrigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function u(c){if(c.ep)return;c.ep=!0;const d=s(c);fetch(c.href,d)}})();function Qm(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}var lu={exports:{}},ho={},uu={exports:{}},fe={};/** - * @license React - * react.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Wf;function qm(){if(Wf)return fe;Wf=1;var r=Symbol.for("react.element"),i=Symbol.for("react.portal"),s=Symbol.for("react.fragment"),u=Symbol.for("react.strict_mode"),c=Symbol.for("react.profiler"),d=Symbol.for("react.provider"),p=Symbol.for("react.context"),m=Symbol.for("react.forward_ref"),v=Symbol.for("react.suspense"),x=Symbol.for("react.memo"),E=Symbol.for("react.lazy"),j=Symbol.iterator;function O(S){return S===null||typeof S!="object"?null:(S=j&&S[j]||S["@@iterator"],typeof S=="function"?S:null)}var P={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},I=Object.assign,R={};function L(S,D,oe){this.props=S,this.context=D,this.refs=R,this.updater=oe||P}L.prototype.isReactComponent={},L.prototype.setState=function(S,D){if(typeof S!="object"&&typeof S!="function"&&S!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,S,D,"setState")},L.prototype.forceUpdate=function(S){this.updater.enqueueForceUpdate(this,S,"forceUpdate")};function V(){}V.prototype=L.prototype;function F(S,D,oe){this.props=S,this.context=D,this.refs=R,this.updater=oe||P}var W=F.prototype=new V;W.constructor=F,I(W,L.prototype),W.isPureReactComponent=!0;var K=Array.isArray,$=Object.prototype.hasOwnProperty,T={current:null},H={key:!0,ref:!0,__self:!0,__source:!0};function se(S,D,oe){var le,de={},ce=null,ve=null;if(D!=null)for(le in D.ref!==void 0&&(ve=D.ref),D.key!==void 0&&(ce=""+D.key),D)$.call(D,le)&&!H.hasOwnProperty(le)&&(de[le]=D[le]);var pe=arguments.length-2;if(pe===1)de.children=oe;else if(1>>1,D=Q[S];if(0>>1;Sc(de,q))cec(ve,de)?(Q[S]=ve,Q[ce]=q,S=ce):(Q[S]=de,Q[le]=q,S=le);else if(cec(ve,q))Q[S]=ve,Q[ce]=q,S=ce;else break e}}return ee}function c(Q,ee){var q=Q.sortIndex-ee.sortIndex;return q!==0?q:Q.id-ee.id}if(typeof performance=="object"&&typeof performance.now=="function"){var d=performance;r.unstable_now=function(){return d.now()}}else{var p=Date,m=p.now();r.unstable_now=function(){return p.now()-m}}var v=[],x=[],E=1,j=null,O=3,P=!1,I=!1,R=!1,L=typeof setTimeout=="function"?setTimeout:null,V=typeof clearTimeout=="function"?clearTimeout:null,F=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function W(Q){for(var ee=s(x);ee!==null;){if(ee.callback===null)u(x);else if(ee.startTime<=Q)u(x),ee.sortIndex=ee.expirationTime,i(v,ee);else break;ee=s(x)}}function K(Q){if(R=!1,W(Q),!I)if(s(v)!==null)I=!0,We($);else{var ee=s(x);ee!==null&&Se(K,ee.startTime-Q)}}function $(Q,ee){I=!1,R&&(R=!1,V(se),se=-1),P=!0;var q=O;try{for(W(ee),j=s(v);j!==null&&(!(j.expirationTime>ee)||Q&&!qt());){var S=j.callback;if(typeof S=="function"){j.callback=null,O=j.priorityLevel;var D=S(j.expirationTime<=ee);ee=r.unstable_now(),typeof D=="function"?j.callback=D:j===s(v)&&u(v),W(ee)}else u(v);j=s(v)}if(j!==null)var oe=!0;else{var le=s(x);le!==null&&Se(K,le.startTime-ee),oe=!1}return oe}finally{j=null,O=q,P=!1}}var T=!1,H=null,se=-1,Ve=5,At=-1;function qt(){return!(r.unstable_now()-AtQ||125S?(Q.sortIndex=q,i(x,Q),s(v)===null&&Q===s(x)&&(R?(V(se),se=-1):R=!0,Se(K,q-S))):(Q.sortIndex=D,i(v,Q),I||P||(I=!0,We($))),Q},r.unstable_shouldYield=qt,r.unstable_wrapCallback=function(Q){var ee=O;return function(){var q=O;O=ee;try{return Q.apply(this,arguments)}finally{O=q}}}}(fu)),fu}var Yf;function Km(){return Yf||(Yf=1,cu.exports=Ym()),cu.exports}/** - * @license React - * react-dom.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Kf;function Xm(){if(Kf)return st;Kf=1;var r=Bu(),i=Km();function s(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),v=Object.prototype.hasOwnProperty,x=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,E={},j={};function O(e){return v.call(j,e)?!0:v.call(E,e)?!1:x.test(e)?j[e]=!0:(E[e]=!0,!1)}function P(e,t,n,o){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return o?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function I(e,t,n,o){if(t===null||typeof t>"u"||P(e,t,n,o))return!0;if(o)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function R(e,t,n,o,l,a,f){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=o,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=f}var L={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){L[e]=new R(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];L[t]=new R(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){L[e]=new R(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){L[e]=new R(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){L[e]=new R(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){L[e]=new R(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){L[e]=new R(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){L[e]=new R(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){L[e]=new R(e,5,!1,e.toLowerCase(),null,!1,!1)});var V=/[\-:]([a-z])/g;function F(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(V,F);L[t]=new R(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(V,F);L[t]=new R(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(V,F);L[t]=new R(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){L[e]=new R(e,1,!1,e.toLowerCase(),null,!1,!1)}),L.xlinkHref=new R("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){L[e]=new R(e,1,!1,e.toLowerCase(),null,!0,!0)});function W(e,t,n,o){var l=L.hasOwnProperty(t)?L[t]:null;(l!==null?l.type!==0:o||!(2h||l[f]!==a[h]){var y=` -`+l[f].replace(" at new "," at ");return e.displayName&&y.includes("")&&(y=y.replace("",e.displayName)),y}while(1<=f&&0<=h);break}}}finally{oe=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?D(e):""}function de(e){switch(e.tag){case 5:return D(e.type);case 16:return D("Lazy");case 13:return D("Suspense");case 19:return D("SuspenseList");case 0:case 2:case 15:return e=le(e.type,!1),e;case 11:return e=le(e.type.render,!1),e;case 1:return e=le(e.type,!0),e;default:return""}}function ce(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case H:return"Fragment";case T:return"Portal";case Ve:return"Profiler";case se:return"StrictMode";case Je:return"Suspense";case at:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case qt:return(e.displayName||"Context")+".Consumer";case At:return(e._context.displayName||"Context")+".Provider";case gt:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case yt:return t=e.displayName||null,t!==null?t:ce(e.type)||"Memo";case We:t=e._payload,e=e._init;try{return ce(e(t))}catch{}}return null}function ve(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return ce(t);case 8:return t===se?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function pe(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ge(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Be(e){var t=ge(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),o=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(f){o=""+f,a.call(this,f)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return o},setValue:function(f){o=""+f},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function bt(e){e._valueTracker||(e._valueTracker=Be(e))}function Rt(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),o="";return e&&(o=ge(e)?e.checked?"true":"false":e.value),e=o,e!==n?(t.setValue(e),!0):!1}function Ao(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function hs(e,t){var n=t.checked;return q({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Ku(e,t){var n=t.defaultValue==null?"":t.defaultValue,o=t.checked!=null?t.checked:t.defaultChecked;n=pe(t.value!=null?t.value:n),e._wrapperState={initialChecked:o,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Xu(e,t){t=t.checked,t!=null&&W(e,"checked",t,!1)}function ms(e,t){Xu(e,t);var n=pe(t.value),o=t.type;if(n!=null)o==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(o==="submit"||o==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?gs(e,t.type,n):t.hasOwnProperty("defaultValue")&&gs(e,t.type,pe(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Ju(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var o=t.type;if(!(o!=="submit"&&o!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function gs(e,t,n){(t!=="number"||Ao(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var jr=Array.isArray;function Gn(e,t,n,o){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Ro.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Ir(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var _r={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Kp=["Webkit","ms","Moz","O"];Object.keys(_r).forEach(function(e){Kp.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),_r[t]=_r[e]})});function oa(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||_r.hasOwnProperty(e)&&_r[e]?(""+t).trim():t+"px"}function ia(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var o=n.indexOf("--")===0,l=oa(n,t[n],o);n==="float"&&(n="cssFloat"),o?e.setProperty(n,l):e[n]=l}}var Xp=q({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function ws(e,t){if(t){if(Xp[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(s(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(s(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(s(61))}if(t.style!=null&&typeof t.style!="object")throw Error(s(62))}}function xs(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Ss=null;function ks(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Es=null,Yn=null,Kn=null;function sa(e){if(e=Jr(e)){if(typeof Es!="function")throw Error(s(280));var t=e.stateNode;t&&(t=Yo(t),Es(e.stateNode,e.type,t))}}function la(e){Yn?Kn?Kn.push(e):Kn=[e]:Yn=e}function ua(){if(Yn){var e=Yn,t=Kn;if(Kn=Yn=null,sa(e),t)for(e=0;e>>=0,e===0?32:31-(uh(e)/ah|0)|0}var No=64,Oo=4194304;function Lr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function To(e,t){var n=e.pendingLanes;if(n===0)return 0;var o=0,l=e.suspendedLanes,a=e.pingedLanes,f=n&268435455;if(f!==0){var h=f&~l;h!==0?o=Lr(h):(a&=f,a!==0&&(o=Lr(a)))}else f=n&~l,f!==0?o=Lr(f):a!==0&&(o=Lr(a));if(o===0)return 0;if(t!==0&&t!==o&&!(t&l)&&(l=o&-o,a=t&-t,l>=a||l===16&&(a&4194240)!==0))return t;if(o&4&&(o|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=o;0n;n++)t.push(e);return t}function Dr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Pt(t),e[t]=n}function ph(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var o=e.eventTimes;for(e=e.expirationTimes;0=Vr),za=" ",Ma=!1;function Ua(e,t){switch(e){case"keyup":return $h.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Fa(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Zn=!1;function Vh(e,t){switch(e){case"compositionend":return Fa(t);case"keypress":return t.which!==32?null:(Ma=!0,za);case"textInput":return e=t.data,e===za&&Ma?null:e;default:return null}}function Wh(e,t){if(Zn)return e==="compositionend"||!$s&&Ua(e,t)?(e=_a(),Uo=Ds=an=null,Zn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=o}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=qa(n)}}function Ga(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ga(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ya(){for(var e=window,t=Ao();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ao(e.document)}return t}function Ws(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Zh(e){var t=Ya(),n=e.focusedElem,o=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Ga(n.ownerDocument.documentElement,n)){if(o!==null&&Ws(n)){if(t=o.start,e=o.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,a=Math.min(o.start,l);o=o.end===void 0?a:Math.min(o.end,l),!e.extend&&a>o&&(l=o,o=a,a=l),l=ba(n,a);var f=ba(n,o);l&&f&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==f.node||e.focusOffset!==f.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),a>o?(e.addRange(t),e.extend(f.node,f.offset)):(t.setEnd(f.node,f.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,er=null,Qs=null,br=null,qs=!1;function Ka(e,t,n){var o=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;qs||er==null||er!==Ao(o)||(o=er,"selectionStart"in o&&Ws(o)?o={start:o.selectionStart,end:o.selectionEnd}:(o=(o.ownerDocument&&o.ownerDocument.defaultView||window).getSelection(),o={anchorNode:o.anchorNode,anchorOffset:o.anchorOffset,focusNode:o.focusNode,focusOffset:o.focusOffset}),br&&qr(br,o)||(br=o,o=qo(Qs,"onSelect"),0ir||(e.current=ol[ir],ol[ir]=null,ir--)}function ke(e,t){ir++,ol[ir]=e.current,e.current=t}var pn={},Qe=dn(pn),tt=dn(!1),Pn=pn;function sr(e,t){var n=e.type.contextTypes;if(!n)return pn;var o=e.stateNode;if(o&&o.__reactInternalMemoizedUnmaskedChildContext===t)return o.__reactInternalMemoizedMaskedChildContext;var l={},a;for(a in n)l[a]=t[a];return o&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function nt(e){return e=e.childContextTypes,e!=null}function Ko(){Ce(tt),Ce(Qe)}function fc(e,t,n){if(Qe.current!==pn)throw Error(s(168));ke(Qe,t),ke(tt,n)}function dc(e,t,n){var o=e.stateNode;if(t=t.childContextTypes,typeof o.getChildContext!="function")return n;o=o.getChildContext();for(var l in o)if(!(l in t))throw Error(s(108,ve(e)||"Unknown",l));return q({},n,o)}function Xo(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||pn,Pn=Qe.current,ke(Qe,e),ke(tt,tt.current),!0}function pc(e,t,n){var o=e.stateNode;if(!o)throw Error(s(169));n?(e=dc(e,t,Pn),o.__reactInternalMemoizedMergedChildContext=e,Ce(tt),Ce(Qe),ke(Qe,e)):Ce(tt),ke(tt,n)}var Yt=null,Jo=!1,il=!1;function hc(e){Yt===null?Yt=[e]:Yt.push(e)}function fm(e){Jo=!0,hc(e)}function hn(){if(!il&&Yt!==null){il=!0;var e=0,t=xe;try{var n=Yt;for(xe=1;e>=f,l-=f,Kt=1<<32-Pt(t)+l|n<re?(Ue=ne,ne=null):Ue=ne.sibling;var ye=z(k,ne,C[re],B);if(ye===null){ne===null&&(ne=Ue);break}e&&ne&&ye.alternate===null&&t(k,ne),w=a(ye,w,re),te===null?Z=ye:te.sibling=ye,te=ye,ne=Ue}if(re===C.length)return n(k,ne),Re&&In(k,re),Z;if(ne===null){for(;rere?(Ue=ne,ne=null):Ue=ne.sibling;var En=z(k,ne,ye.value,B);if(En===null){ne===null&&(ne=Ue);break}e&&ne&&En.alternate===null&&t(k,ne),w=a(En,w,re),te===null?Z=En:te.sibling=En,te=En,ne=Ue}if(ye.done)return n(k,ne),Re&&In(k,re),Z;if(ne===null){for(;!ye.done;re++,ye=C.next())ye=U(k,ye.value,B),ye!==null&&(w=a(ye,w,re),te===null?Z=ye:te.sibling=ye,te=ye);return Re&&In(k,re),Z}for(ne=o(k,ne);!ye.done;re++,ye=C.next())ye=b(ne,k,re,ye.value,B),ye!==null&&(e&&ye.alternate!==null&&ne.delete(ye.key===null?re:ye.key),w=a(ye,w,re),te===null?Z=ye:te.sibling=ye,te=ye);return e&&ne.forEach(function(Wm){return t(k,Wm)}),Re&&In(k,re),Z}function Ne(k,w,C,B){if(typeof C=="object"&&C!==null&&C.type===H&&C.key===null&&(C=C.props.children),typeof C=="object"&&C!==null){switch(C.$$typeof){case $:e:{for(var Z=C.key,te=w;te!==null;){if(te.key===Z){if(Z=C.type,Z===H){if(te.tag===7){n(k,te.sibling),w=l(te,C.props.children),w.return=k,k=w;break e}}else if(te.elementType===Z||typeof Z=="object"&&Z!==null&&Z.$$typeof===We&&xc(Z)===te.type){n(k,te.sibling),w=l(te,C.props),w.ref=Zr(k,te,C),w.return=k,k=w;break e}n(k,te);break}else t(k,te);te=te.sibling}C.type===H?(w=Mn(C.props.children,k.mode,B,C.key),w.return=k,k=w):(B=Ri(C.type,C.key,C.props,null,k.mode,B),B.ref=Zr(k,w,C),B.return=k,k=B)}return f(k);case T:e:{for(te=C.key;w!==null;){if(w.key===te)if(w.tag===4&&w.stateNode.containerInfo===C.containerInfo&&w.stateNode.implementation===C.implementation){n(k,w.sibling),w=l(w,C.children||[]),w.return=k,k=w;break e}else{n(k,w);break}else t(k,w);w=w.sibling}w=nu(C,k.mode,B),w.return=k,k=w}return f(k);case We:return te=C._init,Ne(k,w,te(C._payload),B)}if(jr(C))return Y(k,w,C,B);if(ee(C))return X(k,w,C,B);ni(k,C)}return typeof C=="string"&&C!==""||typeof C=="number"?(C=""+C,w!==null&&w.tag===6?(n(k,w.sibling),w=l(w,C),w.return=k,k=w):(n(k,w),w=tu(C,k.mode,B),w.return=k,k=w),f(k)):n(k,w)}return Ne}var cr=Sc(!0),kc=Sc(!1),ri=dn(null),oi=null,fr=null,fl=null;function dl(){fl=fr=oi=null}function pl(e){var t=ri.current;Ce(ri),e._currentValue=t}function hl(e,t,n){for(;e!==null;){var o=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,o!==null&&(o.childLanes|=t)):o!==null&&(o.childLanes&t)!==t&&(o.childLanes|=t),e===n)break;e=e.return}}function dr(e,t){oi=e,fl=fr=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(rt=!0),e.firstContext=null)}function xt(e){var t=e._currentValue;if(fl!==e)if(e={context:e,memoizedValue:t,next:null},fr===null){if(oi===null)throw Error(s(308));fr=e,oi.dependencies={lanes:0,firstContext:e}}else fr=fr.next=e;return t}var _n=null;function ml(e){_n===null?_n=[e]:_n.push(e)}function Ec(e,t,n,o){var l=t.interleaved;return l===null?(n.next=n,ml(t)):(n.next=l.next,l.next=n),t.interleaved=n,Jt(e,o)}function Jt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var mn=!1;function gl(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Cc(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Zt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function gn(e,t,n){var o=e.updateQueue;if(o===null)return null;if(o=o.shared,me&2){var l=o.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),o.pending=t,Jt(e,n)}return l=o.interleaved,l===null?(t.next=t,ml(o)):(t.next=l.next,l.next=t),o.interleaved=t,Jt(e,n)}function ii(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var o=t.lanes;o&=e.pendingLanes,n|=o,t.lanes=n,_s(e,n)}}function Ac(e,t){var n=e.updateQueue,o=e.alternate;if(o!==null&&(o=o.updateQueue,n===o)){var l=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var f={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};a===null?l=a=f:a=a.next=f,n=n.next}while(n!==null);a===null?l=a=t:a=a.next=t}else l=a=t;n={baseState:o.baseState,firstBaseUpdate:l,lastBaseUpdate:a,shared:o.shared,effects:o.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function si(e,t,n,o){var l=e.updateQueue;mn=!1;var a=l.firstBaseUpdate,f=l.lastBaseUpdate,h=l.shared.pending;if(h!==null){l.shared.pending=null;var y=h,A=y.next;y.next=null,f===null?a=A:f.next=A,f=y;var M=e.alternate;M!==null&&(M=M.updateQueue,h=M.lastBaseUpdate,h!==f&&(h===null?M.firstBaseUpdate=A:h.next=A,M.lastBaseUpdate=y))}if(a!==null){var U=l.baseState;f=0,M=A=y=null,h=a;do{var z=h.lane,b=h.eventTime;if((o&z)===z){M!==null&&(M=M.next={eventTime:b,lane:0,tag:h.tag,payload:h.payload,callback:h.callback,next:null});e:{var Y=e,X=h;switch(z=t,b=n,X.tag){case 1:if(Y=X.payload,typeof Y=="function"){U=Y.call(b,U,z);break e}U=Y;break e;case 3:Y.flags=Y.flags&-65537|128;case 0:if(Y=X.payload,z=typeof Y=="function"?Y.call(b,U,z):Y,z==null)break e;U=q({},U,z);break e;case 2:mn=!0}}h.callback!==null&&h.lane!==0&&(e.flags|=64,z=l.effects,z===null?l.effects=[h]:z.push(h))}else b={eventTime:b,lane:z,tag:h.tag,payload:h.payload,callback:h.callback,next:null},M===null?(A=M=b,y=U):M=M.next=b,f|=z;if(h=h.next,h===null){if(h=l.shared.pending,h===null)break;z=h,h=z.next,z.next=null,l.lastBaseUpdate=z,l.shared.pending=null}}while(!0);if(M===null&&(y=U),l.baseState=y,l.firstBaseUpdate=A,l.lastBaseUpdate=M,t=l.shared.interleaved,t!==null){l=t;do f|=l.lane,l=l.next;while(l!==t)}else a===null&&(l.shared.lanes=0);Tn|=f,e.lanes=f,e.memoizedState=U}}function Rc(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var o=Sl.transition;Sl.transition={};try{e(!1),t()}finally{xe=n,Sl.transition=o}}function Qc(){return St().memoizedState}function mm(e,t,n){var o=xn(e);if(n={lane:o,action:n,hasEagerState:!1,eagerState:null,next:null},qc(e))bc(t,n);else if(n=Ec(e,t,n,o),n!==null){var l=et();Tt(n,e,o,l),Gc(n,t,o)}}function gm(e,t,n){var o=xn(e),l={lane:o,action:n,hasEagerState:!1,eagerState:null,next:null};if(qc(e))bc(t,l);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var f=t.lastRenderedState,h=a(f,n);if(l.hasEagerState=!0,l.eagerState=h,jt(h,f)){var y=t.interleaved;y===null?(l.next=l,ml(t)):(l.next=y.next,y.next=l),t.interleaved=l;return}}catch{}finally{}n=Ec(e,t,l,o),n!==null&&(l=et(),Tt(n,e,o,l),Gc(n,t,o))}}function qc(e){var t=e.alternate;return e===je||t!==null&&t===je}function bc(e,t){ro=ai=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Gc(e,t,n){if(n&4194240){var o=t.lanes;o&=e.pendingLanes,n|=o,t.lanes=n,_s(e,n)}}var di={readContext:xt,useCallback:qe,useContext:qe,useEffect:qe,useImperativeHandle:qe,useInsertionEffect:qe,useLayoutEffect:qe,useMemo:qe,useReducer:qe,useRef:qe,useState:qe,useDebugValue:qe,useDeferredValue:qe,useTransition:qe,useMutableSource:qe,useSyncExternalStore:qe,useId:qe,unstable_isNewReconciler:!1},ym={readContext:xt,useCallback:function(e,t){return Bt().memoizedState=[e,t===void 0?null:t],e},useContext:xt,useEffect:Mc,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,ci(4194308,4,Bc.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ci(4194308,4,e,t)},useInsertionEffect:function(e,t){return ci(4,2,e,t)},useMemo:function(e,t){var n=Bt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var o=Bt();return t=n!==void 0?n(t):t,o.memoizedState=o.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},o.queue=e,e=e.dispatch=mm.bind(null,je,e),[o.memoizedState,e]},useRef:function(e){var t=Bt();return e={current:e},t.memoizedState=e},useState:Dc,useDebugValue:jl,useDeferredValue:function(e){return Bt().memoizedState=e},useTransition:function(){var e=Dc(!1),t=e[0];return e=hm.bind(null,e[1]),Bt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var o=je,l=Bt();if(Re){if(n===void 0)throw Error(s(407));n=n()}else{if(n=t(),Me===null)throw Error(s(349));On&30||_c(o,t,n)}l.memoizedState=n;var a={value:n,getSnapshot:t};return l.queue=a,Mc(Oc.bind(null,o,a,e),[e]),o.flags|=2048,so(9,Nc.bind(null,o,a,n,t),void 0,null),n},useId:function(){var e=Bt(),t=Me.identifierPrefix;if(Re){var n=Xt,o=Kt;n=(o&~(1<<32-Pt(o)-1)).toString(32)+n,t=":"+t+"R"+n,n=oo++,0<\/script>",e=e.removeChild(e.firstChild)):typeof o.is=="string"?e=f.createElement(n,{is:o.is}):(e=f.createElement(n),n==="select"&&(f=e,o.multiple?f.multiple=!0:o.size&&(f.size=o.size))):e=f.createElementNS(e,n),e[Ut]=t,e[Xr]=o,mf(e,t,!1,!1),t.stateNode=e;e:{switch(f=xs(n,o),n){case"dialog":Ee("cancel",e),Ee("close",e),l=o;break;case"iframe":case"object":case"embed":Ee("load",e),l=o;break;case"video":case"audio":for(l=0;lyr&&(t.flags|=128,o=!0,lo(a,!1),t.lanes=4194304)}else{if(!o)if(e=li(f),e!==null){if(t.flags|=128,o=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),lo(a,!0),a.tail===null&&a.tailMode==="hidden"&&!f.alternate&&!Re)return be(t),null}else 2*_e()-a.renderingStartTime>yr&&n!==1073741824&&(t.flags|=128,o=!0,lo(a,!1),t.lanes=4194304);a.isBackwards?(f.sibling=t.child,t.child=f):(n=a.last,n!==null?n.sibling=f:t.child=f,a.last=f)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=_e(),t.sibling=null,n=Pe.current,ke(Pe,o?n&1|2:n&1),t):(be(t),null);case 22:case 23:return Jl(),o=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==o&&(t.flags|=8192),o&&t.mode&1?pt&1073741824&&(be(t),t.subtreeFlags&6&&(t.flags|=8192)):be(t),null;case 24:return null;case 25:return null}throw Error(s(156,t.tag))}function Am(e,t){switch(ll(t),t.tag){case 1:return nt(t.type)&&Ko(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return pr(),Ce(tt),Ce(Qe),xl(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return vl(t),null;case 13:if(Ce(Pe),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(s(340));ar()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Ce(Pe),null;case 4:return pr(),null;case 10:return pl(t.type._context),null;case 22:case 23:return Jl(),null;case 24:return null;default:return null}}var gi=!1,Ge=!1,Rm=typeof WeakSet=="function"?WeakSet:Set,G=null;function mr(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(o){Ie(e,t,o)}else n.current=null}function Bl(e,t,n){try{n()}catch(o){Ie(e,t,o)}}var vf=!1;function Pm(e,t){if(Js=zo,e=Ya(),Ws(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var o=n.getSelection&&n.getSelection();if(o&&o.rangeCount!==0){n=o.anchorNode;var l=o.anchorOffset,a=o.focusNode;o=o.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break e}var f=0,h=-1,y=-1,A=0,M=0,U=e,z=null;t:for(;;){for(var b;U!==n||l!==0&&U.nodeType!==3||(h=f+l),U!==a||o!==0&&U.nodeType!==3||(y=f+o),U.nodeType===3&&(f+=U.nodeValue.length),(b=U.firstChild)!==null;)z=U,U=b;for(;;){if(U===e)break t;if(z===n&&++A===l&&(h=f),z===a&&++M===o&&(y=f),(b=U.nextSibling)!==null)break;U=z,z=U.parentNode}U=b}n=h===-1||y===-1?null:{start:h,end:y}}else n=null}n=n||{start:0,end:0}}else n=null;for(Zs={focusedElem:e,selectionRange:n},zo=!1,G=t;G!==null;)if(t=G,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,G=e;else for(;G!==null;){t=G;try{var Y=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(Y!==null){var X=Y.memoizedProps,Ne=Y.memoizedState,k=t.stateNode,w=k.getSnapshotBeforeUpdate(t.elementType===t.type?X:_t(t.type,X),Ne);k.__reactInternalSnapshotBeforeUpdate=w}break;case 3:var C=t.stateNode.containerInfo;C.nodeType===1?C.textContent="":C.nodeType===9&&C.documentElement&&C.removeChild(C.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(s(163))}}catch(B){Ie(t,t.return,B)}if(e=t.sibling,e!==null){e.return=t.return,G=e;break}G=t.return}return Y=vf,vf=!1,Y}function uo(e,t,n){var o=t.updateQueue;if(o=o!==null?o.lastEffect:null,o!==null){var l=o=o.next;do{if((l.tag&e)===e){var a=l.destroy;l.destroy=void 0,a!==void 0&&Bl(t,n,a)}l=l.next}while(l!==o)}}function yi(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var o=n.create;n.destroy=o()}n=n.next}while(n!==t)}}function $l(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function wf(e){var t=e.alternate;t!==null&&(e.alternate=null,wf(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ut],delete t[Xr],delete t[rl],delete t[am],delete t[cm])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function xf(e){return e.tag===5||e.tag===3||e.tag===4}function Sf(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||xf(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Hl(e,t,n){var o=e.tag;if(o===5||o===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Go));else if(o!==4&&(e=e.child,e!==null))for(Hl(e,t,n),e=e.sibling;e!==null;)Hl(e,t,n),e=e.sibling}function Vl(e,t,n){var o=e.tag;if(o===5||o===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(o!==4&&(e=e.child,e!==null))for(Vl(e,t,n),e=e.sibling;e!==null;)Vl(e,t,n),e=e.sibling}var $e=null,Nt=!1;function yn(e,t,n){for(n=n.child;n!==null;)kf(e,t,n),n=n.sibling}function kf(e,t,n){if(Mt&&typeof Mt.onCommitFiberUnmount=="function")try{Mt.onCommitFiberUnmount(_o,n)}catch{}switch(n.tag){case 5:Ge||mr(n,t);case 6:var o=$e,l=Nt;$e=null,yn(e,t,n),$e=o,Nt=l,$e!==null&&(Nt?(e=$e,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):$e.removeChild(n.stateNode));break;case 18:$e!==null&&(Nt?(e=$e,n=n.stateNode,e.nodeType===8?nl(e.parentNode,n):e.nodeType===1&&nl(e,n),Br(e)):nl($e,n.stateNode));break;case 4:o=$e,l=Nt,$e=n.stateNode.containerInfo,Nt=!0,yn(e,t,n),$e=o,Nt=l;break;case 0:case 11:case 14:case 15:if(!Ge&&(o=n.updateQueue,o!==null&&(o=o.lastEffect,o!==null))){l=o=o.next;do{var a=l,f=a.destroy;a=a.tag,f!==void 0&&(a&2||a&4)&&Bl(n,t,f),l=l.next}while(l!==o)}yn(e,t,n);break;case 1:if(!Ge&&(mr(n,t),o=n.stateNode,typeof o.componentWillUnmount=="function"))try{o.props=n.memoizedProps,o.state=n.memoizedState,o.componentWillUnmount()}catch(h){Ie(n,t,h)}yn(e,t,n);break;case 21:yn(e,t,n);break;case 22:n.mode&1?(Ge=(o=Ge)||n.memoizedState!==null,yn(e,t,n),Ge=o):yn(e,t,n);break;default:yn(e,t,n)}}function Ef(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Rm),t.forEach(function(o){var l=zm.bind(null,e,o);n.has(o)||(n.add(o),o.then(l,l))})}}function Ot(e,t){var n=t.deletions;if(n!==null)for(var o=0;ol&&(l=f),o&=~a}if(o=l,o=_e()-o,o=(120>o?120:480>o?480:1080>o?1080:1920>o?1920:3e3>o?3e3:4320>o?4320:1960*Im(o/1960))-o,10e?16:e,wn===null)var o=!1;else{if(e=wn,wn=null,ki=0,me&6)throw Error(s(331));var l=me;for(me|=4,G=e.current;G!==null;){var a=G,f=a.child;if(G.flags&16){var h=a.deletions;if(h!==null){for(var y=0;y_e()-ql?Dn(e,0):Ql|=n),it(e,t)}function zf(e,t){t===0&&(e.mode&1?(t=Oo,Oo<<=1,!(Oo&130023424)&&(Oo=4194304)):t=1);var n=et();e=Jt(e,t),e!==null&&(Dr(e,t,n),it(e,n))}function Dm(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),zf(e,n)}function zm(e,t){var n=0;switch(e.tag){case 13:var o=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:o=e.stateNode;break;default:throw Error(s(314))}o!==null&&o.delete(t),zf(e,n)}var Mf;Mf=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||tt.current)rt=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return rt=!1,Em(e,t,n);rt=!!(e.flags&131072)}else rt=!1,Re&&t.flags&1048576&&mc(t,ei,t.index);switch(t.lanes=0,t.tag){case 2:var o=t.type;mi(e,t),e=t.pendingProps;var l=sr(t,Qe.current);dr(t,n),l=El(null,t,o,e,l,n);var a=Cl();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,nt(o)?(a=!0,Xo(t)):a=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,gl(t),l.updater=pi,t.stateNode=l,l._reactInternals=t,_l(t,o,e,n),t=Ll(null,t,o,!0,a,n)):(t.tag=0,Re&&a&&sl(t),Ze(null,t,l,n),t=t.child),t;case 16:o=t.elementType;e:{switch(mi(e,t),e=t.pendingProps,l=o._init,o=l(o._payload),t.type=o,l=t.tag=Um(o),e=_t(o,e),l){case 0:t=Tl(null,t,o,e,n);break e;case 1:t=af(null,t,o,e,n);break e;case 11:t=rf(null,t,o,e,n);break e;case 14:t=of(null,t,o,_t(o.type,e),n);break e}throw Error(s(306,o,""))}return t;case 0:return o=t.type,l=t.pendingProps,l=t.elementType===o?l:_t(o,l),Tl(e,t,o,l,n);case 1:return o=t.type,l=t.pendingProps,l=t.elementType===o?l:_t(o,l),af(e,t,o,l,n);case 3:e:{if(cf(t),e===null)throw Error(s(387));o=t.pendingProps,a=t.memoizedState,l=a.element,Cc(e,t),si(t,o,null,n);var f=t.memoizedState;if(o=f.element,a.isDehydrated)if(a={element:o,isDehydrated:!1,cache:f.cache,pendingSuspenseBoundaries:f.pendingSuspenseBoundaries,transitions:f.transitions},t.updateQueue.baseState=a,t.memoizedState=a,t.flags&256){l=hr(Error(s(423)),t),t=ff(e,t,o,n,l);break e}else if(o!==l){l=hr(Error(s(424)),t),t=ff(e,t,o,n,l);break e}else for(dt=fn(t.stateNode.containerInfo.firstChild),ft=t,Re=!0,It=null,n=kc(t,null,o,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(ar(),o===l){t=en(e,t,n);break e}Ze(e,t,o,n)}t=t.child}return t;case 5:return Pc(t),e===null&&al(t),o=t.type,l=t.pendingProps,a=e!==null?e.memoizedProps:null,f=l.children,el(o,l)?f=null:a!==null&&el(o,a)&&(t.flags|=32),uf(e,t),Ze(e,t,f,n),t.child;case 6:return e===null&&al(t),null;case 13:return df(e,t,n);case 4:return yl(t,t.stateNode.containerInfo),o=t.pendingProps,e===null?t.child=cr(t,null,o,n):Ze(e,t,o,n),t.child;case 11:return o=t.type,l=t.pendingProps,l=t.elementType===o?l:_t(o,l),rf(e,t,o,l,n);case 7:return Ze(e,t,t.pendingProps,n),t.child;case 8:return Ze(e,t,t.pendingProps.children,n),t.child;case 12:return Ze(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(o=t.type._context,l=t.pendingProps,a=t.memoizedProps,f=l.value,ke(ri,o._currentValue),o._currentValue=f,a!==null)if(jt(a.value,f)){if(a.children===l.children&&!tt.current){t=en(e,t,n);break e}}else for(a=t.child,a!==null&&(a.return=t);a!==null;){var h=a.dependencies;if(h!==null){f=a.child;for(var y=h.firstContext;y!==null;){if(y.context===o){if(a.tag===1){y=Zt(-1,n&-n),y.tag=2;var A=a.updateQueue;if(A!==null){A=A.shared;var M=A.pending;M===null?y.next=y:(y.next=M.next,M.next=y),A.pending=y}}a.lanes|=n,y=a.alternate,y!==null&&(y.lanes|=n),hl(a.return,n,t),h.lanes|=n;break}y=y.next}}else if(a.tag===10)f=a.type===t.type?null:a.child;else if(a.tag===18){if(f=a.return,f===null)throw Error(s(341));f.lanes|=n,h=f.alternate,h!==null&&(h.lanes|=n),hl(f,n,t),f=a.sibling}else f=a.child;if(f!==null)f.return=a;else for(f=a;f!==null;){if(f===t){f=null;break}if(a=f.sibling,a!==null){a.return=f.return,f=a;break}f=f.return}a=f}Ze(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,o=t.pendingProps.children,dr(t,n),l=xt(l),o=o(l),t.flags|=1,Ze(e,t,o,n),t.child;case 14:return o=t.type,l=_t(o,t.pendingProps),l=_t(o.type,l),of(e,t,o,l,n);case 15:return sf(e,t,t.type,t.pendingProps,n);case 17:return o=t.type,l=t.pendingProps,l=t.elementType===o?l:_t(o,l),mi(e,t),t.tag=1,nt(o)?(e=!0,Xo(t)):e=!1,dr(t,n),Kc(t,o,l),_l(t,o,l,n),Ll(null,t,o,!0,e,n);case 19:return hf(e,t,n);case 22:return lf(e,t,n)}throw Error(s(156,t.tag))};function Uf(e,t){return ga(e,t)}function Mm(e,t,n,o){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=o,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Et(e,t,n,o){return new Mm(e,t,n,o)}function eu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Um(e){if(typeof e=="function")return eu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===gt)return 11;if(e===yt)return 14}return 2}function kn(e,t){var n=e.alternate;return n===null?(n=Et(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Ri(e,t,n,o,l,a){var f=2;if(o=e,typeof e=="function")eu(e)&&(f=1);else if(typeof e=="string")f=5;else e:switch(e){case H:return Mn(n.children,l,a,t);case se:f=8,l|=8;break;case Ve:return e=Et(12,n,t,l|2),e.elementType=Ve,e.lanes=a,e;case Je:return e=Et(13,n,t,l),e.elementType=Je,e.lanes=a,e;case at:return e=Et(19,n,t,l),e.elementType=at,e.lanes=a,e;case Se:return Pi(n,l,a,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case At:f=10;break e;case qt:f=9;break e;case gt:f=11;break e;case yt:f=14;break e;case We:f=16,o=null;break e}throw Error(s(130,e==null?e:typeof e,""))}return t=Et(f,n,t,l),t.elementType=e,t.type=o,t.lanes=a,t}function Mn(e,t,n,o){return e=Et(7,e,o,t),e.lanes=n,e}function Pi(e,t,n,o){return e=Et(22,e,o,t),e.elementType=Se,e.lanes=n,e.stateNode={isHidden:!1},e}function tu(e,t,n){return e=Et(6,e,null,t),e.lanes=n,e}function nu(e,t,n){return t=Et(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Fm(e,t,n,o,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Is(0),this.expirationTimes=Is(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Is(0),this.identifierPrefix=o,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function ru(e,t,n,o,l,a,f,h,y){return e=new Fm(e,t,n,h,y),t===1?(t=1,a===!0&&(t|=8)):t=0,a=Et(3,null,null,t),e.current=a,a.stateNode=e,a.memoizedState={element:o,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},gl(a),e}function Bm(e,t,n){var o=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(r)}catch(i){console.error(i)}}return r(),au.exports=Xm(),au.exports}var Jf;function Zm(){if(Jf)return Li;Jf=1;var r=Jm();return Li.createRoot=r.createRoot,Li.hydrateRoot=r.hydrateRoot,Li}var eg=Zm(),Ke=function(){return Ke=Object.assign||function(i){for(var s,u=1,c=arguments.length;u0?Fe(Ar,--Ct):0,kr--,Te===10&&(kr=1,rs--),Te}function Lt(){return Te=Ct2||Au(Te)>3?"":" "}function cg(r,i){for(;--i&&Lt()&&!(Te<48||Te>102||Te>57&&Te<65||Te>70&&Te<97););return is(r,Hi()+(i<6&&$n()==32&&Lt()==32))}function Ru(r){for(;Lt();)switch(Te){case r:return Ct;case 34:case 39:r!==34&&r!==39&&Ru(Te);break;case 40:r===41&&Ru(r);break;case 92:Lt();break}return Ct}function fg(r,i){for(;Lt()&&r+Te!==57;)if(r+Te===84&&$n()===47)break;return"/*"+is(i,Ct-1)+"*"+Hu(r===47?r:Lt())}function dg(r){for(;!Au($n());)Lt();return is(r,Ct)}function pg(r){return ug(Vi("",null,null,null,[""],r=lg(r),0,[0],r))}function Vi(r,i,s,u,c,d,p,m,v){for(var x=0,E=0,j=p,O=0,P=0,I=0,R=1,L=1,V=1,F=0,W="",K=c,$=d,T=u,H=W;L;)switch(I=F,F=Lt()){case 40:if(I!=108&&Fe(H,j-1)==58){$i(H+=ae(du(F),"&","&\f"),"&\f",Kd(x?m[x-1]:0))!=-1&&(V=-1);break}case 34:case 39:case 91:H+=du(F);break;case 9:case 10:case 13:case 32:H+=ag(I);break;case 92:H+=cg(Hi()-1,7);continue;case 47:switch($n()){case 42:case 47:go(hg(fg(Lt(),Hi()),i,s,v),v);break;default:H+="/"}break;case 123*R:m[x++]=Vt(H)*V;case 125*R:case 59:case 0:switch(F){case 0:case 125:L=0;case 59+E:V==-1&&(H=ae(H,/\f/g,"")),P>0&&Vt(H)-j&&go(P>32?td(H+";",u,s,j-1,v):td(ae(H," ","")+";",u,s,j-2,v),v);break;case 59:H+=";";default:if(go(T=ed(H,i,s,x,E,c,m,W,K=[],$=[],j,d),d),F===123)if(E===0)Vi(H,i,T,T,K,d,j,m,$);else switch(O===99&&Fe(H,3)===110?100:O){case 100:case 108:case 109:case 115:Vi(r,T,T,u&&go(ed(r,T,T,0,0,c,m,W,c,K=[],j,$),$),c,$,j,m,u?K:$);break;default:Vi(H,T,T,T,[""],$,0,m,$)}}x=E=P=0,R=V=1,W=H="",j=p;break;case 58:j=1+Vt(H),P=I;default:if(R<1){if(F==123)--R;else if(F==125&&R++==0&&sg()==125)continue}switch(H+=Hu(F),F*R){case 38:V=E>0?1:(H+="\f",-1);break;case 44:m[x++]=(Vt(H)-1)*V,V=1;break;case 64:$n()===45&&(H+=du(Lt())),O=$n(),E=j=Vt(W=H+=dg(Hi())),F++;break;case 45:I===45&&Vt(H)==2&&(R=0)}}return d}function ed(r,i,s,u,c,d,p,m,v,x,E,j){for(var O=c-1,P=c===0?d:[""],I=Jd(P),R=0,L=0,V=0;R0?P[F]+" "+W:ae(W,/&\f/g,P[F])))&&(v[V++]=K);return os(r,i,s,c===0?ns:m,v,x,E,j)}function hg(r,i,s,u){return os(r,i,s,Gd,Hu(ig()),Sr(r,2,-2),0,u)}function td(r,i,s,u,c){return os(r,i,s,$u,Sr(r,0,u),Sr(r,u+1,-1),u,c)}function ep(r,i,s){switch(rg(r,i)){case 5103:return we+"print-"+r+r;case 5737:case 4201:case 3177:case 3433:case 1641:case 4457:case 2921:case 5572:case 6356:case 5844:case 3191:case 6645:case 3005:case 6391:case 5879:case 5623:case 6135:case 4599:case 4855:case 4215:case 6389:case 5109:case 5365:case 5621:case 3829:return we+r+r;case 4789:return wo+r+r;case 5349:case 4246:case 4810:case 6968:case 2756:return we+r+wo+r+Ae+r+r;case 5936:switch(Fe(r,i+11)){case 114:return we+r+Ae+ae(r,/[svh]\w+-[tblr]{2}/,"tb")+r;case 108:return we+r+Ae+ae(r,/[svh]\w+-[tblr]{2}/,"tb-rl")+r;case 45:return we+r+Ae+ae(r,/[svh]\w+-[tblr]{2}/,"lr")+r}case 6828:case 4268:case 2903:return we+r+Ae+r+r;case 6165:return we+r+Ae+"flex-"+r+r;case 5187:return we+r+ae(r,/(\w+).+(:[^]+)/,we+"box-$1$2"+Ae+"flex-$1$2")+r;case 5443:return we+r+Ae+"flex-item-"+ae(r,/flex-|-self/g,"")+(nn(r,/flex-|baseline/)?"":Ae+"grid-row-"+ae(r,/flex-|-self/g,""))+r;case 4675:return we+r+Ae+"flex-line-pack"+ae(r,/align-content|flex-|-self/g,"")+r;case 5548:return we+r+Ae+ae(r,"shrink","negative")+r;case 5292:return we+r+Ae+ae(r,"basis","preferred-size")+r;case 6060:return we+"box-"+ae(r,"-grow","")+we+r+Ae+ae(r,"grow","positive")+r;case 4554:return we+ae(r,/([^-])(transform)/g,"$1"+we+"$2")+r;case 6187:return ae(ae(ae(r,/(zoom-|grab)/,we+"$1"),/(image-set)/,we+"$1"),r,"")+r;case 5495:case 3959:return ae(r,/(image-set\([^]*)/,we+"$1$`$1");case 4968:return ae(ae(r,/(.+:)(flex-)?(.*)/,we+"box-pack:$3"+Ae+"flex-pack:$3"),/s.+-b[^;]+/,"justify")+we+r+r;case 4200:if(!nn(r,/flex-|baseline/))return Ae+"grid-column-align"+Sr(r,i)+r;break;case 2592:case 3360:return Ae+ae(r,"template-","")+r;case 4384:case 3616:return s&&s.some(function(u,c){return i=c,nn(u.props,/grid-\w+-end/)})?~$i(r+(s=s[i].value),"span",0)?r:Ae+ae(r,"-start","")+r+Ae+"grid-row-span:"+(~$i(s,"span",0)?nn(s,/\d+/):+nn(s,/\d+/)-+nn(r,/\d+/))+";":Ae+ae(r,"-start","")+r;case 4896:case 4128:return s&&s.some(function(u){return nn(u.props,/grid-\w+-start/)})?r:Ae+ae(ae(r,"-end","-span"),"span ","")+r;case 4095:case 3583:case 4068:case 2532:return ae(r,/(.+)-inline(.+)/,we+"$1$2")+r;case 8116:case 7059:case 5753:case 5535:case 5445:case 5701:case 4933:case 4677:case 5533:case 5789:case 5021:case 4765:if(Vt(r)-1-i>6)switch(Fe(r,i+1)){case 109:if(Fe(r,i+4)!==45)break;case 102:return ae(r,/(.+:)(.+)-([^]+)/,"$1"+we+"$2-$3$1"+wo+(Fe(r,i+3)==108?"$3":"$2-$3"))+r;case 115:return~$i(r,"stretch",0)?ep(ae(r,"stretch","fill-available"),i,s)+r:r}break;case 5152:case 5920:return ae(r,/(.+?):(\d+)(\s*\/\s*(span)?\s*(\d+))?(.*)/,function(u,c,d,p,m,v,x){return Ae+c+":"+d+x+(p?Ae+c+"-span:"+(m?v:+v-+d)+x:"")+r});case 4949:if(Fe(r,i+6)===121)return ae(r,":",":"+we)+r;break;case 6444:switch(Fe(r,Fe(r,14)===45?18:11)){case 120:return ae(r,/(.+:)([^;\s!]+)(;|(\s+)?!.+)?/,"$1"+we+(Fe(r,14)===45?"inline-":"")+"box$3$1"+we+"$2$3$1"+Ae+"$2box$3")+r;case 100:return ae(r,":",":"+Ae)+r}break;case 5719:case 2647:case 2135:case 3927:case 2391:return ae(r,"scroll-","scroll-snap-")+r}return r}function Ki(r,i){for(var s="",u=0;u-1&&!r.return)switch(r.type){case $u:r.return=ep(r.value,r.length,s);return;case Yd:return Ki([Cn(r,{value:ae(r.value,"@","@"+we)})],u);case ns:if(r.length)return og(s=r.props,function(c){switch(nn(c,u=/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":wr(Cn(r,{props:[ae(c,/:(read-\w+)/,":"+wo+"$1")]})),wr(Cn(r,{props:[c]})),Cu(r,{props:Zf(s,u)});break;case"::placeholder":wr(Cn(r,{props:[ae(c,/:(plac\w+)/,":"+we+"input-$1")]})),wr(Cn(r,{props:[ae(c,/:(plac\w+)/,":"+wo+"$1")]})),wr(Cn(r,{props:[ae(c,/:(plac\w+)/,Ae+"input-$1")]})),wr(Cn(r,{props:[c]})),Cu(r,{props:Zf(s,u)});break}return""})}}var wg={animationIterationCount:1,aspectRatio:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},ht={},Er=typeof process<"u"&&ht!==void 0&&(ht.REACT_APP_SC_ATTR||ht.SC_ATTR)||"data-styled",tp="active",np="data-styled-version",ss="6.1.14",Vu=`/*!sc*/ -`,Xi=typeof window<"u"&&"HTMLElement"in window,xg=!!(typeof SC_DISABLE_SPEEDY=="boolean"?SC_DISABLE_SPEEDY:typeof process<"u"&&ht!==void 0&&ht.REACT_APP_SC_DISABLE_SPEEDY!==void 0&&ht.REACT_APP_SC_DISABLE_SPEEDY!==""?ht.REACT_APP_SC_DISABLE_SPEEDY!=="false"&&ht.REACT_APP_SC_DISABLE_SPEEDY:typeof process<"u"&&ht!==void 0&&ht.SC_DISABLE_SPEEDY!==void 0&&ht.SC_DISABLE_SPEEDY!==""&&ht.SC_DISABLE_SPEEDY!=="false"&&ht.SC_DISABLE_SPEEDY),ls=Object.freeze([]),Cr=Object.freeze({});function Sg(r,i,s){return s===void 0&&(s=Cr),r.theme!==s.theme&&r.theme||i||s.theme}var rp=new Set(["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","track","u","ul","use","var","video","wbr","circle","clipPath","defs","ellipse","foreignObject","g","image","line","linearGradient","marker","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","tspan"]),kg=/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~-]+/g,Eg=/(^-|-$)/g;function nd(r){return r.replace(kg,"-").replace(Eg,"")}var Cg=/(a)(d)/gi,Di=52,rd=function(r){return String.fromCharCode(r+(r>25?39:97))};function Pu(r){var i,s="";for(i=Math.abs(r);i>Di;i=i/Di|0)s=rd(i%Di)+s;return(rd(i%Di)+s).replace(Cg,"$1-$2")}var pu,op=5381,xr=function(r,i){for(var s=i.length;s;)r=33*r^i.charCodeAt(--s);return r},ip=function(r){return xr(op,r)};function Ag(r){return Pu(ip(r)>>>0)}function Rg(r){return r.displayName||r.name||"Component"}function hu(r){return typeof r=="string"&&!0}var sp=typeof Symbol=="function"&&Symbol.for,lp=sp?Symbol.for("react.memo"):60115,Pg=sp?Symbol.for("react.forward_ref"):60112,jg={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},Ig={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},up={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},_g=((pu={})[Pg]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},pu[lp]=up,pu);function od(r){return("type"in(i=r)&&i.type.$$typeof)===lp?up:"$$typeof"in r?_g[r.$$typeof]:jg;var i}var Ng=Object.defineProperty,Og=Object.getOwnPropertyNames,id=Object.getOwnPropertySymbols,Tg=Object.getOwnPropertyDescriptor,Lg=Object.getPrototypeOf,sd=Object.prototype;function ap(r,i,s){if(typeof i!="string"){if(sd){var u=Lg(i);u&&u!==sd&&ap(r,u,s)}var c=Og(i);id&&(c=c.concat(id(i)));for(var d=od(r),p=od(i),m=0;m0?" Args: ".concat(i.join(", ")):""))}var Dg=function(){function r(i){this.groupSizes=new Uint32Array(512),this.length=512,this.tag=i}return r.prototype.indexOfGroup=function(i){for(var s=0,u=0;u=this.groupSizes.length){for(var u=this.groupSizes,c=u.length,d=c;i>=d;)if((d<<=1)<0)throw Qn(16,"".concat(i));this.groupSizes=new Uint32Array(d),this.groupSizes.set(u),this.length=d;for(var p=c;p=this.length||this.groupSizes[i]===0)return s;for(var u=this.groupSizes[i],c=this.indexOfGroup(i),d=c+u,p=c;p=0){var u=document.createTextNode(s);return this.element.insertBefore(u,this.nodes[i]||null),this.length++,!0}return!1},r.prototype.deleteRule=function(i){this.element.removeChild(this.nodes[i]),this.length--},r.prototype.getRule=function(i){return i0&&(L+="".concat(V,","))}),v+="".concat(I).concat(R,'{content:"').concat(L,'"}').concat(Vu)},E=0;E0?".".concat(i):O},E=v.slice();E.push(function(O){O.type===ns&&O.value.includes("&")&&(O.props[0]=O.props[0].replace(qg,s).replace(u,x))}),p.prefix&&E.push(vg),E.push(mg);var j=function(O,P,I,R){P===void 0&&(P=""),I===void 0&&(I=""),R===void 0&&(R="&"),i=R,s=P,u=new RegExp("\\".concat(s,"\\b"),"g");var L=O.replace(bg,""),V=pg(I||P?"".concat(I," ").concat(P," { ").concat(L," }"):L);p.namespace&&(V=dp(V,p.namespace));var F=[];return Ki(V,gg(E.concat(yg(function(W){return F.push(W)})))),F};return j.hash=v.length?v.reduce(function(O,P){return P.name||Qn(15),xr(O,P.name)},op).toString():"",j}var Yg=new fp,Iu=Gg(),pp=rn.createContext({shouldForwardProp:void 0,styleSheet:Yg,stylis:Iu});pp.Consumer;rn.createContext(void 0);function cd(){return ue.useContext(pp)}var Kg=function(){function r(i,s){var u=this;this.inject=function(c,d){d===void 0&&(d=Iu);var p=u.name+d.hash;c.hasNameForId(u.id,p)||c.insertRules(u.id,p,d(u.rules,p,"@keyframes"))},this.name=i,this.id="sc-keyframes-".concat(i),this.rules=s,Qu(this,function(){throw Qn(12,String(u.name))})}return r.prototype.getName=function(i){return i===void 0&&(i=Iu),this.name+i.hash},r}(),Xg=function(r){return r>="A"&&r<="Z"};function fd(r){for(var i="",s=0;s>>0);if(!s.hasNameForId(this.componentId,p)){var m=u(d,".".concat(p),void 0,this.componentId);s.insertRules(this.componentId,p,m)}c=Un(c,p),this.staticRulesId=p}else{for(var v=xr(this.baseHash,u.hash),x="",E=0;E>>0);s.hasNameForId(this.componentId,P)||s.insertRules(this.componentId,P,u(x,".".concat(P),void 0,this.componentId)),c=Un(c,P)}}return c},r}(),Zi=rn.createContext(void 0);Zi.Consumer;function ty(r){var i=rn.useContext(Zi),s=ue.useMemo(function(){return function(u,c){if(!u)throw Qn(14);if(Wn(u)){var d=u(c);return d}if(Array.isArray(u)||typeof u!="object")throw Qn(8);return c?Ke(Ke({},c),u):u}(r.theme,i)},[r.theme,i]);return r.children?rn.createElement(Zi.Provider,{value:s},r.children):null}var mu={};function ny(r,i,s){var u=Wu(r),c=r,d=!hu(r),p=i.attrs,m=p===void 0?ls:p,v=i.componentId,x=v===void 0?function(K,$){var T=typeof K!="string"?"sc":nd(K);mu[T]=(mu[T]||0)+1;var H="".concat(T,"-").concat(Ag(ss+T+mu[T]));return $?"".concat($,"-").concat(H):H}(i.displayName,i.parentComponentId):v,E=i.displayName,j=E===void 0?function(K){return hu(K)?"styled.".concat(K):"Styled(".concat(Rg(K),")")}(r):E,O=i.displayName&&i.componentId?"".concat(nd(i.displayName),"-").concat(i.componentId):i.componentId||x,P=u&&c.attrs?c.attrs.concat(m).filter(Boolean):m,I=i.shouldForwardProp;if(u&&c.shouldForwardProp){var R=c.shouldForwardProp;if(i.shouldForwardProp){var L=i.shouldForwardProp;I=function(K,$){return R(K,$)&&L(K,$)}}else I=R}var V=new ey(s,O,u?c.componentStyle:void 0);function F(K,$){return function(T,H,se){var Ve=T.attrs,At=T.componentStyle,qt=T.defaultProps,gt=T.foldedComponentIds,Je=T.styledComponentId,at=T.target,yt=rn.useContext(Zi),We=cd(),Se=T.shouldForwardProp||We.shouldForwardProp,Q=Sg(H,yt,qt)||Cr,ee=function(de,ce,ve){for(var pe,ge=Ke(Ke({},ce),{className:void 0,theme:ve}),Be=0;Ber.$hasUnread?r.theme.colors.text.primary:r.theme.colors.text.muted}; - font-weight: ${r=>r.$hasUnread?"600":"normal"}; - cursor: pointer; - background: ${r=>r.$isActive?r.theme.colors.background.hover:"transparent"}; - border-radius: 4px; - - &:hover { - background: ${r=>r.theme.colors.background.hover}; - color: ${r=>r.theme.colors.text.primary}; - } -`,hd=N.div` - margin-bottom: 8px; -`,Nu=N.div` - padding: 8px 16px; - display: flex; - align-items: center; - color: ${J.colors.text.muted}; - text-transform: uppercase; - font-size: 12px; - font-weight: 600; - cursor: pointer; - user-select: none; - - & > span:nth-child(2) { - flex: 1; - margin-right: auto; - } - - &:hover { - color: ${J.colors.text.primary}; - } -`,md=N.span` - margin-right: 4px; - font-size: 10px; - transition: transform 0.2s; - transform: rotate(${r=>r.$folded?"-90deg":"0deg"}); -`,gd=N.div` - display: ${r=>r.$folded?"none":"block"}; -`,yd=N(yp)` - height: ${r=>r.hasSubtext?"42px":"34px"}; -`,ly=N.div` - position: relative; - width: 32px; - height: 32px; - margin: 0 8px; - flex-shrink: 0; - min-width: 40px; - - img { - width: 32px; - height: 32px; - border-radius: 50%; - } -`,vd=N.div` - font-size: 16px; - line-height: 18px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: ${r=>r.$isActive||r.$hasUnread?r.theme.colors.text.primary:r.theme.colors.text.muted}; - font-weight: ${r=>r.$hasUnread?"600":"normal"}; -`,vp=N.div` - position: absolute; - bottom: 0; - right: 0; - width: 10px; - height: 10px; - border-radius: 50%; - background: ${r=>r.$online?J.colors.status.online:J.colors.status.offline}; - border: 2px solid ${J.colors.background.secondary}; - transform: translate(20%, 20%); -`;N(vp)` - border-color: ${J.colors.background.primary}; -`;const wd=N.button` - background: none; - border: none; - color: ${J.colors.text.muted}; - font-size: 18px; - padding: 0; - cursor: pointer; - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - opacity: 0; - transition: opacity 0.2s, color 0.2s; - - ${Nu}:hover & { - opacity: 1; - } - - &:hover { - color: ${J.colors.text.primary}; - } -`,uy=N.div` - width: 40px; - min-width: 40px; - height: 24px; - margin: 0 8px; - flex-shrink: 0; - position: relative; -`,ay=N.div` - font-size: 12px; - line-height: 13px; - color: ${J.colors.text.muted}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`,xd=N.div` - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - justify-content: center; - gap: 2px; -`,cy=N.img` - width: 24px; - height: 24px; - border-radius: 50%; - border: 2px solid ${J.colors.background.secondary}; - position: absolute; -`;function fy(){return g.jsx(sy,{children:"채널 목록"})}const Sd=r=>{let i;const s=new Set,u=(x,E)=>{const j=typeof x=="function"?x(i):x;if(!Object.is(j,i)){const O=i;i=E??(typeof j!="object"||j===null)?j:Object.assign({},i,j),s.forEach(P=>P(i,O))}},c=()=>i,m={setState:u,getState:c,getInitialState:()=>v,subscribe:x=>(s.add(x),()=>s.delete(x))},v=i=r(u,c,m);return m},dy=r=>r?Sd(r):Sd,py=r=>r;function hy(r,i=py){const s=rn.useSyncExternalStore(r.subscribe,()=>i(r.getState()),()=>i(r.getInitialState()));return rn.useDebugValue(s),s}const kd=r=>{const i=dy(r),s=u=>hy(i,u);return Object.assign(s,i),s},bn=r=>r?kd(r):kd;function wp(r,i){return function(){return r.apply(i,arguments)}}const{toString:my}=Object.prototype,{getPrototypeOf:qu}=Object,us=(r=>i=>{const s=my.call(i);return r[s]||(r[s]=s.slice(8,-1).toLowerCase())})(Object.create(null)),zt=r=>(r=r.toLowerCase(),i=>us(i)===r),as=r=>i=>typeof i===r,{isArray:Rr}=Array,ko=as("undefined");function gy(r){return r!==null&&!ko(r)&&r.constructor!==null&&!ko(r.constructor)&&mt(r.constructor.isBuffer)&&r.constructor.isBuffer(r)}const xp=zt("ArrayBuffer");function yy(r){let i;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?i=ArrayBuffer.isView(r):i=r&&r.buffer&&xp(r.buffer),i}const vy=as("string"),mt=as("function"),Sp=as("number"),cs=r=>r!==null&&typeof r=="object",wy=r=>r===!0||r===!1,qi=r=>{if(us(r)!=="object")return!1;const i=qu(r);return(i===null||i===Object.prototype||Object.getPrototypeOf(i)===null)&&!(Symbol.toStringTag in r)&&!(Symbol.iterator in r)},xy=zt("Date"),Sy=zt("File"),ky=zt("Blob"),Ey=zt("FileList"),Cy=r=>cs(r)&&mt(r.pipe),Ay=r=>{let i;return r&&(typeof FormData=="function"&&r instanceof FormData||mt(r.append)&&((i=us(r))==="formdata"||i==="object"&&mt(r.toString)&&r.toString()==="[object FormData]"))},Ry=zt("URLSearchParams"),[Py,jy,Iy,_y]=["ReadableStream","Request","Response","Headers"].map(zt),Ny=r=>r.trim?r.trim():r.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function Eo(r,i,{allOwnKeys:s=!1}={}){if(r===null||typeof r>"u")return;let u,c;if(typeof r!="object"&&(r=[r]),Rr(r))for(u=0,c=r.length;u0;)if(c=s[u],i===c.toLowerCase())return c;return null}const Fn=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,Ep=r=>!ko(r)&&r!==Fn;function Ou(){const{caseless:r}=Ep(this)&&this||{},i={},s=(u,c)=>{const d=r&&kp(i,c)||c;qi(i[d])&&qi(u)?i[d]=Ou(i[d],u):qi(u)?i[d]=Ou({},u):Rr(u)?i[d]=u.slice():i[d]=u};for(let u=0,c=arguments.length;u(Eo(i,(c,d)=>{s&&mt(c)?r[d]=wp(c,s):r[d]=c},{allOwnKeys:u}),r),Ty=r=>(r.charCodeAt(0)===65279&&(r=r.slice(1)),r),Ly=(r,i,s,u)=>{r.prototype=Object.create(i.prototype,u),r.prototype.constructor=r,Object.defineProperty(r,"super",{value:i.prototype}),s&&Object.assign(r.prototype,s)},Dy=(r,i,s,u)=>{let c,d,p;const m={};if(i=i||{},r==null)return i;do{for(c=Object.getOwnPropertyNames(r),d=c.length;d-- >0;)p=c[d],(!u||u(p,r,i))&&!m[p]&&(i[p]=r[p],m[p]=!0);r=s!==!1&&qu(r)}while(r&&(!s||s(r,i))&&r!==Object.prototype);return i},zy=(r,i,s)=>{r=String(r),(s===void 0||s>r.length)&&(s=r.length),s-=i.length;const u=r.indexOf(i,s);return u!==-1&&u===s},My=r=>{if(!r)return null;if(Rr(r))return r;let i=r.length;if(!Sp(i))return null;const s=new Array(i);for(;i-- >0;)s[i]=r[i];return s},Uy=(r=>i=>r&&i instanceof r)(typeof Uint8Array<"u"&&qu(Uint8Array)),Fy=(r,i)=>{const u=(r&&r[Symbol.iterator]).call(r);let c;for(;(c=u.next())&&!c.done;){const d=c.value;i.call(r,d[0],d[1])}},By=(r,i)=>{let s;const u=[];for(;(s=r.exec(i))!==null;)u.push(s);return u},$y=zt("HTMLFormElement"),Hy=r=>r.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(s,u,c){return u.toUpperCase()+c}),Ed=(({hasOwnProperty:r})=>(i,s)=>r.call(i,s))(Object.prototype),Vy=zt("RegExp"),Cp=(r,i)=>{const s=Object.getOwnPropertyDescriptors(r),u={};Eo(s,(c,d)=>{let p;(p=i(c,d,r))!==!1&&(u[d]=p||c)}),Object.defineProperties(r,u)},Wy=r=>{Cp(r,(i,s)=>{if(mt(r)&&["arguments","caller","callee"].indexOf(s)!==-1)return!1;const u=r[s];if(mt(u)){if(i.enumerable=!1,"writable"in i){i.writable=!1;return}i.set||(i.set=()=>{throw Error("Can not rewrite read-only method '"+s+"'")})}})},Qy=(r,i)=>{const s={},u=c=>{c.forEach(d=>{s[d]=!0})};return Rr(r)?u(r):u(String(r).split(i)),s},qy=()=>{},by=(r,i)=>r!=null&&Number.isFinite(r=+r)?r:i,gu="abcdefghijklmnopqrstuvwxyz",Cd="0123456789",Ap={DIGIT:Cd,ALPHA:gu,ALPHA_DIGIT:gu+gu.toUpperCase()+Cd},Gy=(r=16,i=Ap.ALPHA_DIGIT)=>{let s="";const{length:u}=i;for(;r--;)s+=i[Math.random()*u|0];return s};function Yy(r){return!!(r&&mt(r.append)&&r[Symbol.toStringTag]==="FormData"&&r[Symbol.iterator])}const Ky=r=>{const i=new Array(10),s=(u,c)=>{if(cs(u)){if(i.indexOf(u)>=0)return;if(!("toJSON"in u)){i[c]=u;const d=Rr(u)?[]:{};return Eo(u,(p,m)=>{const v=s(p,c+1);!ko(v)&&(d[m]=v)}),i[c]=void 0,d}}return u};return s(r,0)},Xy=zt("AsyncFunction"),Jy=r=>r&&(cs(r)||mt(r))&&mt(r.then)&&mt(r.catch),Rp=((r,i)=>r?setImmediate:i?((s,u)=>(Fn.addEventListener("message",({source:c,data:d})=>{c===Fn&&d===s&&u.length&&u.shift()()},!1),c=>{u.push(c),Fn.postMessage(s,"*")}))(`axios@${Math.random()}`,[]):s=>setTimeout(s))(typeof setImmediate=="function",mt(Fn.postMessage)),Zy=typeof queueMicrotask<"u"?queueMicrotask.bind(Fn):typeof process<"u"&&process.nextTick||Rp,_={isArray:Rr,isArrayBuffer:xp,isBuffer:gy,isFormData:Ay,isArrayBufferView:yy,isString:vy,isNumber:Sp,isBoolean:wy,isObject:cs,isPlainObject:qi,isReadableStream:Py,isRequest:jy,isResponse:Iy,isHeaders:_y,isUndefined:ko,isDate:xy,isFile:Sy,isBlob:ky,isRegExp:Vy,isFunction:mt,isStream:Cy,isURLSearchParams:Ry,isTypedArray:Uy,isFileList:Ey,forEach:Eo,merge:Ou,extend:Oy,trim:Ny,stripBOM:Ty,inherits:Ly,toFlatObject:Dy,kindOf:us,kindOfTest:zt,endsWith:zy,toArray:My,forEachEntry:Fy,matchAll:By,isHTMLForm:$y,hasOwnProperty:Ed,hasOwnProp:Ed,reduceDescriptors:Cp,freezeMethods:Wy,toObjectSet:Qy,toCamelCase:Hy,noop:qy,toFiniteNumber:by,findKey:kp,global:Fn,isContextDefined:Ep,ALPHABET:Ap,generateString:Gy,isSpecCompliantForm:Yy,toJSONObject:Ky,isAsyncFn:Xy,isThenable:Jy,setImmediate:Rp,asap:Zy};function ie(r,i,s,u,c){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=r,this.name="AxiosError",i&&(this.code=i),s&&(this.config=s),u&&(this.request=u),c&&(this.response=c,this.status=c.status?c.status:null)}_.inherits(ie,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:_.toJSONObject(this.config),code:this.code,status:this.status}}});const Pp=ie.prototype,jp={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(r=>{jp[r]={value:r}});Object.defineProperties(ie,jp);Object.defineProperty(Pp,"isAxiosError",{value:!0});ie.from=(r,i,s,u,c,d)=>{const p=Object.create(Pp);return _.toFlatObject(r,p,function(v){return v!==Error.prototype},m=>m!=="isAxiosError"),ie.call(p,r.message,i,s,u,c),p.cause=r,p.name=r.name,d&&Object.assign(p,d),p};const e0=null;function Tu(r){return _.isPlainObject(r)||_.isArray(r)}function Ip(r){return _.endsWith(r,"[]")?r.slice(0,-2):r}function Ad(r,i,s){return r?r.concat(i).map(function(c,d){return c=Ip(c),!s&&d?"["+c+"]":c}).join(s?".":""):i}function t0(r){return _.isArray(r)&&!r.some(Tu)}const n0=_.toFlatObject(_,{},null,function(i){return/^is[A-Z]/.test(i)});function fs(r,i,s){if(!_.isObject(r))throw new TypeError("target must be an object");i=i||new FormData,s=_.toFlatObject(s,{metaTokens:!0,dots:!1,indexes:!1},!1,function(R,L){return!_.isUndefined(L[R])});const u=s.metaTokens,c=s.visitor||E,d=s.dots,p=s.indexes,v=(s.Blob||typeof Blob<"u"&&Blob)&&_.isSpecCompliantForm(i);if(!_.isFunction(c))throw new TypeError("visitor must be a function");function x(I){if(I===null)return"";if(_.isDate(I))return I.toISOString();if(!v&&_.isBlob(I))throw new ie("Blob is not supported. Use a Buffer instead.");return _.isArrayBuffer(I)||_.isTypedArray(I)?v&&typeof Blob=="function"?new Blob([I]):Buffer.from(I):I}function E(I,R,L){let V=I;if(I&&!L&&typeof I=="object"){if(_.endsWith(R,"{}"))R=u?R:R.slice(0,-2),I=JSON.stringify(I);else if(_.isArray(I)&&t0(I)||(_.isFileList(I)||_.endsWith(R,"[]"))&&(V=_.toArray(I)))return R=Ip(R),V.forEach(function(W,K){!(_.isUndefined(W)||W===null)&&i.append(p===!0?Ad([R],K,d):p===null?R:R+"[]",x(W))}),!1}return Tu(I)?!0:(i.append(Ad(L,R,d),x(I)),!1)}const j=[],O=Object.assign(n0,{defaultVisitor:E,convertValue:x,isVisitable:Tu});function P(I,R){if(!_.isUndefined(I)){if(j.indexOf(I)!==-1)throw Error("Circular reference detected in "+R.join("."));j.push(I),_.forEach(I,function(V,F){(!(_.isUndefined(V)||V===null)&&c.call(i,V,_.isString(F)?F.trim():F,R,O))===!0&&P(V,R?R.concat(F):[F])}),j.pop()}}if(!_.isObject(r))throw new TypeError("data must be an object");return P(r),i}function Rd(r){const i={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(r).replace(/[!'()~]|%20|%00/g,function(u){return i[u]})}function bu(r,i){this._pairs=[],r&&fs(r,this,i)}const _p=bu.prototype;_p.append=function(i,s){this._pairs.push([i,s])};_p.toString=function(i){const s=i?function(u){return i.call(this,u,Rd)}:Rd;return this._pairs.map(function(c){return s(c[0])+"="+s(c[1])},"").join("&")};function r0(r){return encodeURIComponent(r).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function Np(r,i,s){if(!i)return r;const u=s&&s.encode||r0;_.isFunction(s)&&(s={serialize:s});const c=s&&s.serialize;let d;if(c?d=c(i,s):d=_.isURLSearchParams(i)?i.toString():new bu(i,s).toString(u),d){const p=r.indexOf("#");p!==-1&&(r=r.slice(0,p)),r+=(r.indexOf("?")===-1?"?":"&")+d}return r}class Pd{constructor(){this.handlers=[]}use(i,s,u){return this.handlers.push({fulfilled:i,rejected:s,synchronous:u?u.synchronous:!1,runWhen:u?u.runWhen:null}),this.handlers.length-1}eject(i){this.handlers[i]&&(this.handlers[i]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(i){_.forEach(this.handlers,function(u){u!==null&&i(u)})}}const Op={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},o0=typeof URLSearchParams<"u"?URLSearchParams:bu,i0=typeof FormData<"u"?FormData:null,s0=typeof Blob<"u"?Blob:null,l0={isBrowser:!0,classes:{URLSearchParams:o0,FormData:i0,Blob:s0},protocols:["http","https","file","blob","url","data"]},Gu=typeof window<"u"&&typeof document<"u",Lu=typeof navigator=="object"&&navigator||void 0,u0=Gu&&(!Lu||["ReactNative","NativeScript","NS"].indexOf(Lu.product)<0),a0=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",c0=Gu&&window.location.href||"http://localhost",f0=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:Gu,hasStandardBrowserEnv:u0,hasStandardBrowserWebWorkerEnv:a0,navigator:Lu,origin:c0},Symbol.toStringTag,{value:"Module"})),Ye={...f0,...l0};function d0(r,i){return fs(r,new Ye.classes.URLSearchParams,Object.assign({visitor:function(s,u,c,d){return Ye.isNode&&_.isBuffer(s)?(this.append(u,s.toString("base64")),!1):d.defaultVisitor.apply(this,arguments)}},i))}function p0(r){return _.matchAll(/\w+|\[(\w*)]/g,r).map(i=>i[0]==="[]"?"":i[1]||i[0])}function h0(r){const i={},s=Object.keys(r);let u;const c=s.length;let d;for(u=0;u=s.length;return p=!p&&_.isArray(c)?c.length:p,v?(_.hasOwnProp(c,p)?c[p]=[c[p],u]:c[p]=u,!m):((!c[p]||!_.isObject(c[p]))&&(c[p]=[]),i(s,u,c[p],d)&&_.isArray(c[p])&&(c[p]=h0(c[p])),!m)}if(_.isFormData(r)&&_.isFunction(r.entries)){const s={};return _.forEachEntry(r,(u,c)=>{i(p0(u),c,s,0)}),s}return null}function m0(r,i,s){if(_.isString(r))try{return(i||JSON.parse)(r),_.trim(r)}catch(u){if(u.name!=="SyntaxError")throw u}return(0,JSON.stringify)(r)}const Co={transitional:Op,adapter:["xhr","http","fetch"],transformRequest:[function(i,s){const u=s.getContentType()||"",c=u.indexOf("application/json")>-1,d=_.isObject(i);if(d&&_.isHTMLForm(i)&&(i=new FormData(i)),_.isFormData(i))return c?JSON.stringify(Tp(i)):i;if(_.isArrayBuffer(i)||_.isBuffer(i)||_.isStream(i)||_.isFile(i)||_.isBlob(i)||_.isReadableStream(i))return i;if(_.isArrayBufferView(i))return i.buffer;if(_.isURLSearchParams(i))return s.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),i.toString();let m;if(d){if(u.indexOf("application/x-www-form-urlencoded")>-1)return d0(i,this.formSerializer).toString();if((m=_.isFileList(i))||u.indexOf("multipart/form-data")>-1){const v=this.env&&this.env.FormData;return fs(m?{"files[]":i}:i,v&&new v,this.formSerializer)}}return d||c?(s.setContentType("application/json",!1),m0(i)):i}],transformResponse:[function(i){const s=this.transitional||Co.transitional,u=s&&s.forcedJSONParsing,c=this.responseType==="json";if(_.isResponse(i)||_.isReadableStream(i))return i;if(i&&_.isString(i)&&(u&&!this.responseType||c)){const p=!(s&&s.silentJSONParsing)&&c;try{return JSON.parse(i)}catch(m){if(p)throw m.name==="SyntaxError"?ie.from(m,ie.ERR_BAD_RESPONSE,this,null,this.response):m}}return i}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:Ye.classes.FormData,Blob:Ye.classes.Blob},validateStatus:function(i){return i>=200&&i<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};_.forEach(["delete","get","head","post","put","patch"],r=>{Co.headers[r]={}});const g0=_.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),y0=r=>{const i={};let s,u,c;return r&&r.split(` -`).forEach(function(p){c=p.indexOf(":"),s=p.substring(0,c).trim().toLowerCase(),u=p.substring(c+1).trim(),!(!s||i[s]&&g0[s])&&(s==="set-cookie"?i[s]?i[s].push(u):i[s]=[u]:i[s]=i[s]?i[s]+", "+u:u)}),i},jd=Symbol("internals");function mo(r){return r&&String(r).trim().toLowerCase()}function bi(r){return r===!1||r==null?r:_.isArray(r)?r.map(bi):String(r)}function v0(r){const i=Object.create(null),s=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let u;for(;u=s.exec(r);)i[u[1]]=u[2];return i}const w0=r=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(r.trim());function yu(r,i,s,u,c){if(_.isFunction(u))return u.call(this,i,s);if(c&&(i=s),!!_.isString(i)){if(_.isString(u))return i.indexOf(u)!==-1;if(_.isRegExp(u))return u.test(i)}}function x0(r){return r.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(i,s,u)=>s.toUpperCase()+u)}function S0(r,i){const s=_.toCamelCase(" "+i);["get","set","has"].forEach(u=>{Object.defineProperty(r,u+s,{value:function(c,d,p){return this[u].call(this,i,c,d,p)},configurable:!0})})}class lt{constructor(i){i&&this.set(i)}set(i,s,u){const c=this;function d(m,v,x){const E=mo(v);if(!E)throw new Error("header name must be a non-empty string");const j=_.findKey(c,E);(!j||c[j]===void 0||x===!0||x===void 0&&c[j]!==!1)&&(c[j||v]=bi(m))}const p=(m,v)=>_.forEach(m,(x,E)=>d(x,E,v));if(_.isPlainObject(i)||i instanceof this.constructor)p(i,s);else if(_.isString(i)&&(i=i.trim())&&!w0(i))p(y0(i),s);else if(_.isHeaders(i))for(const[m,v]of i.entries())d(v,m,u);else i!=null&&d(s,i,u);return this}get(i,s){if(i=mo(i),i){const u=_.findKey(this,i);if(u){const c=this[u];if(!s)return c;if(s===!0)return v0(c);if(_.isFunction(s))return s.call(this,c,u);if(_.isRegExp(s))return s.exec(c);throw new TypeError("parser must be boolean|regexp|function")}}}has(i,s){if(i=mo(i),i){const u=_.findKey(this,i);return!!(u&&this[u]!==void 0&&(!s||yu(this,this[u],u,s)))}return!1}delete(i,s){const u=this;let c=!1;function d(p){if(p=mo(p),p){const m=_.findKey(u,p);m&&(!s||yu(u,u[m],m,s))&&(delete u[m],c=!0)}}return _.isArray(i)?i.forEach(d):d(i),c}clear(i){const s=Object.keys(this);let u=s.length,c=!1;for(;u--;){const d=s[u];(!i||yu(this,this[d],d,i,!0))&&(delete this[d],c=!0)}return c}normalize(i){const s=this,u={};return _.forEach(this,(c,d)=>{const p=_.findKey(u,d);if(p){s[p]=bi(c),delete s[d];return}const m=i?x0(d):String(d).trim();m!==d&&delete s[d],s[m]=bi(c),u[m]=!0}),this}concat(...i){return this.constructor.concat(this,...i)}toJSON(i){const s=Object.create(null);return _.forEach(this,(u,c)=>{u!=null&&u!==!1&&(s[c]=i&&_.isArray(u)?u.join(", "):u)}),s}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([i,s])=>i+": "+s).join(` -`)}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(i){return i instanceof this?i:new this(i)}static concat(i,...s){const u=new this(i);return s.forEach(c=>u.set(c)),u}static accessor(i){const u=(this[jd]=this[jd]={accessors:{}}).accessors,c=this.prototype;function d(p){const m=mo(p);u[m]||(S0(c,p),u[m]=!0)}return _.isArray(i)?i.forEach(d):d(i),this}}lt.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);_.reduceDescriptors(lt.prototype,({value:r},i)=>{let s=i[0].toUpperCase()+i.slice(1);return{get:()=>r,set(u){this[s]=u}}});_.freezeMethods(lt);function vu(r,i){const s=this||Co,u=i||s,c=lt.from(u.headers);let d=u.data;return _.forEach(r,function(m){d=m.call(s,d,c.normalize(),i?i.status:void 0)}),c.normalize(),d}function Lp(r){return!!(r&&r.__CANCEL__)}function Pr(r,i,s){ie.call(this,r??"canceled",ie.ERR_CANCELED,i,s),this.name="CanceledError"}_.inherits(Pr,ie,{__CANCEL__:!0});function Dp(r,i,s){const u=s.config.validateStatus;!s.status||!u||u(s.status)?r(s):i(new ie("Request failed with status code "+s.status,[ie.ERR_BAD_REQUEST,ie.ERR_BAD_RESPONSE][Math.floor(s.status/100)-4],s.config,s.request,s))}function k0(r){const i=/^([-+\w]{1,25})(:?\/\/|:)/.exec(r);return i&&i[1]||""}function E0(r,i){r=r||10;const s=new Array(r),u=new Array(r);let c=0,d=0,p;return i=i!==void 0?i:1e3,function(v){const x=Date.now(),E=u[d];p||(p=x),s[c]=v,u[c]=x;let j=d,O=0;for(;j!==c;)O+=s[j++],j=j%r;if(c=(c+1)%r,c===d&&(d=(d+1)%r),x-p{s=E,c=null,d&&(clearTimeout(d),d=null),r.apply(null,x)};return[(...x)=>{const E=Date.now(),j=E-s;j>=u?p(x,E):(c=x,d||(d=setTimeout(()=>{d=null,p(c)},u-j)))},()=>c&&p(c)]}const es=(r,i,s=3)=>{let u=0;const c=E0(50,250);return C0(d=>{const p=d.loaded,m=d.lengthComputable?d.total:void 0,v=p-u,x=c(v),E=p<=m;u=p;const j={loaded:p,total:m,progress:m?p/m:void 0,bytes:v,rate:x||void 0,estimated:x&&m&&E?(m-p)/x:void 0,event:d,lengthComputable:m!=null,[i?"download":"upload"]:!0};r(j)},s)},Id=(r,i)=>{const s=r!=null;return[u=>i[0]({lengthComputable:s,total:r,loaded:u}),i[1]]},_d=r=>(...i)=>_.asap(()=>r(...i)),A0=Ye.hasStandardBrowserEnv?((r,i)=>s=>(s=new URL(s,Ye.origin),r.protocol===s.protocol&&r.host===s.host&&(i||r.port===s.port)))(new URL(Ye.origin),Ye.navigator&&/(msie|trident)/i.test(Ye.navigator.userAgent)):()=>!0,R0=Ye.hasStandardBrowserEnv?{write(r,i,s,u,c,d){const p=[r+"="+encodeURIComponent(i)];_.isNumber(s)&&p.push("expires="+new Date(s).toGMTString()),_.isString(u)&&p.push("path="+u),_.isString(c)&&p.push("domain="+c),d===!0&&p.push("secure"),document.cookie=p.join("; ")},read(r){const i=document.cookie.match(new RegExp("(^|;\\s*)("+r+")=([^;]*)"));return i?decodeURIComponent(i[3]):null},remove(r){this.write(r,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function P0(r){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(r)}function j0(r,i){return i?r.replace(/\/?\/$/,"")+"/"+i.replace(/^\/+/,""):r}function zp(r,i){return r&&!P0(i)?j0(r,i):i}const Nd=r=>r instanceof lt?{...r}:r;function qn(r,i){i=i||{};const s={};function u(x,E,j,O){return _.isPlainObject(x)&&_.isPlainObject(E)?_.merge.call({caseless:O},x,E):_.isPlainObject(E)?_.merge({},E):_.isArray(E)?E.slice():E}function c(x,E,j,O){if(_.isUndefined(E)){if(!_.isUndefined(x))return u(void 0,x,j,O)}else return u(x,E,j,O)}function d(x,E){if(!_.isUndefined(E))return u(void 0,E)}function p(x,E){if(_.isUndefined(E)){if(!_.isUndefined(x))return u(void 0,x)}else return u(void 0,E)}function m(x,E,j){if(j in i)return u(x,E);if(j in r)return u(void 0,x)}const v={url:d,method:d,data:d,baseURL:p,transformRequest:p,transformResponse:p,paramsSerializer:p,timeout:p,timeoutMessage:p,withCredentials:p,withXSRFToken:p,adapter:p,responseType:p,xsrfCookieName:p,xsrfHeaderName:p,onUploadProgress:p,onDownloadProgress:p,decompress:p,maxContentLength:p,maxBodyLength:p,beforeRedirect:p,transport:p,httpAgent:p,httpsAgent:p,cancelToken:p,socketPath:p,responseEncoding:p,validateStatus:m,headers:(x,E,j)=>c(Nd(x),Nd(E),j,!0)};return _.forEach(Object.keys(Object.assign({},r,i)),function(E){const j=v[E]||c,O=j(r[E],i[E],E);_.isUndefined(O)&&j!==m||(s[E]=O)}),s}const Mp=r=>{const i=qn({},r);let{data:s,withXSRFToken:u,xsrfHeaderName:c,xsrfCookieName:d,headers:p,auth:m}=i;i.headers=p=lt.from(p),i.url=Np(zp(i.baseURL,i.url),r.params,r.paramsSerializer),m&&p.set("Authorization","Basic "+btoa((m.username||"")+":"+(m.password?unescape(encodeURIComponent(m.password)):"")));let v;if(_.isFormData(s)){if(Ye.hasStandardBrowserEnv||Ye.hasStandardBrowserWebWorkerEnv)p.setContentType(void 0);else if((v=p.getContentType())!==!1){const[x,...E]=v?v.split(";").map(j=>j.trim()).filter(Boolean):[];p.setContentType([x||"multipart/form-data",...E].join("; "))}}if(Ye.hasStandardBrowserEnv&&(u&&_.isFunction(u)&&(u=u(i)),u||u!==!1&&A0(i.url))){const x=c&&d&&R0.read(d);x&&p.set(c,x)}return i},I0=typeof XMLHttpRequest<"u",_0=I0&&function(r){return new Promise(function(s,u){const c=Mp(r);let d=c.data;const p=lt.from(c.headers).normalize();let{responseType:m,onUploadProgress:v,onDownloadProgress:x}=c,E,j,O,P,I;function R(){P&&P(),I&&I(),c.cancelToken&&c.cancelToken.unsubscribe(E),c.signal&&c.signal.removeEventListener("abort",E)}let L=new XMLHttpRequest;L.open(c.method.toUpperCase(),c.url,!0),L.timeout=c.timeout;function V(){if(!L)return;const W=lt.from("getAllResponseHeaders"in L&&L.getAllResponseHeaders()),$={data:!m||m==="text"||m==="json"?L.responseText:L.response,status:L.status,statusText:L.statusText,headers:W,config:r,request:L};Dp(function(H){s(H),R()},function(H){u(H),R()},$),L=null}"onloadend"in L?L.onloadend=V:L.onreadystatechange=function(){!L||L.readyState!==4||L.status===0&&!(L.responseURL&&L.responseURL.indexOf("file:")===0)||setTimeout(V)},L.onabort=function(){L&&(u(new ie("Request aborted",ie.ECONNABORTED,r,L)),L=null)},L.onerror=function(){u(new ie("Network Error",ie.ERR_NETWORK,r,L)),L=null},L.ontimeout=function(){let K=c.timeout?"timeout of "+c.timeout+"ms exceeded":"timeout exceeded";const $=c.transitional||Op;c.timeoutErrorMessage&&(K=c.timeoutErrorMessage),u(new ie(K,$.clarifyTimeoutError?ie.ETIMEDOUT:ie.ECONNABORTED,r,L)),L=null},d===void 0&&p.setContentType(null),"setRequestHeader"in L&&_.forEach(p.toJSON(),function(K,$){L.setRequestHeader($,K)}),_.isUndefined(c.withCredentials)||(L.withCredentials=!!c.withCredentials),m&&m!=="json"&&(L.responseType=c.responseType),x&&([O,I]=es(x,!0),L.addEventListener("progress",O)),v&&L.upload&&([j,P]=es(v),L.upload.addEventListener("progress",j),L.upload.addEventListener("loadend",P)),(c.cancelToken||c.signal)&&(E=W=>{L&&(u(!W||W.type?new Pr(null,r,L):W),L.abort(),L=null)},c.cancelToken&&c.cancelToken.subscribe(E),c.signal&&(c.signal.aborted?E():c.signal.addEventListener("abort",E)));const F=k0(c.url);if(F&&Ye.protocols.indexOf(F)===-1){u(new ie("Unsupported protocol "+F+":",ie.ERR_BAD_REQUEST,r));return}L.send(d||null)})},N0=(r,i)=>{const{length:s}=r=r?r.filter(Boolean):[];if(i||s){let u=new AbortController,c;const d=function(x){if(!c){c=!0,m();const E=x instanceof Error?x:this.reason;u.abort(E instanceof ie?E:new Pr(E instanceof Error?E.message:E))}};let p=i&&setTimeout(()=>{p=null,d(new ie(`timeout ${i} of ms exceeded`,ie.ETIMEDOUT))},i);const m=()=>{r&&(p&&clearTimeout(p),p=null,r.forEach(x=>{x.unsubscribe?x.unsubscribe(d):x.removeEventListener("abort",d)}),r=null)};r.forEach(x=>x.addEventListener("abort",d));const{signal:v}=u;return v.unsubscribe=()=>_.asap(m),v}},O0=function*(r,i){let s=r.byteLength;if(s{const c=T0(r,i);let d=0,p,m=v=>{p||(p=!0,u&&u(v))};return new ReadableStream({async pull(v){try{const{done:x,value:E}=await c.next();if(x){m(),v.close();return}let j=E.byteLength;if(s){let O=d+=j;s(O)}v.enqueue(new Uint8Array(E))}catch(x){throw m(x),x}},cancel(v){return m(v),c.return()}},{highWaterMark:2})},ds=typeof fetch=="function"&&typeof Request=="function"&&typeof Response=="function",Up=ds&&typeof ReadableStream=="function",D0=ds&&(typeof TextEncoder=="function"?(r=>i=>r.encode(i))(new TextEncoder):async r=>new Uint8Array(await new Response(r).arrayBuffer())),Fp=(r,...i)=>{try{return!!r(...i)}catch{return!1}},z0=Up&&Fp(()=>{let r=!1;const i=new Request(Ye.origin,{body:new ReadableStream,method:"POST",get duplex(){return r=!0,"half"}}).headers.has("Content-Type");return r&&!i}),Td=64*1024,Du=Up&&Fp(()=>_.isReadableStream(new Response("").body)),ts={stream:Du&&(r=>r.body)};ds&&(r=>{["text","arrayBuffer","blob","formData","stream"].forEach(i=>{!ts[i]&&(ts[i]=_.isFunction(r[i])?s=>s[i]():(s,u)=>{throw new ie(`Response type '${i}' is not supported`,ie.ERR_NOT_SUPPORT,u)})})})(new Response);const M0=async r=>{if(r==null)return 0;if(_.isBlob(r))return r.size;if(_.isSpecCompliantForm(r))return(await new Request(Ye.origin,{method:"POST",body:r}).arrayBuffer()).byteLength;if(_.isArrayBufferView(r)||_.isArrayBuffer(r))return r.byteLength;if(_.isURLSearchParams(r)&&(r=r+""),_.isString(r))return(await D0(r)).byteLength},U0=async(r,i)=>{const s=_.toFiniteNumber(r.getContentLength());return s??M0(i)},F0=ds&&(async r=>{let{url:i,method:s,data:u,signal:c,cancelToken:d,timeout:p,onDownloadProgress:m,onUploadProgress:v,responseType:x,headers:E,withCredentials:j="same-origin",fetchOptions:O}=Mp(r);x=x?(x+"").toLowerCase():"text";let P=N0([c,d&&d.toAbortSignal()],p),I;const R=P&&P.unsubscribe&&(()=>{P.unsubscribe()});let L;try{if(v&&z0&&s!=="get"&&s!=="head"&&(L=await U0(E,u))!==0){let $=new Request(i,{method:"POST",body:u,duplex:"half"}),T;if(_.isFormData(u)&&(T=$.headers.get("content-type"))&&E.setContentType(T),$.body){const[H,se]=Id(L,es(_d(v)));u=Od($.body,Td,H,se)}}_.isString(j)||(j=j?"include":"omit");const V="credentials"in Request.prototype;I=new Request(i,{...O,signal:P,method:s.toUpperCase(),headers:E.normalize().toJSON(),body:u,duplex:"half",credentials:V?j:void 0});let F=await fetch(I);const W=Du&&(x==="stream"||x==="response");if(Du&&(m||W&&R)){const $={};["status","statusText","headers"].forEach(Ve=>{$[Ve]=F[Ve]});const T=_.toFiniteNumber(F.headers.get("content-length")),[H,se]=m&&Id(T,es(_d(m),!0))||[];F=new Response(Od(F.body,Td,H,()=>{se&&se(),R&&R()}),$)}x=x||"text";let K=await ts[_.findKey(ts,x)||"text"](F,r);return!W&&R&&R(),await new Promise(($,T)=>{Dp($,T,{data:K,headers:lt.from(F.headers),status:F.status,statusText:F.statusText,config:r,request:I})})}catch(V){throw R&&R(),V&&V.name==="TypeError"&&/fetch/i.test(V.message)?Object.assign(new ie("Network Error",ie.ERR_NETWORK,r,I),{cause:V.cause||V}):ie.from(V,V&&V.code,r,I)}}),zu={http:e0,xhr:_0,fetch:F0};_.forEach(zu,(r,i)=>{if(r){try{Object.defineProperty(r,"name",{value:i})}catch{}Object.defineProperty(r,"adapterName",{value:i})}});const Ld=r=>`- ${r}`,B0=r=>_.isFunction(r)||r===null||r===!1,Bp={getAdapter:r=>{r=_.isArray(r)?r:[r];const{length:i}=r;let s,u;const c={};for(let d=0;d`adapter ${m} `+(v===!1?"is not supported by the environment":"is not available in the build"));let p=i?d.length>1?`since : -`+d.map(Ld).join(` -`):" "+Ld(d[0]):"as no adapter specified";throw new ie("There is no suitable adapter to dispatch the request "+p,"ERR_NOT_SUPPORT")}return u},adapters:zu};function wu(r){if(r.cancelToken&&r.cancelToken.throwIfRequested(),r.signal&&r.signal.aborted)throw new Pr(null,r)}function Dd(r){return wu(r),r.headers=lt.from(r.headers),r.data=vu.call(r,r.transformRequest),["post","put","patch"].indexOf(r.method)!==-1&&r.headers.setContentType("application/x-www-form-urlencoded",!1),Bp.getAdapter(r.adapter||Co.adapter)(r).then(function(u){return wu(r),u.data=vu.call(r,r.transformResponse,u),u.headers=lt.from(u.headers),u},function(u){return Lp(u)||(wu(r),u&&u.response&&(u.response.data=vu.call(r,r.transformResponse,u.response),u.response.headers=lt.from(u.response.headers))),Promise.reject(u)})}const $p="1.7.9",ps={};["object","boolean","number","function","string","symbol"].forEach((r,i)=>{ps[r]=function(u){return typeof u===r||"a"+(i<1?"n ":" ")+r}});const zd={};ps.transitional=function(i,s,u){function c(d,p){return"[Axios v"+$p+"] Transitional option '"+d+"'"+p+(u?". "+u:"")}return(d,p,m)=>{if(i===!1)throw new ie(c(p," has been removed"+(s?" in "+s:"")),ie.ERR_DEPRECATED);return s&&!zd[p]&&(zd[p]=!0,console.warn(c(p," has been deprecated since v"+s+" and will be removed in the near future"))),i?i(d,p,m):!0}};ps.spelling=function(i){return(s,u)=>(console.warn(`${u} is likely a misspelling of ${i}`),!0)};function $0(r,i,s){if(typeof r!="object")throw new ie("options must be an object",ie.ERR_BAD_OPTION_VALUE);const u=Object.keys(r);let c=u.length;for(;c-- >0;){const d=u[c],p=i[d];if(p){const m=r[d],v=m===void 0||p(m,d,r);if(v!==!0)throw new ie("option "+d+" must be "+v,ie.ERR_BAD_OPTION_VALUE);continue}if(s!==!0)throw new ie("Unknown option "+d,ie.ERR_BAD_OPTION)}}const Gi={assertOptions:$0,validators:ps},Ht=Gi.validators;class Vn{constructor(i){this.defaults=i,this.interceptors={request:new Pd,response:new Pd}}async request(i,s){try{return await this._request(i,s)}catch(u){if(u instanceof Error){let c={};Error.captureStackTrace?Error.captureStackTrace(c):c=new Error;const d=c.stack?c.stack.replace(/^.+\n/,""):"";try{u.stack?d&&!String(u.stack).endsWith(d.replace(/^.+\n.+\n/,""))&&(u.stack+=` -`+d):u.stack=d}catch{}}throw u}}_request(i,s){typeof i=="string"?(s=s||{},s.url=i):s=i||{},s=qn(this.defaults,s);const{transitional:u,paramsSerializer:c,headers:d}=s;u!==void 0&&Gi.assertOptions(u,{silentJSONParsing:Ht.transitional(Ht.boolean),forcedJSONParsing:Ht.transitional(Ht.boolean),clarifyTimeoutError:Ht.transitional(Ht.boolean)},!1),c!=null&&(_.isFunction(c)?s.paramsSerializer={serialize:c}:Gi.assertOptions(c,{encode:Ht.function,serialize:Ht.function},!0)),Gi.assertOptions(s,{baseUrl:Ht.spelling("baseURL"),withXsrfToken:Ht.spelling("withXSRFToken")},!0),s.method=(s.method||this.defaults.method||"get").toLowerCase();let p=d&&_.merge(d.common,d[s.method]);d&&_.forEach(["delete","get","head","post","put","patch","common"],I=>{delete d[I]}),s.headers=lt.concat(p,d);const m=[];let v=!0;this.interceptors.request.forEach(function(R){typeof R.runWhen=="function"&&R.runWhen(s)===!1||(v=v&&R.synchronous,m.unshift(R.fulfilled,R.rejected))});const x=[];this.interceptors.response.forEach(function(R){x.push(R.fulfilled,R.rejected)});let E,j=0,O;if(!v){const I=[Dd.bind(this),void 0];for(I.unshift.apply(I,m),I.push.apply(I,x),O=I.length,E=Promise.resolve(s);j{if(!u._listeners)return;let d=u._listeners.length;for(;d-- >0;)u._listeners[d](c);u._listeners=null}),this.promise.then=c=>{let d;const p=new Promise(m=>{u.subscribe(m),d=m}).then(c);return p.cancel=function(){u.unsubscribe(d)},p},i(function(d,p,m){u.reason||(u.reason=new Pr(d,p,m),s(u.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(i){if(this.reason){i(this.reason);return}this._listeners?this._listeners.push(i):this._listeners=[i]}unsubscribe(i){if(!this._listeners)return;const s=this._listeners.indexOf(i);s!==-1&&this._listeners.splice(s,1)}toAbortSignal(){const i=new AbortController,s=u=>{i.abort(u)};return this.subscribe(s),i.signal.unsubscribe=()=>this.unsubscribe(s),i.signal}static source(){let i;return{token:new Yu(function(c){i=c}),cancel:i}}}function H0(r){return function(s){return r.apply(null,s)}}function V0(r){return _.isObject(r)&&r.isAxiosError===!0}const Mu={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Mu).forEach(([r,i])=>{Mu[i]=r});function Hp(r){const i=new Vn(r),s=wp(Vn.prototype.request,i);return _.extend(s,Vn.prototype,i,{allOwnKeys:!0}),_.extend(s,i,null,{allOwnKeys:!0}),s.create=function(c){return Hp(qn(r,c))},s}const he=Hp(Co);he.Axios=Vn;he.CanceledError=Pr;he.CancelToken=Yu;he.isCancel=Lp;he.VERSION=$p;he.toFormData=fs;he.AxiosError=ie;he.Cancel=he.CanceledError;he.all=function(i){return Promise.all(i)};he.spread=H0;he.isAxiosError=V0;he.mergeConfig=qn;he.AxiosHeaders=lt;he.formToJSON=r=>Tp(_.isHTMLForm(r)?new FormData(r):r);he.getAdapter=Bp.getAdapter;he.HttpStatusCode=Mu;he.default=he;const Xe={apiBaseUrl:"/api"},Dt=bn(r=>({users:[],fetchUsers:async()=>{try{const i=await he.get(`${Xe.apiBaseUrl}/users`);r({users:i.data})}catch(i){console.error("사용자 목록 조회 실패:",i)}},updateUserStatus:async i=>{try{await he.patch(`${Xe.apiBaseUrl}/users/${i}/userStatus`,{newLastActiveAt:new Date().toISOString()})}catch(s){console.error("사용자 상태 업데이트 실패:",s)}}})),Wt=bn(r=>({profileImages:{},fetchProfileImage:async i=>{try{const s=await he.get(`${Xe.apiBaseUrl}/binaryContents/${i}`),u=s.data.bytes,d=`data:${s.data.contentType};base64,${u}`;return r(p=>({profileImages:{...p.profileImages,[i]:d}})),d}catch(s){return console.error("프로필 이미지 로딩 실패:",s),null}}}));function Vp(r,i){let s;try{s=r()}catch{return}return{getItem:c=>{var d;const p=v=>v===null?null:JSON.parse(v,void 0),m=(d=s.getItem(c))!=null?d:null;return m instanceof Promise?m.then(p):p(m)},setItem:(c,d)=>s.setItem(c,JSON.stringify(d,void 0)),removeItem:c=>s.removeItem(c)}}const Uu=r=>i=>{try{const s=r(i);return s instanceof Promise?s:{then(u){return Uu(u)(s)},catch(u){return this}}}catch(s){return{then(u){return this},catch(u){return Uu(u)(s)}}}},W0=(r,i)=>(s,u,c)=>{let d={storage:Vp(()=>localStorage),partialize:R=>R,version:0,merge:(R,L)=>({...L,...R}),...i},p=!1;const m=new Set,v=new Set;let x=d.storage;if(!x)return r((...R)=>{console.warn(`[zustand persist middleware] Unable to update item '${d.name}', the given storage is currently unavailable.`),s(...R)},u,c);const E=()=>{const R=d.partialize({...u()});return x.setItem(d.name,{state:R,version:d.version})},j=c.setState;c.setState=(R,L)=>{j(R,L),E()};const O=r((...R)=>{s(...R),E()},u,c);c.getInitialState=()=>O;let P;const I=()=>{var R,L;if(!x)return;p=!1,m.forEach(F=>{var W;return F((W=u())!=null?W:O)});const V=((L=d.onRehydrateStorage)==null?void 0:L.call(d,(R=u())!=null?R:O))||void 0;return Uu(x.getItem.bind(x))(d.name).then(F=>{if(F)if(typeof F.version=="number"&&F.version!==d.version){if(d.migrate){const W=d.migrate(F.state,F.version);return W instanceof Promise?W.then(K=>[!0,K]):[!0,W]}console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return[!1,F.state];return[!1,void 0]}).then(F=>{var W;const[K,$]=F;if(P=d.merge($,(W=u())!=null?W:O),s(P,!0),K)return E()}).then(()=>{V==null||V(P,void 0),P=u(),p=!0,v.forEach(F=>F(P))}).catch(F=>{V==null||V(void 0,F)})};return c.persist={setOptions:R=>{d={...d,...R},R.storage&&(x=R.storage)},clearStorage:()=>{x==null||x.removeItem(d.name)},getOptions:()=>d,rehydrate:()=>I(),hasHydrated:()=>p,onHydrate:R=>(m.add(R),()=>{m.delete(R)}),onFinishHydration:R=>(v.add(R),()=>{v.delete(R)})},d.skipHydration||I(),P||O},Q0=W0,ut=bn(Q0(r=>({currentUserId:null,setCurrentUser:i=>r({currentUserId:i.id}),logout:()=>{const i=ut.getState().currentUserId;i&&Dt.getState().updateUserStatus(i),r({currentUserId:null})},updateUser:async(i,s)=>{try{const u=await he.patch(`${Xe.apiBaseUrl}/users/${i}`,s,{headers:{"Content-Type":"multipart/form-data"}});return await Dt.getState().fetchUsers(),u.data}catch(u){throw console.error("사용자 정보 수정 실패:",u),u}}}),{name:"user-storage",storage:Vp(()=>sessionStorage)})),Qt="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAw2SURBVHgB7d3PT1XpHcfxBy5g6hipSMolGViACThxJDbVRZ2FXejKlf9h/4GmC1fTRdkwC8fE0JgyJuICFkCjEA04GeZe6P0cPC0698I95zzPc57v5f1K6DSto3A8n/v9nufXGfrr338+dgBMGnYAzCLAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwbcTDvyuWh//33w1/1dexwMRBgYxTW5vVh9/vxYTcxPpR9jY0OffZrdt8fu82ttlvfbLv9j4R5kBHgxCmcE1eH3NfTDTc7PfxZte3lJNgjbmlxxK3+1HKrr1oOg4kAJ0pVdnG+4ZqTw7+psEUoxF91Qv/Di1+db/q+ZpvD7g+T6gb04XLyv6mF3//osuqvTmDn3RGdQCAEOCG6+W/ONdzNTnCrhPZLN2Yb2T99hVhdwOLcSOf37f7hknUN4yedgLoGeb3Rdv/qdAIE2S8CnIDzAuGDQrzXeTZee1OtndaHy9LCSOHvU3++vv693nLPX9LS+0KAa6QQLC2o4sb5a1A7rYGtMqPU+l7v3hpx85+qeVnfdH7W2c7z/Pcrh1RjD5gHromq2JOHY9HCK2Ojzk1dL1fhH90fqxzenDoO/X79DMjhbAQ4Mg1OPXl4KauGodrls6j6FaXKq+dZn/IQ13ENBgkBjiRvQR99V2/lmZos9lc+PxOuxdd1uL3gp6pfVDwDR6Ab9cG9Me9VLAZ1CiHpmXhz6yibakJxVODAZpoN9/iBzfCq+sboFkJ/SAwyrlxAujE1WJWSIiO/sYKlxSpTnbEBqnBxVOBA9LybWnjloM8An6ysitc1NCe5FcvgqgVw/85o1OmhItY32n39uqnJuC3/FAEuhavmmcLra77UN7XP2322qRNX494aqvgojqvmUcrhFa1+6tdXkae6tMiEhR3FEWBPNOCTcni1rZCli4OHAHuQ4mjzaewJHlxMI1Wked5Uw7v99ijbwqd/FnVQQ7WmQyiOAFegZ7a736ZzCU820h+7nbfHbnO7XSq4p3+vmHbfMwdcBgGuoO4dNQrZxtaR+08nqNueT73Y2D7qTIW5aLRXGcUR4JL03FtHeBXa9Y2jyhX2PHudiqg/K9ZuoY3t/uan8TkCXIKCG/u5V2Fae9N2a+vtKO2tjqfVnxfj5zw5O4sWugwCXIJa51hiB/e0tfVWdkZX6CrMCHl5BLigWDt0RCc6rrxo1XZQu6rw6qt2tq47FD0G9Lu8E79FgAvIWucIO3QU2B9ftpK4sVWFZ5rDQTYbqHUOcdztRcJCjgLUToauvrqpny4fJlWVlp/5P4BOH1IcbFcdAe6Tght6h5FeiaLwpnZTq5VW2HzN1eYfUoS3OgLcp9sL4cOrkKT6YrI8dFUHnDQYR3j94Rm4D9kLxQLuV009vKdpXbXae00vFdm8UWVZJ3ojwH3QcS+hnn1VifSMaemVoPqeVzqDT6rG2oivQS5dH33l70ZS262w7n04yhae8MrTMAhwH0KNPFsfyNH3vd+pxkwD1Ydn4HOodQ5VfTXHyrMgqiDA55ibCbNJX1VLc6xAFQT4HCEGr9Q6s3wQPhDgM4RqnzWVQusMHwjwGTS66puCS/WFLwT4DCHOKia88IkA96BjTkOcVbzDQgZ4RIB7CBFejTzz7AufCHAPWn3lGwse4BsB7uGa5wqcLS3k7XvwjAD3cOWy84pnX4RAgHvw/QzMLhyEQIC7CLF4Y4+DyxEAAe4iRIB3PzD6DP8IcBejnncPagCL/bAIgQB34fsc5P2PtM8IgwBHcMjJqQiEAHfBm+JhBQGO4IDlkwiEAHdx2PIbuFhv+MPFQ4C7ODx0Xo2OOiAIAhwBz9QIhQB34XvOlhYaoRDgLg5+dl7pcACqMEIgwF2EWDV1bZwAwz8C3IVOzfAd4omrXGr4x13Vg++jb6YmudTwj7uqh733fgOsM6YZzIJvBLiH3Q/+NyDMB3pNCy4u3k7Yw+57/wNZM9PDbu2NGwjqJiauDrmvpxufXiv6+f+v63fw8SjrZDgLLBwC3INO0NBAls+2V220jurZNXw6h8K6ODfibsye/UjQnNR/nnQcGk/IX/DNsbp+EeAetAVQVaQ56fe5dXGu4X54YTPASwsj7uZ8o/CHmkJ/Y7aRfb3eaBNkj3gGPsNOgNZPN7G1RR36fh8/uJS96LxqR6Kf/9H9MRa2eEKAz7C5FaZS3l6w0/goaArchMeFKPkHwrVxbr+quIJn0LNqiFZPVSjEmx98U7UNVS016PWXe6NU4ooI8DnWN8O8DuX+H0eTnxdeWgjb7uv3/vMd9lpWQYDPEep9Rrp5by+kOy+s7+/mfPhWXyPzFrqRVHHlzpFPgYTwTScg87NphjhmZdTgGMohwH1YexPupdx3b40mN5ij6tuMuHabKlweV60PGo0OdTB7ioM5WjEWW5PNHqVw1fq09ibcu33zqZpUQjzTjN/Ws1urHK5an9bWW0Ffj5JSiOv4HiaYEy6Fq9YnLa1cfRWuCku+wOHmXL2DOnUEmGOHyiHABagKh17Dqxv57rcj7k+3RpKfJ0b9CHBBKy/ivOhIU0yPH4xdqD3EV37HB1ZRBLignc6c8MZW2FY6p5ZSK7b0bNyMOM3CTiE7CHAJz1+2or7vV1Msj74by4IcoyKHOMygH4fhptsHFgEuQRXqx5fx7zYFWRX5ycNL2UqpUFV5512cDuNLvAS9ONawlaQ10jpSJsZ64S+d3iCvm3777XGntW9nx9fsfqh+JK5+Nq0Qi43WvTgCXMHqq5abma53g75Gqmen9fX/alz1CBtNmenfj7k6yvIxQ3Wiha5AN/r3K4fJtX55hVarvVTy8AB9OMV0GGdwf+AQ4IpU4f75LN27Tzt9HtwbKzynrNF2zXvHsvOWClwGAfZAN18dg1r9UnuthSFF6WeK1doS4HIIsCeqVrHbziLUUpdZornc6S5iDC5p8A3FEWCPVn9KO8RlTpVUeJ8u/xLsUAPR780UUjkE2LOUQ6x11jPN4n/l+WDdaqDznEOdO3YREOAAFOJUn4mrTA3p51KQNU/sM8g8/5bHPHAgeibWAND9O2mdtlF147yCm2/o0IeBXlyuAwDKfjDotBMWcJRHBQ5IlUUVa1Bv0O1squnkVSllvd5kAXQVBDiwfBAo5pyqFbo2od5+cVEQ4Ag0CKRnYrWedVfjlLqBlEfsrSDAEWnwJx8Eqsve+zQCrA+SOq/DoCDAkeWDQE+X63k23txKIzRUXz8IcE00Qv23f/wSta3Odim9q/+Zc6Pz3Ev19YNppJrpRtaXXrGinUMhp5zUvqfg+Uu2HvlCgBORB1nzqYtzDTc77ffoHC3CSGEAS4N5zPv6Q4ATo7lVfV253MoWXegMrKob6xWaFKax9PzNdJpfBDhRqlL7n6qy2mqFWeuY9QaDfttsfRCoXd1NYOS5rnPEBh0BNuB0mGVifOgk1Ncb2VJGbVLIdxnp12qqaHO7HXQHURH6ngZ5RVqdCLBBqqj62jCwiknbBJefEd5QCDCCUWgV3hRa+EFFgBEEbXMcBBjeabR55UWLUzYiIMDwRoHVK1iZKoqHAMMLqm49CDAqyxefID42MwCGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhv0XZkN9IbEGbp4AAAAASUVORK5CYII=";function Md({channel:r,isActive:i,onClick:s,hasUnread:u}){const c=ut(x=>x.currentUserId),d=Dt(x=>x.users),p=Wt(x=>x.profileImages);if(r.type==="PUBLIC")return g.jsxs(yp,{$isActive:i,onClick:s,$hasUnread:u,children:["# ",r.name]});const m=r.participantIds.map(x=>d.find(E=>E.id===x)).filter(Boolean);if(m.length>2){const x=m.filter(E=>E.id!==c).map(E=>E.username).join(", ");return g.jsxs(yd,{$isActive:i,onClick:s,children:[g.jsx(uy,{children:m.filter(E=>E.id!==c).slice(0,2).map((E,j)=>g.jsx(cy,{src:E.profileId?p[E.profileId]:Qt,style:{position:"absolute",left:j*16,zIndex:2-j}},E.id))}),g.jsxs(xd,{children:[g.jsx(vd,{$hasUnread:u,children:x}),g.jsxs(ay,{children:["멤버 ",m.length,"명"]})]})]})}const v=m.filter(x=>x.id!==c)[0];return g.jsxs(yd,{$isActive:i,onClick:s,children:[g.jsxs(ly,{children:[g.jsx("img",{src:v.profileId?p[v.profileId]:Qt,alt:"profile"}),g.jsx(vp,{$online:v.online})]}),g.jsx(xd,{children:g.jsx(vd,{$hasUnread:u,children:v.username})})]})}function q0({isOpen:r,onClose:i,user:s,onSubmit:u}){const[c,d]=ue.useState(s.username),[p,m]=ue.useState(s.email),[v,x]=ue.useState(""),[E,j]=ue.useState(null),[O,P]=ue.useState(""),[I,R]=ue.useState(null),L=Wt(T=>T.profileImages),V=Wt(T=>T.fetchProfileImage),F=ut(T=>T.logout);ue.useEffect(()=>{s.profileId&&!L[s.profileId]&&V(s.profileId)},[s.profileId,L,V]);const W=()=>{d(s.username),m(s.email),x(""),j(null),R(null),P(""),i()},K=T=>{const H=T.target.files[0];if(H){j(H);const se=new FileReader;se.onloadend=()=>{R(se.result)},se.readAsDataURL(H)}},$=async T=>{T.preventDefault(),P("");try{const H=new FormData,se={};c!==s.username&&(se.newUsername=c),p!==s.email&&(se.newEmail=p),v&&(se.newPassword=v),(Object.keys(se).length>0||E)&&(H.append("userUpdateRequest",new Blob([JSON.stringify(se)],{type:"application/json"})),E&&H.append("profile",E),await u(H)),i()}catch{P("사용자 정보 수정에 실패했습니다.")}};return r?g.jsx(b0,{children:g.jsxs(G0,{children:[g.jsx("h2",{children:"프로필 수정"}),g.jsxs("form",{onSubmit:$,children:[g.jsxs(Mi,{children:[g.jsx(Ui,{children:"프로필 이미지"}),g.jsxs(K0,{children:[g.jsx(X0,{src:I||L[s.profileId]||Qt,alt:"profile"}),g.jsx(J0,{type:"file",accept:"image/*",onChange:K,id:"profile-image"}),g.jsx(Z0,{htmlFor:"profile-image",children:"이미지 변경"})]})]}),g.jsxs(Mi,{children:[g.jsxs(Ui,{children:["사용자명 ",g.jsx(Fd,{children:"*"})]}),g.jsx(xu,{type:"text",value:c,onChange:T=>d(T.target.value),required:!0})]}),g.jsxs(Mi,{children:[g.jsxs(Ui,{children:["이메일 ",g.jsx(Fd,{children:"*"})]}),g.jsx(xu,{type:"email",value:p,onChange:T=>m(T.target.value),required:!0})]}),g.jsxs(Mi,{children:[g.jsx(Ui,{children:"새 비밀번호"}),g.jsx(xu,{type:"password",placeholder:"변경하지 않으려면 비워두세요",value:v,onChange:T=>x(T.target.value)})]}),O&&g.jsx(Y0,{children:O}),g.jsxs(ev,{children:[g.jsx(Ud,{type:"button",onClick:W,$secondary:!0,children:"취소"}),g.jsx(Ud,{type:"submit",children:"저장"})]})]}),g.jsx(tv,{onClick:F,children:"로그아웃"})]})}):null}const b0=N.div` - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -`,G0=N.div` - background: ${({theme:r})=>r.colors.background.secondary}; - padding: 32px; - border-radius: 5px; - width: 100%; - max-width: 480px; - - h2 { - color: ${({theme:r})=>r.colors.text.primary}; - margin-bottom: 24px; - text-align: center; - font-size: 24px; - } -`,xu=N.input` - width: 100%; - padding: 10px; - margin-bottom: 10px; - border: none; - border-radius: 4px; - background: ${({theme:r})=>r.colors.background.input}; - color: ${({theme:r})=>r.colors.text.primary}; - - &::placeholder { - color: ${({theme:r})=>r.colors.text.muted}; - } - - &:focus { - outline: none; - box-shadow: 0 0 0 2px ${({theme:r})=>r.colors.brand.primary}; - } -`,Ud=N.button` - width: 100%; - padding: 10px; - border: none; - border-radius: 4px; - background: ${({$secondary:r,theme:i})=>r?"transparent":i.colors.brand.primary}; - color: ${({theme:r})=>r.colors.text.primary}; - cursor: pointer; - font-weight: 500; - - &:hover { - background: ${({$secondary:r,theme:i})=>r?i.colors.background.hover:i.colors.brand.hover}; - } -`,Y0=N.div` - color: ${({theme:r})=>r.colors.status.error}; - font-size: 14px; - margin-bottom: 10px; -`,K0=N.div` - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 20px; -`,X0=N.img` - width: 100px; - height: 100px; - border-radius: 50%; - margin-bottom: 10px; - object-fit: cover; -`,J0=N.input` - display: none; -`,Z0=N.label` - color: ${({theme:r})=>r.colors.brand.primary}; - cursor: pointer; - font-size: 14px; - - &:hover { - text-decoration: underline; - } -`,ev=N.div` - display: flex; - gap: 10px; - margin-top: 20px; -`,tv=N.button` - width: 100%; - padding: 10px; - margin-top: 16px; - border: none; - border-radius: 4px; - background: transparent; - color: ${({theme:r})=>r.colors.status.error}; - cursor: pointer; - font-weight: 500; - - &:hover { - background: ${({theme:r})=>r.colors.status.error}20; - } -`,Mi=N.div` - margin-bottom: 20px; -`,Ui=N.label` - display: block; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 12px; - font-weight: 700; - margin-bottom: 8px; -`,Fd=N.span` - color: ${({theme:r})=>r.colors.status.error}; -`,Wp=N.div` - position: absolute; - bottom: -3px; - right: -3px; - width: 16px; - height: 16px; - border-radius: 50%; - background: ${r=>r.$online?J.colors.status.online:J.colors.status.offline}; - border: 4px solid ${r=>r.$background||J.colors.background.secondary}; -`,nv=N.div` - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.5rem 0.75rem; - background-color: ${({theme:r})=>r.colors.background.tertiary}; - width: 100%; - height: 52px; -`,rv=N.div` - position: relative; - width: 32px; - height: 32px; - flex-shrink: 0; -`,ov=N.img` - width: 100%; - height: 100%; - border-radius: 50%; - object-fit: cover; -`,iv=N.div` - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - justify-content: center; -`,sv=N.div` - font-weight: 500; - color: ${({theme:r})=>r.colors.text.primary}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 0.875rem; - line-height: 1.2; -`,lv=N.div` - font-size: 0.75rem; - color: ${({theme:r})=>r.colors.text.secondary}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.2; -`,uv=N.div` - display: flex; - align-items: center; - flex-shrink: 0; -`,av=N.button` - background: none; - border: none; - padding: 0.25rem; - cursor: pointer; - color: ${({theme:r})=>r.colors.text.secondary}; - font-size: 18px; - - &:hover { - color: ${({theme:r})=>r.colors.text.primary}; - } -`;function cv({user:r}){const[i,s]=ue.useState(!1);ut(m=>m.logout);const u=ut(m=>m.updateUser),c=Wt(m=>m.profileImages),d=Wt(m=>m.fetchProfileImage);ue.useEffect(()=>{r.profileId&&!c[r.profileId]&&d(r.profileId)},[r.profileId,c,d]);const p=async m=>{await u(r.id,m)};return g.jsxs(g.Fragment,{children:[g.jsxs(nv,{children:[g.jsxs(rv,{children:[g.jsx(ov,{src:c[r.profileId]||Qt}),g.jsx(Wp,{$online:r.online,$background:J.colors.background.tertiary})]}),g.jsxs(iv,{children:[g.jsx(sv,{children:r.username}),g.jsx(lv,{children:r.email})]}),g.jsx(uv,{children:g.jsx(av,{onClick:()=>s(!0),children:"⚙️"})})]}),g.jsx(q0,{isOpen:i,onClose:()=>s(!1),user:r,onSubmit:p})]})}const fv=N.div` - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.85); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -`,dv=N.div` - background: ${J.colors.background.primary}; - border-radius: 4px; - width: 440px; - max-width: 90%; -`,pv=N.div` - padding: 16px; - display: flex; - justify-content: space-between; - align-items: center; -`,hv=N.h2` - color: ${J.colors.text.primary}; - font-size: 20px; - font-weight: 600; - margin: 0; -`,mv=N.div` - padding: 0 16px 16px; -`,gv=N.form` - display: flex; - flex-direction: column; - gap: 16px; -`,Su=N.div` - display: flex; - flex-direction: column; - gap: 8px; -`,ku=N.label` - color: ${J.colors.text.primary}; - font-size: 12px; - font-weight: 600; - text-transform: uppercase; -`,yv=N.p` - color: ${J.colors.text.muted}; - font-size: 14px; - margin: -4px 0 0; -`,Fu=N.input` - padding: 10px; - background: ${J.colors.background.tertiary}; - border: none; - border-radius: 3px; - color: ${J.colors.text.primary}; - font-size: 16px; - - &:focus { - outline: none; - box-shadow: 0 0 0 2px ${J.colors.status.online}; - } - - &::placeholder { - color: ${J.colors.text.muted}; - } -`,vv=N.button` - margin-top: 8px; - padding: 12px; - background: ${J.colors.status.online}; - color: white; - border: none; - border-radius: 3px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s; - - &:hover { - background: #3ca374; - } -`,wv=N.button` - background: none; - border: none; - color: ${J.colors.text.muted}; - font-size: 24px; - cursor: pointer; - padding: 4px; - line-height: 1; - - &:hover { - color: ${J.colors.text.primary}; - } -`,xv=N(Fu)` - margin-bottom: 8px; -`,Sv=N.div` - max-height: 300px; - overflow-y: auto; - background: ${J.colors.background.tertiary}; - border-radius: 4px; -`,kv=N.div` - display: flex; - align-items: center; - padding: 8px 12px; - cursor: pointer; - transition: background 0.2s; - - &:hover { - background: ${J.colors.background.hover}; - } - - & + & { - border-top: 1px solid ${J.colors.border.primary}; - } -`,Ev=N.input` - margin-right: 12px; - width: 16px; - height: 16px; - cursor: pointer; -`,Bd=N.img` - width: 32px; - height: 32px; - border-radius: 50%; - margin-right: 12px; -`,Cv=N.div` - flex: 1; - min-width: 0; -`,Av=N.div` - color: ${J.colors.text.primary}; - font-size: 14px; - font-weight: 500; -`,Rv=N.div` - color: ${J.colors.text.muted}; - font-size: 12px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`,Pv=N.div` - padding: 16px; - text-align: center; - color: ${J.colors.text.muted}; -`,jv=N.div` - color: ${J.colors.status.error}; - font-size: 14px; - padding: 8px 0; - text-align: center; - background-color: ${({theme:r})=>r.colors.background.tertiary}; - border-radius: 4px; - margin-bottom: 8px; -`,Bn=bn((r,i)=>({channels:[],pollingInterval:null,fetchChannels:async s=>{try{const u=await he.get(`${Xe.apiBaseUrl}/channels`,{params:{userId:s}});return r({channels:u.data}),u.data}catch(u){console.error("채널 목록 조회 실패:",u)}},startPolling:s=>{i().pollingInterval&&clearInterval(i().pollingInterval);const u=setInterval(()=>{i().fetchChannels(s)},3e3);r({pollingInterval:u})},stopPolling:()=>{i().pollingInterval&&(clearInterval(i().pollingInterval),r({pollingInterval:null}))},createPublicChannel:async s=>{try{const u=await he.post(`${Xe.apiBaseUrl}/channels/public`,s),c={...u.data,participantIds:[],lastMessageAt:u.data.createdAt};return r(d=>({channels:[...d.channels,c]})),c}catch(u){throw console.error("공개 채널 생성 실패:",u),u}},createPrivateChannel:async s=>{try{const u=await he.post(`${Xe.apiBaseUrl}/channels/private`,{participantIds:s}),c={...u.data,participantIds:s,lastMessageAt:u.data.createdAt};return r(d=>({channels:[...d.channels,c]})),c}catch(u){throw console.error("비공개 채널 생성 실패:",u),u}}}));function Iv({isOpen:r,type:i,onClose:s,onCreateSuccess:u}){const[c,d]=ue.useState({name:"",description:""}),[p,m]=ue.useState(""),[v,x]=ue.useState([]),[E,j]=ue.useState(""),O=Dt($=>$.users),P=Wt($=>$.profileImages),I=ut($=>$.currentUserId),R=ue.useMemo(()=>O.filter($=>$.id!==I).filter($=>$.username.toLowerCase().includes(p.toLowerCase())||$.email.toLowerCase().includes(p.toLowerCase())),[p,O]),L=Bn($=>$.createPublicChannel),V=Bn($=>$.createPrivateChannel),F=$=>{const{name:T,value:H}=$.target;d(se=>({...se,[T]:H}))},W=$=>{x(T=>T.includes($)?T.filter(H=>H!==$):[...T,$])},K=async $=>{var T,H;$.preventDefault(),j("");try{if(i==="PUBLIC"){if(!c.name.trim()){j("채널 이름을 입력해주세요.");return}await L({name:c.name,description:c.description})}else{if(v.length===0){j("대화 상대를 선택해주세요.");return}const se=[...v,I];await V(se)}u()}catch(se){console.error("채널 생성 실패:",se),j(((H=(T=se.response)==null?void 0:T.data)==null?void 0:H.message)||"채널 생성에 실패했습니다. 다시 시도해주세요.")}};return r?g.jsx(fv,{onClick:s,children:g.jsxs(dv,{onClick:$=>$.stopPropagation(),children:[g.jsxs(pv,{children:[g.jsx(hv,{children:i==="PUBLIC"?"채널 만들기":"개인 메시지 시작하기"}),g.jsx(wv,{onClick:s,children:"×"})]}),g.jsx(mv,{children:g.jsxs(gv,{onSubmit:K,children:[E&&g.jsx(jv,{children:E}),i==="PUBLIC"?g.jsxs(g.Fragment,{children:[g.jsxs(Su,{children:[g.jsx(ku,{children:"채널 이름"}),g.jsx(Fu,{name:"name",value:c.name,onChange:F,placeholder:"새로운-채널",required:!0})]}),g.jsxs(Su,{children:[g.jsx(ku,{children:"채널 설명"}),g.jsx(yv,{children:"이 채널의 주제를 설명해주세요."}),g.jsx(Fu,{name:"description",value:c.description,onChange:F,placeholder:"채널 설명을 입력하세요"})]})]}):g.jsxs(Su,{children:[g.jsx(ku,{children:"사용자 검색"}),g.jsx(xv,{type:"text",value:p,onChange:$=>m($.target.value),placeholder:"사용자명 또는 이메일로 검색"}),g.jsx(Sv,{children:R.length>0?R.map($=>g.jsxs(kv,{children:[g.jsx(Ev,{type:"checkbox",checked:v.includes($.id),onChange:()=>W($.id)}),$.profileId?g.jsx(Bd,{src:P[$.profileId]}):g.jsx(Bd,{src:Qt}),g.jsxs(Cv,{children:[g.jsx(Av,{children:$.username}),g.jsx(Rv,{children:$.email})]})]},$.id)):g.jsx(Pv,{children:"검색 결과가 없습니다."})})]}),g.jsx(vv,{type:"submit",children:i==="PUBLIC"?"채널 만들기":"대화 시작하기"})]})})]})}):null}const yo=bn((r,i)=>({readStatuses:{},fetchReadStatuses:async()=>{try{const s=ut.getState().currentUserId;if(!s)return;const c=(await he.get(`${Xe.apiBaseUrl}/readStatuses`,{params:{userId:s}})).data.reduce((d,p)=>(d[p.channelId]={id:p.id,lastReadAt:p.lastReadAt},d),{});r({readStatuses:c})}catch(s){console.error("읽음 상태 조회 실패:",s)}},updateReadStatus:async s=>{try{const u=ut.getState().currentUserId;if(!u)return;const c=i().readStatuses[s];let d;c?d=await he.patch(`${Xe.apiBaseUrl}/readStatuses/${c.id}`,{newLastReadAt:new Date().toISOString()}):d=await he.post(`${Xe.apiBaseUrl}/readStatuses`,{userId:u,channelId:s,lastReadAt:new Date().toISOString()}),r(p=>({readStatuses:{...p.readStatuses,[s]:{id:d.data.id,lastReadAt:d.data.lastReadAt}}}))}catch(u){console.error("읽음 상태 업데이트 실패:",u)}},hasUnreadMessages:(s,u)=>{const c=i().readStatuses[s],d=c==null?void 0:c.lastReadAt;return!d||new Date(u)>new Date(d)}}));function _v({currentUser:r,activeChannel:i,onChannelSelect:s}){var K,$;const[u,c]=ue.useState({PUBLIC:!1,PRIVATE:!1}),[d,p]=ue.useState({isOpen:!1,type:null}),m=Bn(T=>T.channels),v=Bn(T=>T.fetchChannels),x=Bn(T=>T.startPolling),E=Bn(T=>T.stopPolling);yo(T=>T.readStatuses);const j=yo(T=>T.fetchReadStatuses),O=yo(T=>T.updateReadStatus),P=yo(T=>T.hasUnreadMessages);ue.useEffect(()=>{if(r)return v(r.id),j(),x(r.id),()=>{E()}},[r,v,j,x,E]);const I=T=>{c(H=>({...H,[T]:!H[T]}))},R=(T,H)=>{H.stopPropagation(),p({isOpen:!0,type:T})},L=()=>{p({isOpen:!1,type:null})},V=async T=>{try{await v(r.id),L()}catch(H){console.error("채널 생성 실패:",H)}},F=T=>{s(T),O(T.id)},W=m.reduce((T,H)=>(T[H.type]||(T[H.type]=[]),T[H.type].push(H),T),{});return g.jsxs(oy,{children:[g.jsx(fy,{}),g.jsxs(iy,{children:[g.jsxs(hd,{children:[g.jsxs(Nu,{onClick:()=>I("PUBLIC"),children:[g.jsx(md,{$folded:u.PUBLIC,children:"▼"}),g.jsx("span",{children:"일반 채널"}),g.jsx(wd,{onClick:T=>R("PUBLIC",T),children:"+"})]}),g.jsx(gd,{$folded:u.PUBLIC,children:(K=W.PUBLIC)==null?void 0:K.map(T=>g.jsx(Md,{channel:T,isActive:(i==null?void 0:i.id)===T.id,hasUnread:P(T.id,T.lastMessageAt),onClick:()=>F(T)},T.id))})]}),g.jsxs(hd,{children:[g.jsxs(Nu,{onClick:()=>I("PRIVATE"),children:[g.jsx(md,{$folded:u.PRIVATE,children:"▼"}),g.jsx("span",{children:"개인 메시지"}),g.jsx(wd,{onClick:T=>R("PRIVATE",T),children:"+"})]}),g.jsx(gd,{$folded:u.PRIVATE,children:($=W.PRIVATE)==null?void 0:$.map(T=>g.jsx(Md,{channel:T,isActive:(i==null?void 0:i.id)===T.id,hasUnread:P(T.id,T.lastMessageAt),onClick:()=>F(T)},T.id))})]})]}),g.jsx(Nv,{children:g.jsx(cv,{user:r})}),g.jsx(Iv,{isOpen:d.isOpen,type:d.type,onClose:L,onCreateSuccess:V})]})}const Nv=N.div` - margin-top: auto; - border-top: 1px solid ${({theme:r})=>r.colors.border.primary}; - background-color: ${({theme:r})=>r.colors.background.tertiary}; -`,Ov=N.div` - flex: 1; - display: flex; - flex-direction: column; - background: ${({theme:r})=>r.colors.background.primary}; -`,Tv=N.div` - display: flex; - flex-direction: column; - height: 100%; - background: ${({theme:r})=>r.colors.background.primary}; -`,Lv=N(Tv)` - justify-content: center; - align-items: center; - flex: 1; - padding: 0 20px; -`,Dv=N.div` - text-align: center; - max-width: 400px; - padding: 20px; - margin-bottom: 80px; -`,zv=N.div` - font-size: 48px; - margin-bottom: 16px; - animation: wave 2s infinite; - transform-origin: 70% 70%; - - @keyframes wave { - 0% { transform: rotate(0deg); } - 10% { transform: rotate(14deg); } - 20% { transform: rotate(-8deg); } - 30% { transform: rotate(14deg); } - 40% { transform: rotate(-4deg); } - 50% { transform: rotate(10deg); } - 60% { transform: rotate(0deg); } - 100% { transform: rotate(0deg); } - } -`,Mv=N.h2` - color: ${({theme:r})=>r.colors.text.primary}; - font-size: 28px; - font-weight: 700; - margin-bottom: 16px; -`,Uv=N.p` - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 16px; - line-height: 1.6; - word-break: keep-all; -`,$d=N.div` - height: 48px; - padding: 0 16px; - background: ${J.colors.background.primary}; - border-bottom: 1px solid ${J.colors.border.primary}; - display: flex; - align-items: center; -`,Hd=N.div` - display: flex; - align-items: center; - gap: 8px; - height: 100%; -`,Fv=N.div` - display: flex; - align-items: center; - gap: 12px; - height: 100%; -`,Bv=N.div` - position: relative; - width: 24px; - height: 24px; - flex-shrink: 0; -`,Vd=N.img` - width: 24px; - height: 24px; - border-radius: 50%; -`,$v=N.div` - position: relative; - width: 40px; - height: 24px; - flex-shrink: 0; -`,Hv=N.div` - position: absolute; - bottom: -2px; - right: -2px; - width: 14px; - height: 14px; - border-radius: 50%; - background: ${r=>r.online?J.colors.status.online:J.colors.status.offline}; - border: 3px solid ${J.colors.background.secondary}; -`,Vv=N(Hv)` - border-color: ${J.colors.background.primary}; - bottom: -3px; - right: -3px; -`,Wv=N.div` - font-size: 12px; - color: ${J.colors.text.muted}; - line-height: 13px; -`,Wd=N.div` - font-weight: bold; - color: ${J.colors.text.primary}; - line-height: 20px; - font-size: 16px; -`,Qv=N.div` - flex: 1; - display: flex; - flex-direction: column-reverse; - overflow-y: auto; -`,qv=N.div` - padding: 16px; - display: flex; - flex-direction: column; -`,bv=N.div` - margin-bottom: 16px; - display: flex; - align-items: flex-start; -`,Gv=N.div` - position: relative; - margin-right: 16px; - flex-shrink: 0; -`,Yv=N.img` - width: 40px; - height: 40px; - border-radius: 50%; -`,Kv=N.div` - display: flex; - align-items: center; - margin-bottom: 4px; -`,Xv=N.span` - font-weight: bold; - color: ${J.colors.text.primary}; - margin-right: 8px; -`,Jv=N.span` - font-size: 0.75rem; - color: ${J.colors.text.muted}; -`,Zv=N.div` - color: ${J.colors.text.secondary}; - margin-top: 4px; -`,e1=N.form` - display: flex; - align-items: center; - gap: 8px; - padding: 16px; - background: ${({theme:r})=>r.colors.background.secondary}; -`,t1=N.textarea` - flex: 1; - padding: 12px; - background: ${({theme:r})=>r.colors.background.tertiary}; - border: none; - border-radius: 4px; - color: ${({theme:r})=>r.colors.text.primary}; - font-size: 14px; - resize: none; - min-height: 44px; - max-height: 144px; - - &:focus { - outline: none; - } - - &::placeholder { - color: ${({theme:r})=>r.colors.text.muted}; - } -`,n1=N.button` - background: none; - border: none; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 24px; - cursor: pointer; - padding: 4px 8px; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - color: ${({theme:r})=>r.colors.text.primary}; - } -`;N.div` - flex: 1; - display: flex; - align-items: center; - justify-content: center; - color: ${J.colors.text.muted}; - font-size: 16px; - font-weight: 500; - padding: 20px; - text-align: center; -`;const Qd=N.div` - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 8px; - width: 100%; -`,r1=N.a` - display: block; - border-radius: 4px; - overflow: hidden; - max-width: 300px; - - img { - width: 100%; - height: auto; - display: block; - } -`,o1=N.a` - display: flex; - align-items: center; - gap: 12px; - padding: 12px; - background: ${({theme:r})=>r.colors.background.tertiary}; - border-radius: 8px; - text-decoration: none; - width: fit-content; - - &:hover { - background: ${({theme:r})=>r.colors.background.hover}; - } -`,i1=N.div` - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - font-size: 40px; - color: #0B93F6; -`,s1=N.div` - display: flex; - flex-direction: column; - gap: 2px; -`,l1=N.span` - font-size: 14px; - color: #0B93F6; - font-weight: 500; -`,u1=N.span` - font-size: 13px; - color: ${({theme:r})=>r.colors.text.muted}; -`,a1=N.div` - display: flex; - flex-wrap: wrap; - gap: 8px; - padding: 8px 0; -`,Qp=N.div` - position: relative; - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - background: ${({theme:r})=>r.colors.background.tertiary}; - border-radius: 4px; - max-width: 300px; -`,c1=N(Qp)` - padding: 0; - overflow: hidden; - width: 200px; - height: 120px; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } -`,f1=N.div` - color: #0B93F6; - font-size: 20px; -`,d1=N.div` - font-size: 13px; - color: ${({theme:r})=>r.colors.text.primary}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`,qd=N.button` - position: absolute; - top: -6px; - right: -6px; - width: 20px; - height: 20px; - border-radius: 50%; - background: ${({theme:r})=>r.colors.background.secondary}; - border: none; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 16px; - line-height: 1; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - padding: 0; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - - &:hover { - color: ${({theme:r})=>r.colors.text.primary}; - } -`,vo=bn((r,i)=>({messages:[],pollingIntervals:{},lastMessageId:null,fetchMessages:async s=>{try{const u=await he.get(`${Xe.apiBaseUrl}/messages`,{params:{channelId:s}}),c=u.data[u.data.length-1],d=(c==null?void 0:c.id)!==i().lastMessageId;return r({messages:u.data,lastMessageId:c==null?void 0:c.id}),d}catch(u){return console.error("메시지 목록 조회 실패:",u),!1}},startPolling:s=>{const u=i();u.pollingIntervals[s]&&clearTimeout(u.pollingIntervals[s]);let c=300;const d=3e3;r(m=>({pollingIntervals:{...m.pollingIntervals,[s]:!0}}));const p=async()=>{const m=i();if(!m.pollingIntervals[s])return;if(await m.fetchMessages(s)?c=300:c=Math.min(c*1.5,d),i().pollingIntervals[s]){const x=setTimeout(p,c);r(E=>({pollingIntervals:{...E.pollingIntervals,[s]:x}}))}};p()},stopPolling:s=>{const{pollingIntervals:u}=i();if(u[s]){const c=u[s];typeof c=="number"&&clearTimeout(c),r(d=>{const p={...d.pollingIntervals};return delete p[s],{pollingIntervals:p}})}},createMessage:async s=>{try{const u=new FormData;u.append("messageCreateRequest",new Blob([JSON.stringify({content:s.content,channelId:s.channelId,authorId:s.authorId})],{type:"application/json"})),s.attachments&&s.attachments.forEach(p=>{u.append("attachments",p)});const c=await he.post(`${Xe.apiBaseUrl}/messages`,u,{headers:{"Content-Type":"multipart/form-data"}}),d=yo.getState().updateReadStatus;return await d(s.channelId),r(p=>({messages:[...p.messages,c.data]})),c.data}catch(u){throw console.error("메시지 생성 실패:",u),u}}})),p1=bn((r,i)=>({attachments:{},fetchAttachment:async s=>{if(i().attachments[s])return i().attachments[s];try{const u=await he.get(`${Xe.apiBaseUrl}/binaryContents/${s}`),{bytes:c,contentType:d,fileName:p,size:m}=u.data,x={url:`data:${d};base64,${c}`,contentType:d,originalName:p,size:m};return r(E=>({attachments:{...E.attachments,[s]:x}})),x}catch(u){return console.error("첨부파일 정보 조회 실패:",u),null}}})),h1=r=>r<1024?r+" B":r<1024*1024?(r/1024).toFixed(2)+" KB":r<1024*1024*1024?(r/(1024*1024)).toFixed(2)+" MB":(r/(1024*1024*1024)).toFixed(2)+" GB";function m1({channel:r}){const i=vo(P=>P.messages),s=vo(P=>P.fetchMessages),u=vo(P=>P.startPolling),c=vo(P=>P.stopPolling),d=Wt(P=>P.profileImages),p=Dt(P=>P.users),{attachments:m,fetchAttachment:v}=p1();ue.useEffect(()=>{if(r!=null&&r.id)return s(r.id),u(r.id),()=>{c(r.id)}},[r==null?void 0:r.id,s,u,c]),ue.useEffect(()=>{i.forEach(P=>{var I;(I=P.attachmentIds)==null||I.forEach(R=>{m[R]||v(R)})})},[i,m,v]);const x=async(P,I)=>{try{const R=await he.get(`${Xe.apiBaseUrl}/binaryContents/${P}`,{responseType:"blob"}),L=new Blob([R.data],{type:R.headers["content-type"]}),V=window.URL.createObjectURL(L),F=document.createElement("a");F.href=V,F.download=I,F.style.display="none",document.body.appendChild(F);try{const K=await(await window.showSaveFilePicker({suggestedName:I,types:[{description:"Files",accept:{"*/*":[".txt",".pdf",".doc",".docx",".xls",".xlsx",".jpg",".jpeg",".png",".gif"]}}]})).createWritable();await K.write(L),await K.close()}catch(W){W.name!=="AbortError"&&F.click()}document.body.removeChild(F),window.URL.revokeObjectURL(V)}catch(R){console.error("파일 다운로드 실패:",R)}},E=P=>P!=null&&P.length?P.map(I=>{const R=m[I];return R?R.contentType.startsWith("image/")?g.jsx(Qd,{children:g.jsx(r1,{href:"#",onClick:V=>{V.preventDefault(),x(I,R.originalName)},children:g.jsx("img",{src:R.url,alt:R.originalName})})},I):g.jsx(Qd,{children:g.jsxs(o1,{href:"#",onClick:V=>{V.preventDefault(),x(I,R.originalName)},children:[g.jsx(i1,{children:g.jsxs("svg",{width:"40",height:"40",viewBox:"0 0 40 40",fill:"none",children:[g.jsx("path",{d:"M8 3C8 1.89543 8.89543 1 10 1H22L32 11V37C32 38.1046 31.1046 39 30 39H10C8.89543 39 8 38.1046 8 37V3Z",fill:"#0B93F6",fillOpacity:"0.1"}),g.jsx("path",{d:"M22 1L32 11H24C22.8954 11 22 10.1046 22 9V1Z",fill:"#0B93F6",fillOpacity:"0.3"}),g.jsx("path",{d:"M13 19H27M13 25H27M13 31H27",stroke:"#0B93F6",strokeWidth:"2",strokeLinecap:"round"})]})}),g.jsxs(s1,{children:[g.jsx(l1,{children:R.originalName}),g.jsx(u1,{children:h1(R.size)})]})]})},I):null}):null,j=P=>new Date(P).toLocaleTimeString(),O=[...i].sort((P,I)=>P.createdAt.localeCompare(I.createdAt));return g.jsx(Qv,{children:g.jsx(qv,{children:O.map(P=>{const I=p.find(R=>R.id===P.authorId);return g.jsxs(bv,{children:[g.jsx(Gv,{children:g.jsx(Yv,{src:I&&I.profileId?d[I.profileId]:Qt,alt:I&&I.username||"알 수 없음"})}),g.jsxs("div",{children:[g.jsxs(Kv,{children:[g.jsx(Xv,{children:I&&I.username||"알 수 없음"}),g.jsx(Jv,{children:j(P.createdAt)})]}),g.jsx(Zv,{children:P.content}),E(P.attachmentIds)]})]},P.id)})})})}function g1({channel:r}){const[i,s]=ue.useState(""),[u,c]=ue.useState([]),d=vo(O=>O.createMessage),p=ut(O=>O.currentUserId),m=async O=>{if(O.preventDefault(),!(!i.trim()&&u.length===0))try{await d({content:i.trim(),channelId:r.id,authorId:p,attachments:u}),s(""),c([])}catch(P){console.error("메시지 전송 실패:",P)}},v=O=>{const P=Array.from(O.target.files);c(I=>[...I,...P]),O.target.value=""},x=O=>{c(P=>P.filter((I,R)=>R!==O))},E=O=>{O.key==="Enter"&&!O.shiftKey&&(O.preventDefault(),m(O))},j=(O,P)=>O.type.startsWith("image/")?g.jsxs(c1,{children:[g.jsx("img",{src:URL.createObjectURL(O),alt:O.name}),g.jsx(qd,{onClick:()=>x(P),children:"×"})]},P):g.jsxs(Qp,{children:[g.jsx(f1,{children:"📎"}),g.jsx(d1,{children:O.name}),g.jsx(qd,{onClick:()=>x(P),children:"×"})]},P);return ue.useEffect(()=>()=>{u.forEach(O=>{O.type.startsWith("image/")&&URL.revokeObjectURL(O)})},[u]),r?g.jsxs(g.Fragment,{children:[u.length>0&&g.jsx(a1,{children:u.map((O,P)=>j(O,P))}),g.jsxs(e1,{onSubmit:m,children:[g.jsxs(n1,{as:"label",children:["+",g.jsx("input",{type:"file",multiple:!0,onChange:v,style:{display:"none"}})]}),g.jsx(t1,{value:i,onChange:O=>s(O.target.value),onKeyPress:E,placeholder:r.type==="PUBLIC"?`#${r.name}에 메시지 보내기`:"메시지 보내기"})]})]}):null}function y1({channel:r}){const i=ut(v=>v.currentUserId),s=Dt(v=>v.users),u=Wt(v=>v.profileImages);if(!r)return null;if(r.type==="PUBLIC")return g.jsx($d,{children:g.jsx(Hd,{children:g.jsxs(Wd,{children:["# ",r.name]})})});const c=r.participantIds.map(v=>s.find(x=>x.id===v)).filter(Boolean),d=c.filter(v=>v.id!==i),p=c.length>2,m=c.filter(v=>v.id!==i).map(v=>v.username).join(", ");return g.jsx($d,{children:g.jsx(Hd,{children:g.jsxs(Fv,{children:[p?g.jsx($v,{children:d.slice(0,2).map((v,x)=>g.jsx(Vd,{src:v.profileId?u[v.profileId]:Qt,style:{position:"absolute",left:x*16,zIndex:2-x}},v.id))}):g.jsxs(Bv,{children:[g.jsx(Vd,{src:d[0].profileId?u[d[0].profileId]:Qt}),g.jsx(Vv,{online:d[0].online})]}),g.jsxs("div",{children:[g.jsx(Wd,{children:m}),p&&g.jsxs(Wv,{children:["멤버 ",c.length,"명"]})]})]})})})}function v1({channel:r}){return r?g.jsxs(Ov,{children:[g.jsx(y1,{channel:r}),g.jsx(m1,{channel:r}),g.jsx(g1,{channel:r})]}):g.jsx(Lv,{children:g.jsxs(Dv,{children:[g.jsx(zv,{children:"👋"}),g.jsx(Mv,{children:"채널을 선택해주세요"}),g.jsxs(Uv,{children:["왼쪽의 채널 목록에서 채널을 선택하여",g.jsx("br",{}),"대화를 시작하세요."]})]})})}const w1=N.div` - width: 240px; - background: ${J.colors.background.secondary}; - border-left: 1px solid ${J.colors.border.primary}; -`,x1=N.div` - padding: 16px; - font-size: 14px; - font-weight: bold; - color: ${J.colors.text.muted}; - text-transform: uppercase; -`,S1=N.div` - padding: 8px 16px; - display: flex; - align-items: center; - color: ${J.colors.text.muted}; -`,k1=N.div` - position: relative; - width: 32px; - height: 32px; - margin-right: 12px; -`,bd=N.img` - width: 100%; - height: 100%; - border-radius: 50%; -`,E1=N.div` - display: flex; - align-items: center; -`;N.div` - width: 8px; - height: 8px; - border-radius: 50%; - margin-right: 8px; - background: ${r=>J.colors.status[r.status]}; -`;function C1({member:r}){const i=Wt(u=>u.profileImages),s=Wt(u=>u.fetchProfileImage);return ue.useEffect(()=>{r.profileId&&!i[r.profileId]&&s(r.profileId)},[r.profileId,i,s]),g.jsxs(S1,{children:[g.jsxs(k1,{children:[i[r.profileId]?g.jsx(bd,{src:i[r.profileId]}):g.jsx(bd,{src:Qt}),g.jsx(Wp,{$online:r.online})]}),g.jsx(E1,{children:r.username})]})}function A1(){const r=Dt(c=>c.users),i=Dt(c=>c.fetchUsers),s=ut(c=>c.currentUserId);ue.useEffect(()=>{i()},[i]);const u=[...r].sort((c,d)=>c.id===s?-1:d.id===s?1:c.online&&!d.online?-1:!c.online&&d.online?1:c.username.localeCompare(d.username));return g.jsxs(w1,{children:[g.jsxs(x1,{children:["멤버 목록 - ",r.length]}),u.map(c=>g.jsx(C1,{member:c},c.id))]})}const qp=N.div` - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -`,bp=N.div` - background: ${J.colors.background.primary}; - padding: 32px; - border-radius: 8px; - width: 440px; - - h2 { - color: ${J.colors.text.primary}; - margin-bottom: 24px; - font-size: 24px; - font-weight: bold; - } - - form { - display: flex; - flex-direction: column; - gap: 16px; - } -`,xo=N.input` - width: 100%; - padding: 10px; - border-radius: 4px; - background: ${J.colors.background.input}; - border: none; - color: ${J.colors.text.primary}; - font-size: 16px; - - &::placeholder { - color: ${J.colors.text.muted}; - } - - &:focus { - outline: none; - } -`,Gp=N.button` - width: 100%; - padding: 12px; - border-radius: 4px; - background: ${J.colors.brand.primary}; - color: white; - font-size: 16px; - font-weight: 500; - border: none; - cursor: pointer; - transition: background-color 0.2s; - - &:hover { - background: ${J.colors.brand.hover}; - } -`,Yp=N.div` - color: ${J.colors.status.error}; - font-size: 14px; - text-align: center; -`,R1=N.p` - text-align: center; - margin-top: 16px; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 14px; -`,P1=N.span` - color: ${({theme:r})=>r.colors.brand.primary}; - cursor: pointer; - - &:hover { - text-decoration: underline; - } -`;function j1({isOpen:r,onClose:i}){const[s,u]=ue.useState(""),[c,d]=ue.useState(""),[p,m]=ue.useState(""),[v,x]=ue.useState(null),[E,j]=ue.useState(null),[O,P]=ue.useState(""),I=ut(V=>V.setCurrentUser),R=V=>{const F=V.target.files[0];if(F){x(F);const W=new FileReader;W.onloadend=()=>{j(W.result)},W.readAsDataURL(F)}},L=async V=>{V.preventDefault(),P("");try{const F=new FormData;F.append("userCreateRequest",new Blob([JSON.stringify({email:s,username:c,password:p})],{type:"application/json"})),v&&F.append("profile",v);const W=await he.post(`${Xe.apiBaseUrl}/users`,F);I(W.data),i()}catch{P("회원가입에 실패했습니다.")}};return r?g.jsx(qp,{children:g.jsxs(bp,{children:[g.jsx("h2",{children:"계정 만들기"}),g.jsxs("form",{onSubmit:L,children:[g.jsxs(Fi,{children:[g.jsxs(Bi,{children:["이메일 ",g.jsx(Eu,{children:"*"})]}),g.jsx(xo,{type:"email",value:s,onChange:V=>u(V.target.value),required:!0})]}),g.jsxs(Fi,{children:[g.jsxs(Bi,{children:["사용자명 ",g.jsx(Eu,{children:"*"})]}),g.jsx(xo,{type:"text",value:c,onChange:V=>d(V.target.value),required:!0})]}),g.jsxs(Fi,{children:[g.jsxs(Bi,{children:["비밀번호 ",g.jsx(Eu,{children:"*"})]}),g.jsx(xo,{type:"password",value:p,onChange:V=>m(V.target.value),required:!0})]}),g.jsxs(Fi,{children:[g.jsx(Bi,{children:"프로필 이미지"}),g.jsxs(I1,{children:[g.jsx(_1,{src:E||Qt,alt:"profile"}),g.jsx(N1,{type:"file",accept:"image/*",onChange:R,id:"profile-image"}),g.jsx(O1,{htmlFor:"profile-image",children:"이미지 변경"})]})]}),O&&g.jsx(Yp,{children:O}),g.jsx(Gp,{type:"submit",children:"계속하기"}),g.jsx(L1,{onClick:i,children:"이미 계정이 있으신가요?"})]})]})}):null}const Fi=N.div` - margin-bottom: 20px; -`,Bi=N.label` - display: block; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 12px; - font-weight: 700; - margin-bottom: 8px; -`,Eu=N.span` - color: ${({theme:r})=>r.colors.status.error}; -`,I1=N.div` - display: flex; - flex-direction: column; - align-items: center; - margin: 10px 0; -`,_1=N.img` - width: 80px; - height: 80px; - border-radius: 50%; - margin-bottom: 10px; - object-fit: cover; -`,N1=N.input` - display: none; -`,O1=N.label` - color: ${({theme:r})=>r.colors.brand.primary}; - cursor: pointer; - font-size: 14px; - - &:hover { - text-decoration: underline; - } -`;N.p` - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 12px; - margin-top: 16px; - text-align: center; -`;const T1=N.span` - color: ${({theme:r})=>r.colors.brand.primary}; - cursor: pointer; - - &:hover { - text-decoration: underline; - } -`,L1=N(T1)` - display: block; - text-align: center; - margin-top: 16px; -`,D1=({isOpen:r,onClose:i})=>{const[s,u]=ue.useState(""),[c,d]=ue.useState(""),[p,m]=ue.useState(""),[v,x]=ue.useState(!1),E=ut(P=>P.setCurrentUser),{fetchUsers:j}=Dt(),O=async()=>{var P;try{const I=await he.post(`${Xe.apiBaseUrl}/auth/login`,{username:s,password:c});I.status===200&&(await j(),E(I.data),m(""),i())}catch(I){console.error("로그인 에러:",I),((P=I.response)==null?void 0:P.status)===401?m("아이디 또는 비밀번호가 올바르지 않습니다."):m("로그인에 실패했습니다.")}};return r?g.jsxs(g.Fragment,{children:[g.jsx(qp,{children:g.jsxs(bp,{children:[g.jsx("h2",{children:"돌아오신 것을 환영해요!"}),g.jsxs("form",{onSubmit:P=>{P.preventDefault(),O()},children:[g.jsx(xo,{type:"text",placeholder:"사용자 이름",value:s,onChange:P=>u(P.target.value)}),g.jsx(xo,{type:"password",placeholder:"비밀번호",value:c,onChange:P=>d(P.target.value)}),p&&g.jsx(Yp,{children:p}),g.jsx(Gp,{type:"submit",children:"로그인"})]}),g.jsxs(R1,{children:["계정이 필요한가요? ",g.jsx(P1,{onClick:()=>x(!0),children:"가입하기"})]})]})}),g.jsx(j1,{isOpen:v,onClose:()=>x(!1)})]}):null};function z1(){const r=ut(v=>v.currentUserId),i=Dt(v=>v.users),{fetchUsers:s,updateUserStatus:u}=Dt(),[c,d]=ue.useState(null),p=Bn(v=>v.channels),m=r?i.find(v=>v.id===r):null;return ue.useEffect(()=>{let v;if(r){s(),u(r),v=setInterval(()=>{u(r)},3e4);const x=setInterval(()=>{s()},6e4);return()=>{clearInterval(v),clearInterval(x)}}},[r,s,u]),g.jsx(ty,{theme:J,children:m?g.jsxs(M1,{children:[g.jsx(_v,{channels:p,currentUser:m,activeChannel:c,onChannelSelect:d}),g.jsx(v1,{channel:c}),g.jsx(A1,{})]}):g.jsx(D1,{isOpen:!0,onClose:()=>{}})})}const M1=N.div` - display: flex; - height: 100vh; - width: 100vw; - position: relative; -`;eg.createRoot(document.getElementById("root")).render(g.jsx(ue.StrictMode,{children:g.jsx(z1,{})})); diff --git a/src/main/resources/static/assets/index-D8OMG6Bz.js b/src/main/resources/static/assets/index-D8OMG6Bz.js new file mode 100644 index 000000000..84f4ce135 --- /dev/null +++ b/src/main/resources/static/assets/index-D8OMG6Bz.js @@ -0,0 +1,1015 @@ +var rg=Object.defineProperty;var og=(r,i,s)=>i in r?rg(r,i,{enumerable:!0,configurable:!0,writable:!0,value:s}):r[i]=s;var ed=(r,i,s)=>og(r,typeof i!="symbol"?i+"":i,s);(function(){const i=document.createElement("link").relList;if(i&&i.supports&&i.supports("modulepreload"))return;for(const c of document.querySelectorAll('link[rel="modulepreload"]'))l(c);new MutationObserver(c=>{for(const f of c)if(f.type==="childList")for(const p of f.addedNodes)p.tagName==="LINK"&&p.rel==="modulepreload"&&l(p)}).observe(document,{childList:!0,subtree:!0});function s(c){const f={};return c.integrity&&(f.integrity=c.integrity),c.referrerPolicy&&(f.referrerPolicy=c.referrerPolicy),c.crossOrigin==="use-credentials"?f.credentials="include":c.crossOrigin==="anonymous"?f.credentials="omit":f.credentials="same-origin",f}function l(c){if(c.ep)return;c.ep=!0;const f=s(c);fetch(c.href,f)}})();function ig(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}var mu={exports:{}},yo={},gu={exports:{}},fe={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var td;function sg(){if(td)return fe;td=1;var r=Symbol.for("react.element"),i=Symbol.for("react.portal"),s=Symbol.for("react.fragment"),l=Symbol.for("react.strict_mode"),c=Symbol.for("react.profiler"),f=Symbol.for("react.provider"),p=Symbol.for("react.context"),g=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),v=Symbol.for("react.memo"),S=Symbol.for("react.lazy"),A=Symbol.iterator;function R(E){return E===null||typeof E!="object"?null:(E=A&&E[A]||E["@@iterator"],typeof E=="function"?E:null)}var I={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},_=Object.assign,C={};function O(E,D,se){this.props=E,this.context=D,this.refs=C,this.updater=se||I}O.prototype.isReactComponent={},O.prototype.setState=function(E,D){if(typeof E!="object"&&typeof E!="function"&&E!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,E,D,"setState")},O.prototype.forceUpdate=function(E){this.updater.enqueueForceUpdate(this,E,"forceUpdate")};function F(){}F.prototype=O.prototype;function B(E,D,se){this.props=E,this.context=D,this.refs=C,this.updater=se||I}var V=B.prototype=new F;V.constructor=B,_(V,O.prototype),V.isPureReactComponent=!0;var Q=Array.isArray,H=Object.prototype.hasOwnProperty,L={current:null},b={key:!0,ref:!0,__self:!0,__source:!0};function re(E,D,se){var ue,de={},ce=null,ve=null;if(D!=null)for(ue in D.ref!==void 0&&(ve=D.ref),D.key!==void 0&&(ce=""+D.key),D)H.call(D,ue)&&!b.hasOwnProperty(ue)&&(de[ue]=D[ue]);var pe=arguments.length-2;if(pe===1)de.children=se;else if(1>>1,D=W[E];if(0>>1;Ec(de,Y))cec(ve,de)?(W[E]=ve,W[ce]=Y,E=ce):(W[E]=de,W[ue]=Y,E=ue);else if(cec(ve,Y))W[E]=ve,W[ce]=Y,E=ce;else break e}}return Z}function c(W,Z){var Y=W.sortIndex-Z.sortIndex;return Y!==0?Y:W.id-Z.id}if(typeof performance=="object"&&typeof performance.now=="function"){var f=performance;r.unstable_now=function(){return f.now()}}else{var p=Date,g=p.now();r.unstable_now=function(){return p.now()-g}}var x=[],v=[],S=1,A=null,R=3,I=!1,_=!1,C=!1,O=typeof setTimeout=="function"?setTimeout:null,F=typeof clearTimeout=="function"?clearTimeout:null,B=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function V(W){for(var Z=s(v);Z!==null;){if(Z.callback===null)l(v);else if(Z.startTime<=W)l(v),Z.sortIndex=Z.expirationTime,i(x,Z);else break;Z=s(v)}}function Q(W){if(C=!1,V(W),!_)if(s(x)!==null)_=!0,We(H);else{var Z=s(v);Z!==null&&Se(Q,Z.startTime-W)}}function H(W,Z){_=!1,C&&(C=!1,F(re),re=-1),I=!0;var Y=R;try{for(V(Z),A=s(x);A!==null&&(!(A.expirationTime>Z)||W&&!at());){var E=A.callback;if(typeof E=="function"){A.callback=null,R=A.priorityLevel;var D=E(A.expirationTime<=Z);Z=r.unstable_now(),typeof D=="function"?A.callback=D:A===s(x)&&l(x),V(Z)}else l(x);A=s(x)}if(A!==null)var se=!0;else{var ue=s(v);ue!==null&&Se(Q,ue.startTime-Z),se=!1}return se}finally{A=null,R=Y,I=!1}}var L=!1,b=null,re=-1,ye=5,Ne=-1;function at(){return!(r.unstable_now()-NeW||125E?(W.sortIndex=Y,i(v,W),s(x)===null&&W===s(v)&&(C?(F(re),re=-1):C=!0,Se(Q,Y-E))):(W.sortIndex=D,i(x,W),_||I||(_=!0,We(H))),W},r.unstable_shouldYield=at,r.unstable_wrapCallback=function(W){var Z=R;return function(){var Y=R;R=Z;try{return W.apply(this,arguments)}finally{R=Y}}}}(wu)),wu}var sd;function cg(){return sd||(sd=1,vu.exports=ag()),vu.exports}/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var ld;function fg(){if(ld)return lt;ld=1;var r=Ku(),i=cg();function s(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),x=Object.prototype.hasOwnProperty,v=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,S={},A={};function R(e){return x.call(A,e)?!0:x.call(S,e)?!1:v.test(e)?A[e]=!0:(S[e]=!0,!1)}function I(e,t,n,o){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return o?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function _(e,t,n,o){if(t===null||typeof t>"u"||I(e,t,n,o))return!0;if(o)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function C(e,t,n,o,u,a,d){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=o,this.attributeNamespace=u,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=d}var O={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){O[e]=new C(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];O[t]=new C(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){O[e]=new C(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){O[e]=new C(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){O[e]=new C(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){O[e]=new C(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){O[e]=new C(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){O[e]=new C(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){O[e]=new C(e,5,!1,e.toLowerCase(),null,!1,!1)});var F=/[\-:]([a-z])/g;function B(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(F,B);O[t]=new C(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(F,B);O[t]=new C(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(F,B);O[t]=new C(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){O[e]=new C(e,1,!1,e.toLowerCase(),null,!1,!1)}),O.xlinkHref=new C("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){O[e]=new C(e,1,!1,e.toLowerCase(),null,!0,!0)});function V(e,t,n,o){var u=O.hasOwnProperty(t)?O[t]:null;(u!==null?u.type!==0:o||!(2m||u[d]!==a[m]){var y=` +`+u[d].replace(" at new "," at ");return e.displayName&&y.includes("")&&(y=y.replace("",e.displayName)),y}while(1<=d&&0<=m);break}}}finally{se=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?D(e):""}function de(e){switch(e.tag){case 5:return D(e.type);case 16:return D("Lazy");case 13:return D("Suspense");case 19:return D("SuspenseList");case 0:case 2:case 15:return e=ue(e.type,!1),e;case 11:return e=ue(e.type.render,!1),e;case 1:return e=ue(e.type,!0),e;default:return""}}function ce(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case b:return"Fragment";case L:return"Portal";case ye:return"Profiler";case re:return"StrictMode";case Ze:return"Suspense";case ct:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case at:return(e.displayName||"Context")+".Consumer";case Ne:return(e._context.displayName||"Context")+".Provider";case wt:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case xt:return t=e.displayName||null,t!==null?t:ce(e.type)||"Memo";case We:t=e._payload,e=e._init;try{return ce(e(t))}catch{}}return null}function ve(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return ce(t);case 8:return t===re?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function pe(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function me(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function He(e){var t=me(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),o=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var u=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return u.call(this)},set:function(d){o=""+d,a.call(this,d)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return o},setValue:function(d){o=""+d},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Yt(e){e._valueTracker||(e._valueTracker=He(e))}function Pt(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),o="";return e&&(o=me(e)?e.checked?"true":"false":e.value),e=o,e!==n?(t.setValue(e),!0):!1}function No(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Es(e,t){var n=t.checked;return Y({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function sa(e,t){var n=t.defaultValue==null?"":t.defaultValue,o=t.checked!=null?t.checked:t.defaultChecked;n=pe(t.value!=null?t.value:n),e._wrapperState={initialChecked:o,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function la(e,t){t=t.checked,t!=null&&V(e,"checked",t,!1)}function Cs(e,t){la(e,t);var n=pe(t.value),o=t.type;if(n!=null)o==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(o==="submit"||o==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?ks(e,t.type,n):t.hasOwnProperty("defaultValue")&&ks(e,t.type,pe(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function ua(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var o=t.type;if(!(o!=="submit"&&o!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function ks(e,t,n){(t!=="number"||No(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Ir=Array.isArray;function qn(e,t,n,o){if(e=e.options,t){t={};for(var u=0;u"+t.valueOf().toString()+"",t=Oo.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Nr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Or={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},uh=["Webkit","ms","Moz","O"];Object.keys(Or).forEach(function(e){uh.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Or[t]=Or[e]})});function ha(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Or.hasOwnProperty(e)&&Or[e]?(""+t).trim():t+"px"}function ma(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var o=n.indexOf("--")===0,u=ha(n,t[n],o);n==="float"&&(n="cssFloat"),o?e.setProperty(n,u):e[n]=u}}var ah=Y({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Rs(e,t){if(t){if(ah[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(s(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(s(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(s(61))}if(t.style!=null&&typeof t.style!="object")throw Error(s(62))}}function Ps(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var _s=null;function Ts(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Is=null,Qn=null,Gn=null;function ga(e){if(e=to(e)){if(typeof Is!="function")throw Error(s(280));var t=e.stateNode;t&&(t=ni(t),Is(e.stateNode,e.type,t))}}function ya(e){Qn?Gn?Gn.push(e):Gn=[e]:Qn=e}function va(){if(Qn){var e=Qn,t=Gn;if(Gn=Qn=null,ga(e),t)for(e=0;e>>=0,e===0?32:31-(xh(e)/Sh|0)|0}var Uo=64,Fo=4194304;function zr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Bo(e,t){var n=e.pendingLanes;if(n===0)return 0;var o=0,u=e.suspendedLanes,a=e.pingedLanes,d=n&268435455;if(d!==0){var m=d&~u;m!==0?o=zr(m):(a&=d,a!==0&&(o=zr(a)))}else d=n&~u,d!==0?o=zr(d):a!==0&&(o=zr(a));if(o===0)return 0;if(t!==0&&t!==o&&!(t&u)&&(u=o&-o,a=t&-t,u>=a||u===16&&(a&4194240)!==0))return t;if(o&4&&(o|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=o;0n;n++)t.push(e);return t}function Ur(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-_t(t),e[t]=n}function jh(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var o=e.eventTimes;for(e=e.expirationTimes;0=Yr),Ya=" ",qa=!1;function Qa(e,t){switch(e){case"keyup":return Zh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ga(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Jn=!1;function tm(e,t){switch(e){case"compositionend":return Ga(t);case"keypress":return t.which!==32?null:(qa=!0,Ya);case"textInput":return e=t.data,e===Ya&&qa?null:e;default:return null}}function nm(e,t){if(Jn)return e==="compositionend"||!Gs&&Qa(e,t)?(e=Ba(),Wo=bs=an=null,Jn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=o}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=nc(n)}}function oc(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?oc(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ic(){for(var e=window,t=No();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=No(e.document)}return t}function Js(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function fm(e){var t=ic(),n=e.focusedElem,o=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&oc(n.ownerDocument.documentElement,n)){if(o!==null&&Js(n)){if(t=o.start,e=o.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var u=n.textContent.length,a=Math.min(o.start,u);o=o.end===void 0?a:Math.min(o.end,u),!e.extend&&a>o&&(u=o,o=a,a=u),u=rc(n,a);var d=rc(n,o);u&&d&&(e.rangeCount!==1||e.anchorNode!==u.node||e.anchorOffset!==u.offset||e.focusNode!==d.node||e.focusOffset!==d.offset)&&(t=t.createRange(),t.setStart(u.node,u.offset),e.removeAllRanges(),a>o?(e.addRange(t),e.extend(d.node,d.offset)):(t.setEnd(d.node,d.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Zn=null,Zs=null,Kr=null,el=!1;function sc(e,t,n){var o=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;el||Zn==null||Zn!==No(o)||(o=Zn,"selectionStart"in o&&Js(o)?o={start:o.selectionStart,end:o.selectionEnd}:(o=(o.ownerDocument&&o.ownerDocument.defaultView||window).getSelection(),o={anchorNode:o.anchorNode,anchorOffset:o.anchorOffset,focusNode:o.focusNode,focusOffset:o.focusOffset}),Kr&&Gr(Kr,o)||(Kr=o,o=Zo(Zs,"onSelect"),0or||(e.current=dl[or],dl[or]=null,or--)}function Ee(e,t){or++,dl[or]=e.current,e.current=t}var pn={},Ye=dn(pn),nt=dn(!1),Rn=pn;function ir(e,t){var n=e.type.contextTypes;if(!n)return pn;var o=e.stateNode;if(o&&o.__reactInternalMemoizedUnmaskedChildContext===t)return o.__reactInternalMemoizedMaskedChildContext;var u={},a;for(a in n)u[a]=t[a];return o&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=u),u}function rt(e){return e=e.childContextTypes,e!=null}function ri(){ke(nt),ke(Ye)}function Sc(e,t,n){if(Ye.current!==pn)throw Error(s(168));Ee(Ye,t),Ee(nt,n)}function Ec(e,t,n){var o=e.stateNode;if(t=t.childContextTypes,typeof o.getChildContext!="function")return n;o=o.getChildContext();for(var u in o)if(!(u in t))throw Error(s(108,ve(e)||"Unknown",u));return Y({},n,o)}function oi(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||pn,Rn=Ye.current,Ee(Ye,e),Ee(nt,nt.current),!0}function Cc(e,t,n){var o=e.stateNode;if(!o)throw Error(s(169));n?(e=Ec(e,t,Rn),o.__reactInternalMemoizedMergedChildContext=e,ke(nt),ke(Ye),Ee(Ye,e)):ke(nt),Ee(nt,n)}var Qt=null,ii=!1,pl=!1;function kc(e){Qt===null?Qt=[e]:Qt.push(e)}function Cm(e){ii=!0,kc(e)}function hn(){if(!pl&&Qt!==null){pl=!0;var e=0,t=xe;try{var n=Qt;for(xe=1;e>=d,u-=d,Gt=1<<32-_t(t)+u|n<oe?(Be=ne,ne=null):Be=ne.sibling;var ge=M(k,ne,j[oe],$);if(ge===null){ne===null&&(ne=Be);break}e&&ne&&ge.alternate===null&&t(k,ne),w=a(ge,w,oe),te===null?J=ge:te.sibling=ge,te=ge,ne=Be}if(oe===j.length)return n(k,ne),Ae&&_n(k,oe),J;if(ne===null){for(;oeoe?(Be=ne,ne=null):Be=ne.sibling;var Cn=M(k,ne,ge.value,$);if(Cn===null){ne===null&&(ne=Be);break}e&&ne&&Cn.alternate===null&&t(k,ne),w=a(Cn,w,oe),te===null?J=Cn:te.sibling=Cn,te=Cn,ne=Be}if(ge.done)return n(k,ne),Ae&&_n(k,oe),J;if(ne===null){for(;!ge.done;oe++,ge=j.next())ge=U(k,ge.value,$),ge!==null&&(w=a(ge,w,oe),te===null?J=ge:te.sibling=ge,te=ge);return Ae&&_n(k,oe),J}for(ne=o(k,ne);!ge.done;oe++,ge=j.next())ge=q(ne,k,oe,ge.value,$),ge!==null&&(e&&ge.alternate!==null&&ne.delete(ge.key===null?oe:ge.key),w=a(ge,w,oe),te===null?J=ge:te.sibling=ge,te=ge);return e&&ne.forEach(function(ng){return t(k,ng)}),Ae&&_n(k,oe),J}function Ie(k,w,j,$){if(typeof j=="object"&&j!==null&&j.type===b&&j.key===null&&(j=j.props.children),typeof j=="object"&&j!==null){switch(j.$$typeof){case H:e:{for(var J=j.key,te=w;te!==null;){if(te.key===J){if(J=j.type,J===b){if(te.tag===7){n(k,te.sibling),w=u(te,j.props.children),w.return=k,k=w;break e}}else if(te.elementType===J||typeof J=="object"&&J!==null&&J.$$typeof===We&&Tc(J)===te.type){n(k,te.sibling),w=u(te,j.props),w.ref=no(k,te,j),w.return=k,k=w;break e}n(k,te);break}else t(k,te);te=te.sibling}j.type===b?(w=zn(j.props.children,k.mode,$,j.key),w.return=k,k=w):($=Oi(j.type,j.key,j.props,null,k.mode,$),$.ref=no(k,w,j),$.return=k,k=$)}return d(k);case L:e:{for(te=j.key;w!==null;){if(w.key===te)if(w.tag===4&&w.stateNode.containerInfo===j.containerInfo&&w.stateNode.implementation===j.implementation){n(k,w.sibling),w=u(w,j.children||[]),w.return=k,k=w;break e}else{n(k,w);break}else t(k,w);w=w.sibling}w=cu(j,k.mode,$),w.return=k,k=w}return d(k);case We:return te=j._init,Ie(k,w,te(j._payload),$)}if(Ir(j))return K(k,w,j,$);if(Z(j))return X(k,w,j,$);ai(k,j)}return typeof j=="string"&&j!==""||typeof j=="number"?(j=""+j,w!==null&&w.tag===6?(n(k,w.sibling),w=u(w,j),w.return=k,k=w):(n(k,w),w=au(j,k.mode,$),w.return=k,k=w),d(k)):n(k,w)}return Ie}var ar=Ic(!0),Nc=Ic(!1),ci=dn(null),fi=null,cr=null,wl=null;function xl(){wl=cr=fi=null}function Sl(e){var t=ci.current;ke(ci),e._currentValue=t}function El(e,t,n){for(;e!==null;){var o=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,o!==null&&(o.childLanes|=t)):o!==null&&(o.childLanes&t)!==t&&(o.childLanes|=t),e===n)break;e=e.return}}function fr(e,t){fi=e,wl=cr=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(ot=!0),e.firstContext=null)}function Ct(e){var t=e._currentValue;if(wl!==e)if(e={context:e,memoizedValue:t,next:null},cr===null){if(fi===null)throw Error(s(308));cr=e,fi.dependencies={lanes:0,firstContext:e}}else cr=cr.next=e;return t}var Tn=null;function Cl(e){Tn===null?Tn=[e]:Tn.push(e)}function Oc(e,t,n,o){var u=t.interleaved;return u===null?(n.next=n,Cl(t)):(n.next=u.next,u.next=n),t.interleaved=n,Xt(e,o)}function Xt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var mn=!1;function kl(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Lc(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Jt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function gn(e,t,n){var o=e.updateQueue;if(o===null)return null;if(o=o.shared,he&2){var u=o.pending;return u===null?t.next=t:(t.next=u.next,u.next=t),o.pending=t,Xt(e,n)}return u=o.interleaved,u===null?(t.next=t,Cl(o)):(t.next=u.next,u.next=t),o.interleaved=t,Xt(e,n)}function di(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var o=t.lanes;o&=e.pendingLanes,n|=o,t.lanes=n,Us(e,n)}}function Dc(e,t){var n=e.updateQueue,o=e.alternate;if(o!==null&&(o=o.updateQueue,n===o)){var u=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var d={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};a===null?u=a=d:a=a.next=d,n=n.next}while(n!==null);a===null?u=a=t:a=a.next=t}else u=a=t;n={baseState:o.baseState,firstBaseUpdate:u,lastBaseUpdate:a,shared:o.shared,effects:o.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function pi(e,t,n,o){var u=e.updateQueue;mn=!1;var a=u.firstBaseUpdate,d=u.lastBaseUpdate,m=u.shared.pending;if(m!==null){u.shared.pending=null;var y=m,P=y.next;y.next=null,d===null?a=P:d.next=P,d=y;var z=e.alternate;z!==null&&(z=z.updateQueue,m=z.lastBaseUpdate,m!==d&&(m===null?z.firstBaseUpdate=P:m.next=P,z.lastBaseUpdate=y))}if(a!==null){var U=u.baseState;d=0,z=P=y=null,m=a;do{var M=m.lane,q=m.eventTime;if((o&M)===M){z!==null&&(z=z.next={eventTime:q,lane:0,tag:m.tag,payload:m.payload,callback:m.callback,next:null});e:{var K=e,X=m;switch(M=t,q=n,X.tag){case 1:if(K=X.payload,typeof K=="function"){U=K.call(q,U,M);break e}U=K;break e;case 3:K.flags=K.flags&-65537|128;case 0:if(K=X.payload,M=typeof K=="function"?K.call(q,U,M):K,M==null)break e;U=Y({},U,M);break e;case 2:mn=!0}}m.callback!==null&&m.lane!==0&&(e.flags|=64,M=u.effects,M===null?u.effects=[m]:M.push(m))}else q={eventTime:q,lane:M,tag:m.tag,payload:m.payload,callback:m.callback,next:null},z===null?(P=z=q,y=U):z=z.next=q,d|=M;if(m=m.next,m===null){if(m=u.shared.pending,m===null)break;M=m,m=M.next,M.next=null,u.lastBaseUpdate=M,u.shared.pending=null}}while(!0);if(z===null&&(y=U),u.baseState=y,u.firstBaseUpdate=P,u.lastBaseUpdate=z,t=u.shared.interleaved,t!==null){u=t;do d|=u.lane,u=u.next;while(u!==t)}else a===null&&(u.shared.lanes=0);On|=d,e.lanes=d,e.memoizedState=U}}function Mc(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var o=_l.transition;_l.transition={};try{e(!1),t()}finally{xe=n,_l.transition=o}}function tf(){return kt().memoizedState}function Rm(e,t,n){var o=xn(e);if(n={lane:o,action:n,hasEagerState:!1,eagerState:null,next:null},nf(e))rf(t,n);else if(n=Oc(e,t,n,o),n!==null){var u=tt();Dt(n,e,o,u),of(n,t,o)}}function Pm(e,t,n){var o=xn(e),u={lane:o,action:n,hasEagerState:!1,eagerState:null,next:null};if(nf(e))rf(t,u);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var d=t.lastRenderedState,m=a(d,n);if(u.hasEagerState=!0,u.eagerState=m,Tt(m,d)){var y=t.interleaved;y===null?(u.next=u,Cl(t)):(u.next=y.next,y.next=u),t.interleaved=u;return}}catch{}finally{}n=Oc(e,t,u,o),n!==null&&(u=tt(),Dt(n,e,o,u),of(n,t,o))}}function nf(e){var t=e.alternate;return e===Pe||t!==null&&t===Pe}function rf(e,t){so=gi=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function of(e,t,n){if(n&4194240){var o=t.lanes;o&=e.pendingLanes,n|=o,t.lanes=n,Us(e,n)}}var wi={readContext:Ct,useCallback:qe,useContext:qe,useEffect:qe,useImperativeHandle:qe,useInsertionEffect:qe,useLayoutEffect:qe,useMemo:qe,useReducer:qe,useRef:qe,useState:qe,useDebugValue:qe,useDeferredValue:qe,useTransition:qe,useMutableSource:qe,useSyncExternalStore:qe,useId:qe,unstable_isNewReconciler:!1},_m={readContext:Ct,useCallback:function(e,t){return Ht().memoizedState=[e,t===void 0?null:t],e},useContext:Ct,useEffect:qc,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,yi(4194308,4,Kc.bind(null,t,e),n)},useLayoutEffect:function(e,t){return yi(4194308,4,e,t)},useInsertionEffect:function(e,t){return yi(4,2,e,t)},useMemo:function(e,t){var n=Ht();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var o=Ht();return t=n!==void 0?n(t):t,o.memoizedState=o.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},o.queue=e,e=e.dispatch=Rm.bind(null,Pe,e),[o.memoizedState,e]},useRef:function(e){var t=Ht();return e={current:e},t.memoizedState=e},useState:Wc,useDebugValue:Ml,useDeferredValue:function(e){return Ht().memoizedState=e},useTransition:function(){var e=Wc(!1),t=e[0];return e=Am.bind(null,e[1]),Ht().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var o=Pe,u=Ht();if(Ae){if(n===void 0)throw Error(s(407));n=n()}else{if(n=t(),Fe===null)throw Error(s(349));Nn&30||Bc(o,t,n)}u.memoizedState=n;var a={value:n,getSnapshot:t};return u.queue=a,qc(Hc.bind(null,o,a,e),[e]),o.flags|=2048,ao(9,$c.bind(null,o,a,n,t),void 0,null),n},useId:function(){var e=Ht(),t=Fe.identifierPrefix;if(Ae){var n=Kt,o=Gt;n=(o&~(1<<32-_t(o)-1)).toString(32)+n,t=":"+t+"R"+n,n=lo++,0<\/script>",e=e.removeChild(e.firstChild)):typeof o.is=="string"?e=d.createElement(n,{is:o.is}):(e=d.createElement(n),n==="select"&&(d=e,o.multiple?d.multiple=!0:o.size&&(d.size=o.size))):e=d.createElementNS(e,n),e[Bt]=t,e[eo]=o,jf(e,t,!1,!1),t.stateNode=e;e:{switch(d=Ps(n,o),n){case"dialog":Ce("cancel",e),Ce("close",e),u=o;break;case"iframe":case"object":case"embed":Ce("load",e),u=o;break;case"video":case"audio":for(u=0;ugr&&(t.flags|=128,o=!0,co(a,!1),t.lanes=4194304)}else{if(!o)if(e=hi(d),e!==null){if(t.flags|=128,o=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),co(a,!0),a.tail===null&&a.tailMode==="hidden"&&!d.alternate&&!Ae)return Qe(t),null}else 2*Te()-a.renderingStartTime>gr&&n!==1073741824&&(t.flags|=128,o=!0,co(a,!1),t.lanes=4194304);a.isBackwards?(d.sibling=t.child,t.child=d):(n=a.last,n!==null?n.sibling=d:t.child=d,a.last=d)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=Te(),t.sibling=null,n=Re.current,Ee(Re,o?n&1|2:n&1),t):(Qe(t),null);case 22:case 23:return su(),o=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==o&&(t.flags|=8192),o&&t.mode&1?ht&1073741824&&(Qe(t),t.subtreeFlags&6&&(t.flags|=8192)):Qe(t),null;case 24:return null;case 25:return null}throw Error(s(156,t.tag))}function zm(e,t){switch(ml(t),t.tag){case 1:return rt(t.type)&&ri(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return dr(),ke(nt),ke(Ye),Pl(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Al(t),null;case 13:if(ke(Re),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(s(340));ur()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return ke(Re),null;case 4:return dr(),null;case 10:return Sl(t.type._context),null;case 22:case 23:return su(),null;case 24:return null;default:return null}}var Ci=!1,Ge=!1,Um=typeof WeakSet=="function"?WeakSet:Set,G=null;function hr(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(o){_e(e,t,o)}else n.current=null}function Ql(e,t,n){try{n()}catch(o){_e(e,t,o)}}var Pf=!1;function Fm(e,t){if(sl=bo,e=ic(),Js(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var o=n.getSelection&&n.getSelection();if(o&&o.rangeCount!==0){n=o.anchorNode;var u=o.anchorOffset,a=o.focusNode;o=o.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break e}var d=0,m=-1,y=-1,P=0,z=0,U=e,M=null;t:for(;;){for(var q;U!==n||u!==0&&U.nodeType!==3||(m=d+u),U!==a||o!==0&&U.nodeType!==3||(y=d+o),U.nodeType===3&&(d+=U.nodeValue.length),(q=U.firstChild)!==null;)M=U,U=q;for(;;){if(U===e)break t;if(M===n&&++P===u&&(m=d),M===a&&++z===o&&(y=d),(q=U.nextSibling)!==null)break;U=M,M=U.parentNode}U=q}n=m===-1||y===-1?null:{start:m,end:y}}else n=null}n=n||{start:0,end:0}}else n=null;for(ll={focusedElem:e,selectionRange:n},bo=!1,G=t;G!==null;)if(t=G,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,G=e;else for(;G!==null;){t=G;try{var K=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(K!==null){var X=K.memoizedProps,Ie=K.memoizedState,k=t.stateNode,w=k.getSnapshotBeforeUpdate(t.elementType===t.type?X:Nt(t.type,X),Ie);k.__reactInternalSnapshotBeforeUpdate=w}break;case 3:var j=t.stateNode.containerInfo;j.nodeType===1?j.textContent="":j.nodeType===9&&j.documentElement&&j.removeChild(j.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(s(163))}}catch($){_e(t,t.return,$)}if(e=t.sibling,e!==null){e.return=t.return,G=e;break}G=t.return}return K=Pf,Pf=!1,K}function fo(e,t,n){var o=t.updateQueue;if(o=o!==null?o.lastEffect:null,o!==null){var u=o=o.next;do{if((u.tag&e)===e){var a=u.destroy;u.destroy=void 0,a!==void 0&&Ql(t,n,a)}u=u.next}while(u!==o)}}function ki(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var o=n.create;n.destroy=o()}n=n.next}while(n!==t)}}function Gl(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function _f(e){var t=e.alternate;t!==null&&(e.alternate=null,_f(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Bt],delete t[eo],delete t[fl],delete t[Sm],delete t[Em])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Tf(e){return e.tag===5||e.tag===3||e.tag===4}function If(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Tf(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Kl(e,t,n){var o=e.tag;if(o===5||o===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ti));else if(o!==4&&(e=e.child,e!==null))for(Kl(e,t,n),e=e.sibling;e!==null;)Kl(e,t,n),e=e.sibling}function Xl(e,t,n){var o=e.tag;if(o===5||o===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(o!==4&&(e=e.child,e!==null))for(Xl(e,t,n),e=e.sibling;e!==null;)Xl(e,t,n),e=e.sibling}var be=null,Ot=!1;function yn(e,t,n){for(n=n.child;n!==null;)Nf(e,t,n),n=n.sibling}function Nf(e,t,n){if(Ft&&typeof Ft.onCommitFiberUnmount=="function")try{Ft.onCommitFiberUnmount(zo,n)}catch{}switch(n.tag){case 5:Ge||hr(n,t);case 6:var o=be,u=Ot;be=null,yn(e,t,n),be=o,Ot=u,be!==null&&(Ot?(e=be,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):be.removeChild(n.stateNode));break;case 18:be!==null&&(Ot?(e=be,n=n.stateNode,e.nodeType===8?cl(e.parentNode,n):e.nodeType===1&&cl(e,n),br(e)):cl(be,n.stateNode));break;case 4:o=be,u=Ot,be=n.stateNode.containerInfo,Ot=!0,yn(e,t,n),be=o,Ot=u;break;case 0:case 11:case 14:case 15:if(!Ge&&(o=n.updateQueue,o!==null&&(o=o.lastEffect,o!==null))){u=o=o.next;do{var a=u,d=a.destroy;a=a.tag,d!==void 0&&(a&2||a&4)&&Ql(n,t,d),u=u.next}while(u!==o)}yn(e,t,n);break;case 1:if(!Ge&&(hr(n,t),o=n.stateNode,typeof o.componentWillUnmount=="function"))try{o.props=n.memoizedProps,o.state=n.memoizedState,o.componentWillUnmount()}catch(m){_e(n,t,m)}yn(e,t,n);break;case 21:yn(e,t,n);break;case 22:n.mode&1?(Ge=(o=Ge)||n.memoizedState!==null,yn(e,t,n),Ge=o):yn(e,t,n);break;default:yn(e,t,n)}}function Of(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Um),t.forEach(function(o){var u=Qm.bind(null,e,o);n.has(o)||(n.add(o),o.then(u,u))})}}function Lt(e,t){var n=t.deletions;if(n!==null)for(var o=0;ou&&(u=d),o&=~a}if(o=u,o=Te()-o,o=(120>o?120:480>o?480:1080>o?1080:1920>o?1920:3e3>o?3e3:4320>o?4320:1960*$m(o/1960))-o,10e?16:e,wn===null)var o=!1;else{if(e=wn,wn=null,_i=0,he&6)throw Error(s(331));var u=he;for(he|=4,G=e.current;G!==null;){var a=G,d=a.child;if(G.flags&16){var m=a.deletions;if(m!==null){for(var y=0;yTe()-eu?Dn(e,0):Zl|=n),st(e,t)}function Yf(e,t){t===0&&(e.mode&1?(t=Fo,Fo<<=1,!(Fo&130023424)&&(Fo=4194304)):t=1);var n=tt();e=Xt(e,t),e!==null&&(Ur(e,t,n),st(e,n))}function qm(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Yf(e,n)}function Qm(e,t){var n=0;switch(e.tag){case 13:var o=e.stateNode,u=e.memoizedState;u!==null&&(n=u.retryLane);break;case 19:o=e.stateNode;break;default:throw Error(s(314))}o!==null&&o.delete(t),Yf(e,n)}var qf;qf=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||nt.current)ot=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return ot=!1,Dm(e,t,n);ot=!!(e.flags&131072)}else ot=!1,Ae&&t.flags&1048576&&jc(t,li,t.index);switch(t.lanes=0,t.tag){case 2:var o=t.type;Ei(e,t),e=t.pendingProps;var u=ir(t,Ye.current);fr(t,n),u=Il(null,t,o,e,u,n);var a=Nl();return t.flags|=1,typeof u=="object"&&u!==null&&typeof u.render=="function"&&u.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,rt(o)?(a=!0,oi(t)):a=!1,t.memoizedState=u.state!==null&&u.state!==void 0?u.state:null,kl(t),u.updater=xi,t.stateNode=u,u._reactInternals=t,Ul(t,o,e,n),t=Hl(null,t,o,!0,a,n)):(t.tag=0,Ae&&a&&hl(t),et(null,t,u,n),t=t.child),t;case 16:o=t.elementType;e:{switch(Ei(e,t),e=t.pendingProps,u=o._init,o=u(o._payload),t.type=o,u=t.tag=Km(o),e=Nt(o,e),u){case 0:t=$l(null,t,o,e,n);break e;case 1:t=wf(null,t,o,e,n);break e;case 11:t=hf(null,t,o,e,n);break e;case 14:t=mf(null,t,o,Nt(o.type,e),n);break e}throw Error(s(306,o,""))}return t;case 0:return o=t.type,u=t.pendingProps,u=t.elementType===o?u:Nt(o,u),$l(e,t,o,u,n);case 1:return o=t.type,u=t.pendingProps,u=t.elementType===o?u:Nt(o,u),wf(e,t,o,u,n);case 3:e:{if(xf(t),e===null)throw Error(s(387));o=t.pendingProps,a=t.memoizedState,u=a.element,Lc(e,t),pi(t,o,null,n);var d=t.memoizedState;if(o=d.element,a.isDehydrated)if(a={element:o,isDehydrated:!1,cache:d.cache,pendingSuspenseBoundaries:d.pendingSuspenseBoundaries,transitions:d.transitions},t.updateQueue.baseState=a,t.memoizedState=a,t.flags&256){u=pr(Error(s(423)),t),t=Sf(e,t,o,n,u);break e}else if(o!==u){u=pr(Error(s(424)),t),t=Sf(e,t,o,n,u);break e}else for(pt=fn(t.stateNode.containerInfo.firstChild),dt=t,Ae=!0,It=null,n=Nc(t,null,o,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(ur(),o===u){t=Zt(e,t,n);break e}et(e,t,o,n)}t=t.child}return t;case 5:return zc(t),e===null&&yl(t),o=t.type,u=t.pendingProps,a=e!==null?e.memoizedProps:null,d=u.children,ul(o,u)?d=null:a!==null&&ul(o,a)&&(t.flags|=32),vf(e,t),et(e,t,d,n),t.child;case 6:return e===null&&yl(t),null;case 13:return Ef(e,t,n);case 4:return jl(t,t.stateNode.containerInfo),o=t.pendingProps,e===null?t.child=ar(t,null,o,n):et(e,t,o,n),t.child;case 11:return o=t.type,u=t.pendingProps,u=t.elementType===o?u:Nt(o,u),hf(e,t,o,u,n);case 7:return et(e,t,t.pendingProps,n),t.child;case 8:return et(e,t,t.pendingProps.children,n),t.child;case 12:return et(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(o=t.type._context,u=t.pendingProps,a=t.memoizedProps,d=u.value,Ee(ci,o._currentValue),o._currentValue=d,a!==null)if(Tt(a.value,d)){if(a.children===u.children&&!nt.current){t=Zt(e,t,n);break e}}else for(a=t.child,a!==null&&(a.return=t);a!==null;){var m=a.dependencies;if(m!==null){d=a.child;for(var y=m.firstContext;y!==null;){if(y.context===o){if(a.tag===1){y=Jt(-1,n&-n),y.tag=2;var P=a.updateQueue;if(P!==null){P=P.shared;var z=P.pending;z===null?y.next=y:(y.next=z.next,z.next=y),P.pending=y}}a.lanes|=n,y=a.alternate,y!==null&&(y.lanes|=n),El(a.return,n,t),m.lanes|=n;break}y=y.next}}else if(a.tag===10)d=a.type===t.type?null:a.child;else if(a.tag===18){if(d=a.return,d===null)throw Error(s(341));d.lanes|=n,m=d.alternate,m!==null&&(m.lanes|=n),El(d,n,t),d=a.sibling}else d=a.child;if(d!==null)d.return=a;else for(d=a;d!==null;){if(d===t){d=null;break}if(a=d.sibling,a!==null){a.return=d.return,d=a;break}d=d.return}a=d}et(e,t,u.children,n),t=t.child}return t;case 9:return u=t.type,o=t.pendingProps.children,fr(t,n),u=Ct(u),o=o(u),t.flags|=1,et(e,t,o,n),t.child;case 14:return o=t.type,u=Nt(o,t.pendingProps),u=Nt(o.type,u),mf(e,t,o,u,n);case 15:return gf(e,t,t.type,t.pendingProps,n);case 17:return o=t.type,u=t.pendingProps,u=t.elementType===o?u:Nt(o,u),Ei(e,t),t.tag=1,rt(o)?(e=!0,oi(t)):e=!1,fr(t,n),lf(t,o,u),Ul(t,o,u,n),Hl(null,t,o,!0,e,n);case 19:return kf(e,t,n);case 22:return yf(e,t,n)}throw Error(s(156,t.tag))};function Qf(e,t){return Aa(e,t)}function Gm(e,t,n,o){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=o,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function At(e,t,n,o){return new Gm(e,t,n,o)}function uu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Km(e){if(typeof e=="function")return uu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===wt)return 11;if(e===xt)return 14}return 2}function En(e,t){var n=e.alternate;return n===null?(n=At(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Oi(e,t,n,o,u,a){var d=2;if(o=e,typeof e=="function")uu(e)&&(d=1);else if(typeof e=="string")d=5;else e:switch(e){case b:return zn(n.children,u,a,t);case re:d=8,u|=8;break;case ye:return e=At(12,n,t,u|2),e.elementType=ye,e.lanes=a,e;case Ze:return e=At(13,n,t,u),e.elementType=Ze,e.lanes=a,e;case ct:return e=At(19,n,t,u),e.elementType=ct,e.lanes=a,e;case Se:return Li(n,u,a,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Ne:d=10;break e;case at:d=9;break e;case wt:d=11;break e;case xt:d=14;break e;case We:d=16,o=null;break e}throw Error(s(130,e==null?e:typeof e,""))}return t=At(d,n,t,u),t.elementType=e,t.type=o,t.lanes=a,t}function zn(e,t,n,o){return e=At(7,e,o,t),e.lanes=n,e}function Li(e,t,n,o){return e=At(22,e,o,t),e.elementType=Se,e.lanes=n,e.stateNode={isHidden:!1},e}function au(e,t,n){return e=At(6,e,null,t),e.lanes=n,e}function cu(e,t,n){return t=At(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Xm(e,t,n,o,u){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=zs(0),this.expirationTimes=zs(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=zs(0),this.identifierPrefix=o,this.onRecoverableError=u,this.mutableSourceEagerHydrationData=null}function fu(e,t,n,o,u,a,d,m,y){return e=new Xm(e,t,n,m,y),t===1?(t=1,a===!0&&(t|=8)):t=0,a=At(3,null,null,t),e.current=a,a.stateNode=e,a.memoizedState={element:o,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},kl(a),e}function Jm(e,t,n){var o=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(r)}catch(i){console.error(i)}}return r(),yu.exports=fg(),yu.exports}var ad;function pg(){if(ad)return $i;ad=1;var r=dg();return $i.createRoot=r.createRoot,$i.hydrateRoot=r.hydrateRoot,$i}var hg=pg(),Xe=function(){return Xe=Object.assign||function(i){for(var s,l=1,c=arguments.length;l0?$e(Ar,--Rt):0,Cr--,Le===10&&(Cr=1,fs--),Le}function Mt(){return Le=Rt2||Lu(Le)>3?"":" "}function kg(r,i){for(;--i&&Mt()&&!(Le<48||Le>102||Le>57&&Le<65||Le>70&&Le<97););return ps(r,Gi()+(i<6&&Bn()==32&&Mt()==32))}function Du(r){for(;Mt();)switch(Le){case r:return Rt;case 34:case 39:r!==34&&r!==39&&Du(Le);break;case 40:r===41&&Du(r);break;case 92:Mt();break}return Rt}function jg(r,i){for(;Mt()&&r+Le!==57;)if(r+Le===84&&Bn()===47)break;return"/*"+ps(i,Rt-1)+"*"+Ju(r===47?r:Mt())}function Ag(r){for(;!Lu(Bn());)Mt();return ps(r,Rt)}function Rg(r){return Eg(Ki("",null,null,null,[""],r=Sg(r),0,[0],r))}function Ki(r,i,s,l,c,f,p,g,x){for(var v=0,S=0,A=p,R=0,I=0,_=0,C=1,O=1,F=1,B=0,V="",Q=c,H=f,L=l,b=V;O;)switch(_=B,B=Mt()){case 40:if(_!=108&&$e(b,A-1)==58){Qi(b+=ae(xu(B),"&","&\f"),"&\f",up(v?g[v-1]:0))!=-1&&(F=-1);break}case 34:case 39:case 91:b+=xu(B);break;case 9:case 10:case 13:case 32:b+=Cg(_);break;case 92:b+=kg(Gi()-1,7);continue;case 47:switch(Bn()){case 42:case 47:Eo(Pg(jg(Mt(),Gi()),i,s,x),x);break;default:b+="/"}break;case 123*C:g[v++]=Wt(b)*F;case 125*C:case 59:case 0:switch(B){case 0:case 125:O=0;case 59+S:F==-1&&(b=ae(b,/\f/g,"")),I>0&&Wt(b)-A&&Eo(I>32?dd(b+";",l,s,A-1,x):dd(ae(b," ","")+";",l,s,A-2,x),x);break;case 59:b+=";";default:if(Eo(L=fd(b,i,s,v,S,c,g,V,Q=[],H=[],A,f),f),B===123)if(S===0)Ki(b,i,L,L,Q,f,A,g,H);else switch(R===99&&$e(b,3)===110?100:R){case 100:case 108:case 109:case 115:Ki(r,L,L,l&&Eo(fd(r,L,L,0,0,c,g,V,c,Q=[],A,H),H),c,H,A,g,l?Q:H);break;default:Ki(b,L,L,L,[""],H,0,g,H)}}v=S=I=0,C=F=1,V=b="",A=p;break;case 58:A=1+Wt(b),I=_;default:if(C<1){if(B==123)--C;else if(B==125&&C++==0&&xg()==125)continue}switch(b+=Ju(B),B*C){case 38:F=S>0?1:(b+="\f",-1);break;case 44:g[v++]=(Wt(b)-1)*F,F=1;break;case 64:Bn()===45&&(b+=xu(Mt())),R=Bn(),S=A=Wt(V=b+=Ag(Gi())),B++;break;case 45:_===45&&Wt(b)==2&&(C=0)}}return f}function fd(r,i,s,l,c,f,p,g,x,v,S,A){for(var R=c-1,I=c===0?f:[""],_=cp(I),C=0,O=0,F=0;C0?I[B]+" "+V:ae(V,/&\f/g,I[B])))&&(x[F++]=Q);return ds(r,i,s,c===0?cs:g,x,v,S,A)}function Pg(r,i,s,l){return ds(r,i,s,sp,Ju(wg()),Er(r,2,-2),0,l)}function dd(r,i,s,l,c){return ds(r,i,s,Xu,Er(r,0,l),Er(r,l+1,-1),l,c)}function dp(r,i,s){switch(yg(r,i)){case 5103:return we+"print-"+r+r;case 5737:case 4201:case 3177:case 3433:case 1641:case 4457:case 2921:case 5572:case 6356:case 5844:case 3191:case 6645:case 3005:case 6391:case 5879:case 5623:case 6135:case 4599:case 4855:case 4215:case 6389:case 5109:case 5365:case 5621:case 3829:return we+r+r;case 4789:return Co+r+r;case 5349:case 4246:case 4810:case 6968:case 2756:return we+r+Co+r+je+r+r;case 5936:switch($e(r,i+11)){case 114:return we+r+je+ae(r,/[svh]\w+-[tblr]{2}/,"tb")+r;case 108:return we+r+je+ae(r,/[svh]\w+-[tblr]{2}/,"tb-rl")+r;case 45:return we+r+je+ae(r,/[svh]\w+-[tblr]{2}/,"lr")+r}case 6828:case 4268:case 2903:return we+r+je+r+r;case 6165:return we+r+je+"flex-"+r+r;case 5187:return we+r+ae(r,/(\w+).+(:[^]+)/,we+"box-$1$2"+je+"flex-$1$2")+r;case 5443:return we+r+je+"flex-item-"+ae(r,/flex-|-self/g,"")+(tn(r,/flex-|baseline/)?"":je+"grid-row-"+ae(r,/flex-|-self/g,""))+r;case 4675:return we+r+je+"flex-line-pack"+ae(r,/align-content|flex-|-self/g,"")+r;case 5548:return we+r+je+ae(r,"shrink","negative")+r;case 5292:return we+r+je+ae(r,"basis","preferred-size")+r;case 6060:return we+"box-"+ae(r,"-grow","")+we+r+je+ae(r,"grow","positive")+r;case 4554:return we+ae(r,/([^-])(transform)/g,"$1"+we+"$2")+r;case 6187:return ae(ae(ae(r,/(zoom-|grab)/,we+"$1"),/(image-set)/,we+"$1"),r,"")+r;case 5495:case 3959:return ae(r,/(image-set\([^]*)/,we+"$1$`$1");case 4968:return ae(ae(r,/(.+:)(flex-)?(.*)/,we+"box-pack:$3"+je+"flex-pack:$3"),/s.+-b[^;]+/,"justify")+we+r+r;case 4200:if(!tn(r,/flex-|baseline/))return je+"grid-column-align"+Er(r,i)+r;break;case 2592:case 3360:return je+ae(r,"template-","")+r;case 4384:case 3616:return s&&s.some(function(l,c){return i=c,tn(l.props,/grid-\w+-end/)})?~Qi(r+(s=s[i].value),"span",0)?r:je+ae(r,"-start","")+r+je+"grid-row-span:"+(~Qi(s,"span",0)?tn(s,/\d+/):+tn(s,/\d+/)-+tn(r,/\d+/))+";":je+ae(r,"-start","")+r;case 4896:case 4128:return s&&s.some(function(l){return tn(l.props,/grid-\w+-start/)})?r:je+ae(ae(r,"-end","-span"),"span ","")+r;case 4095:case 3583:case 4068:case 2532:return ae(r,/(.+)-inline(.+)/,we+"$1$2")+r;case 8116:case 7059:case 5753:case 5535:case 5445:case 5701:case 4933:case 4677:case 5533:case 5789:case 5021:case 4765:if(Wt(r)-1-i>6)switch($e(r,i+1)){case 109:if($e(r,i+4)!==45)break;case 102:return ae(r,/(.+:)(.+)-([^]+)/,"$1"+we+"$2-$3$1"+Co+($e(r,i+3)==108?"$3":"$2-$3"))+r;case 115:return~Qi(r,"stretch",0)?dp(ae(r,"stretch","fill-available"),i,s)+r:r}break;case 5152:case 5920:return ae(r,/(.+?):(\d+)(\s*\/\s*(span)?\s*(\d+))?(.*)/,function(l,c,f,p,g,x,v){return je+c+":"+f+v+(p?je+c+"-span:"+(g?x:+x-+f)+v:"")+r});case 4949:if($e(r,i+6)===121)return ae(r,":",":"+we)+r;break;case 6444:switch($e(r,$e(r,14)===45?18:11)){case 120:return ae(r,/(.+:)([^;\s!]+)(;|(\s+)?!.+)?/,"$1"+we+($e(r,14)===45?"inline-":"")+"box$3$1"+we+"$2$3$1"+je+"$2box$3")+r;case 100:return ae(r,":",":"+je)+r}break;case 5719:case 2647:case 2135:case 3927:case 2391:return ae(r,"scroll-","scroll-snap-")+r}return r}function rs(r,i){for(var s="",l=0;l-1&&!r.return)switch(r.type){case Xu:r.return=dp(r.value,r.length,s);return;case lp:return rs([kn(r,{value:ae(r.value,"@","@"+we)})],l);case cs:if(r.length)return vg(s=r.props,function(c){switch(tn(c,l=/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":vr(kn(r,{props:[ae(c,/:(read-\w+)/,":"+Co+"$1")]})),vr(kn(r,{props:[c]})),Ou(r,{props:cd(s,l)});break;case"::placeholder":vr(kn(r,{props:[ae(c,/:(plac\w+)/,":"+we+"input-$1")]})),vr(kn(r,{props:[ae(c,/:(plac\w+)/,":"+Co+"$1")]})),vr(kn(r,{props:[ae(c,/:(plac\w+)/,je+"input-$1")]})),vr(kn(r,{props:[c]})),Ou(r,{props:cd(s,l)});break}return""})}}var Og={animationIterationCount:1,aspectRatio:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},mt={},kr=typeof process<"u"&&mt!==void 0&&(mt.REACT_APP_SC_ATTR||mt.SC_ATTR)||"data-styled",pp="active",hp="data-styled-version",hs="6.1.14",Zu=`/*!sc*/ +`,os=typeof window<"u"&&"HTMLElement"in window,Lg=!!(typeof SC_DISABLE_SPEEDY=="boolean"?SC_DISABLE_SPEEDY:typeof process<"u"&&mt!==void 0&&mt.REACT_APP_SC_DISABLE_SPEEDY!==void 0&&mt.REACT_APP_SC_DISABLE_SPEEDY!==""?mt.REACT_APP_SC_DISABLE_SPEEDY!=="false"&&mt.REACT_APP_SC_DISABLE_SPEEDY:typeof process<"u"&&mt!==void 0&&mt.SC_DISABLE_SPEEDY!==void 0&&mt.SC_DISABLE_SPEEDY!==""&&mt.SC_DISABLE_SPEEDY!=="false"&&mt.SC_DISABLE_SPEEDY),ms=Object.freeze([]),jr=Object.freeze({});function Dg(r,i,s){return s===void 0&&(s=jr),r.theme!==s.theme&&r.theme||i||s.theme}var mp=new Set(["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","track","u","ul","use","var","video","wbr","circle","clipPath","defs","ellipse","foreignObject","g","image","line","linearGradient","marker","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","tspan"]),Mg=/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~-]+/g,zg=/(^-|-$)/g;function pd(r){return r.replace(Mg,"-").replace(zg,"")}var Ug=/(a)(d)/gi,Hi=52,hd=function(r){return String.fromCharCode(r+(r>25?39:97))};function Mu(r){var i,s="";for(i=Math.abs(r);i>Hi;i=i/Hi|0)s=hd(i%Hi)+s;return(hd(i%Hi)+s).replace(Ug,"$1-$2")}var Su,gp=5381,wr=function(r,i){for(var s=i.length;s;)r=33*r^i.charCodeAt(--s);return r},yp=function(r){return wr(gp,r)};function Fg(r){return Mu(yp(r)>>>0)}function Bg(r){return r.displayName||r.name||"Component"}function Eu(r){return typeof r=="string"&&!0}var vp=typeof Symbol=="function"&&Symbol.for,wp=vp?Symbol.for("react.memo"):60115,$g=vp?Symbol.for("react.forward_ref"):60112,Hg={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},bg={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},xp={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},Vg=((Su={})[$g]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},Su[wp]=xp,Su);function md(r){return("type"in(i=r)&&i.type.$$typeof)===wp?xp:"$$typeof"in r?Vg[r.$$typeof]:Hg;var i}var Wg=Object.defineProperty,Yg=Object.getOwnPropertyNames,gd=Object.getOwnPropertySymbols,qg=Object.getOwnPropertyDescriptor,Qg=Object.getPrototypeOf,yd=Object.prototype;function Sp(r,i,s){if(typeof i!="string"){if(yd){var l=Qg(i);l&&l!==yd&&Sp(r,l,s)}var c=Yg(i);gd&&(c=c.concat(gd(i)));for(var f=md(r),p=md(i),g=0;g0?" Args: ".concat(i.join(", ")):""))}var Gg=function(){function r(i){this.groupSizes=new Uint32Array(512),this.length=512,this.tag=i}return r.prototype.indexOfGroup=function(i){for(var s=0,l=0;l=this.groupSizes.length){for(var l=this.groupSizes,c=l.length,f=c;i>=f;)if((f<<=1)<0)throw Vn(16,"".concat(i));this.groupSizes=new Uint32Array(f),this.groupSizes.set(l),this.length=f;for(var p=c;p=this.length||this.groupSizes[i]===0)return s;for(var l=this.groupSizes[i],c=this.indexOfGroup(i),f=c+l,p=c;p=0){var l=document.createTextNode(s);return this.element.insertBefore(l,this.nodes[i]||null),this.length++,!0}return!1},r.prototype.deleteRule=function(i){this.element.removeChild(this.nodes[i]),this.length--},r.prototype.getRule=function(i){return i0&&(O+="".concat(F,","))}),x+="".concat(_).concat(C,'{content:"').concat(O,'"}').concat(Zu)},S=0;S0?".".concat(i):R},S=x.slice();S.push(function(R){R.type===cs&&R.value.includes("&")&&(R.props[0]=R.props[0].replace(sy,s).replace(l,v))}),p.prefix&&S.push(Ng),S.push(_g);var A=function(R,I,_,C){I===void 0&&(I=""),_===void 0&&(_=""),C===void 0&&(C="&"),i=C,s=I,l=new RegExp("\\".concat(s,"\\b"),"g");var O=R.replace(ly,""),F=Rg(_||I?"".concat(_," ").concat(I," { ").concat(O," }"):O);p.namespace&&(F=kp(F,p.namespace));var B=[];return rs(F,Tg(S.concat(Ig(function(V){return B.push(V)})))),B};return A.hash=x.length?x.reduce(function(R,I){return I.name||Vn(15),wr(R,I.name)},gp).toString():"",A}var ay=new Cp,Uu=uy(),jp=gt.createContext({shouldForwardProp:void 0,styleSheet:ay,stylis:Uu});jp.Consumer;gt.createContext(void 0);function Sd(){return ie.useContext(jp)}var cy=function(){function r(i,s){var l=this;this.inject=function(c,f){f===void 0&&(f=Uu);var p=l.name+f.hash;c.hasNameForId(l.id,p)||c.insertRules(l.id,p,f(l.rules,p,"@keyframes"))},this.name=i,this.id="sc-keyframes-".concat(i),this.rules=s,ta(this,function(){throw Vn(12,String(l.name))})}return r.prototype.getName=function(i){return i===void 0&&(i=Uu),this.name+i.hash},r}(),fy=function(r){return r>="A"&&r<="Z"};function Ed(r){for(var i="",s=0;s>>0);if(!s.hasNameForId(this.componentId,p)){var g=l(f,".".concat(p),void 0,this.componentId);s.insertRules(this.componentId,p,g)}c=Un(c,p),this.staticRulesId=p}else{for(var x=wr(this.baseHash,l.hash),v="",S=0;S>>0);s.hasNameForId(this.componentId,I)||s.insertRules(this.componentId,I,l(v,".".concat(I),void 0,this.componentId)),c=Un(c,I)}}return c},r}(),ss=gt.createContext(void 0);ss.Consumer;function Cd(r){var i=gt.useContext(ss),s=ie.useMemo(function(){return function(l,c){if(!l)throw Vn(14);if(bn(l)){var f=l(c);return f}if(Array.isArray(l)||typeof l!="object")throw Vn(8);return c?Xe(Xe({},c),l):l}(r.theme,i)},[r.theme,i]);return r.children?gt.createElement(ss.Provider,{value:s},r.children):null}var Cu={};function my(r,i,s){var l=ea(r),c=r,f=!Eu(r),p=i.attrs,g=p===void 0?ms:p,x=i.componentId,v=x===void 0?function(Q,H){var L=typeof Q!="string"?"sc":pd(Q);Cu[L]=(Cu[L]||0)+1;var b="".concat(L,"-").concat(Fg(hs+L+Cu[L]));return H?"".concat(H,"-").concat(b):b}(i.displayName,i.parentComponentId):x,S=i.displayName,A=S===void 0?function(Q){return Eu(Q)?"styled.".concat(Q):"Styled(".concat(Bg(Q),")")}(r):S,R=i.displayName&&i.componentId?"".concat(pd(i.displayName),"-").concat(i.componentId):i.componentId||v,I=l&&c.attrs?c.attrs.concat(g).filter(Boolean):g,_=i.shouldForwardProp;if(l&&c.shouldForwardProp){var C=c.shouldForwardProp;if(i.shouldForwardProp){var O=i.shouldForwardProp;_=function(Q,H){return C(Q,H)&&O(Q,H)}}else _=C}var F=new hy(s,R,l?c.componentStyle:void 0);function B(Q,H){return function(L,b,re){var ye=L.attrs,Ne=L.componentStyle,at=L.defaultProps,wt=L.foldedComponentIds,Ze=L.styledComponentId,ct=L.target,xt=gt.useContext(ss),We=Sd(),Se=L.shouldForwardProp||We.shouldForwardProp,W=Dg(b,xt,at)||jr,Z=function(de,ce,ve){for(var pe,me=Xe(Xe({},ce),{className:void 0,theme:ve}),He=0;Hei=>{const s=yy.call(i);return r[s]||(r[s]=s.slice(8,-1).toLowerCase())})(Object.create(null)),Ut=r=>(r=r.toLowerCase(),i=>gs(i)===r),ys=r=>i=>typeof i===r,{isArray:Rr}=Array,Po=ys("undefined");function vy(r){return r!==null&&!Po(r)&&r.constructor!==null&&!Po(r.constructor)&&yt(r.constructor.isBuffer)&&r.constructor.isBuffer(r)}const Tp=Ut("ArrayBuffer");function wy(r){let i;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?i=ArrayBuffer.isView(r):i=r&&r.buffer&&Tp(r.buffer),i}const xy=ys("string"),yt=ys("function"),Ip=ys("number"),vs=r=>r!==null&&typeof r=="object",Sy=r=>r===!0||r===!1,Zi=r=>{if(gs(r)!=="object")return!1;const i=na(r);return(i===null||i===Object.prototype||Object.getPrototypeOf(i)===null)&&!(Symbol.toStringTag in r)&&!(Symbol.iterator in r)},Ey=Ut("Date"),Cy=Ut("File"),ky=Ut("Blob"),jy=Ut("FileList"),Ay=r=>vs(r)&&yt(r.pipe),Ry=r=>{let i;return r&&(typeof FormData=="function"&&r instanceof FormData||yt(r.append)&&((i=gs(r))==="formdata"||i==="object"&&yt(r.toString)&&r.toString()==="[object FormData]"))},Py=Ut("URLSearchParams"),[_y,Ty,Iy,Ny]=["ReadableStream","Request","Response","Headers"].map(Ut),Oy=r=>r.trim?r.trim():r.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function _o(r,i,{allOwnKeys:s=!1}={}){if(r===null||typeof r>"u")return;let l,c;if(typeof r!="object"&&(r=[r]),Rr(r))for(l=0,c=r.length;l0;)if(c=s[l],i===c.toLowerCase())return c;return null}const Fn=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,Op=r=>!Po(r)&&r!==Fn;function Bu(){const{caseless:r}=Op(this)&&this||{},i={},s=(l,c)=>{const f=r&&Np(i,c)||c;Zi(i[f])&&Zi(l)?i[f]=Bu(i[f],l):Zi(l)?i[f]=Bu({},l):Rr(l)?i[f]=l.slice():i[f]=l};for(let l=0,c=arguments.length;l(_o(i,(c,f)=>{s&&yt(c)?r[f]=_p(c,s):r[f]=c},{allOwnKeys:l}),r),Dy=r=>(r.charCodeAt(0)===65279&&(r=r.slice(1)),r),My=(r,i,s,l)=>{r.prototype=Object.create(i.prototype,l),r.prototype.constructor=r,Object.defineProperty(r,"super",{value:i.prototype}),s&&Object.assign(r.prototype,s)},zy=(r,i,s,l)=>{let c,f,p;const g={};if(i=i||{},r==null)return i;do{for(c=Object.getOwnPropertyNames(r),f=c.length;f-- >0;)p=c[f],(!l||l(p,r,i))&&!g[p]&&(i[p]=r[p],g[p]=!0);r=s!==!1&&na(r)}while(r&&(!s||s(r,i))&&r!==Object.prototype);return i},Uy=(r,i,s)=>{r=String(r),(s===void 0||s>r.length)&&(s=r.length),s-=i.length;const l=r.indexOf(i,s);return l!==-1&&l===s},Fy=r=>{if(!r)return null;if(Rr(r))return r;let i=r.length;if(!Ip(i))return null;const s=new Array(i);for(;i-- >0;)s[i]=r[i];return s},By=(r=>i=>r&&i instanceof r)(typeof Uint8Array<"u"&&na(Uint8Array)),$y=(r,i)=>{const l=(r&&r[Symbol.iterator]).call(r);let c;for(;(c=l.next())&&!c.done;){const f=c.value;i.call(r,f[0],f[1])}},Hy=(r,i)=>{let s;const l=[];for(;(s=r.exec(i))!==null;)l.push(s);return l},by=Ut("HTMLFormElement"),Vy=r=>r.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(s,l,c){return l.toUpperCase()+c}),Ad=(({hasOwnProperty:r})=>(i,s)=>r.call(i,s))(Object.prototype),Wy=Ut("RegExp"),Lp=(r,i)=>{const s=Object.getOwnPropertyDescriptors(r),l={};_o(s,(c,f)=>{let p;(p=i(c,f,r))!==!1&&(l[f]=p||c)}),Object.defineProperties(r,l)},Yy=r=>{Lp(r,(i,s)=>{if(yt(r)&&["arguments","caller","callee"].indexOf(s)!==-1)return!1;const l=r[s];if(yt(l)){if(i.enumerable=!1,"writable"in i){i.writable=!1;return}i.set||(i.set=()=>{throw Error("Can not rewrite read-only method '"+s+"'")})}})},qy=(r,i)=>{const s={},l=c=>{c.forEach(f=>{s[f]=!0})};return Rr(r)?l(r):l(String(r).split(i)),s},Qy=()=>{},Gy=(r,i)=>r!=null&&Number.isFinite(r=+r)?r:i,ku="abcdefghijklmnopqrstuvwxyz",Rd="0123456789",Dp={DIGIT:Rd,ALPHA:ku,ALPHA_DIGIT:ku+ku.toUpperCase()+Rd},Ky=(r=16,i=Dp.ALPHA_DIGIT)=>{let s="";const{length:l}=i;for(;r--;)s+=i[Math.random()*l|0];return s};function Xy(r){return!!(r&&yt(r.append)&&r[Symbol.toStringTag]==="FormData"&&r[Symbol.iterator])}const Jy=r=>{const i=new Array(10),s=(l,c)=>{if(vs(l)){if(i.indexOf(l)>=0)return;if(!("toJSON"in l)){i[c]=l;const f=Rr(l)?[]:{};return _o(l,(p,g)=>{const x=s(p,c+1);!Po(x)&&(f[g]=x)}),i[c]=void 0,f}}return l};return s(r,0)},Zy=Ut("AsyncFunction"),ev=r=>r&&(vs(r)||yt(r))&&yt(r.then)&&yt(r.catch),Mp=((r,i)=>r?setImmediate:i?((s,l)=>(Fn.addEventListener("message",({source:c,data:f})=>{c===Fn&&f===s&&l.length&&l.shift()()},!1),c=>{l.push(c),Fn.postMessage(s,"*")}))(`axios@${Math.random()}`,[]):s=>setTimeout(s))(typeof setImmediate=="function",yt(Fn.postMessage)),tv=typeof queueMicrotask<"u"?queueMicrotask.bind(Fn):typeof process<"u"&&process.nextTick||Mp,N={isArray:Rr,isArrayBuffer:Tp,isBuffer:vy,isFormData:Ry,isArrayBufferView:wy,isString:xy,isNumber:Ip,isBoolean:Sy,isObject:vs,isPlainObject:Zi,isReadableStream:_y,isRequest:Ty,isResponse:Iy,isHeaders:Ny,isUndefined:Po,isDate:Ey,isFile:Cy,isBlob:ky,isRegExp:Wy,isFunction:yt,isStream:Ay,isURLSearchParams:Py,isTypedArray:By,isFileList:jy,forEach:_o,merge:Bu,extend:Ly,trim:Oy,stripBOM:Dy,inherits:My,toFlatObject:zy,kindOf:gs,kindOfTest:Ut,endsWith:Uy,toArray:Fy,forEachEntry:$y,matchAll:Hy,isHTMLForm:by,hasOwnProperty:Ad,hasOwnProp:Ad,reduceDescriptors:Lp,freezeMethods:Yy,toObjectSet:qy,toCamelCase:Vy,noop:Qy,toFiniteNumber:Gy,findKey:Np,global:Fn,isContextDefined:Op,ALPHABET:Dp,generateString:Ky,isSpecCompliantForm:Xy,toJSONObject:Jy,isAsyncFn:Zy,isThenable:ev,setImmediate:Mp,asap:tv};function le(r,i,s,l,c){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=r,this.name="AxiosError",i&&(this.code=i),s&&(this.config=s),l&&(this.request=l),c&&(this.response=c,this.status=c.status?c.status:null)}N.inherits(le,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:N.toJSONObject(this.config),code:this.code,status:this.status}}});const zp=le.prototype,Up={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(r=>{Up[r]={value:r}});Object.defineProperties(le,Up);Object.defineProperty(zp,"isAxiosError",{value:!0});le.from=(r,i,s,l,c,f)=>{const p=Object.create(zp);return N.toFlatObject(r,p,function(x){return x!==Error.prototype},g=>g!=="isAxiosError"),le.call(p,r.message,i,s,l,c),p.cause=r,p.name=r.name,f&&Object.assign(p,f),p};const nv=null;function $u(r){return N.isPlainObject(r)||N.isArray(r)}function Fp(r){return N.endsWith(r,"[]")?r.slice(0,-2):r}function Pd(r,i,s){return r?r.concat(i).map(function(c,f){return c=Fp(c),!s&&f?"["+c+"]":c}).join(s?".":""):i}function rv(r){return N.isArray(r)&&!r.some($u)}const ov=N.toFlatObject(N,{},null,function(i){return/^is[A-Z]/.test(i)});function ws(r,i,s){if(!N.isObject(r))throw new TypeError("target must be an object");i=i||new FormData,s=N.toFlatObject(s,{metaTokens:!0,dots:!1,indexes:!1},!1,function(C,O){return!N.isUndefined(O[C])});const l=s.metaTokens,c=s.visitor||S,f=s.dots,p=s.indexes,x=(s.Blob||typeof Blob<"u"&&Blob)&&N.isSpecCompliantForm(i);if(!N.isFunction(c))throw new TypeError("visitor must be a function");function v(_){if(_===null)return"";if(N.isDate(_))return _.toISOString();if(!x&&N.isBlob(_))throw new le("Blob is not supported. Use a Buffer instead.");return N.isArrayBuffer(_)||N.isTypedArray(_)?x&&typeof Blob=="function"?new Blob([_]):Buffer.from(_):_}function S(_,C,O){let F=_;if(_&&!O&&typeof _=="object"){if(N.endsWith(C,"{}"))C=l?C:C.slice(0,-2),_=JSON.stringify(_);else if(N.isArray(_)&&rv(_)||(N.isFileList(_)||N.endsWith(C,"[]"))&&(F=N.toArray(_)))return C=Fp(C),F.forEach(function(V,Q){!(N.isUndefined(V)||V===null)&&i.append(p===!0?Pd([C],Q,f):p===null?C:C+"[]",v(V))}),!1}return $u(_)?!0:(i.append(Pd(O,C,f),v(_)),!1)}const A=[],R=Object.assign(ov,{defaultVisitor:S,convertValue:v,isVisitable:$u});function I(_,C){if(!N.isUndefined(_)){if(A.indexOf(_)!==-1)throw Error("Circular reference detected in "+C.join("."));A.push(_),N.forEach(_,function(F,B){(!(N.isUndefined(F)||F===null)&&c.call(i,F,N.isString(B)?B.trim():B,C,R))===!0&&I(F,C?C.concat(B):[B])}),A.pop()}}if(!N.isObject(r))throw new TypeError("data must be an object");return I(r),i}function _d(r){const i={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(r).replace(/[!'()~]|%20|%00/g,function(l){return i[l]})}function ra(r,i){this._pairs=[],r&&ws(r,this,i)}const Bp=ra.prototype;Bp.append=function(i,s){this._pairs.push([i,s])};Bp.toString=function(i){const s=i?function(l){return i.call(this,l,_d)}:_d;return this._pairs.map(function(c){return s(c[0])+"="+s(c[1])},"").join("&")};function iv(r){return encodeURIComponent(r).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function $p(r,i,s){if(!i)return r;const l=s&&s.encode||iv;N.isFunction(s)&&(s={serialize:s});const c=s&&s.serialize;let f;if(c?f=c(i,s):f=N.isURLSearchParams(i)?i.toString():new ra(i,s).toString(l),f){const p=r.indexOf("#");p!==-1&&(r=r.slice(0,p)),r+=(r.indexOf("?")===-1?"?":"&")+f}return r}class Td{constructor(){this.handlers=[]}use(i,s,l){return this.handlers.push({fulfilled:i,rejected:s,synchronous:l?l.synchronous:!1,runWhen:l?l.runWhen:null}),this.handlers.length-1}eject(i){this.handlers[i]&&(this.handlers[i]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(i){N.forEach(this.handlers,function(l){l!==null&&i(l)})}}const Hp={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},sv=typeof URLSearchParams<"u"?URLSearchParams:ra,lv=typeof FormData<"u"?FormData:null,uv=typeof Blob<"u"?Blob:null,av={isBrowser:!0,classes:{URLSearchParams:sv,FormData:lv,Blob:uv},protocols:["http","https","file","blob","url","data"]},oa=typeof window<"u"&&typeof document<"u",Hu=typeof navigator=="object"&&navigator||void 0,cv=oa&&(!Hu||["ReactNative","NativeScript","NS"].indexOf(Hu.product)<0),fv=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",dv=oa&&window.location.href||"http://localhost",pv=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:oa,hasStandardBrowserEnv:cv,hasStandardBrowserWebWorkerEnv:fv,navigator:Hu,origin:dv},Symbol.toStringTag,{value:"Module"})),Ke={...pv,...av};function hv(r,i){return ws(r,new Ke.classes.URLSearchParams,Object.assign({visitor:function(s,l,c,f){return Ke.isNode&&N.isBuffer(s)?(this.append(l,s.toString("base64")),!1):f.defaultVisitor.apply(this,arguments)}},i))}function mv(r){return N.matchAll(/\w+|\[(\w*)]/g,r).map(i=>i[0]==="[]"?"":i[1]||i[0])}function gv(r){const i={},s=Object.keys(r);let l;const c=s.length;let f;for(l=0;l=s.length;return p=!p&&N.isArray(c)?c.length:p,x?(N.hasOwnProp(c,p)?c[p]=[c[p],l]:c[p]=l,!g):((!c[p]||!N.isObject(c[p]))&&(c[p]=[]),i(s,l,c[p],f)&&N.isArray(c[p])&&(c[p]=gv(c[p])),!g)}if(N.isFormData(r)&&N.isFunction(r.entries)){const s={};return N.forEachEntry(r,(l,c)=>{i(mv(l),c,s,0)}),s}return null}function yv(r,i,s){if(N.isString(r))try{return(i||JSON.parse)(r),N.trim(r)}catch(l){if(l.name!=="SyntaxError")throw l}return(0,JSON.stringify)(r)}const To={transitional:Hp,adapter:["xhr","http","fetch"],transformRequest:[function(i,s){const l=s.getContentType()||"",c=l.indexOf("application/json")>-1,f=N.isObject(i);if(f&&N.isHTMLForm(i)&&(i=new FormData(i)),N.isFormData(i))return c?JSON.stringify(bp(i)):i;if(N.isArrayBuffer(i)||N.isBuffer(i)||N.isStream(i)||N.isFile(i)||N.isBlob(i)||N.isReadableStream(i))return i;if(N.isArrayBufferView(i))return i.buffer;if(N.isURLSearchParams(i))return s.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),i.toString();let g;if(f){if(l.indexOf("application/x-www-form-urlencoded")>-1)return hv(i,this.formSerializer).toString();if((g=N.isFileList(i))||l.indexOf("multipart/form-data")>-1){const x=this.env&&this.env.FormData;return ws(g?{"files[]":i}:i,x&&new x,this.formSerializer)}}return f||c?(s.setContentType("application/json",!1),yv(i)):i}],transformResponse:[function(i){const s=this.transitional||To.transitional,l=s&&s.forcedJSONParsing,c=this.responseType==="json";if(N.isResponse(i)||N.isReadableStream(i))return i;if(i&&N.isString(i)&&(l&&!this.responseType||c)){const p=!(s&&s.silentJSONParsing)&&c;try{return JSON.parse(i)}catch(g){if(p)throw g.name==="SyntaxError"?le.from(g,le.ERR_BAD_RESPONSE,this,null,this.response):g}}return i}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:Ke.classes.FormData,Blob:Ke.classes.Blob},validateStatus:function(i){return i>=200&&i<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};N.forEach(["delete","get","head","post","put","patch"],r=>{To.headers[r]={}});const vv=N.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),wv=r=>{const i={};let s,l,c;return r&&r.split(` +`).forEach(function(p){c=p.indexOf(":"),s=p.substring(0,c).trim().toLowerCase(),l=p.substring(c+1).trim(),!(!s||i[s]&&vv[s])&&(s==="set-cookie"?i[s]?i[s].push(l):i[s]=[l]:i[s]=i[s]?i[s]+", "+l:l)}),i},Id=Symbol("internals");function vo(r){return r&&String(r).trim().toLowerCase()}function es(r){return r===!1||r==null?r:N.isArray(r)?r.map(es):String(r)}function xv(r){const i=Object.create(null),s=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let l;for(;l=s.exec(r);)i[l[1]]=l[2];return i}const Sv=r=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(r.trim());function ju(r,i,s,l,c){if(N.isFunction(l))return l.call(this,i,s);if(c&&(i=s),!!N.isString(i)){if(N.isString(l))return i.indexOf(l)!==-1;if(N.isRegExp(l))return l.test(i)}}function Ev(r){return r.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(i,s,l)=>s.toUpperCase()+l)}function Cv(r,i){const s=N.toCamelCase(" "+i);["get","set","has"].forEach(l=>{Object.defineProperty(r,l+s,{value:function(c,f,p){return this[l].call(this,i,c,f,p)},configurable:!0})})}class ut{constructor(i){i&&this.set(i)}set(i,s,l){const c=this;function f(g,x,v){const S=vo(x);if(!S)throw new Error("header name must be a non-empty string");const A=N.findKey(c,S);(!A||c[A]===void 0||v===!0||v===void 0&&c[A]!==!1)&&(c[A||x]=es(g))}const p=(g,x)=>N.forEach(g,(v,S)=>f(v,S,x));if(N.isPlainObject(i)||i instanceof this.constructor)p(i,s);else if(N.isString(i)&&(i=i.trim())&&!Sv(i))p(wv(i),s);else if(N.isHeaders(i))for(const[g,x]of i.entries())f(x,g,l);else i!=null&&f(s,i,l);return this}get(i,s){if(i=vo(i),i){const l=N.findKey(this,i);if(l){const c=this[l];if(!s)return c;if(s===!0)return xv(c);if(N.isFunction(s))return s.call(this,c,l);if(N.isRegExp(s))return s.exec(c);throw new TypeError("parser must be boolean|regexp|function")}}}has(i,s){if(i=vo(i),i){const l=N.findKey(this,i);return!!(l&&this[l]!==void 0&&(!s||ju(this,this[l],l,s)))}return!1}delete(i,s){const l=this;let c=!1;function f(p){if(p=vo(p),p){const g=N.findKey(l,p);g&&(!s||ju(l,l[g],g,s))&&(delete l[g],c=!0)}}return N.isArray(i)?i.forEach(f):f(i),c}clear(i){const s=Object.keys(this);let l=s.length,c=!1;for(;l--;){const f=s[l];(!i||ju(this,this[f],f,i,!0))&&(delete this[f],c=!0)}return c}normalize(i){const s=this,l={};return N.forEach(this,(c,f)=>{const p=N.findKey(l,f);if(p){s[p]=es(c),delete s[f];return}const g=i?Ev(f):String(f).trim();g!==f&&delete s[f],s[g]=es(c),l[g]=!0}),this}concat(...i){return this.constructor.concat(this,...i)}toJSON(i){const s=Object.create(null);return N.forEach(this,(l,c)=>{l!=null&&l!==!1&&(s[c]=i&&N.isArray(l)?l.join(", "):l)}),s}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([i,s])=>i+": "+s).join(` +`)}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(i){return i instanceof this?i:new this(i)}static concat(i,...s){const l=new this(i);return s.forEach(c=>l.set(c)),l}static accessor(i){const l=(this[Id]=this[Id]={accessors:{}}).accessors,c=this.prototype;function f(p){const g=vo(p);l[g]||(Cv(c,p),l[g]=!0)}return N.isArray(i)?i.forEach(f):f(i),this}}ut.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);N.reduceDescriptors(ut.prototype,({value:r},i)=>{let s=i[0].toUpperCase()+i.slice(1);return{get:()=>r,set(l){this[s]=l}}});N.freezeMethods(ut);function Au(r,i){const s=this||To,l=i||s,c=ut.from(l.headers);let f=l.data;return N.forEach(r,function(g){f=g.call(s,f,c.normalize(),i?i.status:void 0)}),c.normalize(),f}function Vp(r){return!!(r&&r.__CANCEL__)}function Pr(r,i,s){le.call(this,r??"canceled",le.ERR_CANCELED,i,s),this.name="CanceledError"}N.inherits(Pr,le,{__CANCEL__:!0});function Wp(r,i,s){const l=s.config.validateStatus;!s.status||!l||l(s.status)?r(s):i(new le("Request failed with status code "+s.status,[le.ERR_BAD_REQUEST,le.ERR_BAD_RESPONSE][Math.floor(s.status/100)-4],s.config,s.request,s))}function kv(r){const i=/^([-+\w]{1,25})(:?\/\/|:)/.exec(r);return i&&i[1]||""}function jv(r,i){r=r||10;const s=new Array(r),l=new Array(r);let c=0,f=0,p;return i=i!==void 0?i:1e3,function(x){const v=Date.now(),S=l[f];p||(p=v),s[c]=x,l[c]=v;let A=f,R=0;for(;A!==c;)R+=s[A++],A=A%r;if(c=(c+1)%r,c===f&&(f=(f+1)%r),v-p{s=S,c=null,f&&(clearTimeout(f),f=null),r.apply(null,v)};return[(...v)=>{const S=Date.now(),A=S-s;A>=l?p(v,S):(c=v,f||(f=setTimeout(()=>{f=null,p(c)},l-A)))},()=>c&&p(c)]}const ls=(r,i,s=3)=>{let l=0;const c=jv(50,250);return Av(f=>{const p=f.loaded,g=f.lengthComputable?f.total:void 0,x=p-l,v=c(x),S=p<=g;l=p;const A={loaded:p,total:g,progress:g?p/g:void 0,bytes:x,rate:v||void 0,estimated:v&&g&&S?(g-p)/v:void 0,event:f,lengthComputable:g!=null,[i?"download":"upload"]:!0};r(A)},s)},Nd=(r,i)=>{const s=r!=null;return[l=>i[0]({lengthComputable:s,total:r,loaded:l}),i[1]]},Od=r=>(...i)=>N.asap(()=>r(...i)),Rv=Ke.hasStandardBrowserEnv?((r,i)=>s=>(s=new URL(s,Ke.origin),r.protocol===s.protocol&&r.host===s.host&&(i||r.port===s.port)))(new URL(Ke.origin),Ke.navigator&&/(msie|trident)/i.test(Ke.navigator.userAgent)):()=>!0,Pv=Ke.hasStandardBrowserEnv?{write(r,i,s,l,c,f){const p=[r+"="+encodeURIComponent(i)];N.isNumber(s)&&p.push("expires="+new Date(s).toGMTString()),N.isString(l)&&p.push("path="+l),N.isString(c)&&p.push("domain="+c),f===!0&&p.push("secure"),document.cookie=p.join("; ")},read(r){const i=document.cookie.match(new RegExp("(^|;\\s*)("+r+")=([^;]*)"));return i?decodeURIComponent(i[3]):null},remove(r){this.write(r,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function _v(r){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(r)}function Tv(r,i){return i?r.replace(/\/?\/$/,"")+"/"+i.replace(/^\/+/,""):r}function Yp(r,i){return r&&!_v(i)?Tv(r,i):i}const Ld=r=>r instanceof ut?{...r}:r;function Wn(r,i){i=i||{};const s={};function l(v,S,A,R){return N.isPlainObject(v)&&N.isPlainObject(S)?N.merge.call({caseless:R},v,S):N.isPlainObject(S)?N.merge({},S):N.isArray(S)?S.slice():S}function c(v,S,A,R){if(N.isUndefined(S)){if(!N.isUndefined(v))return l(void 0,v,A,R)}else return l(v,S,A,R)}function f(v,S){if(!N.isUndefined(S))return l(void 0,S)}function p(v,S){if(N.isUndefined(S)){if(!N.isUndefined(v))return l(void 0,v)}else return l(void 0,S)}function g(v,S,A){if(A in i)return l(v,S);if(A in r)return l(void 0,v)}const x={url:f,method:f,data:f,baseURL:p,transformRequest:p,transformResponse:p,paramsSerializer:p,timeout:p,timeoutMessage:p,withCredentials:p,withXSRFToken:p,adapter:p,responseType:p,xsrfCookieName:p,xsrfHeaderName:p,onUploadProgress:p,onDownloadProgress:p,decompress:p,maxContentLength:p,maxBodyLength:p,beforeRedirect:p,transport:p,httpAgent:p,httpsAgent:p,cancelToken:p,socketPath:p,responseEncoding:p,validateStatus:g,headers:(v,S,A)=>c(Ld(v),Ld(S),A,!0)};return N.forEach(Object.keys(Object.assign({},r,i)),function(S){const A=x[S]||c,R=A(r[S],i[S],S);N.isUndefined(R)&&A!==g||(s[S]=R)}),s}const qp=r=>{const i=Wn({},r);let{data:s,withXSRFToken:l,xsrfHeaderName:c,xsrfCookieName:f,headers:p,auth:g}=i;i.headers=p=ut.from(p),i.url=$p(Yp(i.baseURL,i.url),r.params,r.paramsSerializer),g&&p.set("Authorization","Basic "+btoa((g.username||"")+":"+(g.password?unescape(encodeURIComponent(g.password)):"")));let x;if(N.isFormData(s)){if(Ke.hasStandardBrowserEnv||Ke.hasStandardBrowserWebWorkerEnv)p.setContentType(void 0);else if((x=p.getContentType())!==!1){const[v,...S]=x?x.split(";").map(A=>A.trim()).filter(Boolean):[];p.setContentType([v||"multipart/form-data",...S].join("; "))}}if(Ke.hasStandardBrowserEnv&&(l&&N.isFunction(l)&&(l=l(i)),l||l!==!1&&Rv(i.url))){const v=c&&f&&Pv.read(f);v&&p.set(c,v)}return i},Iv=typeof XMLHttpRequest<"u",Nv=Iv&&function(r){return new Promise(function(s,l){const c=qp(r);let f=c.data;const p=ut.from(c.headers).normalize();let{responseType:g,onUploadProgress:x,onDownloadProgress:v}=c,S,A,R,I,_;function C(){I&&I(),_&&_(),c.cancelToken&&c.cancelToken.unsubscribe(S),c.signal&&c.signal.removeEventListener("abort",S)}let O=new XMLHttpRequest;O.open(c.method.toUpperCase(),c.url,!0),O.timeout=c.timeout;function F(){if(!O)return;const V=ut.from("getAllResponseHeaders"in O&&O.getAllResponseHeaders()),H={data:!g||g==="text"||g==="json"?O.responseText:O.response,status:O.status,statusText:O.statusText,headers:V,config:r,request:O};Wp(function(b){s(b),C()},function(b){l(b),C()},H),O=null}"onloadend"in O?O.onloadend=F:O.onreadystatechange=function(){!O||O.readyState!==4||O.status===0&&!(O.responseURL&&O.responseURL.indexOf("file:")===0)||setTimeout(F)},O.onabort=function(){O&&(l(new le("Request aborted",le.ECONNABORTED,r,O)),O=null)},O.onerror=function(){l(new le("Network Error",le.ERR_NETWORK,r,O)),O=null},O.ontimeout=function(){let Q=c.timeout?"timeout of "+c.timeout+"ms exceeded":"timeout exceeded";const H=c.transitional||Hp;c.timeoutErrorMessage&&(Q=c.timeoutErrorMessage),l(new le(Q,H.clarifyTimeoutError?le.ETIMEDOUT:le.ECONNABORTED,r,O)),O=null},f===void 0&&p.setContentType(null),"setRequestHeader"in O&&N.forEach(p.toJSON(),function(Q,H){O.setRequestHeader(H,Q)}),N.isUndefined(c.withCredentials)||(O.withCredentials=!!c.withCredentials),g&&g!=="json"&&(O.responseType=c.responseType),v&&([R,_]=ls(v,!0),O.addEventListener("progress",R)),x&&O.upload&&([A,I]=ls(x),O.upload.addEventListener("progress",A),O.upload.addEventListener("loadend",I)),(c.cancelToken||c.signal)&&(S=V=>{O&&(l(!V||V.type?new Pr(null,r,O):V),O.abort(),O=null)},c.cancelToken&&c.cancelToken.subscribe(S),c.signal&&(c.signal.aborted?S():c.signal.addEventListener("abort",S)));const B=kv(c.url);if(B&&Ke.protocols.indexOf(B)===-1){l(new le("Unsupported protocol "+B+":",le.ERR_BAD_REQUEST,r));return}O.send(f||null)})},Ov=(r,i)=>{const{length:s}=r=r?r.filter(Boolean):[];if(i||s){let l=new AbortController,c;const f=function(v){if(!c){c=!0,g();const S=v instanceof Error?v:this.reason;l.abort(S instanceof le?S:new Pr(S instanceof Error?S.message:S))}};let p=i&&setTimeout(()=>{p=null,f(new le(`timeout ${i} of ms exceeded`,le.ETIMEDOUT))},i);const g=()=>{r&&(p&&clearTimeout(p),p=null,r.forEach(v=>{v.unsubscribe?v.unsubscribe(f):v.removeEventListener("abort",f)}),r=null)};r.forEach(v=>v.addEventListener("abort",f));const{signal:x}=l;return x.unsubscribe=()=>N.asap(g),x}},Lv=function*(r,i){let s=r.byteLength;if(s{const c=Dv(r,i);let f=0,p,g=x=>{p||(p=!0,l&&l(x))};return new ReadableStream({async pull(x){try{const{done:v,value:S}=await c.next();if(v){g(),x.close();return}let A=S.byteLength;if(s){let R=f+=A;s(R)}x.enqueue(new Uint8Array(S))}catch(v){throw g(v),v}},cancel(x){return g(x),c.return()}},{highWaterMark:2})},xs=typeof fetch=="function"&&typeof Request=="function"&&typeof Response=="function",Qp=xs&&typeof ReadableStream=="function",zv=xs&&(typeof TextEncoder=="function"?(r=>i=>r.encode(i))(new TextEncoder):async r=>new Uint8Array(await new Response(r).arrayBuffer())),Gp=(r,...i)=>{try{return!!r(...i)}catch{return!1}},Uv=Qp&&Gp(()=>{let r=!1;const i=new Request(Ke.origin,{body:new ReadableStream,method:"POST",get duplex(){return r=!0,"half"}}).headers.has("Content-Type");return r&&!i}),Md=64*1024,bu=Qp&&Gp(()=>N.isReadableStream(new Response("").body)),us={stream:bu&&(r=>r.body)};xs&&(r=>{["text","arrayBuffer","blob","formData","stream"].forEach(i=>{!us[i]&&(us[i]=N.isFunction(r[i])?s=>s[i]():(s,l)=>{throw new le(`Response type '${i}' is not supported`,le.ERR_NOT_SUPPORT,l)})})})(new Response);const Fv=async r=>{if(r==null)return 0;if(N.isBlob(r))return r.size;if(N.isSpecCompliantForm(r))return(await new Request(Ke.origin,{method:"POST",body:r}).arrayBuffer()).byteLength;if(N.isArrayBufferView(r)||N.isArrayBuffer(r))return r.byteLength;if(N.isURLSearchParams(r)&&(r=r+""),N.isString(r))return(await zv(r)).byteLength},Bv=async(r,i)=>{const s=N.toFiniteNumber(r.getContentLength());return s??Fv(i)},$v=xs&&(async r=>{let{url:i,method:s,data:l,signal:c,cancelToken:f,timeout:p,onDownloadProgress:g,onUploadProgress:x,responseType:v,headers:S,withCredentials:A="same-origin",fetchOptions:R}=qp(r);v=v?(v+"").toLowerCase():"text";let I=Ov([c,f&&f.toAbortSignal()],p),_;const C=I&&I.unsubscribe&&(()=>{I.unsubscribe()});let O;try{if(x&&Uv&&s!=="get"&&s!=="head"&&(O=await Bv(S,l))!==0){let H=new Request(i,{method:"POST",body:l,duplex:"half"}),L;if(N.isFormData(l)&&(L=H.headers.get("content-type"))&&S.setContentType(L),H.body){const[b,re]=Nd(O,ls(Od(x)));l=Dd(H.body,Md,b,re)}}N.isString(A)||(A=A?"include":"omit");const F="credentials"in Request.prototype;_=new Request(i,{...R,signal:I,method:s.toUpperCase(),headers:S.normalize().toJSON(),body:l,duplex:"half",credentials:F?A:void 0});let B=await fetch(_);const V=bu&&(v==="stream"||v==="response");if(bu&&(g||V&&C)){const H={};["status","statusText","headers"].forEach(ye=>{H[ye]=B[ye]});const L=N.toFiniteNumber(B.headers.get("content-length")),[b,re]=g&&Nd(L,ls(Od(g),!0))||[];B=new Response(Dd(B.body,Md,b,()=>{re&&re(),C&&C()}),H)}v=v||"text";let Q=await us[N.findKey(us,v)||"text"](B,r);return!V&&C&&C(),await new Promise((H,L)=>{Wp(H,L,{data:Q,headers:ut.from(B.headers),status:B.status,statusText:B.statusText,config:r,request:_})})}catch(F){throw C&&C(),F&&F.name==="TypeError"&&/fetch/i.test(F.message)?Object.assign(new le("Network Error",le.ERR_NETWORK,r,_),{cause:F.cause||F}):le.from(F,F&&F.code,r,_)}}),Vu={http:nv,xhr:Nv,fetch:$v};N.forEach(Vu,(r,i)=>{if(r){try{Object.defineProperty(r,"name",{value:i})}catch{}Object.defineProperty(r,"adapterName",{value:i})}});const zd=r=>`- ${r}`,Hv=r=>N.isFunction(r)||r===null||r===!1,Kp={getAdapter:r=>{r=N.isArray(r)?r:[r];const{length:i}=r;let s,l;const c={};for(let f=0;f`adapter ${g} `+(x===!1?"is not supported by the environment":"is not available in the build"));let p=i?f.length>1?`since : +`+f.map(zd).join(` +`):" "+zd(f[0]):"as no adapter specified";throw new le("There is no suitable adapter to dispatch the request "+p,"ERR_NOT_SUPPORT")}return l},adapters:Vu};function Ru(r){if(r.cancelToken&&r.cancelToken.throwIfRequested(),r.signal&&r.signal.aborted)throw new Pr(null,r)}function Ud(r){return Ru(r),r.headers=ut.from(r.headers),r.data=Au.call(r,r.transformRequest),["post","put","patch"].indexOf(r.method)!==-1&&r.headers.setContentType("application/x-www-form-urlencoded",!1),Kp.getAdapter(r.adapter||To.adapter)(r).then(function(l){return Ru(r),l.data=Au.call(r,r.transformResponse,l),l.headers=ut.from(l.headers),l},function(l){return Vp(l)||(Ru(r),l&&l.response&&(l.response.data=Au.call(r,r.transformResponse,l.response),l.response.headers=ut.from(l.response.headers))),Promise.reject(l)})}const Xp="1.7.9",Ss={};["object","boolean","number","function","string","symbol"].forEach((r,i)=>{Ss[r]=function(l){return typeof l===r||"a"+(i<1?"n ":" ")+r}});const Fd={};Ss.transitional=function(i,s,l){function c(f,p){return"[Axios v"+Xp+"] Transitional option '"+f+"'"+p+(l?". "+l:"")}return(f,p,g)=>{if(i===!1)throw new le(c(p," has been removed"+(s?" in "+s:"")),le.ERR_DEPRECATED);return s&&!Fd[p]&&(Fd[p]=!0,console.warn(c(p," has been deprecated since v"+s+" and will be removed in the near future"))),i?i(f,p,g):!0}};Ss.spelling=function(i){return(s,l)=>(console.warn(`${l} is likely a misspelling of ${i}`),!0)};function bv(r,i,s){if(typeof r!="object")throw new le("options must be an object",le.ERR_BAD_OPTION_VALUE);const l=Object.keys(r);let c=l.length;for(;c-- >0;){const f=l[c],p=i[f];if(p){const g=r[f],x=g===void 0||p(g,f,r);if(x!==!0)throw new le("option "+f+" must be "+x,le.ERR_BAD_OPTION_VALUE);continue}if(s!==!0)throw new le("Unknown option "+f,le.ERR_BAD_OPTION)}}const ts={assertOptions:bv,validators:Ss},Vt=ts.validators;class Hn{constructor(i){this.defaults=i,this.interceptors={request:new Td,response:new Td}}async request(i,s){try{return await this._request(i,s)}catch(l){if(l instanceof Error){let c={};Error.captureStackTrace?Error.captureStackTrace(c):c=new Error;const f=c.stack?c.stack.replace(/^.+\n/,""):"";try{l.stack?f&&!String(l.stack).endsWith(f.replace(/^.+\n.+\n/,""))&&(l.stack+=` +`+f):l.stack=f}catch{}}throw l}}_request(i,s){typeof i=="string"?(s=s||{},s.url=i):s=i||{},s=Wn(this.defaults,s);const{transitional:l,paramsSerializer:c,headers:f}=s;l!==void 0&&ts.assertOptions(l,{silentJSONParsing:Vt.transitional(Vt.boolean),forcedJSONParsing:Vt.transitional(Vt.boolean),clarifyTimeoutError:Vt.transitional(Vt.boolean)},!1),c!=null&&(N.isFunction(c)?s.paramsSerializer={serialize:c}:ts.assertOptions(c,{encode:Vt.function,serialize:Vt.function},!0)),ts.assertOptions(s,{baseUrl:Vt.spelling("baseURL"),withXsrfToken:Vt.spelling("withXSRFToken")},!0),s.method=(s.method||this.defaults.method||"get").toLowerCase();let p=f&&N.merge(f.common,f[s.method]);f&&N.forEach(["delete","get","head","post","put","patch","common"],_=>{delete f[_]}),s.headers=ut.concat(p,f);const g=[];let x=!0;this.interceptors.request.forEach(function(C){typeof C.runWhen=="function"&&C.runWhen(s)===!1||(x=x&&C.synchronous,g.unshift(C.fulfilled,C.rejected))});const v=[];this.interceptors.response.forEach(function(C){v.push(C.fulfilled,C.rejected)});let S,A=0,R;if(!x){const _=[Ud.bind(this),void 0];for(_.unshift.apply(_,g),_.push.apply(_,v),R=_.length,S=Promise.resolve(s);A{if(!l._listeners)return;let f=l._listeners.length;for(;f-- >0;)l._listeners[f](c);l._listeners=null}),this.promise.then=c=>{let f;const p=new Promise(g=>{l.subscribe(g),f=g}).then(c);return p.cancel=function(){l.unsubscribe(f)},p},i(function(f,p,g){l.reason||(l.reason=new Pr(f,p,g),s(l.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(i){if(this.reason){i(this.reason);return}this._listeners?this._listeners.push(i):this._listeners=[i]}unsubscribe(i){if(!this._listeners)return;const s=this._listeners.indexOf(i);s!==-1&&this._listeners.splice(s,1)}toAbortSignal(){const i=new AbortController,s=l=>{i.abort(l)};return this.subscribe(s),i.signal.unsubscribe=()=>this.unsubscribe(s),i.signal}static source(){let i;return{token:new ia(function(c){i=c}),cancel:i}}}function Vv(r){return function(s){return r.apply(null,s)}}function Wv(r){return N.isObject(r)&&r.isAxiosError===!0}const Wu={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Wu).forEach(([r,i])=>{Wu[i]=r});function Jp(r){const i=new Hn(r),s=_p(Hn.prototype.request,i);return N.extend(s,Hn.prototype,i,{allOwnKeys:!0}),N.extend(s,i,null,{allOwnKeys:!0}),s.create=function(c){return Jp(Wn(r,c))},s}const De=Jp(To);De.Axios=Hn;De.CanceledError=Pr;De.CancelToken=ia;De.isCancel=Vp;De.VERSION=Xp;De.toFormData=ws;De.AxiosError=le;De.Cancel=De.CanceledError;De.all=function(i){return Promise.all(i)};De.spread=Vv;De.isAxiosError=Wv;De.mergeConfig=Wn;De.AxiosHeaders=ut;De.formToJSON=r=>bp(N.isHTMLForm(r)?new FormData(r):r);De.getAdapter=Kp.getAdapter;De.HttpStatusCode=Wu;De.default=De;const Yv={apiBaseUrl:"/api"};class qv{constructor(){ed(this,"events",{})}on(i,s){return this.events[i]||(this.events[i]=[]),this.events[i].push(s),()=>this.off(i,s)}off(i,s){this.events[i]&&(this.events[i]=this.events[i].filter(l=>l!==s))}emit(i,...s){this.events[i]&&this.events[i].forEach(l=>{l(...s)})}}const as=new qv,Je=De.create({baseURL:Yv.apiBaseUrl,headers:{"Content-Type":"application/json"}});Je.interceptors.response.use(r=>r,r=>{var s,l,c;const i=(s=r.response)==null?void 0:s.data;if(i){const f=(c=(l=r.response)==null?void 0:l.headers)==null?void 0:c["discodeit-request-id"];f&&(i.requestId=f),r.response.data=i}return as.emit("api-error",r),r.response&&r.response.status===401&&as.emit("auth-error"),Promise.reject(r)});const Qv=()=>Je.defaults.baseURL,Gv=async(r,i)=>{const s={username:r,password:i};return(await Je.post("/auth/login",s)).data},Kv=async r=>(await Je.post("/users",r,{headers:{"Content-Type":"multipart/form-data"}})).data,Bd=r=>{let i;const s=new Set,l=(v,S)=>{const A=typeof v=="function"?v(i):v;if(!Object.is(A,i)){const R=i;i=S??(typeof A!="object"||A===null)?A:Object.assign({},i,A),s.forEach(I=>I(i,R))}},c=()=>i,g={setState:l,getState:c,getInitialState:()=>x,subscribe:v=>(s.add(v),()=>s.delete(v))},x=i=r(l,c,g);return g},Xv=r=>r?Bd(r):Bd,Jv=r=>r;function Zv(r,i=Jv){const s=gt.useSyncExternalStore(r.subscribe,()=>i(r.getState()),()=>i(r.getInitialState()));return gt.useDebugValue(s),s}const $d=r=>{const i=Xv(r),s=l=>Zv(i,l);return Object.assign(s,i),s},_r=r=>r?$d(r):$d,e0=async(r,i)=>(await Je.patch(`/users/${r}`,i,{headers:{"Content-Type":"multipart/form-data"}})).data,t0=async()=>(await Je.get("/users")).data,n0=async r=>(await Je.patch(`/users/${r}/userStatus`,{newLastActiveAt:new Date().toISOString()})).data,nn=_r(r=>({users:[],fetchUsers:async()=>{try{const i=await t0();r({users:i})}catch(i){console.error("사용자 목록 조회 실패:",i)}},updateUserStatus:async i=>{try{await n0(i)}catch(s){console.error("사용자 상태 업데이트 실패:",s)}}}));function Zp(r,i){let s;try{s=r()}catch{return}return{getItem:c=>{var f;const p=x=>x===null?null:JSON.parse(x,void 0),g=(f=s.getItem(c))!=null?f:null;return g instanceof Promise?g.then(p):p(g)},setItem:(c,f)=>s.setItem(c,JSON.stringify(f,void 0)),removeItem:c=>s.removeItem(c)}}const Yu=r=>i=>{try{const s=r(i);return s instanceof Promise?s:{then(l){return Yu(l)(s)},catch(l){return this}}}catch(s){return{then(l){return this},catch(l){return Yu(l)(s)}}}},r0=(r,i)=>(s,l,c)=>{let f={storage:Zp(()=>localStorage),partialize:C=>C,version:0,merge:(C,O)=>({...O,...C}),...i},p=!1;const g=new Set,x=new Set;let v=f.storage;if(!v)return r((...C)=>{console.warn(`[zustand persist middleware] Unable to update item '${f.name}', the given storage is currently unavailable.`),s(...C)},l,c);const S=()=>{const C=f.partialize({...l()});return v.setItem(f.name,{state:C,version:f.version})},A=c.setState;c.setState=(C,O)=>{A(C,O),S()};const R=r((...C)=>{s(...C),S()},l,c);c.getInitialState=()=>R;let I;const _=()=>{var C,O;if(!v)return;p=!1,g.forEach(B=>{var V;return B((V=l())!=null?V:R)});const F=((O=f.onRehydrateStorage)==null?void 0:O.call(f,(C=l())!=null?C:R))||void 0;return Yu(v.getItem.bind(v))(f.name).then(B=>{if(B)if(typeof B.version=="number"&&B.version!==f.version){if(f.migrate){const V=f.migrate(B.state,B.version);return V instanceof Promise?V.then(Q=>[!0,Q]):[!0,V]}console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return[!1,B.state];return[!1,void 0]}).then(B=>{var V;const[Q,H]=B;if(I=f.merge(H,(V=l())!=null?V:R),s(I,!0),Q)return S()}).then(()=>{F==null||F(I,void 0),I=l(),p=!0,x.forEach(B=>B(I))}).catch(B=>{F==null||F(void 0,B)})};return c.persist={setOptions:C=>{f={...f,...C},C.storage&&(v=C.storage)},clearStorage:()=>{v==null||v.removeItem(f.name)},getOptions:()=>f,rehydrate:()=>_(),hasHydrated:()=>p,onHydrate:C=>(g.add(C),()=>{g.delete(C)}),onFinishHydration:C=>(x.add(C),()=>{x.delete(C)})},f.skipHydration||_(),I||R},o0=r0,vt=_r()(o0(r=>({currentUserId:null,setCurrentUser:i=>r({currentUserId:i.id}),logout:()=>{const i=vt.getState().currentUserId;i&&nn.getState().updateUserStatus(i),r({currentUserId:null})},updateUser:async(i,s)=>{try{const l=await e0(i,s);return await nn.getState().fetchUsers(),l}catch(l){throw console.error("사용자 정보 수정 실패:",l),l}}}),{name:"user-storage",storage:Zp(()=>sessionStorage)})),ee={colors:{brand:{primary:"#5865F2",hover:"#4752C4"},background:{primary:"#1a1a1a",secondary:"#2a2a2a",tertiary:"#333333",input:"#40444B",hover:"rgba(255, 255, 255, 0.1)"},text:{primary:"#ffffff",secondary:"#cccccc",muted:"#999999"},status:{online:"#43b581",idle:"#faa61a",dnd:"#f04747",offline:"#747f8d",error:"#ED4245"},border:{primary:"#404040"}}},eh=T.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +`,th=T.div` + background: ${ee.colors.background.primary}; + padding: 32px; + border-radius: 8px; + width: 440px; + + h2 { + color: ${ee.colors.text.primary}; + margin-bottom: 24px; + font-size: 24px; + font-weight: bold; + } + + form { + display: flex; + flex-direction: column; + gap: 16px; + } +`,ko=T.input` + width: 100%; + padding: 10px; + border-radius: 4px; + background: ${ee.colors.background.input}; + border: none; + color: ${ee.colors.text.primary}; + font-size: 16px; + + &::placeholder { + color: ${ee.colors.text.muted}; + } + + &:focus { + outline: none; + } +`,nh=T.button` + width: 100%; + padding: 12px; + border-radius: 4px; + background: ${ee.colors.brand.primary}; + color: white; + font-size: 16px; + font-weight: 500; + border: none; + cursor: pointer; + transition: background-color 0.2s; + + &:hover { + background: ${ee.colors.brand.hover}; + } +`,rh=T.div` + color: ${ee.colors.status.error}; + font-size: 14px; + text-align: center; +`,i0=T.p` + text-align: center; + margin-top: 16px; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 14px; +`,s0=T.span` + color: ${({theme:r})=>r.colors.brand.primary}; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +`,Vi=T.div` + margin-bottom: 20px; +`,Wi=T.label` + display: block; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 12px; + font-weight: 700; + margin-bottom: 8px; +`,Pu=T.span` + color: ${({theme:r})=>r.colors.status.error}; +`,l0=T.div` + display: flex; + flex-direction: column; + align-items: center; + margin: 10px 0; +`,u0=T.img` + width: 80px; + height: 80px; + border-radius: 50%; + margin-bottom: 10px; + object-fit: cover; +`,a0=T.input` + display: none; +`,c0=T.label` + color: ${({theme:r})=>r.colors.brand.primary}; + cursor: pointer; + font-size: 14px; + + &:hover { + text-decoration: underline; + } +`,f0=T.span` + color: ${({theme:r})=>r.colors.brand.primary}; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +`,d0=T(f0)` + display: block; + text-align: center; + margin-top: 16px; +`,zt="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAw2SURBVHgB7d3PT1XpHcfxBy5g6hipSMolGViACThxJDbVRZ2FXejKlf9h/4GmC1fTRdkwC8fE0JgyJuICFkCjEA04GeZe6P0cPC0698I95zzPc57v5f1K6DSto3A8n/v9nufXGfrr338+dgBMGnYAzCLAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwbcTDvyuWh//33w1/1dexwMRBgYxTW5vVh9/vxYTcxPpR9jY0OffZrdt8fu82ttlvfbLv9j4R5kBHgxCmcE1eH3NfTDTc7PfxZte3lJNgjbmlxxK3+1HKrr1oOg4kAJ0pVdnG+4ZqTw7+psEUoxF91Qv/Di1+db/q+ZpvD7g+T6gb04XLyv6mF3//osuqvTmDn3RGdQCAEOCG6+W/ONdzNTnCrhPZLN2Yb2T99hVhdwOLcSOf37f7hknUN4yedgLoGeb3Rdv/qdAIE2S8CnIDzAuGDQrzXeTZee1OtndaHy9LCSOHvU3++vv693nLPX9LS+0KAa6QQLC2o4sb5a1A7rYGtMqPU+l7v3hpx85+qeVnfdH7W2c7z/Pcrh1RjD5gHromq2JOHY9HCK2Ojzk1dL1fhH90fqxzenDoO/X79DMjhbAQ4Mg1OPXl4KauGodrls6j6FaXKq+dZn/IQ13ENBgkBjiRvQR99V2/lmZos9lc+PxOuxdd1uL3gp6pfVDwDR6Ab9cG9Me9VLAZ1CiHpmXhz6yibakJxVODAZpoN9/iBzfCq+sboFkJ/SAwyrlxAujE1WJWSIiO/sYKlxSpTnbEBqnBxVOBA9LybWnjloM8An6ysitc1NCe5FcvgqgVw/85o1OmhItY32n39uqnJuC3/FAEuhavmmcLra77UN7XP2322qRNX494aqvgojqvmUcrhFa1+6tdXkae6tMiEhR3FEWBPNOCTcni1rZCli4OHAHuQ4mjzaewJHlxMI1Wked5Uw7v99ijbwqd/FnVQQ7WmQyiOAFegZ7a736ZzCU820h+7nbfHbnO7XSq4p3+vmHbfMwdcBgGuoO4dNQrZxtaR+08nqNueT73Y2D7qTIW5aLRXGcUR4JL03FtHeBXa9Y2jyhX2PHudiqg/K9ZuoY3t/uan8TkCXIKCG/u5V2Fae9N2a+vtKO2tjqfVnxfj5zw5O4sWugwCXIJa51hiB/e0tfVWdkZX6CrMCHl5BLigWDt0RCc6rrxo1XZQu6rw6qt2tq47FD0G9Lu8E79FgAvIWucIO3QU2B9ftpK4sVWFZ5rDQTYbqHUOcdztRcJCjgLUToauvrqpny4fJlWVlp/5P4BOH1IcbFcdAe6Tght6h5FeiaLwpnZTq5VW2HzN1eYfUoS3OgLcp9sL4cOrkKT6YrI8dFUHnDQYR3j94Rm4D9kLxQLuV009vKdpXbXae00vFdm8UWVZJ3ojwH3QcS+hnn1VifSMaemVoPqeVzqDT6rG2oivQS5dH33l70ZS262w7n04yhae8MrTMAhwH0KNPFsfyNH3vd+pxkwD1Ydn4HOodQ5VfTXHyrMgqiDA55ibCbNJX1VLc6xAFQT4HCEGr9Q6s3wQPhDgM4RqnzWVQusMHwjwGTS66puCS/WFLwT4DCHOKia88IkA96BjTkOcVbzDQgZ4RIB7CBFejTzz7AufCHAPWn3lGwse4BsB7uGa5wqcLS3k7XvwjAD3cOWy84pnX4RAgHvw/QzMLhyEQIC7CLF4Y4+DyxEAAe4iRIB3PzD6DP8IcBejnncPagCL/bAIgQB34fsc5P2PtM8IgwBHcMjJqQiEAHfBm+JhBQGO4IDlkwiEAHdx2PIbuFhv+MPFQ4C7ODx0Xo2OOiAIAhwBz9QIhQB34XvOlhYaoRDgLg5+dl7pcACqMEIgwF2EWDV1bZwAwz8C3IVOzfAd4omrXGr4x13Vg++jb6YmudTwj7uqh733fgOsM6YZzIJvBLiH3Q/+NyDMB3pNCy4u3k7Yw+57/wNZM9PDbu2NGwjqJiauDrmvpxufXiv6+f+v63fw8SjrZDgLLBwC3INO0NBAls+2V220jurZNXw6h8K6ODfibsye/UjQnNR/nnQcGk/IX/DNsbp+EeAetAVQVaQ56fe5dXGu4X54YTPASwsj7uZ8o/CHmkJ/Y7aRfb3eaBNkj3gGPsNOgNZPN7G1RR36fh8/uJS96LxqR6Kf/9H9MRa2eEKAz7C5FaZS3l6w0/goaArchMeFKPkHwrVxbr+quIJn0LNqiFZPVSjEmx98U7UNVS016PWXe6NU4ooI8DnWN8O8DuX+H0eTnxdeWgjb7uv3/vMd9lpWQYDPEep9Rrp5by+kOy+s7+/mfPhWXyPzFrqRVHHlzpFPgYTwTScg87NphjhmZdTgGMohwH1YexPupdx3b40mN5ij6tuMuHabKlweV60PGo0OdTB7ioM5WjEWW5PNHqVw1fq09ibcu33zqZpUQjzTjN/Ws1urHK5an9bWW0Ffj5JSiOv4HiaYEy6Fq9YnLa1cfRWuCku+wOHmXL2DOnUEmGOHyiHABagKh17Dqxv57rcj7k+3RpKfJ0b9CHBBKy/ivOhIU0yPH4xdqD3EV37HB1ZRBLignc6c8MZW2FY6p5ZSK7b0bNyMOM3CTiE7CHAJz1+2or7vV1Msj74by4IcoyKHOMygH4fhptsHFgEuQRXqx5fx7zYFWRX5ycNL2UqpUFV5512cDuNLvAS9ONawlaQ10jpSJsZ64S+d3iCvm3777XGntW9nx9fsfqh+JK5+Nq0Qi43WvTgCXMHqq5abma53g75Gqmen9fX/alz1CBtNmenfj7k6yvIxQ3Wiha5AN/r3K4fJtX55hVarvVTy8AB9OMV0GGdwf+AQ4IpU4f75LN27Tzt9HtwbKzynrNF2zXvHsvOWClwGAfZAN18dg1r9UnuthSFF6WeK1doS4HIIsCeqVrHbziLUUpdZornc6S5iDC5p8A3FEWCPVn9KO8RlTpVUeJ8u/xLsUAPR780UUjkE2LOUQ6x11jPN4n/l+WDdaqDznEOdO3YREOAAFOJUn4mrTA3p51KQNU/sM8g8/5bHPHAgeibWAND9O2mdtlF147yCm2/o0IeBXlyuAwDKfjDotBMWcJRHBQ5IlUUVa1Bv0O1squnkVSllvd5kAXQVBDiwfBAo5pyqFbo2od5+cVEQ4Ag0CKRnYrWedVfjlLqBlEfsrSDAEWnwJx8Eqsve+zQCrA+SOq/DoCDAkeWDQE+X63k23txKIzRUXz8IcE00Qv23f/wSta3Odim9q/+Zc6Pz3Ev19YNppJrpRtaXXrGinUMhp5zUvqfg+Uu2HvlCgBORB1nzqYtzDTc77ffoHC3CSGEAS4N5zPv6Q4ATo7lVfV253MoWXegMrKob6xWaFKax9PzNdJpfBDhRqlL7n6qy2mqFWeuY9QaDfttsfRCoXd1NYOS5rnPEBh0BNuB0mGVifOgk1Ncb2VJGbVLIdxnp12qqaHO7HXQHURH6ngZ5RVqdCLBBqqj62jCwiknbBJefEd5QCDCCUWgV3hRa+EFFgBEEbXMcBBjeabR55UWLUzYiIMDwRoHVK1iZKoqHAMMLqm49CDAqyxefID42MwCGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhv0XZkN9IbEGbp4AAAAASUVORK5CYII=",p0=({isOpen:r,onClose:i})=>{const[s,l]=ie.useState(""),[c,f]=ie.useState(""),[p,g]=ie.useState(""),[x,v]=ie.useState(null),[S,A]=ie.useState(null),[R,I]=ie.useState(""),_=vt(F=>F.setCurrentUser),C=F=>{var V;const B=(V=F.target.files)==null?void 0:V[0];if(B){v(B);const Q=new FileReader;Q.onloadend=()=>{A(Q.result)},Q.readAsDataURL(B)}},O=async F=>{F.preventDefault(),I("");try{const B=new FormData;B.append("userCreateRequest",new Blob([JSON.stringify({email:s,username:c,password:p})],{type:"application/json"})),x&&B.append("profile",x);const V=await Kv(B);_(V),i()}catch{I("회원가입에 실패했습니다.")}};return r?h.jsx(eh,{children:h.jsxs(th,{children:[h.jsx("h2",{children:"계정 만들기"}),h.jsxs("form",{onSubmit:O,children:[h.jsxs(Vi,{children:[h.jsxs(Wi,{children:["이메일 ",h.jsx(Pu,{children:"*"})]}),h.jsx(ko,{type:"email",value:s,onChange:F=>l(F.target.value),required:!0})]}),h.jsxs(Vi,{children:[h.jsxs(Wi,{children:["사용자명 ",h.jsx(Pu,{children:"*"})]}),h.jsx(ko,{type:"text",value:c,onChange:F=>f(F.target.value),required:!0})]}),h.jsxs(Vi,{children:[h.jsxs(Wi,{children:["비밀번호 ",h.jsx(Pu,{children:"*"})]}),h.jsx(ko,{type:"password",value:p,onChange:F=>g(F.target.value),required:!0})]}),h.jsxs(Vi,{children:[h.jsx(Wi,{children:"프로필 이미지"}),h.jsxs(l0,{children:[h.jsx(u0,{src:S||zt,alt:"profile"}),h.jsx(a0,{type:"file",accept:"image/*",onChange:C,id:"profile-image"}),h.jsx(c0,{htmlFor:"profile-image",children:"이미지 변경"})]})]}),R&&h.jsx(rh,{children:R}),h.jsx(nh,{type:"submit",children:"계속하기"}),h.jsx(d0,{onClick:i,children:"이미 계정이 있으신가요?"})]})]})}):null},h0=({isOpen:r,onClose:i})=>{const[s,l]=ie.useState(""),[c,f]=ie.useState(""),[p,g]=ie.useState(""),[x,v]=ie.useState(!1),S=vt(I=>I.setCurrentUser),{fetchUsers:A}=nn(),R=async()=>{var I;try{const _=await Gv(s,c);await A(),S(_),g(""),i()}catch(_){console.error("로그인 에러:",_),((I=_.response)==null?void 0:I.status)===401?g("아이디 또는 비밀번호가 올바르지 않습니다."):g("로그인에 실패했습니다.")}};return r?h.jsxs(h.Fragment,{children:[h.jsx(eh,{children:h.jsxs(th,{children:[h.jsx("h2",{children:"돌아오신 것을 환영해요!"}),h.jsxs("form",{onSubmit:I=>{I.preventDefault(),R()},children:[h.jsx(ko,{type:"text",placeholder:"사용자 이름",value:s,onChange:I=>l(I.target.value)}),h.jsx(ko,{type:"password",placeholder:"비밀번호",value:c,onChange:I=>f(I.target.value)}),p&&h.jsx(rh,{children:p}),h.jsx(nh,{type:"submit",children:"로그인"})]}),h.jsxs(i0,{children:["계정이 필요한가요? ",h.jsx(s0,{onClick:()=>v(!0),children:"가입하기"})]})]})}),h.jsx(p0,{isOpen:x,onClose:()=>v(!1)})]}):null},m0=async r=>(await Je.get(`/channels?userId=${r}`)).data,g0=async r=>(await Je.post("/channels/public",r)).data,y0=async r=>{const i={participantIds:r};return(await Je.post("/channels/private",i)).data},v0=async r=>(await Je.get("/readStatuses",{params:{userId:r}})).data,w0=async(r,i)=>{const s={newLastReadAt:i};return(await Je.patch(`/readStatuses/${r}`,s)).data},x0=async(r,i,s)=>{const l={userId:r,channelId:i,lastReadAt:s};return(await Je.post("/readStatuses",l)).data},jo=_r((r,i)=>({readStatuses:{},fetchReadStatuses:async()=>{try{const s=vt.getState().currentUserId;if(!s)return;const c=(await v0(s)).reduce((f,p)=>(f[p.channelId]={id:p.id,lastReadAt:p.lastReadAt},f),{});r({readStatuses:c})}catch(s){console.error("읽음 상태 조회 실패:",s)}},updateReadStatus:async s=>{try{const l=vt.getState().currentUserId;if(!l)return;const c=i().readStatuses[s];let f;c?f=await w0(c.id,new Date().toISOString()):f=await x0(l,s,new Date().toISOString()),r(p=>({readStatuses:{...p.readStatuses,[s]:{id:f.id,lastReadAt:f.lastReadAt}}}))}catch(l){console.error("읽음 상태 업데이트 실패:",l)}},hasUnreadMessages:(s,l)=>{const c=i().readStatuses[s],f=c==null?void 0:c.lastReadAt;return!f||new Date(l)>new Date(f)}})),xr=_r((r,i)=>({channels:[],pollingInterval:null,loading:!1,error:null,fetchChannels:async s=>{r({loading:!0,error:null});try{const l=await m0(s);r(f=>{const p=new Set(f.channels.map(S=>S.id)),g=l.filter(S=>!p.has(S.id));return{channels:[...f.channels.filter(S=>l.some(A=>A.id===S.id)),...g],loading:!1}});const{fetchReadStatuses:c}=jo.getState();return c(),l}catch(l){return r({error:l,loading:!1}),[]}},startPolling:s=>{const l=i().pollingInterval;l&&clearInterval(l);const c=setInterval(()=>{i().fetchChannels(s)},3e3);r({pollingInterval:c})},stopPolling:()=>{const s=i().pollingInterval;s&&(clearInterval(s),r({pollingInterval:null}))},createPublicChannel:async s=>{try{const l=await g0(s);return r(c=>c.channels.some(p=>p.id===l.id)?c:{channels:[...c.channels,{...l,participantIds:[],lastMessageAt:new Date().toISOString()}]}),l}catch(l){throw console.error("공개 채널 생성 실패:",l),l}},createPrivateChannel:async s=>{try{const l=await y0(s);return r(c=>c.channels.some(p=>p.id===l.id)?c:{channels:[...c.channels,{...l,participantIds:s,lastMessageAt:new Date().toISOString()}]}),l}catch(l){throw console.error("비공개 채널 생성 실패:",l),l}}})),S0=async r=>(await Je.get(`/binaryContents/${r}`)).data,E0=r=>`${Qv()}/binaryContents/${r}/download`,Yn=_r((r,i)=>({binaryContents:{},fetchBinaryContent:async s=>{if(i().binaryContents[s])return i().binaryContents[s];try{const l=await S0(s),{contentType:c,fileName:f,size:p}=l,x={url:E0(s),contentType:c,fileName:f,size:p};return r(v=>({binaryContents:{...v.binaryContents,[s]:x}})),x}catch(l){return console.error("첨부파일 정보 조회 실패:",l),null}}})),Io=T.div` + position: absolute; + bottom: -3px; + right: -3px; + width: 16px; + height: 16px; + border-radius: 50%; + background: ${r=>r.$online?ee.colors.status.online:ee.colors.status.offline}; + border: 4px solid ${r=>r.$background||ee.colors.background.secondary}; +`;T.div` + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 8px; + background: ${r=>ee.colors.status[r.status||"offline"]||ee.colors.status.offline}; +`;const Tr=T.div` + position: relative; + width: ${r=>r.$size||"32px"}; + height: ${r=>r.$size||"32px"}; + flex-shrink: 0; + margin: ${r=>r.$margin||"0"}; +`,rn=T.img` + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; + border: ${r=>r.$border||"none"}; +`;function C0({isOpen:r,onClose:i,user:s}){var L,b;const[l,c]=ie.useState(s.username),[f,p]=ie.useState(s.email),[g,x]=ie.useState(""),[v,S]=ie.useState(null),[A,R]=ie.useState(""),[I,_]=ie.useState(null),{binaryContents:C,fetchBinaryContent:O}=Yn(),{logout:F,updateUser:B}=vt();ie.useEffect(()=>{var re;(re=s.profile)!=null&&re.id&&!C[s.profile.id]&&O(s.profile.id)},[s.profile,C,O]);const V=()=>{c(s.username),p(s.email),x(""),S(null),_(null),R(""),i()},Q=re=>{var Ne;const ye=(Ne=re.target.files)==null?void 0:Ne[0];if(ye){S(ye);const at=new FileReader;at.onloadend=()=>{_(at.result)},at.readAsDataURL(ye)}},H=async re=>{re.preventDefault(),R("");try{const ye=new FormData,Ne={};l!==s.username&&(Ne.newUsername=l),f!==s.email&&(Ne.newEmail=f),g&&(Ne.newPassword=g),(Object.keys(Ne).length>0||v)&&(ye.append("userUpdateRequest",new Blob([JSON.stringify(Ne)],{type:"application/json"})),v&&ye.append("profile",v),await B(s.id,ye)),i()}catch{R("사용자 정보 수정에 실패했습니다.")}};return r?h.jsx(k0,{children:h.jsxs(j0,{children:[h.jsx("h2",{children:"프로필 수정"}),h.jsxs("form",{onSubmit:H,children:[h.jsxs(Yi,{children:[h.jsx(qi,{children:"프로필 이미지"}),h.jsxs(R0,{children:[h.jsx(P0,{src:I||((L=s.profile)!=null&&L.id?(b=C[s.profile.id])==null?void 0:b.url:void 0)||zt,alt:"profile"}),h.jsx(_0,{type:"file",accept:"image/*",onChange:Q,id:"profile-image"}),h.jsx(T0,{htmlFor:"profile-image",children:"이미지 변경"})]})]}),h.jsxs(Yi,{children:[h.jsxs(qi,{children:["사용자명 ",h.jsx(bd,{children:"*"})]}),h.jsx(_u,{type:"text",value:l,onChange:re=>c(re.target.value),required:!0})]}),h.jsxs(Yi,{children:[h.jsxs(qi,{children:["이메일 ",h.jsx(bd,{children:"*"})]}),h.jsx(_u,{type:"email",value:f,onChange:re=>p(re.target.value),required:!0})]}),h.jsxs(Yi,{children:[h.jsx(qi,{children:"새 비밀번호"}),h.jsx(_u,{type:"password",placeholder:"변경하지 않으려면 비워두세요",value:g,onChange:re=>x(re.target.value)})]}),A&&h.jsx(A0,{children:A}),h.jsxs(I0,{children:[h.jsx(Hd,{type:"button",onClick:V,$secondary:!0,children:"취소"}),h.jsx(Hd,{type:"submit",children:"저장"})]})]}),h.jsx(N0,{onClick:F,children:"로그아웃"})]})}):null}const k0=T.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +`,j0=T.div` + background: ${({theme:r})=>r.colors.background.secondary}; + padding: 32px; + border-radius: 5px; + width: 100%; + max-width: 480px; + + h2 { + color: ${({theme:r})=>r.colors.text.primary}; + margin-bottom: 24px; + text-align: center; + font-size: 24px; + } +`,_u=T.input` + width: 100%; + padding: 10px; + margin-bottom: 10px; + border: none; + border-radius: 4px; + background: ${({theme:r})=>r.colors.background.input}; + color: ${({theme:r})=>r.colors.text.primary}; + + &::placeholder { + color: ${({theme:r})=>r.colors.text.muted}; + } + + &:focus { + outline: none; + box-shadow: 0 0 0 2px ${({theme:r})=>r.colors.brand.primary}; + } +`,Hd=T.button` + width: 100%; + padding: 10px; + border: none; + border-radius: 4px; + background: ${({$secondary:r,theme:i})=>r?"transparent":i.colors.brand.primary}; + color: ${({theme:r})=>r.colors.text.primary}; + cursor: pointer; + font-weight: 500; + + &:hover { + background: ${({$secondary:r,theme:i})=>r?i.colors.background.hover:i.colors.brand.hover}; + } +`,A0=T.div` + color: ${({theme:r})=>r.colors.status.error}; + font-size: 14px; + margin-bottom: 10px; +`,R0=T.div` + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 20px; +`,P0=T.img` + width: 100px; + height: 100px; + border-radius: 50%; + margin-bottom: 10px; + object-fit: cover; +`,_0=T.input` + display: none; +`,T0=T.label` + color: ${({theme:r})=>r.colors.brand.primary}; + cursor: pointer; + font-size: 14px; + + &:hover { + text-decoration: underline; + } +`,I0=T.div` + display: flex; + gap: 10px; + margin-top: 20px; +`,N0=T.button` + width: 100%; + padding: 10px; + margin-top: 16px; + border: none; + border-radius: 4px; + background: transparent; + color: ${({theme:r})=>r.colors.status.error}; + cursor: pointer; + font-weight: 500; + + &:hover { + background: ${({theme:r})=>r.colors.status.error}20; + } +`,Yi=T.div` + margin-bottom: 20px; +`,qi=T.label` + display: block; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 12px; + font-weight: 700; + margin-bottom: 8px; +`,bd=T.span` + color: ${({theme:r})=>r.colors.status.error}; +`,O0=T.div` + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.5rem 0.75rem; + background-color: ${({theme:r})=>r.colors.background.tertiary}; + width: 100%; + height: 52px; +`,L0=T(Tr)``;T(rn)``;const D0=T.div` + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; +`,M0=T.div` + font-weight: 500; + color: ${({theme:r})=>r.colors.text.primary}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 0.875rem; + line-height: 1.2; +`,z0=T.div` + font-size: 0.75rem; + color: ${({theme:r})=>r.colors.text.secondary}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.2; +`,U0=T.div` + display: flex; + align-items: center; + flex-shrink: 0; +`,F0=T.button` + background: none; + border: none; + padding: 0.25rem; + cursor: pointer; + color: ${({theme:r})=>r.colors.text.secondary}; + font-size: 18px; + + &:hover { + color: ${({theme:r})=>r.colors.text.primary}; + } +`;function B0({user:r}){var f,p;const[i,s]=ie.useState(!1),{binaryContents:l,fetchBinaryContent:c}=Yn();return ie.useEffect(()=>{var g;(g=r.profile)!=null&&g.id&&!l[r.profile.id]&&c(r.profile.id)},[r.profile,l,c]),h.jsxs(h.Fragment,{children:[h.jsxs(O0,{children:[h.jsxs(L0,{children:[h.jsx(rn,{src:(f=r.profile)!=null&&f.id?(p=l[r.profile.id])==null?void 0:p.url:zt,alt:r.username}),h.jsx(Io,{$online:!0})]}),h.jsxs(D0,{children:[h.jsx(M0,{children:r.username}),h.jsx(z0,{children:"온라인"})]}),h.jsx(U0,{children:h.jsx(F0,{onClick:()=>s(!0),children:"⚙️"})})]}),h.jsx(C0,{isOpen:i,onClose:()=>s(!1),user:r})]})}const $0=T.div` + width: 240px; + background: ${ee.colors.background.secondary}; + border-right: 1px solid ${ee.colors.border.primary}; + display: flex; + flex-direction: column; +`,H0=T.div` + flex: 1; + overflow-y: auto; +`,b0=T.div` + padding: 16px; + font-size: 16px; + font-weight: bold; + color: ${ee.colors.text.primary}; +`,oh=T.div` + height: 34px; + padding: 0 8px; + margin: 1px 8px; + display: flex; + align-items: center; + gap: 6px; + color: ${r=>r.$hasUnread?r.theme.colors.text.primary:r.theme.colors.text.muted}; + font-weight: ${r=>r.$hasUnread?"600":"normal"}; + cursor: pointer; + background: ${r=>r.$isActive?r.theme.colors.background.hover:"transparent"}; + border-radius: 4px; + + &:hover { + background: ${r=>r.theme.colors.background.hover}; + color: ${r=>r.theme.colors.text.primary}; + } +`,Vd=T.div` + margin-bottom: 8px; +`,qu=T.div` + padding: 8px 16px; + display: flex; + align-items: center; + color: ${ee.colors.text.muted}; + text-transform: uppercase; + font-size: 12px; + font-weight: 600; + cursor: pointer; + user-select: none; + + & > span:nth-child(2) { + flex: 1; + margin-right: auto; + } + + &:hover { + color: ${ee.colors.text.primary}; + } +`,Wd=T.span` + margin-right: 4px; + font-size: 10px; + transition: transform 0.2s; + transform: rotate(${r=>r.$folded?"-90deg":"0deg"}); +`,Yd=T.div` + display: ${r=>r.$folded?"none":"block"}; +`,qd=T(oh)` + height: ${r=>r.hasSubtext?"42px":"34px"}; +`,V0=T(Tr)` + width: 32px; + height: 32px; + margin: 0 8px; +`,Qd=T.div` + font-size: 16px; + line-height: 18px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: ${r=>r.$isActive||r.$hasUnread?r.theme.colors.text.primary:r.theme.colors.text.muted}; + font-weight: ${r=>r.$hasUnread?"600":"normal"}; +`;T(Io)` + border-color: ${ee.colors.background.primary}; +`;const Gd=T.button` + background: none; + border: none; + color: ${ee.colors.text.muted}; + font-size: 18px; + padding: 0; + cursor: pointer; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.2s, color 0.2s; + + ${qu}:hover & { + opacity: 1; + } + + &:hover { + color: ${ee.colors.text.primary}; + } +`,W0=T(Tr)` + width: 40px; + height: 24px; + margin: 0 8px; +`,Y0=T.div` + font-size: 12px; + line-height: 13px; + color: ${ee.colors.text.muted}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`,Kd=T.div` + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; + gap: 2px; +`,q0=T.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.85); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +`,Q0=T.div` + background: ${ee.colors.background.primary}; + border-radius: 4px; + width: 440px; + max-width: 90%; +`,G0=T.div` + padding: 16px; + display: flex; + justify-content: space-between; + align-items: center; +`,K0=T.h2` + color: ${ee.colors.text.primary}; + font-size: 20px; + font-weight: 600; + margin: 0; +`,X0=T.div` + padding: 0 16px 16px; +`,J0=T.form` + display: flex; + flex-direction: column; + gap: 16px; +`,Tu=T.div` + display: flex; + flex-direction: column; + gap: 8px; +`,Iu=T.label` + color: ${ee.colors.text.primary}; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; +`,Z0=T.p` + color: ${ee.colors.text.muted}; + font-size: 14px; + margin: -4px 0 0; +`,Qu=T.input` + padding: 10px; + background: ${ee.colors.background.tertiary}; + border: none; + border-radius: 3px; + color: ${ee.colors.text.primary}; + font-size: 16px; + + &:focus { + outline: none; + box-shadow: 0 0 0 2px ${ee.colors.status.online}; + } + + &::placeholder { + color: ${ee.colors.text.muted}; + } +`,e1=T.button` + margin-top: 8px; + padding: 12px; + background: ${ee.colors.status.online}; + color: white; + border: none; + border-radius: 3px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: #3ca374; + } +`,t1=T.button` + background: none; + border: none; + color: ${ee.colors.text.muted}; + font-size: 24px; + cursor: pointer; + padding: 4px; + line-height: 1; + + &:hover { + color: ${ee.colors.text.primary}; + } +`,n1=T(Qu)` + margin-bottom: 8px; +`,r1=T.div` + max-height: 300px; + overflow-y: auto; + background: ${ee.colors.background.tertiary}; + border-radius: 4px; +`,o1=T.div` + display: flex; + align-items: center; + padding: 8px 12px; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: ${ee.colors.background.hover}; + } + + & + & { + border-top: 1px solid ${ee.colors.border.primary}; + } +`,i1=T.input` + margin-right: 12px; + width: 16px; + height: 16px; + cursor: pointer; +`,Xd=T.img` + width: 32px; + height: 32px; + border-radius: 50%; + margin-right: 12px; +`,s1=T.div` + flex: 1; + min-width: 0; +`,l1=T.div` + color: ${ee.colors.text.primary}; + font-size: 14px; + font-weight: 500; +`,u1=T.div` + color: ${ee.colors.text.muted}; + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`,a1=T.div` + padding: 16px; + text-align: center; + color: ${ee.colors.text.muted}; +`,c1=T.div` + color: ${ee.colors.status.error}; + font-size: 14px; + padding: 8px 0; + text-align: center; + background-color: ${({theme:r})=>r.colors.background.tertiary}; + border-radius: 4px; + margin-bottom: 8px; +`;function f1(){return h.jsx(b0,{children:"채널 목록"})}function Jd({channel:r,isActive:i,onClick:s,hasUnread:l}){var x;const c=vt(v=>v.currentUserId),{binaryContents:f}=Yn();if(r.type==="PUBLIC")return h.jsxs(oh,{$isActive:i,onClick:s,$hasUnread:l,children:["# ",r.name]});const p=r.participants;if(p.length>2){const v=p.filter(S=>S.id!==c).map(S=>S.username).join(", ");return h.jsxs(qd,{$isActive:i,onClick:s,children:[h.jsx(W0,{children:p.filter(S=>S.id!==c).slice(0,2).map((S,A)=>{var R;return h.jsx(rn,{src:S.profile?(R=f[S.profile.id])==null?void 0:R.url:zt,style:{position:"absolute",left:A*16,zIndex:2-A,width:"24px",height:"24px",border:"2px solid #2a2a2a"}},S.id)})}),h.jsxs(Kd,{children:[h.jsx(Qd,{$hasUnread:l,children:v}),h.jsxs(Y0,{children:["멤버 ",p.length,"명"]})]})]})}const g=p.filter(v=>v.id!==c)[0];return g&&h.jsxs(qd,{$isActive:i,onClick:s,children:[h.jsxs(V0,{children:[h.jsx(rn,{src:g.profile?(x=f[g.profile.id])==null?void 0:x.url:zt,alt:"profile"}),h.jsx(Io,{$online:g.online})]}),h.jsx(Kd,{children:h.jsx(Qd,{$hasUnread:l,children:g.username})})]})}function d1({isOpen:r,type:i,onClose:s,onCreateSuccess:l}){const[c,f]=ie.useState({name:"",description:""}),[p,g]=ie.useState(""),[x,v]=ie.useState([]),[S,A]=ie.useState(""),R=nn(H=>H.users),I=Yn(H=>H.binaryContents),_=vt(H=>H.currentUserId),C=ie.useMemo(()=>R.filter(H=>H.id!==_).filter(H=>H.username.toLowerCase().includes(p.toLowerCase())||H.email.toLowerCase().includes(p.toLowerCase())),[p,R,_]),O=xr(H=>H.createPublicChannel),F=xr(H=>H.createPrivateChannel),B=H=>{const{name:L,value:b}=H.target;f(re=>({...re,[L]:b}))},V=H=>{v(L=>L.includes(H)?L.filter(b=>b!==H):[...L,H])},Q=async H=>{var L,b;H.preventDefault(),A("");try{let re;if(i==="PUBLIC"){if(!c.name.trim()){A("채널 이름을 입력해주세요.");return}const ye={name:c.name,description:c.description};re=await O(ye)}else{if(x.length===0){A("대화 상대를 선택해주세요.");return}const ye=_&&[...x,_]||x;re=await F(ye)}l(re)}catch(re){console.error("채널 생성 실패:",re),A(((b=(L=re.response)==null?void 0:L.data)==null?void 0:b.message)||"채널 생성에 실패했습니다. 다시 시도해주세요.")}};return r?h.jsx(q0,{onClick:s,children:h.jsxs(Q0,{onClick:H=>H.stopPropagation(),children:[h.jsxs(G0,{children:[h.jsx(K0,{children:i==="PUBLIC"?"채널 만들기":"개인 메시지 시작하기"}),h.jsx(t1,{onClick:s,children:"×"})]}),h.jsx(X0,{children:h.jsxs(J0,{onSubmit:Q,children:[S&&h.jsx(c1,{children:S}),i==="PUBLIC"?h.jsxs(h.Fragment,{children:[h.jsxs(Tu,{children:[h.jsx(Iu,{children:"채널 이름"}),h.jsx(Qu,{name:"name",value:c.name,onChange:B,placeholder:"새로운-채널",required:!0})]}),h.jsxs(Tu,{children:[h.jsx(Iu,{children:"채널 설명"}),h.jsx(Z0,{children:"이 채널의 주제를 설명해주세요."}),h.jsx(Qu,{name:"description",value:c.description,onChange:B,placeholder:"채널 설명을 입력하세요"})]})]}):h.jsxs(Tu,{children:[h.jsx(Iu,{children:"사용자 검색"}),h.jsx(n1,{type:"text",value:p,onChange:H=>g(H.target.value),placeholder:"사용자명 또는 이메일로 검색"}),h.jsx(r1,{children:C.length>0?C.map(H=>h.jsxs(o1,{children:[h.jsx(i1,{type:"checkbox",checked:x.includes(H.id),onChange:()=>V(H.id)}),H.profile?h.jsx(Xd,{src:I[H.profile.id].url}):h.jsx(Xd,{src:zt}),h.jsxs(s1,{children:[h.jsx(l1,{children:H.username}),h.jsx(u1,{children:H.email})]})]},H.id)):h.jsx(a1,{children:"검색 결과가 없습니다."})})]}),h.jsx(e1,{type:"submit",children:i==="PUBLIC"?"채널 만들기":"대화 시작하기"})]})})]})}):null}function p1({currentUser:r,activeChannel:i,onChannelSelect:s}){var Q,H;const[l,c]=ie.useState({PUBLIC:!1,PRIVATE:!1}),[f,p]=ie.useState({isOpen:!1,type:null}),g=xr(L=>L.channels),x=xr(L=>L.fetchChannels),v=xr(L=>L.startPolling),S=xr(L=>L.stopPolling),A=jo(L=>L.fetchReadStatuses),R=jo(L=>L.updateReadStatus),I=jo(L=>L.hasUnreadMessages);ie.useEffect(()=>{if(r)return x(r.id),A(),v(r.id),()=>{S()}},[r,x,A,v,S]);const _=L=>{c(b=>({...b,[L]:!b[L]}))},C=(L,b)=>{b.stopPropagation(),p({isOpen:!0,type:L})},O=()=>{p({isOpen:!1,type:null})},F=async L=>{try{const re=(await x(r.id)).find(ye=>ye.id===L.id);re&&s(re),O()}catch(b){console.error("채널 생성 실패:",b)}},B=L=>{s(L),R(L.id)},V=g.reduce((L,b)=>(L[b.type]||(L[b.type]=[]),L[b.type].push(b),L),{});return h.jsxs($0,{children:[h.jsx(f1,{}),h.jsxs(H0,{children:[h.jsxs(Vd,{children:[h.jsxs(qu,{onClick:()=>_("PUBLIC"),children:[h.jsx(Wd,{$folded:l.PUBLIC,children:"▼"}),h.jsx("span",{children:"일반 채널"}),h.jsx(Gd,{onClick:L=>C("PUBLIC",L),children:"+"})]}),h.jsx(Yd,{$folded:l.PUBLIC,children:(Q=V.PUBLIC)==null?void 0:Q.map(L=>h.jsx(Jd,{channel:L,isActive:(i==null?void 0:i.id)===L.id,hasUnread:I(L.id,L.lastMessageAt),onClick:()=>B(L)},L.id))})]}),h.jsxs(Vd,{children:[h.jsxs(qu,{onClick:()=>_("PRIVATE"),children:[h.jsx(Wd,{$folded:l.PRIVATE,children:"▼"}),h.jsx("span",{children:"개인 메시지"}),h.jsx(Gd,{onClick:L=>C("PRIVATE",L),children:"+"})]}),h.jsx(Yd,{$folded:l.PRIVATE,children:(H=V.PRIVATE)==null?void 0:H.map(L=>h.jsx(Jd,{channel:L,isActive:(i==null?void 0:i.id)===L.id,hasUnread:I(L.id,L.lastMessageAt),onClick:()=>B(L)},L.id))})]})]}),h.jsx(h1,{children:h.jsx(B0,{user:r})}),h.jsx(d1,{isOpen:f.isOpen,type:f.type,onClose:O,onCreateSuccess:F})]})}const h1=T.div` + margin-top: auto; + border-top: 1px solid ${({theme:r})=>r.colors.border.primary}; + background-color: ${({theme:r})=>r.colors.background.tertiary}; +`,m1=T.div` + flex: 1; + display: flex; + flex-direction: column; + background: ${({theme:r})=>r.colors.background.primary}; +`,g1=T.div` + display: flex; + flex-direction: column; + height: 100%; + background: ${({theme:r})=>r.colors.background.primary}; +`,y1=T(g1)` + justify-content: center; + align-items: center; + flex: 1; + padding: 0 20px; +`,v1=T.div` + text-align: center; + max-width: 400px; + padding: 20px; + margin-bottom: 80px; +`,w1=T.div` + font-size: 48px; + margin-bottom: 16px; + animation: wave 2s infinite; + transform-origin: 70% 70%; + + @keyframes wave { + 0% { transform: rotate(0deg); } + 10% { transform: rotate(14deg); } + 20% { transform: rotate(-8deg); } + 30% { transform: rotate(14deg); } + 40% { transform: rotate(-4deg); } + 50% { transform: rotate(10deg); } + 60% { transform: rotate(0deg); } + 100% { transform: rotate(0deg); } + } +`,x1=T.h2` + color: ${({theme:r})=>r.colors.text.primary}; + font-size: 28px; + font-weight: 700; + margin-bottom: 16px; +`,S1=T.p` + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 16px; + line-height: 1.6; + word-break: keep-all; +`,Zd=T.div` + height: 48px; + padding: 0 16px; + background: ${ee.colors.background.primary}; + border-bottom: 1px solid ${ee.colors.border.primary}; + display: flex; + align-items: center; +`,ep=T.div` + display: flex; + align-items: center; + gap: 8px; + height: 100%; +`,E1=T.div` + display: flex; + align-items: center; + gap: 12px; + height: 100%; +`,C1=T(Tr)` + width: 24px; + height: 24px; +`;T.img` + width: 24px; + height: 24px; + border-radius: 50%; +`;const k1=T.div` + position: relative; + width: 40px; + height: 24px; + flex-shrink: 0; +`,j1=T(Io)` + border-color: ${ee.colors.background.primary}; + bottom: -3px; + right: -3px; +`,A1=T.div` + font-size: 12px; + color: ${ee.colors.text.muted}; + line-height: 13px; +`,tp=T.div` + font-weight: bold; + color: ${ee.colors.text.primary}; + line-height: 20px; + font-size: 16px; +`,R1=T.div` + flex: 1; + display: flex; + flex-direction: column-reverse; + overflow-y: auto; +`,P1=T.div` + padding: 16px; + display: flex; + flex-direction: column; +`,_1=T.div` + margin-bottom: 16px; + display: flex; + align-items: flex-start; +`,T1=T(Tr)` + margin-right: 16px; + width: 40px; + height: 40px; +`;T.img` + width: 40px; + height: 40px; + border-radius: 50%; +`;const I1=T.div` + display: flex; + align-items: center; + margin-bottom: 4px; +`,N1=T.span` + font-weight: bold; + color: ${ee.colors.text.primary}; + margin-right: 8px; +`,O1=T.span` + font-size: 0.75rem; + color: ${ee.colors.text.muted}; +`,L1=T.div` + color: ${ee.colors.text.secondary}; + margin-top: 4px; +`,D1=T.form` + display: flex; + align-items: center; + gap: 8px; + padding: 16px; + background: ${({theme:r})=>r.colors.background.secondary}; +`,M1=T.textarea` + flex: 1; + padding: 12px; + background: ${({theme:r})=>r.colors.background.tertiary}; + border: none; + border-radius: 4px; + color: ${({theme:r})=>r.colors.text.primary}; + font-size: 14px; + resize: none; + min-height: 44px; + max-height: 144px; + + &:focus { + outline: none; + } + + &::placeholder { + color: ${({theme:r})=>r.colors.text.muted}; + } +`,z1=T.button` + background: none; + border: none; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 24px; + cursor: pointer; + padding: 4px 8px; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + color: ${({theme:r})=>r.colors.text.primary}; + } +`;T.div` + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: ${ee.colors.text.muted}; + font-size: 16px; + font-weight: 500; + padding: 20px; + text-align: center; +`;const np=T.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 8px; + width: 100%; +`,U1=T.a` + display: block; + border-radius: 4px; + overflow: hidden; + max-width: 300px; + + img { + width: 100%; + height: auto; + display: block; + } +`,F1=T.a` + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background: ${({theme:r})=>r.colors.background.tertiary}; + border-radius: 8px; + text-decoration: none; + width: fit-content; + + &:hover { + background: ${({theme:r})=>r.colors.background.hover}; + } +`,B1=T.div` + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + font-size: 40px; + color: #0B93F6; +`,$1=T.div` + display: flex; + flex-direction: column; + gap: 2px; +`,H1=T.span` + font-size: 14px; + color: #0B93F6; + font-weight: 500; +`,b1=T.span` + font-size: 13px; + color: ${({theme:r})=>r.colors.text.muted}; +`,V1=T.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 8px 0; +`,ih=T.div` + position: relative; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: ${({theme:r})=>r.colors.background.tertiary}; + border-radius: 4px; + max-width: 300px; +`,W1=T(ih)` + padding: 0; + overflow: hidden; + width: 200px; + height: 120px; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } +`,Y1=T.div` + color: #0B93F6; + font-size: 20px; +`,q1=T.div` + font-size: 13px; + color: ${({theme:r})=>r.colors.text.primary}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`,rp=T.button` + position: absolute; + top: -6px; + right: -6px; + width: 20px; + height: 20px; + border-radius: 50%; + background: ${({theme:r})=>r.colors.background.secondary}; + border: none; + color: ${({theme:r})=>r.colors.text.muted}; + font-size: 16px; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + padding: 0; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + + &:hover { + color: ${({theme:r})=>r.colors.text.primary}; + } +`;function Q1({channel:r}){var x;const i=vt(v=>v.currentUserId),s=nn(v=>v.users),l=Yn(v=>v.binaryContents);if(!r)return null;if(r.type==="PUBLIC")return h.jsx(Zd,{children:h.jsx(ep,{children:h.jsxs(tp,{children:["# ",r.name]})})});const c=r.participants.map(v=>s.find(S=>S.id===v.id)).filter(Boolean),f=c.filter(v=>v.id!==i),p=c.length>2,g=c.filter(v=>v.id!==i).map(v=>v.username).join(", ");return h.jsx(Zd,{children:h.jsx(ep,{children:h.jsxs(E1,{children:[p?h.jsx(k1,{children:f.slice(0,2).map((v,S)=>{var A;return h.jsx(rn,{src:v.profile?(A=l[v.profile.id])==null?void 0:A.url:zt,style:{position:"absolute",left:S*16,zIndex:2-S,width:"24px",height:"24px"}},v.id)})}):h.jsxs(C1,{children:[h.jsx(rn,{src:f[0].profile?(x=l[f[0].profile.id])==null?void 0:x.url:zt}),h.jsx(j1,{$online:f[0].online})]}),h.jsxs("div",{children:[h.jsx(tp,{children:g}),p&&h.jsxs(A1,{children:["멤버 ",c.length,"명"]})]})]})})})}const G1=async(r,i)=>{var l;return(await Je.get("/messages",{params:{channelId:r,page:i==null?void 0:i.page,size:i==null?void 0:i.size,sort:(l=i==null?void 0:i.sort)==null?void 0:l.join(",")}})).data},K1=async(r,i)=>{const s=new FormData,l={content:r.content,channelId:r.channelId,authorId:r.authorId};return s.append("messageCreateRequest",new Blob([JSON.stringify(l)],{type:"application/json"})),i&&i.length>0&&i.forEach(f=>{s.append("attachments",f)}),(await Je.post("/messages",s,{headers:{"Content-Type":"multipart/form-data"}})).data},Nu={page:0,size:50,sort:["createdAt,desc"]},sh=_r((r,i)=>({messages:[],pollingIntervals:{},lastMessageId:null,pagination:{currentPage:0,pageSize:50,hasNext:!1},fetchMessages:async(s,l=Nu)=>{try{const c=await G1(s,l),f=c.content,p=f.length>0?f[0]:null,g=(p==null?void 0:p.id)!==i().lastMessageId;return r(x=>{var _;const v=l.page===0,S=s!==((_=x.messages[0])==null?void 0:_.channelId),A=v&&(x.messages.length===0||S);let R=[],I={...x.pagination};if(A)R=f,I={currentPage:c.number,pageSize:c.size,hasNext:c.hasNext};else if(v){const C=new Set(x.messages.map(F=>F.id));R=[...f.filter(F=>!C.has(F.id)&&(x.messages.length===0||F.createdAt>x.messages[0].createdAt)),...x.messages]}else{if(x.messages.length>0){const C=new Set(x.messages.map(F=>F.id)),O=f.filter(F=>!C.has(F.id)&&F.createdAt{const{pagination:l}=i();if(!l.hasNext)return;const c={...Nu,page:l.currentPage+1};await i().fetchMessages(s,c)},startPolling:s=>{const l=i();if(l.pollingIntervals[s]){const g=l.pollingIntervals[s];typeof g=="number"&&clearTimeout(g)}let c=300;const f=3e3;r(g=>({pollingIntervals:{...g.pollingIntervals,[s]:!0}}));const p=async()=>{const g=i();if(!g.pollingIntervals[s])return;if(await g.fetchMessages(s,Nu)?c=300:c=Math.min(c*1.5,f),i().pollingIntervals[s]){const v=setTimeout(p,c);r(S=>({pollingIntervals:{...S.pollingIntervals,[s]:v}}))}};p()},stopPolling:s=>{const{pollingIntervals:l}=i();if(l[s]){const c=l[s];typeof c=="number"&&clearTimeout(c),r(f=>{const p={...f.pollingIntervals};return delete p[s],{pollingIntervals:p}})}},createMessage:async(s,l)=>{try{const c=await K1(s,l),f=jo.getState().updateReadStatus;return await f(s.channelId),r(p=>p.messages.some(x=>x.id===c.id)?p:{messages:[c,...p.messages],lastMessageId:c.id}),c}catch(c){throw console.error("메시지 생성 실패:",c),c}}}));function X1({channel:r}){const[i,s]=ie.useState(""),[l,c]=ie.useState([]),f=sh(R=>R.createMessage),p=vt(R=>R.currentUserId),g=async R=>{if(R.preventDefault(),!(!i.trim()&&l.length===0))try{await f({content:i.trim(),channelId:r.id,authorId:p??""},l),s(""),c([])}catch(I){console.error("메시지 전송 실패:",I)}},x=R=>{const I=Array.from(R.target.files||[]);c(_=>[..._,...I]),R.target.value=""},v=R=>{c(I=>I.filter((_,C)=>C!==R))},S=R=>{if(R.key==="Enter"&&!R.shiftKey){if(console.log("Enter key pressed"),R.preventDefault(),R.nativeEvent.isComposing)return;g(R)}},A=(R,I)=>R.type.startsWith("image/")?h.jsxs(W1,{children:[h.jsx("img",{src:URL.createObjectURL(R),alt:R.name}),h.jsx(rp,{onClick:()=>v(I),children:"×"})]},I):h.jsxs(ih,{children:[h.jsx(Y1,{children:"📎"}),h.jsx(q1,{children:R.name}),h.jsx(rp,{onClick:()=>v(I),children:"×"})]},I);return ie.useEffect(()=>()=>{l.forEach(R=>{R.type.startsWith("image/")&&URL.revokeObjectURL(URL.createObjectURL(R))})},[l]),r?h.jsxs(h.Fragment,{children:[l.length>0&&h.jsx(V1,{children:l.map((R,I)=>A(R,I))}),h.jsxs(D1,{onSubmit:g,children:[h.jsxs(z1,{as:"label",children:["+",h.jsx("input",{type:"file",multiple:!0,onChange:x,style:{display:"none"}})]}),h.jsx(M1,{value:i,onChange:R=>s(R.target.value),onKeyDown:S,placeholder:r.type==="PUBLIC"?`#${r.name}에 메시지 보내기`:"메시지 보내기"})]})]}):null}/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */var Gu=function(r,i){return Gu=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(s,l){s.__proto__=l}||function(s,l){for(var c in l)l.hasOwnProperty(c)&&(s[c]=l[c])},Gu(r,i)};function J1(r,i){Gu(r,i);function s(){this.constructor=r}r.prototype=i===null?Object.create(i):(s.prototype=i.prototype,new s)}var Ao=function(){return Ao=Object.assign||function(i){for(var s,l=1,c=arguments.length;lr?I():i!==!0&&(c=setTimeout(l?_:I,l===void 0?r-A:r))}return v.cancel=x,v}var Sr={Pixel:"Pixel",Percent:"Percent"},op={unit:Sr.Percent,value:.8};function ip(r){return typeof r=="number"?{unit:Sr.Percent,value:r*100}:typeof r=="string"?r.match(/^(\d*(\.\d+)?)px$/)?{unit:Sr.Pixel,value:parseFloat(r)}:r.match(/^(\d*(\.\d+)?)%$/)?{unit:Sr.Percent,value:parseFloat(r)}:(console.warn('scrollThreshold format is invalid. Valid formats: "120px", "50%"...'),op):(console.warn("scrollThreshold should be string or number"),op)}var ew=function(r){J1(i,r);function i(s){var l=r.call(this,s)||this;return l.lastScrollTop=0,l.actionTriggered=!1,l.startY=0,l.currentY=0,l.dragging=!1,l.maxPullDownDistance=0,l.getScrollableTarget=function(){return l.props.scrollableTarget instanceof HTMLElement?l.props.scrollableTarget:typeof l.props.scrollableTarget=="string"?document.getElementById(l.props.scrollableTarget):(l.props.scrollableTarget===null&&console.warn(`You are trying to pass scrollableTarget but it is null. This might + happen because the element may not have been added to DOM yet. + See https://github.com/ankeetmaini/react-infinite-scroll-component/issues/59 for more info. + `),null)},l.onStart=function(c){l.lastScrollTop||(l.dragging=!0,c instanceof MouseEvent?l.startY=c.pageY:c instanceof TouchEvent&&(l.startY=c.touches[0].pageY),l.currentY=l.startY,l._infScroll&&(l._infScroll.style.willChange="transform",l._infScroll.style.transition="transform 0.2s cubic-bezier(0,0,0.31,1)"))},l.onMove=function(c){l.dragging&&(c instanceof MouseEvent?l.currentY=c.pageY:c instanceof TouchEvent&&(l.currentY=c.touches[0].pageY),!(l.currentY=Number(l.props.pullDownToRefreshThreshold)&&l.setState({pullToRefreshThresholdBreached:!0}),!(l.currentY-l.startY>l.maxPullDownDistance*1.5)&&l._infScroll&&(l._infScroll.style.overflow="visible",l._infScroll.style.transform="translate3d(0px, "+(l.currentY-l.startY)+"px, 0px)")))},l.onEnd=function(){l.startY=0,l.currentY=0,l.dragging=!1,l.state.pullToRefreshThresholdBreached&&(l.props.refreshFunction&&l.props.refreshFunction(),l.setState({pullToRefreshThresholdBreached:!1})),requestAnimationFrame(function(){l._infScroll&&(l._infScroll.style.overflow="auto",l._infScroll.style.transform="none",l._infScroll.style.willChange="unset")})},l.onScrollListener=function(c){typeof l.props.onScroll=="function"&&setTimeout(function(){return l.props.onScroll&&l.props.onScroll(c)},0);var f=l.props.height||l._scrollableNode?c.target:document.documentElement.scrollTop?document.documentElement:document.body;if(!l.actionTriggered){var p=l.props.inverse?l.isElementAtTop(f,l.props.scrollThreshold):l.isElementAtBottom(f,l.props.scrollThreshold);p&&l.props.hasMore&&(l.actionTriggered=!0,l.setState({showLoader:!0}),l.props.next&&l.props.next()),l.lastScrollTop=f.scrollTop}},l.state={showLoader:!1,pullToRefreshThresholdBreached:!1,prevDataLength:s.dataLength},l.throttledOnScrollListener=Z1(150,l.onScrollListener).bind(l),l.onStart=l.onStart.bind(l),l.onMove=l.onMove.bind(l),l.onEnd=l.onEnd.bind(l),l}return i.prototype.componentDidMount=function(){if(typeof this.props.dataLength>"u")throw new Error('mandatory prop "dataLength" is missing. The prop is needed when loading more content. Check README.md for usage');if(this._scrollableNode=this.getScrollableTarget(),this.el=this.props.height?this._infScroll:this._scrollableNode||window,this.el&&this.el.addEventListener("scroll",this.throttledOnScrollListener),typeof this.props.initialScrollY=="number"&&this.el&&this.el instanceof HTMLElement&&this.el.scrollHeight>this.props.initialScrollY&&this.el.scrollTo(0,this.props.initialScrollY),this.props.pullDownToRefresh&&this.el&&(this.el.addEventListener("touchstart",this.onStart),this.el.addEventListener("touchmove",this.onMove),this.el.addEventListener("touchend",this.onEnd),this.el.addEventListener("mousedown",this.onStart),this.el.addEventListener("mousemove",this.onMove),this.el.addEventListener("mouseup",this.onEnd),this.maxPullDownDistance=this._pullDown&&this._pullDown.firstChild&&this._pullDown.firstChild.getBoundingClientRect().height||0,this.forceUpdate(),typeof this.props.refreshFunction!="function"))throw new Error(`Mandatory prop "refreshFunction" missing. + Pull Down To Refresh functionality will not work + as expected. Check README.md for usage'`)},i.prototype.componentWillUnmount=function(){this.el&&(this.el.removeEventListener("scroll",this.throttledOnScrollListener),this.props.pullDownToRefresh&&(this.el.removeEventListener("touchstart",this.onStart),this.el.removeEventListener("touchmove",this.onMove),this.el.removeEventListener("touchend",this.onEnd),this.el.removeEventListener("mousedown",this.onStart),this.el.removeEventListener("mousemove",this.onMove),this.el.removeEventListener("mouseup",this.onEnd)))},i.prototype.componentDidUpdate=function(s){this.props.dataLength!==s.dataLength&&(this.actionTriggered=!1,this.setState({showLoader:!1}))},i.getDerivedStateFromProps=function(s,l){var c=s.dataLength!==l.prevDataLength;return c?Ao(Ao({},l),{prevDataLength:s.dataLength}):null},i.prototype.isElementAtTop=function(s,l){l===void 0&&(l=.8);var c=s===document.body||s===document.documentElement?window.screen.availHeight:s.clientHeight,f=ip(l);return f.unit===Sr.Pixel?s.scrollTop<=f.value+c-s.scrollHeight+1:s.scrollTop<=f.value/100+c-s.scrollHeight+1},i.prototype.isElementAtBottom=function(s,l){l===void 0&&(l=.8);var c=s===document.body||s===document.documentElement?window.screen.availHeight:s.clientHeight,f=ip(l);return f.unit===Sr.Pixel?s.scrollTop+c>=s.scrollHeight-f.value:s.scrollTop+c>=f.value/100*s.scrollHeight},i.prototype.render=function(){var s=this,l=Ao({height:this.props.height||"auto",overflow:"auto",WebkitOverflowScrolling:"touch"},this.props.style),c=this.props.hasChildren||!!(this.props.children&&this.props.children instanceof Array&&this.props.children.length),f=this.props.pullDownToRefresh&&this.props.height?{overflow:"auto"}:{};return gt.createElement("div",{style:f,className:"infinite-scroll-component__outerdiv"},gt.createElement("div",{className:"infinite-scroll-component "+(this.props.className||""),ref:function(p){return s._infScroll=p},style:l},this.props.pullDownToRefresh&>.createElement("div",{style:{position:"relative"},ref:function(p){return s._pullDown=p}},gt.createElement("div",{style:{position:"absolute",left:0,right:0,top:-1*this.maxPullDownDistance}},this.state.pullToRefreshThresholdBreached?this.props.releaseToRefreshContent:this.props.pullDownToRefreshContent)),this.props.children,!this.state.showLoader&&!c&&this.props.hasMore&&this.props.loader,this.state.showLoader&&this.props.hasMore&&this.props.loader,!this.props.hasMore&&this.props.endMessage))},i}(ie.Component);const tw=r=>r<1024?r+" B":r<1024*1024?(r/1024).toFixed(2)+" KB":r<1024*1024*1024?(r/(1024*1024)).toFixed(2)+" MB":(r/(1024*1024*1024)).toFixed(2)+" GB";function nw({channel:r}){const{messages:i,fetchMessages:s,loadMoreMessages:l,pagination:c,startPolling:f,stopPolling:p}=sh(),{binaryContents:g,fetchBinaryContent:x}=Yn();ie.useEffect(()=>{if(r!=null&&r.id)return s(r.id),f(r.id),()=>{p(r.id)}},[r==null?void 0:r.id,s,f,p]),ie.useEffect(()=>{i.forEach(I=>{var _;(_=I.attachments)==null||_.forEach(C=>{g[C.id]||x(C.id)})})},[i,g,x]);const v=async I=>{try{const{url:_,fileName:C}=I,O=document.createElement("a");O.href=_,O.download=C,O.style.display="none",document.body.appendChild(O);try{const B=await(await window.showSaveFilePicker({suggestedName:I.fileName,types:[{description:"Files",accept:{"*/*":[".txt",".pdf",".doc",".docx",".xls",".xlsx",".jpg",".jpeg",".png",".gif"]}}]})).createWritable(),Q=await(await fetch(_)).blob();await B.write(Q),await B.close()}catch(F){F.name!=="AbortError"&&O.click()}document.body.removeChild(O),window.URL.revokeObjectURL(_)}catch(_){console.error("파일 다운로드 실패:",_)}},S=I=>I!=null&&I.length?I.map(_=>{const C=g[_.id];return C?C.contentType.startsWith("image/")?h.jsx(np,{children:h.jsx(U1,{href:"#",onClick:F=>{F.preventDefault(),v(C)},children:h.jsx("img",{src:C.url,alt:C.fileName})})},C.url):h.jsx(np,{children:h.jsxs(F1,{href:"#",onClick:F=>{F.preventDefault(),v(C)},children:[h.jsx(B1,{children:h.jsxs("svg",{width:"40",height:"40",viewBox:"0 0 40 40",fill:"none",children:[h.jsx("path",{d:"M8 3C8 1.89543 8.89543 1 10 1H22L32 11V37C32 38.1046 31.1046 39 30 39H10C8.89543 39 8 38.1046 8 37V3Z",fill:"#0B93F6",fillOpacity:"0.1"}),h.jsx("path",{d:"M22 1L32 11H24C22.8954 11 22 10.1046 22 9V1Z",fill:"#0B93F6",fillOpacity:"0.3"}),h.jsx("path",{d:"M13 19H27M13 25H27M13 31H27",stroke:"#0B93F6",strokeWidth:"2",strokeLinecap:"round"})]})}),h.jsxs($1,{children:[h.jsx(H1,{children:C.fileName}),h.jsx(b1,{children:tw(C.size)})]})]})},C.url):null}):null,A=I=>new Date(I).toLocaleTimeString(),R=()=>{r!=null&&r.id&&l(r.id)};return h.jsx(R1,{children:h.jsx("div",{id:"scrollableDiv",style:{height:"100%",overflow:"auto",display:"flex",flexDirection:"column-reverse"},children:h.jsx(ew,{dataLength:i.length,next:R,hasMore:c.hasNext,loader:h.jsx("h4",{style:{textAlign:"center"},children:"메시지를 불러오는 중..."}),scrollableTarget:"scrollableDiv",style:{display:"flex",flexDirection:"column-reverse"},inverse:!0,endMessage:h.jsx("p",{style:{textAlign:"center"},children:h.jsx("b",{children:c.currentPage>0?"모든 메시지를 불러왔습니다":""})}),children:h.jsx(P1,{children:[...i].reverse().map(I=>{var C;const _=I.author;return h.jsxs(_1,{children:[h.jsx(T1,{children:h.jsx(rn,{src:_&&_.profile?(C=g[_.profile.id])==null?void 0:C.url:zt,alt:_&&_.username||"알 수 없음"})}),h.jsxs("div",{children:[h.jsxs(I1,{children:[h.jsx(N1,{children:_&&_.username||"알 수 없음"}),h.jsx(O1,{children:A(I.createdAt)})]}),h.jsx(L1,{children:I.content}),S(I.attachments)]})]},I.id)})})})})})}function rw({channel:r}){return r?h.jsxs(m1,{children:[h.jsx(Q1,{channel:r}),h.jsx(nw,{channel:r}),h.jsx(X1,{channel:r})]}):h.jsx(y1,{children:h.jsxs(v1,{children:[h.jsx(w1,{children:"👋"}),h.jsx(x1,{children:"채널을 선택해주세요"}),h.jsxs(S1,{children:["왼쪽의 채널 목록에서 채널을 선택하여",h.jsx("br",{}),"대화를 시작하세요."]})]})})}function ow(r,i="yyyy-MM-dd HH:mm:ss"){if(!r||!(r instanceof Date)||isNaN(r.getTime()))return"";const s=r.getFullYear(),l=String(r.getMonth()+1).padStart(2,"0"),c=String(r.getDate()).padStart(2,"0"),f=String(r.getHours()).padStart(2,"0"),p=String(r.getMinutes()).padStart(2,"0"),g=String(r.getSeconds()).padStart(2,"0");return i.replace("yyyy",s.toString()).replace("MM",l).replace("dd",c).replace("HH",f).replace("mm",p).replace("ss",g)}const iw=T.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +`,sw=T.div` + background: ${({theme:r})=>r.colors.background.primary}; + border-radius: 8px; + width: 500px; + max-width: 90%; + padding: 24px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +`,lw=T.div` + display: flex; + align-items: center; + margin-bottom: 16px; +`,uw=T.div` + color: ${({theme:r})=>r.colors.status.error}; + font-size: 24px; + margin-right: 12px; +`,aw=T.h3` + color: ${({theme:r})=>r.colors.text.primary}; + margin: 0; + font-size: 18px; +`,cw=T.div` + background: ${({theme:r})=>r.colors.background.tertiary}; + color: ${({theme:r})=>r.colors.text.muted}; + padding: 2px 8px; + border-radius: 4px; + font-size: 14px; + margin-left: auto; +`,fw=T.p` + color: ${({theme:r})=>r.colors.text.secondary}; + margin-bottom: 20px; + line-height: 1.5; + font-weight: 500; +`,dw=T.div` + margin-bottom: 20px; + background: ${({theme:r})=>r.colors.background.secondary}; + border-radius: 6px; + padding: 12px; +`,wo=T.div` + display: flex; + margin-bottom: 8px; + font-size: 14px; +`,xo=T.span` + color: ${({theme:r})=>r.colors.text.muted}; + min-width: 100px; +`,So=T.span` + color: ${({theme:r})=>r.colors.text.secondary}; + word-break: break-word; +`,pw=T.button` + background: ${({theme:r})=>r.colors.brand.primary}; + color: white; + border: none; + border-radius: 4px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + width: 100%; + + &:hover { + background: ${({theme:r})=>r.colors.brand.hover}; + } +`;function hw({isOpen:r,onClose:i,error:s}){var R,I;if(!r)return null;const l=(R=s==null?void 0:s.response)==null?void 0:R.data,c=(l==null?void 0:l.status)||((I=s==null?void 0:s.response)==null?void 0:I.status)||"오류",f=(l==null?void 0:l.code)||"",p=(l==null?void 0:l.message)||(s==null?void 0:s.message)||"알 수 없는 오류가 발생했습니다.",g=l!=null&&l.timestamp?new Date(l.timestamp):new Date,x=ow(g),v=(l==null?void 0:l.exceptionType)||"",S=(l==null?void 0:l.details)||{},A=(l==null?void 0:l.requestId)||"";return h.jsx(iw,{onClick:i,children:h.jsxs(sw,{onClick:_=>_.stopPropagation(),children:[h.jsxs(lw,{children:[h.jsx(uw,{children:"⚠️"}),h.jsx(aw,{children:"오류가 발생했습니다"}),h.jsxs(cw,{children:[c,f?` (${f})`:""]})]}),h.jsx(fw,{children:p}),h.jsxs(dw,{children:[h.jsxs(wo,{children:[h.jsx(xo,{children:"시간:"}),h.jsx(So,{children:x})]}),A&&h.jsxs(wo,{children:[h.jsx(xo,{children:"요청 ID:"}),h.jsx(So,{children:A})]}),f&&h.jsxs(wo,{children:[h.jsx(xo,{children:"에러 코드:"}),h.jsx(So,{children:f})]}),v&&h.jsxs(wo,{children:[h.jsx(xo,{children:"예외 유형:"}),h.jsx(So,{children:v})]}),Object.keys(S).length>0&&h.jsxs(wo,{children:[h.jsx(xo,{children:"상세 정보:"}),h.jsx(So,{children:Object.entries(S).map(([_,C])=>h.jsxs("div",{children:[_,": ",String(C)]},_))})]})]}),h.jsx(pw,{onClick:i,children:"확인"})]})})}const mw=T.div` + width: 240px; + background: ${ee.colors.background.secondary}; + border-left: 1px solid ${ee.colors.border.primary}; +`,gw=T.div` + padding: 16px; + font-size: 14px; + font-weight: bold; + color: ${ee.colors.text.muted}; + text-transform: uppercase; +`,yw=T.div` + padding: 8px 16px; + display: flex; + align-items: center; + color: ${ee.colors.text.muted}; +`,vw=T(Tr)` + margin-right: 12px; +`;T(rn)``;const ww=T.div` + display: flex; + align-items: center; +`;function xw({member:r}){var l,c,f;const{binaryContents:i,fetchBinaryContent:s}=Yn();return ie.useEffect(()=>{var p;(p=r.profile)!=null&&p.id&&!i[r.profile.id]&&s(r.profile.id)},[(l=r.profile)==null?void 0:l.id,i,s]),h.jsxs(yw,{children:[h.jsxs(vw,{children:[h.jsx(rn,{src:(c=r.profile)!=null&&c.id&&((f=i[r.profile.id])==null?void 0:f.url)||zt,alt:r.username}),h.jsx(Io,{$online:r.online})]}),h.jsx(ww,{children:r.username})]})}function Sw(){const r=nn(c=>c.users),i=nn(c=>c.fetchUsers),s=vt(c=>c.currentUserId);ie.useEffect(()=>{i()},[i]);const l=[...r].sort((c,f)=>c.id===s?-1:f.id===s?1:c.online&&!f.online?-1:!c.online&&f.online?1:c.username.localeCompare(f.username));return h.jsxs(mw,{children:[h.jsxs(gw,{children:["멤버 목록 - ",r.length]}),l.map(c=>h.jsx(xw,{member:c},c.id))]})}function Ew(){const r=vt(C=>C.currentUserId),i=vt(C=>C.logout),s=nn(C=>C.users),{fetchUsers:l,updateUserStatus:c}=nn(),[f,p]=ie.useState(null),[g,x]=ie.useState(null),[v,S]=ie.useState(!1),[A,R]=ie.useState(!0),I=r?s.find(C=>C.id===r):null;ie.useEffect(()=>{(async()=>{try{if(r)try{await c(r),await l()}catch(O){console.warn("사용자 상태 업데이트 실패. 로그아웃합니다.",O),i()}}catch(O){console.error("초기화 오류:",O)}finally{R(!1)}})()},[r,c,l,i]),ie.useEffect(()=>{const C=V=>{x(V),S(!0)},O=()=>{i()},F=as.on("api-error",C),B=as.on("auth-error",O);return()=>{F("api-error",C),B("auth-error",O)}},[i]),ie.useEffect(()=>{let C;if(r){c(r),C=setInterval(()=>{c(r)},3e4);const O=setInterval(()=>{l()},6e4);return()=>{clearInterval(C),clearInterval(O)}}},[r,l,c]);const _=()=>{S(!1),x(null)};return A?h.jsx(Cd,{theme:ee,children:h.jsx(kw,{children:h.jsx(jw,{})})}):h.jsxs(Cd,{theme:ee,children:[I?h.jsxs(Cw,{children:[h.jsx(p1,{currentUser:I,activeChannel:f,onChannelSelect:p}),h.jsx(rw,{channel:f}),h.jsx(Sw,{})]}):h.jsx(h0,{isOpen:!0,onClose:()=>{}}),h.jsx(hw,{isOpen:v,onClose:_,error:g})]})}const Cw=T.div` + display: flex; + height: 100vh; + width: 100vw; + position: relative; +`,kw=T.div` + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + width: 100vw; + background-color: ${({theme:r})=>r.colors.background.primary}; +`,jw=T.div` + width: 40px; + height: 40px; + border: 4px solid ${({theme:r})=>r.colors.background.tertiary}; + border-top: 4px solid ${({theme:r})=>r.colors.brand.primary}; + border-radius: 50%; + animation: spin 1s linear infinite; + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } +`,lh=document.getElementById("root");if(!lh)throw new Error("Root element not found");hg.createRoot(lh).render(h.jsx(ie.StrictMode,{children:h.jsx(Ew,{})})); diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 66e849757..94d9533b4 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -17,7 +17,7 @@ line-height: 1.4; } - + From 89610669ed338db856e736c73e95d23fd8e591a8 Mon Sep 17 00:00:00 2001 From: JunSuHwang <85167454+JunSuHwang@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:36:55 +0900 Subject: [PATCH 45/48] =?UTF-8?q?fix:=20ReadStatus=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=20request=EC=9D=98=20lastReadAt=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#3?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mission/discodeit/readstatus/entity/ReadStatus.java | 8 ++------ .../discodeit/readstatus/service/ReadStatusService.java | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java index 74d7edfa0..a011a4928 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/entity/ReadStatus.java @@ -10,12 +10,14 @@ import jakarta.persistence.Table; import java.time.Instant; import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor @Table(name = "read_statuses") public class ReadStatus extends BaseUpdatableEntity { @@ -30,12 +32,6 @@ public class ReadStatus extends BaseUpdatableEntity { @Column(nullable = false) private Instant lastReadAt; - public ReadStatus(User user, Channel channel) { - this.user = user; - this.channel = channel; - this.lastReadAt = Instant.now(); - } - public void update(Instant newLastReadAt) { boolean anyValueUpdated = false; if (newLastReadAt != null && !newLastReadAt.equals(this.lastReadAt)) { diff --git a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java index cad4fd9c7..3f48ccf87 100644 --- a/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/readstatus/service/ReadStatusService.java @@ -41,7 +41,7 @@ public ReadStatusDto createReadStatus(ReadStatusCreateRequest request) { throw new ReadStatusDuplicationException(); } - ReadStatus readStatus = new ReadStatus(user, channel); + ReadStatus readStatus = new ReadStatus(user, channel, request.lastReadAt()); readStatusRepository.save(readStatus); return readStatusMapper.toDto(readStatus); } From 5821db4c7e33628406866b5af31bbbd03c483aab Mon Sep 17 00:00:00 2001 From: JunSuHwang <85167454+JunSuHwang@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:41:43 +0900 Subject: [PATCH 46/48] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20join,=20leave=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 불필요한 join, leave 메서드 제거 readstatus 생성시 channel의 생성 시점을 주입하도록 변경 --- .../channel/service/BasicChannelService.java | 32 ++++--------------- .../channel/service/ChannelService.java | 4 --- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java index 2e087d7f0..5ad221d31 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/BasicChannelService.java @@ -17,6 +17,7 @@ import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; +import java.time.Instant; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -45,7 +46,12 @@ public ChannelDto createPublicChannel(PublicChannelCreateRequest request) { public ChannelDto createPrivateChannel(PrivateChannelCreateRequest request) { Channel channel = channelMapper.toEntity(request); channelRepository.save(channel); - request.participantIds().forEach(userId -> joinChannel(channel.getId(), userId)); + request.participantIds().forEach( + userId -> { + User findUser = userRepository.findById(userId) + .orElseThrow(UserNotFoundException::new); + readStatusRepository.save(new ReadStatus(findUser, channel, channel.getCreatedAt())); + }); return channelMapper.toDto(channel); } @@ -96,30 +102,6 @@ public void deleteChannel(UUID channelId) { channelRepository.deleteById(channelId); } - @Transactional - @Override - public void joinChannel(UUID channelId, UUID userId) { - Channel channel = channelRepository.findById(channelId) - .orElseThrow(ChannelNotFoundException::new); - User user = userRepository.findById(userId) - .orElseThrow(UserNotFoundException::new); - - readStatusRepository.save(new ReadStatus(user, channel)); - } - - @Transactional - @Override - public void leaveChannel(UUID channelId, UUID userId) { - channelRepository.findById(channelId) - .orElseThrow(ChannelNotFoundException::new); - userRepository.findById(userId) - .orElseThrow(UserNotFoundException::new); - ReadStatus findReadStatus = readStatusRepository.findByUserIdAndChannelId(userId, channelId) - .orElseThrow(ReadStatusNotFoundException::new); - - readStatusRepository.deleteById(findReadStatus.getId()); - } - private void validateChannelExist(String name) { if (channelRepository.existsByName(name)) { throw new ChannelDuplicationException(); diff --git a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java index 7b8db87fd..2c99b7587 100644 --- a/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/channel/service/ChannelService.java @@ -22,8 +22,4 @@ public interface ChannelService { ChannelDto updateChannel(UUID channelId, PublicChannelUpdateRequest channelInfo); void deleteChannel(UUID channelId); - - void joinChannel(UUID channelId, UUID userId); - - void leaveChannel(UUID channelId, UUID userId); } From ea2c641e8a11ddf429e48b85448f222460a9ae84 Mon Sep 17 00:00:00 2001 From: JunSuHwang <85167454+JunSuHwang@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:05:12 +0900 Subject: [PATCH 47/48] =?UTF-8?q?fix:=20MessageDto=EC=9D=98=20attachments?= =?UTF-8?q?=20=EC=9A=94=EC=86=8C=EA=B0=80=20dto=20=ED=98=95=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=ED=99=98=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MessageMapper의 Mapper uses에 BinaryMapper 추가 --- .../sprint/mission/discodeit/message/mapper/MessageMapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java index 753dfbd0d..3b72bb6ea 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java +++ b/src/main/java/com/sprint/mission/discodeit/message/mapper/MessageMapper.java @@ -1,12 +1,13 @@ package com.sprint.mission.discodeit.message.mapper; +import com.sprint.mission.discodeit.binarycontent.mapper.BinaryContentMapper; import com.sprint.mission.discodeit.message.dto.MessageDto; import com.sprint.mission.discodeit.message.entity.Message; import com.sprint.mission.discodeit.user.mapper.UserMapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@Mapper(componentModel = "spring", uses = UserMapper.class) +@Mapper(componentModel = "spring", uses = {UserMapper.class, BinaryContentMapper.class}) public interface MessageMapper { @Mapping(target = "channelId", source = "channel.id") From 0740d493f6b7b64fe04337908b248aa0f48e92e4 Mon Sep 17 00:00:00 2001 From: JunSuHwang <85167454+JunSuHwang@users.noreply.github.com> Date: Wed, 11 Mar 2026 11:09:10 +0900 Subject: [PATCH 48/48] refactor: cursor pagination (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: page dto를 커서 기반으로 리팩토링 * refactor: JPQL 추가 * refactor: controller, service에 cursor 적용 --- .../discodeit/global/dto/MyPageRequest.java | 3 ++- .../discodeit/global/dto/PageResponse.java | 2 +- .../message/controller/MessageController.java | 6 ++--- .../message/repository/MessageRepository.java | 22 ++++++++++++++-- .../message/service/BasicMessageService.java | 26 ++++++++++++++----- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/sprint/mission/discodeit/global/dto/MyPageRequest.java b/src/main/java/com/sprint/mission/discodeit/global/dto/MyPageRequest.java index 85fe01197..abe5f9501 100644 --- a/src/main/java/com/sprint/mission/discodeit/global/dto/MyPageRequest.java +++ b/src/main/java/com/sprint/mission/discodeit/global/dto/MyPageRequest.java @@ -4,7 +4,8 @@ public record MyPageRequest( T t, - Pageable pageable + Pageable pageable, + Object currentCursor ) { } diff --git a/src/main/java/com/sprint/mission/discodeit/global/dto/PageResponse.java b/src/main/java/com/sprint/mission/discodeit/global/dto/PageResponse.java index 77455f8c0..c490adce4 100644 --- a/src/main/java/com/sprint/mission/discodeit/global/dto/PageResponse.java +++ b/src/main/java/com/sprint/mission/discodeit/global/dto/PageResponse.java @@ -4,7 +4,7 @@ public record PageResponse( List content, - int number, + Object nextCursor, int size, boolean hasNext, Long totalElements diff --git a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java index e66c23f0e..ae3c34b06 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java +++ b/src/main/java/com/sprint/mission/discodeit/message/controller/MessageController.java @@ -18,6 +18,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.io.IOException; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -25,10 +26,8 @@ import lombok.RequiredArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.web.PageableDefault; -import org.springframework.data.web.SortDefault; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -162,10 +161,11 @@ public ResponseEntity delete_1( @GetMapping public ResponseEntity> findAllByChannelId( @Parameter(description = "조회할 Channel ID") @RequestParam UUID channelId, + @Parameter(description = "페이징 커서 정보") @RequestParam(required = false) Instant cursor, @ParameterObject @PageableDefault(size = 50, sort = "createdAt", direction = Direction.DESC) Pageable pageable ) { - MyPageRequest messagePagingRequest = new MyPageRequest<>(channelId, pageable); + MyPageRequest messagePagingRequest = new MyPageRequest<>(channelId, pageable, cursor); return ResponseEntity.status(200).body(messageService.findAllByChannelId(messagePagingRequest)); } } diff --git a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java index e205b5213..61c6fe413 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/message/repository/MessageRepository.java @@ -1,18 +1,36 @@ package com.sprint.mission.discodeit.message.repository; import com.sprint.mission.discodeit.message.entity.Message; +import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.UUID; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface MessageRepository extends JpaRepository { List findAllByAuthorId(UUID authorId); - Slice findAllByChannelId(UUID channelId, Pageable pageable); + @Query( + """ + SELECT m + FROM Message m + WHERE m.channel.id = :channelId + ORDER BY m.createdAt DESC + """) + List findFirstPageByChannelId(UUID channelId, Pageable pageable); + + @Query( + """ + SELECT m + FROM Message m + WHERE m.channel.id = :channelId + AND m.createdAt < :cursorCreatedAt + ORDER BY m.createdAt DESC + """) + List findNextPageByChannelId(UUID channelId, Instant cursorCreatedAt, Pageable pageable); Optional findFirstByChannel_IdOrderByCreatedAtDesc(UUID channelId); } diff --git a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java index 727bcb984..56a62ddfe 100644 --- a/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/message/service/BasicMessageService.java @@ -21,11 +21,13 @@ import com.sprint.mission.discodeit.user.entity.User; import com.sprint.mission.discodeit.user.exception.UserNotFoundException; import com.sprint.mission.discodeit.user.repository.UserRepository; +import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Slice; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -87,11 +89,23 @@ public List findAllByUserId(UUID userId) { @Override public PageResponse findAllByChannelId(MyPageRequest request) { - Slice slice = messageRepository.findAllByChannelId(request.t(), - request.pageable()); - Slice messageDtoSlice = slice.map(messageMapper::toDto); - return pageResponseMapper.fromSlice(messageDtoSlice); - + Pageable pageable = PageRequest.of(0, request.pageable().getPageSize() + 1); + List messages = + request.currentCursor() == null + ? messageRepository.findFirstPageByChannelId(request.t(), pageable) + : messageRepository.findNextPageByChannelId(request.t(), + (Instant) request.currentCursor(), + pageable); + boolean hasNext = messages.size() > request.pageable().getPageSize(); + + if (hasNext) { + messages = messages.subList(0, request.pageable().getPageSize()); + } + + Instant nextCursor = hasNext ? messages.get(messages.size() - 1).getCreatedAt() : null; + List messageDtos = toMessageDtoList(messages); + + return new PageResponse<>(messageDtos, nextCursor, messageDtos.size(), hasNext, null); } @Transactional