From 9eb39edc7345acda09859408ecdcf92ea731794e Mon Sep 17 00:00:00 2001 From: snow-sulyoung Date: Fri, 8 May 2026 14:52:58 +0900 Subject: [PATCH] =?UTF-8?q?Revert=20"[=EC=A0=95=EC=9A=B0=EC=A7=84]=20?= =?UTF-8?q?=EC=8A=A4=ED=94=84=EB=A6=B0=ED=8A=B8=20=EB=AF=B8=EC=85=98=204?= =?UTF-8?q?=20"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 183 +++- HELP.md | 10 +- README.md | 5 +- build.gradle | 41 +- discodeit.iml | 8 + gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 9 +- gradlew.bat | 4 +- .../discodeit/DiscodeitApplication.java | 185 +++- .../mission/discodeit/config/WebConfig.java | 28 - .../discodeit/controller/AuthController.java | 26 - .../controller/BinaryContentController.java | 38 - .../controller/ChannelController.java | 65 -- .../controller/MessageController.java | 83 -- .../controller/ReadStatusController.java | 48 - .../discodeit/controller/UserController.java | 111 -- .../discodeit/controller/api/AuthApi.java | 23 - .../controller/api/BinaryContentApi.java | 26 - .../discodeit/controller/api/ChannelApi.java | 56 - .../discodeit/controller/api/MessageApi.java | 50 - .../controller/api/ReadStatusApi.java | 33 - .../discodeit/controller/api/UserApi.java | 66 -- .../discodeit/dto/auth/LoginRequest.java | 7 + .../discodeit/dto/auth/LoginResponse.java | 10 + .../binarycontent/BinaryContentRequest.java | 7 + .../binarycontent/BinaryContentResponse.java | 10 + .../dto/channel/ChannelCreateRequest.java | 10 + .../dto/channel/ChannelResponse.java | 18 + .../dto/channel/ChannelUpdateRequest.java | 10 + .../channel/PrivateChannelCreateRequest.java | 11 + .../discodeit/dto/data/ChannelDto.java | 18 - .../mission/discodeit/dto/data/UserDto.java | 16 - .../dto/message/MessageCreateRequest.java | 13 + .../dto/message/MessageResponse.java | 14 + .../dto/message/MessageUpdateRequest.java | 10 + .../request/BinaryContentCreateRequest.java | 9 - .../discodeit/dto/request/LoginRequest.java | 8 - .../dto/request/MessageCreateRequest.java | 11 - .../dto/request/MessageUpdateRequest.java | 7 - .../request/PrivateChannelCreateRequest.java | 10 - .../request/PublicChannelCreateRequest.java | 8 - .../request/PublicChannelUpdateRequest.java | 8 - .../dto/request/ReadStatusCreateRequest.java | 12 - .../dto/request/ReadStatusUpdateRequest.java | 9 - .../dto/request/UserCreateRequest.java | 9 - .../dto/request/UserStatusCreateRequest.java | 11 - .../dto/request/UserStatusUpdateRequest.java | 9 - .../dto/request/UserUpdateRequest.java | 9 - .../dto/status/ReadStatusResponse.java | 13 + .../dto/status/ReadStatusUpdateRequest.java | 8 + .../dto/status/ReadStatuscreateRequest.java | 9 + .../dto/status/UserStatusCreateRequest.java | 8 + .../dto/status/UserStatusResponse.java | 9 + .../dto/status/UserStatusUpdateRequest.java | 8 + .../discodeit/dto/user/UserCreateRequest.java | 11 + .../discodeit/dto/user/UserResponse.java | 13 + .../discodeit/dto/user/UserUpdateRequest.java | 11 + .../discodeit/entity/BinaryContent.java | 32 +- .../mission/discodeit/entity/Channel.java | 74 +- .../mission/discodeit/entity/ChannelType.java | 4 +- .../mission/discodeit/entity/Message.java | 84 +- .../mission/discodeit/entity/ReadStatus.java | 43 +- .../sprint/mission/discodeit/entity/User.java | 95 +- .../mission/discodeit/entity/UserStatus.java | 50 +- .../exception/GlobalExceptionHandler.java | 35 - .../repository/BinaryContentRepository.java | 11 +- .../repository/ChannelRepository.java | 12 +- .../repository/MessageRepository.java | 13 +- .../repository/ReadStatusRepository.java | 15 +- .../discodeit/repository/UserRepository.java | 17 +- .../repository/UserStatusRepository.java | 15 +- .../file/FileBinaryContentRepository.java | 179 ++-- .../file/FileChannelRepository.java | 177 ++-- .../repository/file/FileLockProvider.java | 16 - .../file/FileMessageRepository.java | 190 ++-- .../file/FileReadStatusRepository.java | 237 ++--- .../repository/file/FileUserRepository.java | 241 ++--- .../file/FileUserStatusRepository.java | 205 ++-- .../jcf/JCFBinaryContentRepository.java | 63 +- .../repository/jcf/JCFChannelRepository.java | 63 +- .../repository/jcf/JCFMessageRepository.java | 77 +- .../jcf/JCFReadStatusRepository.java | 99 +- .../repository/jcf/JCFUserRepository.java | 109 +- .../jcf/JCFUserStatusRepository.java | 78 +- .../discodeit/service/AuthService.java | 7 +- .../service/BinaryContentService.java | 16 +- .../discodeit/service/ChannelService.java | 30 +- .../service/Impl/AuthServiceImpl.java | 33 + .../Impl/BinaryContentServiceImpl.java | 68 ++ .../service/Impl/ChannelServiceImpl.java | 158 +++ .../service/Impl/MessageServiceImpl.java | 103 ++ .../service/Impl/ReadStatusServiceImpl.java | 87 ++ .../service/Impl/UserServiceImpl.java | 124 +++ .../service/Impl/UserStatusServiceImpl.java | 87 ++ .../discodeit/service/MessageService.java | 21 +- .../discodeit/service/ReadStatusService.java | 17 +- .../discodeit/service/UserService.java | 24 +- .../discodeit/service/UserStatusService.java | 24 +- .../service/basic/BasicAuthService.java | 33 - .../basic/BasicBinaryContentService.java | 54 - .../service/basic/BasicChannelService.java | 126 --- .../service/basic/BasicMessageService.java | 100 -- .../service/basic/BasicReadStatusService.java | 80 -- .../service/basic/BasicUserService.java | 144 --- .../service/basic/BasicUserStatusService.java | 84 -- src/main/resources/application.yaml | 14 +- .../resources/static/assets/index-CRrRqFH4.js | 956 ------------------ .../static/assets/index-kQJbKSsj.css | 1 - src/main/resources/static/favicon.ico | Bin 1588 -> 0 bytes src/main/resources/static/index.html | 26 - src/main/resources/static/script.js | 67 -- src/main/resources/static/styles.css | 80 -- src/main/resources/static/user-list.html | 18 - .../discodeit/DiscodeitApplicationTests.java | 6 +- 115 files changed, 2330 insertions(+), 3956 deletions(-) create mode 100644 discodeit.iml delete mode 100644 src/main/java/com/sprint/mission/discodeit/config/WebConfig.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/AuthController.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/MessageController.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/UserController.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/api/AuthApi.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/api/BinaryContentApi.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/api/ChannelApi.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/api/MessageApi.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/api/ReadStatusApi.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/controller/api/UserApi.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/auth/LoginRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/auth/LoginResponse.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/binarycontent/BinaryContentRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/binarycontent/BinaryContentResponse.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelCreateRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelResponse.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelUpdateRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/channel/PrivateChannelCreateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/data/ChannelDto.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/data/UserDto.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/message/MessageCreateRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/message/MessageResponse.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/message/MessageUpdateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/BinaryContentCreateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/LoginRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/MessageUpdateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/PrivateChannelCreateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelCreateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelUpdateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusCreateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusUpdateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/UserCreateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusCreateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusUpdateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/dto/request/UserUpdateRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatusResponse.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatusUpdateRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatuscreateRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusCreateRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusResponse.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusUpdateRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/user/UserCreateRequest.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/user/UserResponse.java create mode 100644 src/main/java/com/sprint/mission/discodeit/dto/user/UserUpdateRequest.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/repository/file/FileLockProvider.java create mode 100644 src/main/java/com/sprint/mission/discodeit/service/Impl/AuthServiceImpl.java create mode 100644 src/main/java/com/sprint/mission/discodeit/service/Impl/BinaryContentServiceImpl.java create mode 100644 src/main/java/com/sprint/mission/discodeit/service/Impl/ChannelServiceImpl.java create mode 100644 src/main/java/com/sprint/mission/discodeit/service/Impl/MessageServiceImpl.java create mode 100644 src/main/java/com/sprint/mission/discodeit/service/Impl/ReadStatusServiceImpl.java create mode 100644 src/main/java/com/sprint/mission/discodeit/service/Impl/UserServiceImpl.java create mode 100644 src/main/java/com/sprint/mission/discodeit/service/Impl/UserStatusServiceImpl.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java delete mode 100644 src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java delete mode 100644 src/main/resources/static/assets/index-CRrRqFH4.js delete mode 100644 src/main/resources/static/assets/index-kQJbKSsj.css delete mode 100644 src/main/resources/static/favicon.ico delete 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/.gitignore b/.gitignore index 43e6c09a..693174ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,145 @@ -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### IntelliJ IDEA ### -.idea +# Created by https://www.toptal.com/developers/gitignore/api/java,windows,intellij+all +# Edit at https://www.toptal.com/developers/gitignore?templates=java,windows,intellij+all + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries +.gradle/ + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format *.iws -*.iml -*.ipr + +# IntelliJ out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### Eclipse ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - -### Mac OS ### -.DS_Store - -### Discodeit ### -.discodeit \ No newline at end of file + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/java,windows,intellij+all \ No newline at end of file diff --git a/HELP.md b/HELP.md index 42c5f002..25ee0b53 100644 --- a/HELP.md +++ b/HELP.md @@ -1,14 +1,17 @@ # Getting Started ### Reference Documentation + For further reference, please consider the following sections: * [Official Gradle documentation](https://docs.gradle.org) -* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/3.4.0/gradle-plugin) -* [Create an OCI image](https://docs.spring.io/spring-boot/3.4.0/gradle-plugin/packaging-oci-image.html) -* [Spring Web](https://docs.spring.io/spring-boot/3.4.0/reference/web/servlet.html) +* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/3.5.13/gradle-plugin) +* [Create an OCI image](https://docs.spring.io/spring-boot/3.5.13/gradle-plugin/packaging-oci-image.html) +* [Spring Boot DevTools](https://docs.spring.io/spring-boot/3.5.13/reference/using/devtools.html) +* [Spring Web](https://docs.spring.io/spring-boot/3.5.13/reference/web/servlet.html) ### Guides + The following guides illustrate how to use some features concretely: * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) @@ -16,6 +19,7 @@ The following guides illustrate how to use some features concretely: * [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) ### Additional Links + These additional references should also help you: * [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) diff --git a/README.md b/README.md index 815bede5..bad90755 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# 0-spring-mission -스프린트 미션 모범 답안 리포지토리입니다. +# README 샘플입니다. + +--- \ No newline at end of file diff --git a/build.gradle b/build.gradle index 20c6762a..2d991724 100644 --- a/build.gradle +++ b/build.gradle @@ -1,40 +1,35 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.4.0' - id 'io.spring.dependency-management' version '1.1.6' + id 'java' + id 'org.springframework.boot' version '3.5.13' + id 'io.spring.dependency-management' version '1.1.7' } group = 'com.sprint.mission' version = '0.0.1-SNAPSHOT' +description = 'discodeit' java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -configurations { - compileOnly { - extendsFrom annotationProcessor - } + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' - implementation 'org.springframework.boot:spring-boot-starter-actuator' - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - developmentOnly 'org.springframework.boot:spring-boot-devtools' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testCompileOnly 'org.projectlombok:lombok' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testAnnotationProcessor 'org.projectlombok:lombok' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/discodeit.iml b/discodeit.iml new file mode 100644 index 00000000..482334b9 --- /dev/null +++ b/discodeit.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 34943 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

Gn4F+B~IivciU9^t0j-Mgao3 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 DDw4oP diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c82..aaaabb3c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d..23d15a93 100644 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a218..db3a6ac2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java b/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java index 8f61230d..8b162e8e 100644 --- a/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java +++ b/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java @@ -1,12 +1,191 @@ package com.sprint.mission.discodeit; +import com.sprint.mission.discodeit.dto.*; +import com.sprint.mission.discodeit.service.*; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.UUID; @SpringBootApplication public class DiscodeitApplication { - public static void main(String[] args) { - SpringApplication.run(DiscodeitApplication.class, args); - } + static List setupUser(UserService userService) { + List users = new ArrayList<>(); + for (int i = 1; i <= 5; i++) { + users.add(userService.create( + new UserCreateRequest( + "woody" + i, + "woody" + i + "@gmail.com", + "Nick" + i, + "1234", + null, + null) + )); + System.out.println( + "유저 생성: " + users.get(i - 1).id() + + "\n닉네임: " + users.get(i - 1).nickname() + ); + System.out.println(); + } + + return users; + } + + static ChannelResponse setupPublicChannel(ChannelService channelService, UUID userId, int index) { + ChannelResponse channel = channelService.createPublicChannel( + new ChannelCreateRequest( + "공지" + index, + userId, + "공지 채널입니다.") + ); + System.out.println( + "채널 이름: " + channel.title() + + "\n채널 생성자: " + channel.userId() + ); + + return channel; + } + + static void setupMessage(MessageService messageService, UUID channelId, UUID userId, int index) { + MessageResponse message = messageService.create( + new MessageCreateRequest( + channelId, + userId, + "메시지 작성" + index, + "안녕하세요." + index, + new ArrayList<>() + ) + ); + System.out.println( + "메시지 생성: " + message.id() + + "\n제목: " + message.title() + + "\n내용: " + message.content() + ); + } + + static void displayAllData(UserService userService, + ChannelService channelService, + MessageService messageService, + UserStatusService userStatusService, + ReadStatusService readStatusService) { + int random = new Random().nextInt(0, 5); + System.out.println("전체 유저: " + userService.findAll().stream().map(UserResponse::nickname).toList()); + List channelList = channelService.findAll(); + System.out.println("전체 채널: " + channelList.stream().map(ChannelResponse::title).toList()); + System.out.println("채널 " + random + 1 + " 소속 메시지: " + messageService.findByChannelId(channelList.get(random).id()).stream().map(MessageResponse::title).toList()); + System.out.println("User Status :" + userStatusService.findAll().stream() + .map(status -> userService.findById(status.userId()).isOnline()) + .toList()); + System.out.println("Read Status :" + readStatusService.finAllByUserId( + userService.findAll().get(random).id()).stream() + .map(status -> userService.findById(status.userId())) + .toList() + ); + } + + static void login(AuthService authService) { + int random = new Random().nextInt(1, 6); + LoginResponse auth = authService.login(new LoginRequest("woody" + random, "1234")); + System.out.println("로그인 성공 " + auth); + + } + + static void updateObject(UserService userService, ChannelService channelService, MessageService messageService) { + int random = new Random().nextInt(0, 5); + UserResponse user = userService.findAll().get(random); + UserUpdateRequest updateUser = new UserUpdateRequest(user.id(), "비밀번호가 안보여서 이름 변경", null, null); + userService.update(updateUser); + System.out.println("유저 " + + user.nickname() + + " 정보 업데이트 결과: " + + userService.findById(user.id()) + ); + + ChannelResponse channel = channelService.findAllByUserId(user.id()).get(random); + ChannelUpdateRequest updateChannel = new ChannelUpdateRequest( + channel.id(), null, "채널 정보 업데이트"); + channelService.update(updateChannel); + System.out.println("채널 " + + channel.title() + + " 정보 업데이트 결과: " + + channelService.findById(channel.id()) + ); + + MessageResponse message = messageService.findByChannelId(channel.id()).get(0); + MessageUpdateRequest updateMessage = new MessageUpdateRequest( + message.id(), null, "메시지 내용 업데이트"); + messageService.update(updateMessage); + System.out.println("채널 " + + message.title() + + " 정보 업데이트 결과: " + + messageService.findByChannelId(channel.id()) + ); + } + + static void deleteData(UserService userService, + ChannelService channelService + ) { + int random = new Random().nextInt(0, 5); + UserResponse user = userService.findAll().get(random); + ChannelResponse channelId = channelService.findAllByUserId(user.id()).get(random); + + System.out.println("유저 " + userService.findAll().get(random).nickname() + " 삭제"); + userService.delete(user.id()); + System.out.println(userService.findAll().stream().map(UserResponse::nickname).toList()); + System.out.println("채널 " + channelService.findAllByUserId(user.id()).get(random).title() + " 삭제"); + channelService.delete(channelId.id()); + System.out.println(channelService.findAll().stream().map(ChannelResponse::title).toList()); + + } + + + public static void main(String[] args) { + ConfigurableApplicationContext context = SpringApplication.run(DiscodeitApplication.class, args); + System.out.println("http://localhost:8080/"); + + System.out.println(Instant.now().toString()); + System.out.println(); + + UserService userService = context.getBean(UserService.class); + ChannelService channelService = context.getBean(ChannelService.class); + MessageService messageService = context.getBean(MessageService.class); + AuthService authService = context.getBean(AuthService.class); + UserStatusService userStatusService = context.getBean(UserStatusService.class); + ReadStatusService readStatusService = context.getBean(ReadStatusService.class); + + System.out.println("======= [1] entity 생성 ======="); + List users = setupUser(userService); + int index = 1; + for (UserResponse user : users) { + ChannelResponse channel = setupPublicChannel(channelService, user.id(), index); + setupMessage(messageService, channel.id(), user.id(), index); + index++; + System.out.println(); + } + + System.out.println("======= [2] 서비스 작동 확인 ======="); + displayAllData(userService, channelService, messageService, userStatusService, readStatusService); + login(authService); + System.out.println(); + + System.out.println("======= [3] 서비스 내용 수정 ======="); + updateObject(userService, channelService, messageService); + System.out.println(); + + System.out.println("======= [4] 데이터 삭제 ======="); + deleteData(userService, channelService); + System.out.println(); + + System.out.println("======= Test 종료 ======="); + + + } } + diff --git a/src/main/java/com/sprint/mission/discodeit/config/WebConfig.java b/src/main/java/com/sprint/mission/discodeit/config/WebConfig.java deleted file mode 100644 index 9f164ab7..00000000 --- a/src/main/java/com/sprint/mission/discodeit/config/WebConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.sprint.mission.discodeit.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import java.util.ArrayList; -import java.util.List; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - - @Override - public void extendMessageConverters(List> converters) { - for (HttpMessageConverter converter : converters) { - if (converter instanceof MappingJackson2HttpMessageConverter jacksonConverter) { - List supportedMediaTypes = new ArrayList<>(jacksonConverter.getSupportedMediaTypes()); - - supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM); - - jacksonConverter.setSupportedMediaTypes(supportedMediaTypes); - } - } - } -} - diff --git a/src/main/java/com/sprint/mission/discodeit/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/controller/AuthController.java deleted file mode 100644 index 4448882f..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/AuthController.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sprint.mission.discodeit.controller; - -import com.sprint.mission.discodeit.controller.api.AuthApi; -import com.sprint.mission.discodeit.dto.request.LoginRequest; -import com.sprint.mission.discodeit.entity.User; -import com.sprint.mission.discodeit.service.AuthService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/auth") -public class AuthController implements AuthApi { - - private final AuthService authService; - - @RequestMapping(path = "/login",method = RequestMethod.POST) - public ResponseEntity login(@RequestBody LoginRequest loginRequest) { - User user = authService.login(loginRequest); - return ResponseEntity - .status(HttpStatus.OK) - .body(user); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java deleted file mode 100644 index 243cbb45..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.sprint.mission.discodeit.controller; - -import com.sprint.mission.discodeit.controller.api.BinaryContentApi; -import com.sprint.mission.discodeit.entity.BinaryContent; -import com.sprint.mission.discodeit.service.BinaryContentService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.UUID; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/binaryContents") -public class BinaryContentController implements BinaryContentApi { - - private final BinaryContentService binaryContentService; - - @RequestMapping(path = "/{binaryContentId}", - method = RequestMethod.GET) - public ResponseEntity find(@PathVariable UUID binaryContentId) { - BinaryContent binaryContent = binaryContentService.find(binaryContentId); - return ResponseEntity - .status(HttpStatus.OK) - .body(binaryContent); - } - - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> findAllByIdIn( - @RequestParam("binaryContentIds") List binaryContentIds) { - List binaryContents = binaryContentService.findAllByIdIn(binaryContentIds); - return ResponseEntity - .status(HttpStatus.OK) - .body(binaryContents); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java deleted file mode 100644 index 4d70d675..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.sprint.mission.discodeit.controller; - -import com.sprint.mission.discodeit.controller.api.ChannelApi; -import com.sprint.mission.discodeit.dto.data.ChannelDto; -import com.sprint.mission.discodeit.dto.request.PrivateChannelCreateRequest; -import com.sprint.mission.discodeit.dto.request.PublicChannelCreateRequest; -import com.sprint.mission.discodeit.dto.request.PublicChannelUpdateRequest; -import com.sprint.mission.discodeit.entity.Channel; -import com.sprint.mission.discodeit.service.ChannelService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.UUID; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/channels") -public class ChannelController implements ChannelApi { - - private final ChannelService channelService; - - @RequestMapping(path = "/public", method = RequestMethod.POST) - public ResponseEntity create(@RequestBody PublicChannelCreateRequest request) { - Channel createdChannel = channelService.create(request); - return ResponseEntity - .status(HttpStatus.CREATED) - .body(createdChannel); - } - - @RequestMapping(path = "/private", method = RequestMethod.POST) - public ResponseEntity create(@RequestBody PrivateChannelCreateRequest request) { - Channel createdChannel = channelService.create(request); - return ResponseEntity - .status(HttpStatus.CREATED) - .body(createdChannel); - } - - @RequestMapping(path = "/{channelId}", method = RequestMethod.PATCH) - public ResponseEntity update(@PathVariable UUID channelId, - @RequestBody PublicChannelUpdateRequest request) { - Channel udpatedChannel = channelService.update(channelId, request); - return ResponseEntity - .status(HttpStatus.OK) - .body(udpatedChannel); - } - - @RequestMapping(path = "/{channelId}", method = RequestMethod.DELETE) - public ResponseEntity delete(@PathVariable UUID channelId) { - channelService.delete(channelId); - return ResponseEntity - .status(HttpStatus.NO_CONTENT) - .build(); - } - - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> findAll(@RequestParam("userId") UUID userId) { - List channels = channelService.findAllByUserId(userId); - return ResponseEntity - .status(HttpStatus.OK) - .body(channels); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/controller/MessageController.java deleted file mode 100644 index 95686cb8..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/MessageController.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.sprint.mission.discodeit.controller; - -import com.sprint.mission.discodeit.controller.api.MessageApi; -import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; -import com.sprint.mission.discodeit.dto.request.MessageCreateRequest; -import com.sprint.mission.discodeit.dto.request.MessageUpdateRequest; -import com.sprint.mission.discodeit.entity.Message; -import com.sprint.mission.discodeit.service.MessageService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -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.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/messages") -public class MessageController implements MessageApi { - - private final MessageService messageService; - - @RequestMapping( - method = RequestMethod.POST, - consumes = {MediaType.MULTIPART_FORM_DATA_VALUE} - ) - public ResponseEntity create( - @RequestPart("messageCreateRequest") MessageCreateRequest messageCreateRequest, - @RequestPart(value = "attachments", 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 RuntimeException(e); - } - }) - .toList()) - .orElse(new ArrayList<>()); - Message createdMessage = messageService.create(messageCreateRequest, attachmentRequests); - return ResponseEntity - .status(HttpStatus.CREATED) - .body(createdMessage); - } - - @RequestMapping(path = "/{messageId}", method = RequestMethod.PATCH) - public ResponseEntity update(@PathVariable UUID messageId, - @RequestBody MessageUpdateRequest request) { - Message updatedMessage = messageService.update(messageId, request); - return ResponseEntity - .status(HttpStatus.OK) - .body(updatedMessage); - } - - @RequestMapping(path = "/{messageId}",method = RequestMethod.DELETE) - public ResponseEntity delete(@PathVariable UUID messageId) { - messageService.delete(messageId); - return ResponseEntity - .status(HttpStatus.NO_CONTENT) - .build(); - } - - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> findAllByChannelId( - @RequestParam("channelId") UUID channelId) { - List messages = messageService.findAllByChannelId(channelId); - return ResponseEntity - .status(HttpStatus.OK) - .body(messages); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java deleted file mode 100644 index 30140b63..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.sprint.mission.discodeit.controller; - -import com.sprint.mission.discodeit.controller.api.ReadStatusApi; -import com.sprint.mission.discodeit.dto.request.ReadStatusCreateRequest; -import com.sprint.mission.discodeit.dto.request.ReadStatusUpdateRequest; -import com.sprint.mission.discodeit.entity.ReadStatus; -import com.sprint.mission.discodeit.service.ReadStatusService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.UUID; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/readStatuses") -public class ReadStatusController implements ReadStatusApi { - - private final ReadStatusService readStatusService; - - @RequestMapping(method = RequestMethod.POST) - public ResponseEntity create(@RequestBody ReadStatusCreateRequest request) { - ReadStatus createdReadStatus = readStatusService.create(request); - return ResponseEntity - .status(HttpStatus.CREATED) - .body(createdReadStatus); - } - - @RequestMapping(path = "/{readStatusId}", - method = RequestMethod.PATCH) - public ResponseEntity update(@PathVariable UUID readStatusId, - @RequestBody ReadStatusUpdateRequest request) { - ReadStatus updatedReadStatus = readStatusService.update(readStatusId, request); - return ResponseEntity - .status(HttpStatus.OK) - .body(updatedReadStatus); - } - - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> findAllByUserId(@RequestParam("userId") UUID userId) { - List readStatuses = readStatusService.findAllByUserId(userId); - return ResponseEntity - .status(HttpStatus.OK) - .body(readStatuses); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/controller/UserController.java deleted file mode 100644 index 56f67a6b..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/UserController.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.sprint.mission.discodeit.controller; - -import com.sprint.mission.discodeit.controller.api.UserApi; -import com.sprint.mission.discodeit.dto.data.UserDto; -import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserStatusUpdateRequest; -import com.sprint.mission.discodeit.dto.request.UserUpdateRequest; -import com.sprint.mission.discodeit.entity.User; -import com.sprint.mission.discodeit.entity.UserStatus; -import com.sprint.mission.discodeit.service.UserService; -import com.sprint.mission.discodeit.service.UserStatusService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -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 -@RestController -@RequestMapping("/api/users") -public class UserController implements UserApi { - - private final UserService userService; - private final UserStatusService userStatusService; - - @RequestMapping( - consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}, - method = RequestMethod.POST - ) - public ResponseEntity create( - @RequestPart("userCreateRequest") UserCreateRequest userCreateRequest, - @RequestPart(value = "profile", required = false) MultipartFile profile - ) { - Optional profileRequest = Optional.ofNullable(profile) - .flatMap(this::resolveProfileRequest); - User createdUser = userService.create(userCreateRequest, profileRequest); - return ResponseEntity - .status(HttpStatus.CREATED) - .body(createdUser); - } - - @RequestMapping( - path = "/{userId}", - consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}, - method = RequestMethod.PATCH - ) - public ResponseEntity update( - @PathVariable UUID userId, - @RequestPart("userUpdateRequest") UserUpdateRequest userUpdateRequest, - @RequestPart(value = "profile", required = false) MultipartFile profile - ) { - Optional profileRequest = Optional.ofNullable(profile) - .flatMap(this::resolveProfileRequest); - User updatedUser = userService.update(userId, userUpdateRequest, profileRequest); - return ResponseEntity - .status(HttpStatus.CREATED) - .body(updatedUser); - } - - @RequestMapping(path = "/{userId}", method = RequestMethod.DELETE) - public ResponseEntity delete(@PathVariable UUID userId) { - userService.delete(userId); - return ResponseEntity - .status(HttpStatus.NO_CONTENT) - .build(); - } - - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> findAll() { - List users = userService.findAll(); - return ResponseEntity - .status(HttpStatus.OK) - .body(users); - } - - @RequestMapping(path = "/{userId}/userStatus", - method = RequestMethod.PATCH) - public ResponseEntity updateUserStatusByUserId( - @PathVariable UUID userId, - @RequestBody UserStatusUpdateRequest request - ) { - UserStatus updatedUserStatus = userStatusService.updateByUserId(userId, request); - return ResponseEntity - .status(HttpStatus.OK) - .body(updatedUserStatus); - } - - private Optional resolveProfileRequest(MultipartFile profileFile) { - if (profileFile.isEmpty()) { - return Optional.empty(); - } else { - try { - BinaryContentCreateRequest binaryContentCreateRequest = new BinaryContentCreateRequest( - profileFile.getOriginalFilename(), - profileFile.getContentType(), - profileFile.getBytes() - ); - return Optional.of(binaryContentCreateRequest); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/api/AuthApi.java b/src/main/java/com/sprint/mission/discodeit/controller/api/AuthApi.java deleted file mode 100644 index c74aab15..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/api/AuthApi.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.sprint.mission.discodeit.controller.api; - -import com.sprint.mission.discodeit.dto.request.LoginRequest; -import com.sprint.mission.discodeit.entity.User; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -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 org.springframework.http.ResponseEntity; - -@Tag(name = "Auth", description = "인증 관련 API") -public interface AuthApi { - - @Operation(summary = "로그인") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "로그인 성공"), - @ApiResponse(responseCode = "400", description = "비밀번호가 일치하지 않음"), - @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음") - }) - ResponseEntity login(@Parameter(description = "로그인 정보") LoginRequest loginRequest); - -} \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/controller/api/BinaryContentApi.java b/src/main/java/com/sprint/mission/discodeit/controller/api/BinaryContentApi.java deleted file mode 100644 index caacb3e0..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/api/BinaryContentApi.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sprint.mission.discodeit.controller.api; - -import com.sprint.mission.discodeit.entity.BinaryContent; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.http.ResponseEntity; - -import java.util.List; -import java.util.UUID; - -@Tag(name = "BinaryContent", description = "첨부 파일 관리 API") -public interface BinaryContentApi { - - @Operation(summary = "첨부 파일 조회") - @ApiResponse(responseCode = "200", description = "첨부 파일 조회 성공") - ResponseEntity find(@Parameter(description = "조회할 첨부 파일 ID") UUID binaryContentId); - - @Operation(summary = "여러 첨부 파일 조회") - @ApiResponse(responseCode = "200", description = "첨부 파일 목록 조회 성공") - ResponseEntity> findAllByIdIn(@Parameter(description = "조회할 첨부 파일 ID 목록") List binaryContentIds); - - - -} \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/controller/api/ChannelApi.java b/src/main/java/com/sprint/mission/discodeit/controller/api/ChannelApi.java deleted file mode 100644 index 33f04fad..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/api/ChannelApi.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.sprint.mission.discodeit.controller.api; - -import com.sprint.mission.discodeit.dto.data.ChannelDto; -import com.sprint.mission.discodeit.dto.request.PrivateChannelCreateRequest; -import com.sprint.mission.discodeit.dto.request.PublicChannelCreateRequest; -import com.sprint.mission.discodeit.dto.request.PublicChannelUpdateRequest; -import com.sprint.mission.discodeit.entity.Channel; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -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 org.springframework.http.ResponseEntity; - -import java.util.List; -import java.util.UUID; - -@Tag(name = "Channel", description = "채널 관리 API") -public interface ChannelApi { - - @Operation(summary = "Public Channel 생성") - @ApiResponses({ - @ApiResponse(responseCode = "201", description = "Public Channel 생성 성공"), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터") - }) - ResponseEntity create(@Parameter(description = "Public Channel 생성 정보") PublicChannelCreateRequest request); - - @Operation(summary = "Private Channel 생성") - @ApiResponses({ - @ApiResponse(responseCode = "201", description = "Private Channel 생성 성공"), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터") - }) - ResponseEntity create(@Parameter(description = "Private Channel 생성 정보") PrivateChannelCreateRequest request); - - @Operation(summary = "Channel 정보 수정") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Channel 정보가 성공적으로 수정됨"), - @ApiResponse(responseCode = "400", description = "수정 정보 오류"), - @ApiResponse(responseCode = "404", description = "채널을 찾을 수 없음") - }) - ResponseEntity update( - @Parameter(description = "수정할 Channel ID") UUID channelId, - @Parameter(description = "수정할 Channel 정보") PublicChannelUpdateRequest request - ); - - @Operation(summary = "Channel 삭제") - @ApiResponses({ - @ApiResponse(responseCode = "204", description = "Channel 삭제 성공"), - @ApiResponse(responseCode = "404", description = "삭제할 채널이 존재하지 않음") - }) - ResponseEntity delete(@Parameter(description = "삭제할 Channel ID") UUID channelId); - - @Operation(summary = "User가 참여 중인 Channel 목록 조회") - @ApiResponse(responseCode = "200", description = "Channel 목록 조회 성공") - ResponseEntity> findAll(@Parameter(description = "조회할 User ID") UUID userId); -} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/api/MessageApi.java b/src/main/java/com/sprint/mission/discodeit/controller/api/MessageApi.java deleted file mode 100644 index 7ae45a3a..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/api/MessageApi.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.sprint.mission.discodeit.controller.api; - -import com.sprint.mission.discodeit.dto.request.MessageCreateRequest; -import com.sprint.mission.discodeit.dto.request.MessageUpdateRequest; -import com.sprint.mission.discodeit.entity.Message; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -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 org.springframework.http.ResponseEntity; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; -import java.util.UUID; - -@Tag(name = "Message", description = "메시지 관리 API") -public interface MessageApi { - - @Operation(summary = "Message 생성") - @ApiResponses({ - @ApiResponse(responseCode = "201", description = "Message 생성 성공"), - @ApiResponse(responseCode = "404", description = "채널을 찾을 수 없음") - }) - ResponseEntity create( - @Parameter(description = "Message 생성 정보") MessageCreateRequest request, - @Parameter(description = "Message 첨부 파일들") List attachments - ); - - @Operation(summary = "Message 내용 수정") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Message가 성공적으로 수정됨"), - @ApiResponse(responseCode = "404", description = "메시지를 찾을 수 없음") - }) - ResponseEntity update( - @Parameter(description = "수정할 Message ID") UUID messageId, - @Parameter(description = "수정할 Message 내용") MessageUpdateRequest request - ); - - @Operation(summary = "Message 삭제") - @ApiResponses({ - @ApiResponse(responseCode = "204", description = "Message 삭제 성공"), - @ApiResponse(responseCode = "404", description = "삭제할 메시지가 존재하지 않음") - }) - ResponseEntity delete(@Parameter(description = "삭제할 Message ID") UUID messageId); - - @Operation(summary = "Channel의 Message 목록 조회") - @ApiResponse(responseCode = "200", description = "Message 목록 조회 성공") - ResponseEntity> findAllByChannelId(@Parameter(description = "조회할 Channel ID") UUID channelId); -} \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/controller/api/ReadStatusApi.java b/src/main/java/com/sprint/mission/discodeit/controller/api/ReadStatusApi.java deleted file mode 100644 index c7f6f3de..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/api/ReadStatusApi.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.sprint.mission.discodeit.controller.api; - -import com.sprint.mission.discodeit.dto.request.ReadStatusCreateRequest; -import com.sprint.mission.discodeit.dto.request.ReadStatusUpdateRequest; -import com.sprint.mission.discodeit.entity.ReadStatus; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -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 org.springframework.http.ResponseEntity; - -import java.util.List; -import java.util.UUID; - -@Tag(name = "ReadStatus", description = "읽음 상태 관리 API") -public interface ReadStatusApi { - - @Operation(summary = "Message 읽음 상태 생성") - @ApiResponses({ - @ApiResponse(responseCode = "201", description = "생성 성공"), - @ApiResponse(responseCode = "400", description = "잘못된 요청"), - @ApiResponse(responseCode = "404", description = "데이터 없음") - }) - ResponseEntity create(@Parameter(description = "Message 읽음 상태 생성 정보") ReadStatusCreateRequest request); - - @Operation(summary = "Message 읽음 상태 수정") - ResponseEntity update(@Parameter(description = "수정할 읽음 상태 ID") UUID readStatusId, - @Parameter(description = "수정할 읽음 상태 정보") ReadStatusUpdateRequest request); - - @Operation(summary = "User의 Message 읽음 상태 목록 조회") - ResponseEntity> findAllByUserId(@Parameter(description = "조회할 User ID") UUID userId); -} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/api/UserApi.java b/src/main/java/com/sprint/mission/discodeit/controller/api/UserApi.java deleted file mode 100644 index 1efcc9b2..00000000 --- a/src/main/java/com/sprint/mission/discodeit/controller/api/UserApi.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.sprint.mission.discodeit.controller.api; - -import com.sprint.mission.discodeit.dto.data.UserDto; -import com.sprint.mission.discodeit.dto.request.UserCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserStatusUpdateRequest; -import com.sprint.mission.discodeit.dto.request.UserUpdateRequest; -import com.sprint.mission.discodeit.entity.User; -import com.sprint.mission.discodeit.entity.UserStatus; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -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 org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; -import java.util.UUID; - -@Tag(name = "User", description = "User 관리 API") -public interface UserApi { - - @Operation(summary = "User 등록", description = "새로운 유저를 등록합니다.") - @ApiResponses({ - @ApiResponse(responseCode = "201", description = "User 등록 성공"), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터") - }) - ResponseEntity create( - @Parameter(description = "User 생성 정보") UserCreateRequest request, - @Parameter(description = "User 프로필 이미지") MultipartFile profile - ); - - @Operation(summary = "User 정보 수정", description = "기존 유저의 정보를 수정합니다.") - @ApiResponses({ - @ApiResponse(responseCode = "201", description = "User 정보가 성공적으로 수정됨"), - @ApiResponse(responseCode = "400", description = "수정 요청 파라미터 오류"), - @ApiResponse(responseCode = "404", description = "해당 ID의 유저를 찾을 수 없음") - }) - ResponseEntity update( - @Parameter(description = "수정할 User ID") UUID userId, - @Parameter(description = "수정할 User 정보") UserUpdateRequest userUpdateRequest, - @Parameter(description = "수정할 User 프로필 이미지") MultipartFile profile - ); - - @Operation(summary = "User 삭제", description = "특정 유저를 삭제합니다.") - @ApiResponses({ - @ApiResponse(responseCode = "204", description = "User 삭제 성공"), - @ApiResponse(responseCode = "404", description = "삭제할 유저가 존재하지 않음") - }) - ResponseEntity delete(@Parameter(description = "삭제할 User ID") UUID userId); - - @Operation(summary = "전체 User 목록 조회", description = "시스템에 등록된 모든 유저를 조회합니다.") - @ApiResponse(responseCode = "200", description = "User 목록 조회 성공") - ResponseEntity> findAll(); - - @Operation(summary = "User 온라인 상태 업데이트", description = "유저의 접속 상태를 변경합니다.") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "User 온라인 상태가 성공적으로 업데이트됨"), - @ApiResponse(responseCode = "404", description = "유저를 찾을 수 없음") - }) - ResponseEntity updateUserStatusByUserId( - @Parameter(description = "상태를 변경할 User ID") UUID userId, - @RequestBody UserStatusUpdateRequest request - ); -} \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/dto/auth/LoginRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/auth/LoginRequest.java new file mode 100644 index 00000000..f9e02fc7 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/auth/LoginRequest.java @@ -0,0 +1,7 @@ +package com.sprint.mission.discodeit.dto.auth; + +public record LoginRequest( + String name, + String password +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/auth/LoginResponse.java b/src/main/java/com/sprint/mission/discodeit/dto/auth/LoginResponse.java new file mode 100644 index 00000000..f8ca0661 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/auth/LoginResponse.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.dto.auth; + +import java.time.Instant; +import java.util.UUID; + +public record LoginResponse( + UUID id, + Instant loginTime +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/binarycontent/BinaryContentRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/binarycontent/BinaryContentRequest.java new file mode 100644 index 00000000..0e34450b --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/binarycontent/BinaryContentRequest.java @@ -0,0 +1,7 @@ +package com.sprint.mission.discodeit.dto.binarycontent; + +public record BinaryContentRequest( + String fileName, + String url +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/binarycontent/BinaryContentResponse.java b/src/main/java/com/sprint/mission/discodeit/dto/binarycontent/BinaryContentResponse.java new file mode 100644 index 00000000..f7883216 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/binarycontent/BinaryContentResponse.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.dto.binarycontent; + +import java.util.UUID; + +public record BinaryContentResponse( + UUID id, + String fileName, + String url +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelCreateRequest.java new file mode 100644 index 00000000..101dd172 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelCreateRequest.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.dto.channel; + +import java.util.UUID; + +public record ChannelCreateRequest( + String title, + UUID userId, + String description +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelResponse.java b/src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelResponse.java new file mode 100644 index 00000000..0e9b2dec --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelResponse.java @@ -0,0 +1,18 @@ +package com.sprint.mission.discodeit.dto.channel; + +import com.sprint.mission.discodeit.entity.ChannelType; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +public record ChannelResponse( + UUID id, + UUID userId, + String title, + String description, + ChannelType type, + Instant updatedAt, + List memberIds +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelUpdateRequest.java new file mode 100644 index 00000000..9e519aff --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/channel/ChannelUpdateRequest.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.dto.channel; + +import java.util.UUID; + +public record ChannelUpdateRequest( + UUID id, + String title, + String description +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/channel/PrivateChannelCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/channel/PrivateChannelCreateRequest.java new file mode 100644 index 00000000..be92ab0c --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/channel/PrivateChannelCreateRequest.java @@ -0,0 +1,11 @@ +package com.sprint.mission.discodeit.dto.channel; + +import java.util.List; +import java.util.UUID; + +public record PrivateChannelCreateRequest( + List memberIds, + UUID userId +) { + +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/data/ChannelDto.java b/src/main/java/com/sprint/mission/discodeit/dto/data/ChannelDto.java deleted file mode 100644 index 21c5d760..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/data/ChannelDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.sprint.mission.discodeit.dto.data; - -import com.sprint.mission.discodeit.entity.ChannelType; - -import java.time.Instant; -import java.util.List; -import java.util.UUID; - -public record ChannelDto( - UUID id, - ChannelType type, - String name, - String description, - List participantIds, - Instant lastMessageAt -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/data/UserDto.java b/src/main/java/com/sprint/mission/discodeit/dto/data/UserDto.java deleted file mode 100644 index d7911dea..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/data/UserDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.sprint.mission.discodeit.dto.data; - -import java.time.Instant; -import java.util.UUID; - -public record UserDto( - UUID id, - Instant createdAt, - Instant updatedAt, - String username, - String email, - UUID profileId, - Boolean online -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/message/MessageCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/message/MessageCreateRequest.java new file mode 100644 index 00000000..e703ed7d --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/message/MessageCreateRequest.java @@ -0,0 +1,13 @@ +package com.sprint.mission.discodeit.dto.message; + +import java.util.List; +import java.util.UUID; + +public record MessageCreateRequest( + UUID channelId, + UUID userId, + String title, + String content, + List attachmentIds +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/message/MessageResponse.java b/src/main/java/com/sprint/mission/discodeit/dto/message/MessageResponse.java new file mode 100644 index 00000000..59ab07ac --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/message/MessageResponse.java @@ -0,0 +1,14 @@ +package com.sprint.mission.discodeit.dto.message; + +import java.util.List; +import java.util.UUID; + +public record MessageResponse( + UUID id, + UUID channelId, + UUID userId, + String title, + String content, + List attachmentIds +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/message/MessageUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/message/MessageUpdateRequest.java new file mode 100644 index 00000000..a90bddc5 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/message/MessageUpdateRequest.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.dto.message; + +import java.util.UUID; + +public record MessageUpdateRequest( + UUID id, + String title, + String content +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/BinaryContentCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/BinaryContentCreateRequest.java deleted file mode 100644 index d86eb989..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/BinaryContentCreateRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -public record BinaryContentCreateRequest( - String fileName, - String contentType, - byte[] bytes -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/LoginRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/LoginRequest.java deleted file mode 100644 index 51ca9e62..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/LoginRequest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -public record LoginRequest( - String username, - String password -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateRequest.java deleted file mode 100644 index 0f65742b..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -import java.util.UUID; - -public record MessageCreateRequest( - String content, - UUID channelId, - UUID authorId -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/MessageUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/MessageUpdateRequest.java deleted file mode 100644 index d786b1e8..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/MessageUpdateRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -public record MessageUpdateRequest( - String newContent -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/PrivateChannelCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/PrivateChannelCreateRequest.java deleted file mode 100644 index 7edd4e82..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/PrivateChannelCreateRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -import java.util.List; -import java.util.UUID; - -public record PrivateChannelCreateRequest( - List participantIds -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelCreateRequest.java deleted file mode 100644 index 48e26327..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelCreateRequest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -public record PublicChannelCreateRequest( - String name, - String description -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelUpdateRequest.java deleted file mode 100644 index d6e51541..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelUpdateRequest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -public record PublicChannelUpdateRequest( - String newName, - String newDescription -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusCreateRequest.java deleted file mode 100644 index 046a4880..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusCreateRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -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/dto/request/ReadStatusUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusUpdateRequest.java deleted file mode 100644 index 16b0c27c..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusUpdateRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -import java.time.Instant; - -public record ReadStatusUpdateRequest( - Instant newLastReadAt -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/UserCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/UserCreateRequest.java deleted file mode 100644 index e10e0ec5..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/UserCreateRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -public record UserCreateRequest( - String username, - String email, - String password -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusCreateRequest.java deleted file mode 100644 index 71c92abb..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusCreateRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -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/dto/request/UserStatusUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusUpdateRequest.java deleted file mode 100644 index c69b2610..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusUpdateRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -import java.time.Instant; - -public record UserStatusUpdateRequest( - Instant newLastActiveAt -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/UserUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/UserUpdateRequest.java deleted file mode 100644 index 1e14e2cb..00000000 --- a/src/main/java/com/sprint/mission/discodeit/dto/request/UserUpdateRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sprint.mission.discodeit.dto.request; - -public record UserUpdateRequest( - String newUsername, - String newEmail, - String newPassword -) { - -} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatusResponse.java b/src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatusResponse.java new file mode 100644 index 00000000..38c55ac7 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatusResponse.java @@ -0,0 +1,13 @@ +package com.sprint.mission.discodeit.dto.status; + +import java.time.Instant; +import java.util.UUID; + +public record ReadStatusResponse( + UUID id, + UUID userId, + UUID channelId, + Instant createdAt, + Instant updatedAt +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatusUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatusUpdateRequest.java new file mode 100644 index 00000000..9695b10b --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatusUpdateRequest.java @@ -0,0 +1,8 @@ +package com.sprint.mission.discodeit.dto.status; + +import java.util.UUID; + +public record ReadStatusUpdateRequest( + UUID id +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatuscreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatuscreateRequest.java new file mode 100644 index 00000000..b1ddab00 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/status/ReadStatuscreateRequest.java @@ -0,0 +1,9 @@ +package com.sprint.mission.discodeit.dto.status; + +import java.util.UUID; + +public record ReadStatuscreateRequest( + UUID userId, + UUID channelId +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusCreateRequest.java new file mode 100644 index 00000000..0b304216 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusCreateRequest.java @@ -0,0 +1,8 @@ +package com.sprint.mission.discodeit.dto.status; + +import java.util.UUID; + +public record UserStatusCreateRequest( + UUID userId +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusResponse.java b/src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusResponse.java new file mode 100644 index 00000000..5d08d2aa --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusResponse.java @@ -0,0 +1,9 @@ +package com.sprint.mission.discodeit.dto.status; + +import java.util.UUID; + +public record UserStatusResponse( + UUID id, + UUID userId +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusUpdateRequest.java new file mode 100644 index 00000000..d04109d5 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/status/UserStatusUpdateRequest.java @@ -0,0 +1,8 @@ +package com.sprint.mission.discodeit.dto.status; + +import java.util.UUID; + +public record UserStatusUpdateRequest( + UUID id +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/user/UserCreateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/user/UserCreateRequest.java new file mode 100644 index 00000000..a7466ff1 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/user/UserCreateRequest.java @@ -0,0 +1,11 @@ +package com.sprint.mission.discodeit.dto.user; + +public record UserCreateRequest( + String name, + String email, + String nickname, + String password, + String profileFileName, + String profileUrl +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/user/UserResponse.java b/src/main/java/com/sprint/mission/discodeit/dto/user/UserResponse.java new file mode 100644 index 00000000..67ddf084 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/user/UserResponse.java @@ -0,0 +1,13 @@ +package com.sprint.mission.discodeit.dto.user; + +import java.util.UUID; + +public record UserResponse( + UUID id, + String name, + String email, + String nickname, + UUID profileId, + boolean isOnline +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/user/UserUpdateRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/user/UserUpdateRequest.java new file mode 100644 index 00000000..048c4efb --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/user/UserUpdateRequest.java @@ -0,0 +1,11 @@ +package com.sprint.mission.discodeit.dto.user; + +import java.util.UUID; + +public record UserUpdateRequest( + UUID id, + String name, + UUID profileId, + String password +) { +} diff --git a/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java index 12948a60..7a37383b 100644 --- a/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java +++ b/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java @@ -2,29 +2,27 @@ import lombok.Getter; +import java.io.Serial; import java.io.Serializable; import java.time.Instant; import java.util.UUID; @Getter public class BinaryContent implements Serializable { + private final UUID id; + private final String fileName; + private final String url; + private final Instant createdAt; - private static final long serialVersionUID = 1L; - private UUID id; - private Instant createdAt; - // - private String fileName; - private Long size; - private String contentType; - private byte[] bytes; + @Serial + private static final long serialVersionUID = 1L; + + public BinaryContent( String fileName, String url) { + this.id = UUID.randomUUID(); + this.fileName = fileName; + this.url = url; + this.createdAt = Instant.now(); + } - 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/entity/Channel.java b/src/main/java/com/sprint/mission/discodeit/entity/Channel.java index ae49b796..88636ed2 100644 --- a/src/main/java/com/sprint/mission/discodeit/entity/Channel.java +++ b/src/main/java/com/sprint/mission/discodeit/entity/Channel.java @@ -2,44 +2,60 @@ import lombok.Getter; +import java.io.Serial; import java.io.Serializable; import java.time.Instant; import java.util.UUID; @Getter public class Channel implements Serializable { + private final UUID id; + private final UUID userId; + private String title; + private String description; + private final ChannelType type; + private final Instant createdAt; + private Instant updatedAt; - private static final long serialVersionUID = 1L; - private UUID id; - private Instant createdAt; - private Instant updatedAt; - // - private ChannelType type; - private String name; - private String description; - - public Channel(ChannelType type, String name, String description) { - this.id = UUID.randomUUID(); - this.createdAt = Instant.now(); - // - this.type = type; - this.name = name; - this.description = description; - } - - public void update(String newName, String newDescription) { - boolean anyValueUpdated = false; - if (newName != null && !newName.equals(this.name)) { - this.name = newName; - anyValueUpdated = true; + @Serial + private static final long serialVersionUID = 1L; + + public Channel(ChannelType type, String title, UUID userId, String description) { + id = UUID.randomUUID(); + this.title = title; + this.userId = userId; + this.description = description; + this.type = type; + createdAt = Instant.now(); + updatedAt = Instant.now(); } - if (newDescription != null && !newDescription.equals(this.description)) { - this.description = newDescription; - anyValueUpdated = true; + + public void update(String title, String description){ + boolean isUpdated = false; + + if(title != null){ + this.title = title; + isUpdated = true; + } + if(description != null){ + this.description = description; + isUpdated = true; + } + + if(isUpdated){ + this.updatedAt = Instant.now(); + } } - if (anyValueUpdated) { - this.updatedAt = Instant.now(); + @Override + public String toString() { + return "Channel{" + + "id=" + id + + ", user=" + userId + + ", title='" + title + '\'' + + ", category='" + description + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'+"\n"; } - } } diff --git a/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java b/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java index 4fca3772..9a2ff3f0 100644 --- a/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java +++ b/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java @@ -1,6 +1,6 @@ package com.sprint.mission.discodeit.entity; public enum ChannelType { - PUBLIC, - PRIVATE, + PUBLIC, + PRIVATE, } diff --git a/src/main/java/com/sprint/mission/discodeit/entity/Message.java b/src/main/java/com/sprint/mission/discodeit/entity/Message.java index 14ac8247..eccd41f9 100644 --- a/src/main/java/com/sprint/mission/discodeit/entity/Message.java +++ b/src/main/java/com/sprint/mission/discodeit/entity/Message.java @@ -2,45 +2,69 @@ import lombok.Getter; +import java.io.Serial; import java.io.Serializable; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @Getter public class Message implements Serializable { + private final UUID id; + private final UUID channelId; + private final UUID userId; + private String title; + private String content; + private final Instant createdAt; + private Instant updatedAt; + private final List attachmentIds; - private static final long serialVersionUID = 1L; - - private UUID id; - private Instant createdAt; - private Instant updatedAt; - // - private String content; - // - private UUID channelId; - private UUID authorId; - private List attachmentIds; - - public Message(String content, UUID channelId, UUID authorId, List attachmentIds) { - this.id = UUID.randomUUID(); - this.createdAt = Instant.now(); - // - this.content = content; - this.channelId = channelId; - this.authorId = authorId; - this.attachmentIds = attachmentIds; - } - - public void update(String newContent) { - boolean anyValueUpdated = false; - if (newContent != null && !newContent.equals(this.content)) { - this.content = newContent; - anyValueUpdated = true; + @Serial + private static final long serialVersionUID = 1L; + + public Message(UUID channelId, UUID userId, String title, String content, List attachmentIds) { + id = UUID.randomUUID(); + this.channelId = channelId; + this.userId = userId; + this.title = title; + this.content = content; + this.attachmentIds = attachmentIds == null ? new ArrayList<>() : attachmentIds; + createdAt = Instant.now(); + updatedAt = Instant.now(); + } + + public void addContents(BinaryContent binaryContent){ + attachmentIds.add(binaryContent.getId()); + } + + public void update(String title, String content){ + boolean isUpdated = false; + + if(title != null){ + this.title = title; + isUpdated = true; + } + if(content != null){ + this.content = content; + isUpdated = true; + } + + if(isUpdated){ + this.updatedAt = Instant.now(); + } } - if (anyValueUpdated) { - this.updatedAt = Instant.now(); + @Override + public String toString() { + return "Message{" + + "id=" + id + + ", channel=" + channelId + + ", user=" + userId + + ", title='" + title + '\'' + + ", content='" + content + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'+"\n"; } - } } diff --git a/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java b/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java index 13ba1ef6..967ca0b7 100644 --- a/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java @@ -2,40 +2,31 @@ import lombok.Getter; +import java.io.Serial; import java.io.Serializable; import java.time.Instant; import java.util.UUID; @Getter -public class ReadStatus implements Serializable { +public class ReadStatus implements Serializable { // 메시지를 읽었는지 여부 + private final UUID id; + private final UUID userId; + private final UUID channelId; + private final Instant createdAt; + private Instant updatedAt; - private static final long serialVersionUID = 1L; - private UUID id; - private Instant createdAt; - private Instant updatedAt; - // - private UUID userId; - private UUID channelId; - private Instant lastReadAt; + @Serial + private static final long serialVersionUID = 1L; - public ReadStatus(UUID userId, UUID channelId, Instant lastReadAt) { - this.id = UUID.randomUUID(); - this.createdAt = Instant.now(); - // - this.userId = userId; - this.channelId = channelId; - this.lastReadAt = lastReadAt; - } - - public void update(Instant newLastReadAt) { - boolean anyValueUpdated = false; - if (newLastReadAt != null && !newLastReadAt.equals(this.lastReadAt)) { - this.lastReadAt = newLastReadAt; - anyValueUpdated = true; + public ReadStatus(UUID userId, UUID channelId) { + this.id = UUID.randomUUID(); + this.userId = userId; + this.channelId = channelId; + this.createdAt = Instant.now(); + this.updatedAt = Instant.now(); } - if (anyValueUpdated) { - this.updatedAt = Instant.now(); + public void updateReadTime(){ + this.updatedAt = Instant.now(); } - } } diff --git a/src/main/java/com/sprint/mission/discodeit/entity/User.java b/src/main/java/com/sprint/mission/discodeit/entity/User.java index 60466729..8e7c0301 100644 --- a/src/main/java/com/sprint/mission/discodeit/entity/User.java +++ b/src/main/java/com/sprint/mission/discodeit/entity/User.java @@ -2,55 +2,70 @@ import lombok.Getter; +import java.io.Serial; import java.io.Serializable; import java.time.Instant; import java.util.UUID; @Getter public class User implements Serializable { + private final UUID id; + private UUID profileId; + private String name; + private final String email; + private final String nickname; + private String password; + private final Instant createdAt; + private Instant updatedAt; - private static final long serialVersionUID = 1L; - - private UUID id; - private Instant createdAt; - private Instant updatedAt; - // - private String username; - private String email; - private String password; - private UUID profileId; // BinaryContent - - public User(String username, String email, String password, UUID profileId) { - this.id = UUID.randomUUID(); - this.createdAt = Instant.now(); - // - this.username = username; - this.email = email; - this.password = password; - this.profileId = profileId; - } - - public void update(String newUsername, String newEmail, String newPassword, UUID newProfileId) { - boolean anyValueUpdated = false; - if (newUsername != null && !newUsername.equals(this.username)) { - this.username = newUsername; - anyValueUpdated = true; - } - if (newEmail != null && !newEmail.equals(this.email)) { - this.email = newEmail; - anyValueUpdated = true; - } - if (newPassword != null && !newPassword.equals(this.password)) { - this.password = newPassword; - anyValueUpdated = true; + @Serial + private static final long serialVersionUID = 1L; + + public User(BinaryContent profile, String name, String email, String nickname, String password) { + this.id = UUID.randomUUID(); + this.profileId = (profile != null ? profile.getId(): null); + this.name = name; + this.email = email; + this.nickname = nickname; + this.password = password; + createdAt = Instant.now(); + updatedAt = Instant.now(); } - if (newProfileId != null && !newProfileId.equals(this.profileId)) { - this.profileId = newProfileId; - anyValueUpdated = true; + + public void update(String name, + UUID profileId, + String password ){ + boolean isUpdated = false; + + if(profileId != null){ + this.profileId = profileId; + isUpdated = true; + } + + if(name != null){ + this.name = name; + isUpdated = true; + } + + if(password != null){ + this.password = password; + isUpdated = true; + } + + if(isUpdated){ + this.updatedAt = Instant.now(); + } } - if (anyValueUpdated) { - this.updatedAt = Instant.now(); + @Override + public String toString() { + return "User{" + + "id=" + id + + ", name='" + name + '\'' + + ", email='" + email + '\'' + + ", nickname='" + nickname + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'+"\n"; } - } } diff --git a/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java index b9142c53..2e93b15e 100644 --- a/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java +++ b/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java @@ -2,45 +2,35 @@ import lombok.Getter; +import java.io.Serial; import java.io.Serializable; -import java.time.Duration; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.UUID; @Getter public class UserStatus implements Serializable { - - private static final long serialVersionUID = 1L; - private UUID id; - private Instant createdAt; - private Instant updatedAt; - // - private UUID userId; - private Instant lastActiveAt; - - public UserStatus(UUID userId, Instant lastActiveAt) { - this.id = UUID.randomUUID(); - this.createdAt = Instant.now(); - // - this.userId = userId; - this.lastActiveAt = lastActiveAt; - } - - public void update(Instant lastActiveAt) { - boolean anyValueUpdated = false; - if (lastActiveAt != null && !lastActiveAt.equals(this.lastActiveAt)) { - this.lastActiveAt = lastActiveAt; - anyValueUpdated = true; + private final UUID id; + private final UUID userId; + private final Instant createdAt; + private Instant updatedAt; + + @Serial + private static final long serialVersionUID = 1L; + + public UserStatus(UUID userId) { + this.id = UUID.randomUUID(); + this.userId = userId; + this.createdAt = Instant.now(); + this.updatedAt = Instant.now(); } - if (anyValueUpdated) { - this.updatedAt = Instant.now(); + public void updateActiveTime() { + this.updatedAt = Instant.now(); } - } - public Boolean isOnline() { - Instant instantFiveMinutesAgo = Instant.now().minus(Duration.ofMinutes(5)); + public boolean isOnline() { + return updatedAt.isAfter(Instant.now().minus(5, ChronoUnit.MINUTES)); + } - return lastActiveAt.isAfter(instantFiveMinutesAgo); - } } diff --git a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java deleted file mode 100644 index 9fd41787..00000000 --- a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.sprint.mission.discodeit.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.util.NoSuchElementException; - -@ControllerAdvice -@ResponseBody -public class GlobalExceptionHandler { - - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleException(IllegalArgumentException e) { - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(e.getMessage()); - } - - @ExceptionHandler(NoSuchElementException.class) - public ResponseEntity handleException(NoSuchElementException e) { - return ResponseEntity - .status(HttpStatus.NOT_FOUND) - .body(e.getMessage()); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleException(Exception e) { - return ResponseEntity - .status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(e.getMessage()); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java index a2599841..09f93d7a 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java @@ -7,14 +7,11 @@ import java.util.UUID; public interface BinaryContentRepository { + BinaryContent save(BinaryContent content); - BinaryContent save(BinaryContent binaryContent); + List findAllByIdIn(List ids); - Optional findById(UUID id); + Optional findById(UUID id); - List findAllByIdIn(List ids); - - boolean existsById(UUID id); - - void deleteById(UUID id); + void deleteById(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java index 93ef97cd..548c06b4 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java @@ -2,19 +2,19 @@ import com.sprint.mission.discodeit.entity.Channel; +import javax.swing.text.html.Option; import java.util.List; import java.util.Optional; import java.util.UUID; public interface ChannelRepository { + Channel save(Channel channel); - Channel save(Channel channel); + Optional findById(UUID id); - Optional findById(UUID id); + List findAll(); - List findAll(); + void delete(UUID id); - boolean existsById(UUID id); - - void deleteById(UUID id); + boolean existsById(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java index 95421b61..7de8d5fb 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java @@ -7,16 +7,15 @@ import java.util.UUID; public interface MessageRepository { + Message save(Message message); - Message save(Message message); + Optional findById(UUID id); - Optional findById(UUID id); + List findAll(); - List findAllByChannelId(UUID channelId); + List findByChannelId(UUID id); - boolean existsById(UUID id); + boolean existsById (UUID id); - void deleteById(UUID id); - - void deleteAllByChannelId(UUID channelId); + void delete(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java index 00f68a1f..6263ab16 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java @@ -7,18 +7,17 @@ import java.util.UUID; public interface ReadStatusRepository { + ReadStatus save(ReadStatus readStatus); - ReadStatus save(ReadStatus readStatus); + List findAll(); - Optional findById(UUID id); + Optional findById(UUID id); - List findAllByUserId(UUID userId); + List findByChannelId(UUID channelId); - List findAllByChannelId(UUID channelId); + List findByUserId(UUID userId); - boolean existsById(UUID id); + Optional findByChannelIdAndUserId(UUID channelId, UUID userId ); - void deleteById(UUID id); - - void deleteAllByChannelId(UUID channelId); + void delete(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java index 2d1df6c8..7d74a4e5 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java @@ -7,20 +7,19 @@ import java.util.UUID; public interface UserRepository { + User save(User user); - User save(User user); + Optional findById(UUID id); - Optional findById(UUID id); + List findAll(); - Optional findByUsername(String username); + boolean existsByNickname(String nickname); - List findAll(); + boolean existsById(UUID id); - boolean existsById(UUID id); + boolean existsByEmail(String email); - void deleteById(UUID id); + Optional findByNameAndPassword(String name, String password); - boolean existsByEmail(String email); - - boolean existsByUsername(String username); + void deleteById(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java index 3eaa2c32..c63da19f 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java @@ -7,18 +7,13 @@ import java.util.UUID; public interface UserStatusRepository { + UserStatus save(UserStatus userStatus); - UserStatus save(UserStatus userStatus); + Optional findById(UUID id); - Optional findById(UUID id); + Optional findByUserId(UUID userId); - Optional findByUserId(UUID userId); + List findAll(); - List findAll(); - - boolean existsById(UUID id); - - void deleteById(UUID id); - - void deleteByUserId(UUID userId); + void delete(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java index d4a773a5..10ac7561 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java @@ -9,120 +9,103 @@ import java.io.*; 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 java.util.concurrent.locks.ReentrantLock; +import java.util.*; import java.util.stream.Stream; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "file") public class FileBinaryContentRepository implements BinaryContentRepository { + private final Path DIRECTORY ; + private final String EXTENSION = ".ser"; - private final Path DIRECTORY; - private final String EXTENSION = ".ser"; - private final FileLockProvider fileLockProvider; + public Path makePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } - public FileBinaryContentRepository( - @Value("${discodeit.repository.file-directory:data}") String fileDirectory, - FileLockProvider fileLockProvider - ) { - this.DIRECTORY = Paths.get(System.getProperty("user.dir"), fileDirectory, - BinaryContent.class.getSimpleName()); - if (Files.notExists(DIRECTORY)) { - try { - Files.createDirectories(DIRECTORY); - } catch (IOException e) { - throw new RuntimeException(e); - } + public FileBinaryContentRepository(@Value("${storage.location}") String storageLocation) { + DIRECTORY = Path.of(storageLocation,"BinaryContents"); + createDirectory(DIRECTORY); } - this.fileLockProvider = fileLockProvider; - } - private Path resolvePath(UUID id) { - return DIRECTORY.resolve(id + EXTENSION); - } + public void createDirectory(Path path) { + if (Files.notExists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } - @Override - public BinaryContent save(BinaryContent binaryContent) { - Path path = resolvePath(binaryContent.getId()); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); + @Override + public BinaryContent save(BinaryContent content) { + if (content == null) throw new NoSuchElementException("BinaryContent 객체가 비어있습니다."); + if (content.getId() == null) throw new NoSuchElementException("BinaryContent ID를 찾을 수 없습니다."); - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(binaryContent); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + Path path = makePath(content.getId()); + try (FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(content); + oos.close(); + return content; + } catch (IOException e) { + return null; + } } - return binaryContent; - } - @Override - public Optional findById(UUID id) { - BinaryContent binaryContentNullable = null; - Path path = resolvePath(id); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - if (Files.exists(path)) { - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - binaryContentNullable = (BinaryContent) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); - } - } - return Optional.ofNullable(binaryContentNullable); - } + public BinaryContent loadBinaryContent(Path path) { + if(Files.notExists(path) || Files.isDirectory(path)) return null; - @Override - public List findAllByIdIn(List ids) { - try (Stream paths = Files.list(DIRECTORY)) { - return paths - .filter(path -> path.toString().endsWith(EXTENSION)) - .map(path -> { - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - return (BinaryContent) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + try(FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ){ + Object obj = ois.readObject(); + if (!( obj instanceof BinaryContent)){ + throw new IllegalArgumentException("파일 내용이 BinaryContent가 아닙니다 :" + path); } - }) - .filter(content -> ids.contains(content.getId())) - .toList(); - } catch (IOException e) { - throw new RuntimeException(e); + return (BinaryContent) obj; + } catch (IOException | ClassNotFoundException e) { + System.err.println("Message 파일 로드 실패 : " + path); + return null; + } } - } - @Override - public boolean existsById(UUID id) { - Path path = resolvePath(id); - return Files.exists(path); - } + @Override + public List findAllByIdIn(List ids) { + if(!Files.isDirectory(DIRECTORY)) return Collections.emptyList(); + Map contentMap = new HashMap<>(); + try (Stream stream = Files.list(DIRECTORY)){ + stream.filter(path -> path.getFileName().toString().endsWith(EXTENSION)) + .forEach(path ->{ + BinaryContent content = loadBinaryContent(path); + contentMap.put(content.getId(),content); + }); + } catch (IOException e) { + throw new NoSuchElementException("경로를 찾을 수 없습니다."); + } - @Override - public void deleteById(UUID id) { - Path path = resolvePath(id); - try { - Files.delete(path); - } catch (IOException e) { - throw new RuntimeException(e); + return ids.stream() + .map(contentMap::get) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(BinaryContent::getFileName)) + .toList(); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(loadBinaryContent(makePath(id))); + } + + @Override + public void deleteById(UUID id) { + try { + boolean deleted = Files.deleteIfExists(makePath(id)); + if(!deleted){ + System.out.println("삭제 실패 : 해당 ID의 파일이 존재하지 않습니다."); + } + } catch (IOException e) { + throw new RuntimeException("파일 삭제 중 오류 발생", e); + } } - } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java index c080824a..723e6bd9 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java @@ -9,119 +9,102 @@ import java.io.*; 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 java.util.concurrent.locks.ReentrantLock; +import java.util.*; import java.util.stream.Stream; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "file") public class FileChannelRepository implements ChannelRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; - private final Path DIRECTORY; - private final String EXTENSION = ".ser"; - private final FileLockProvider fileLockProvider; + public Path makePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } - public FileChannelRepository( - @Value("${discodeit.repository.file-directory:data}") String fileDirectory, - FileLockProvider fileLockProvider - ) { - this.DIRECTORY = Paths.get(System.getProperty("user.dir"), fileDirectory, - Channel.class.getSimpleName()); - if (Files.notExists(DIRECTORY)) { - try { - Files.createDirectories(DIRECTORY); - } catch (IOException e) { - throw new RuntimeException(e); - } + public FileChannelRepository(@Value("${storage.location}") String storageLocation) { + DIRECTORY = Path.of(storageLocation,"Channels"); + createDirectory(DIRECTORY); } - this.fileLockProvider = fileLockProvider; - } - private Path resolvePath(UUID id) { - return DIRECTORY.resolve(id + EXTENSION); - } + public void createDirectory(Path path) { + if (Files.notExists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } - @Override - public Channel save(Channel channel) { - Path path = resolvePath(channel.getId()); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); + @Override + public Channel save(Channel channel) { + if (channel == null) throw new NoSuchElementException("채널 객체를 찾을 수 없습니다."); + if (channel.getId() == null) throw new IllegalArgumentException("Channel ID를 찾을 수 없습니다."); + if (channel.getUserId() == null) throw new IllegalArgumentException("Channel의 작성자 정보가 누락되었습니다."); - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(channel); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + Path path = makePath(channel.getId()); + try (FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(channel); + return channel; + } catch (IOException e) { + return null; + } } - return channel; - } - @Override - public Optional findById(UUID id) { - Channel channelNullable = null; - Path path = resolvePath(id); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - if (Files.exists(path)) { - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - channelNullable = (Channel) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); - } - } - return Optional.ofNullable(channelNullable); - } + public Channel loadChannel(Path path) { + if (Files.notExists(path) || Files.isDirectory(path)) return null; - @Override - public List findAll() { - try (Stream paths = Files.list(DIRECTORY)) { - return paths - .filter(path -> path.toString().endsWith(EXTENSION)) - .map(path -> { - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - return (Channel) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + try (FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Object obj = ois.readObject(); + if (!(obj instanceof Channel)) { + throw new IllegalArgumentException("파일 내용이 Channel이 아닙니다.: " + path); } - }) - .toList(); - } catch (IOException e) { - throw new RuntimeException(e); + return (Channel) obj; + } catch (IOException | ClassNotFoundException e) { + System.err.println("Message 파일 로드 실패 : " + path.getFileName() + ": "+e.getMessage()); + return null; + } + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(loadChannel(makePath(id))); + } + + @Override + public List findAll() { + if (!Files.isDirectory(DIRECTORY)) return Collections.emptyList(); + try (Stream stream = Files.list(DIRECTORY)) { + return stream + .filter(path -> path.getFileName().toString().endsWith(EXTENSION)) + .map(this::loadChannel) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(Channel::getTitle)) + .toList(); + } catch (IOException e) { + throw new NoSuchElementException("경로를 찾을 수 없습니다."); + } } - } - @Override - public boolean existsById(UUID id) { - Path path = resolvePath(id); - return Files.exists(path); - } + @Override + public void delete(UUID id) { + try { + boolean deleted = Files.deleteIfExists(makePath(id)); + if (!deleted) { + System.out.println("삭제 실패 : 해당 ID의 파일이 존재하지 않습니다."); + } + } catch (IOException e) { + throw new RuntimeException("파일 삭제 중 오류 발생", e); + } + } - @Override - public void deleteById(UUID id) { - Path path = resolvePath(id); - try { - Files.delete(path); - } catch (IOException e) { - throw new RuntimeException(e); + @Override + public boolean existsById(UUID id) { + return loadChannel(makePath(id)) != null; } - } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileLockProvider.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileLockProvider.java deleted file mode 100644 index 41f67496..00000000 --- a/src/main/java/com/sprint/mission/discodeit/repository/file/FileLockProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.sprint.mission.discodeit.repository.file; - -import java.nio.file.Path; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantLock; -import org.springframework.stereotype.Component; - -@Component -public class FileLockProvider { - private final Map locks = new ConcurrentHashMap<>(); - - public ReentrantLock getLock(Path path) { - return locks.computeIfAbsent(path, k -> new ReentrantLock()); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java index bef29af3..1f8bc53b 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java @@ -9,126 +9,110 @@ import java.io.*; 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 java.util.concurrent.locks.ReentrantLock; +import java.util.*; import java.util.stream.Stream; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "file") public class FileMessageRepository implements MessageRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; - private final Path DIRECTORY; - private final String EXTENSION = ".ser"; - private final FileLockProvider fileLockProvider; + public Path makePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } - public FileMessageRepository( - @Value("${discodeit.repository.file-directory:data}") String fileDirectory, - FileLockProvider fileLockProvider - ) { - this.DIRECTORY = Paths.get(System.getProperty("user.dir"), fileDirectory, - Message.class.getSimpleName()); - if (Files.notExists(DIRECTORY)) { - try { - Files.createDirectories(DIRECTORY); - } catch (IOException e) { - throw new RuntimeException(e); - } + public FileMessageRepository(@Value("${storage.location}") String storageLocation) { + DIRECTORY = Path.of(storageLocation,"Messages"); + createDirectory(DIRECTORY); } - this.fileLockProvider = fileLockProvider; - } - private Path resolvePath(UUID id) { - return DIRECTORY.resolve(id + EXTENSION); - } + public void createDirectory(Path path) { + if (Files.notExists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } - @Override - public Message save(Message message) { - Path path = resolvePath(message.getId()); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); + @Override + public Message save(Message message) { + if (message == null) throw new NoSuchElementException("Message 객체가 비어있습니다."); + if (message.getId() == null) throw new IllegalArgumentException("Message ID를 찾을 수 없습니다."); + if (message.getUserId() == null) throw new IllegalArgumentException("Message의 작성자 정보가 누락되었습니다."); + if (message.getChannelId() == null) throw new IllegalArgumentException("Message의 채널 정보가 누락되었습니다."); - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(message); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + Path path = makePath(message.getId()); + try (FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(message); + return message; + } catch (IOException e) { + return null; + } } - return message; - } - @Override - public Optional findById(UUID id) { - Message messageNullable = null; - Path path = resolvePath(id); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - if (Files.exists(path)) { - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - messageNullable = (Message) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); - } - } - return Optional.ofNullable(messageNullable); - } + public Message loadMessage(Path path) { + if (Files.notExists(path) || Files.isDirectory(path)) return null; - @Override - public List findAllByChannelId(UUID channelId) { - try (Stream paths = Files.list(DIRECTORY)) { - return paths - .filter(path -> path.toString().endsWith(EXTENSION)) - .map(path -> { - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - return (Message) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + try (FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Object obj = ois.readObject(); + if(!(obj instanceof Message)){ + throw new IllegalArgumentException("파일 내용이 Message가 아닙니다.: " + path); } - }) - .filter(message -> message.getChannelId().equals(channelId)) - .toList(); - } catch (IOException e) { - throw new RuntimeException(e); + return (Message) obj; + } catch (IOException | ClassNotFoundException e) { + System.err.println("Message 파일 로드 실패 : " + path); + return null; + } } - } - @Override - public boolean existsById(UUID id) { - Path path = resolvePath(id); - return Files.exists(path); - } + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(loadMessage(makePath(id))); + } - @Override - public void deleteById(UUID id) { - Path path = resolvePath(id); - try { - Files.delete(path); - } catch (IOException e) { - throw new RuntimeException(e); + @Override + public List findAll() { + if(!Files.isDirectory(DIRECTORY)) return Collections.emptyList(); + try (Stream stream = Files.list(DIRECTORY)) { + return stream + .filter(path -> path.getFileName().toString().endsWith(EXTENSION)) + .map(this::loadMessage) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(Message::getUpdatedAt)) + .toList(); + } catch (IOException e) { + throw new NoSuchElementException("경로를 찾을 수 없습니다."); + } } - } - @Override - public void deleteAllByChannelId(UUID channelId) { - this.findAllByChannelId(channelId) - .forEach(message -> this.deleteById(message.getId())); - } + @Override + public List findByChannelId(UUID id) { + return findAll().stream() + .filter(message -> message.getChannelId().equals(id)) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + return loadMessage(makePath(id)) != null; + } + + @Override + public void delete(UUID id) { + try { + boolean deleted = Files.deleteIfExists(makePath(id)); + if (!deleted) { + System.out.println("삭제 실패 : 해당 ID의 파일이 존재하지 않습니다."); + } + } catch (IOException e) { + throw new RuntimeException("파일 삭제 중 오류 발생", e); + } + } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java index 6fc24f9b..11c65bdd 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java @@ -9,152 +9,119 @@ import java.io.*; 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 java.util.concurrent.locks.ReentrantLock; +import java.util.*; import java.util.stream.Stream; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "file") public class FileReadStatusRepository implements ReadStatusRepository { + private final Path DIRECTORY ; + private final String EXTENSION = ".ser"; - private final Path DIRECTORY; - private final String EXTENSION = ".ser"; - private final FileLockProvider fileLockProvider; - - public FileReadStatusRepository( - @Value("${discodeit.repository.file-directory:data}") String fileDirectory, - FileLockProvider fileLockProvider - ) { - this.DIRECTORY = Paths.get(System.getProperty("user.dir"), fileDirectory, - ReadStatus.class.getSimpleName()); - if (Files.notExists(DIRECTORY)) { - try { - Files.createDirectories(DIRECTORY); - } catch (IOException e) { - throw new RuntimeException(e); - } + public Path makePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); } - this.fileLockProvider = fileLockProvider; - } - - private Path resolvePath(UUID id) { - return DIRECTORY.resolve(id + EXTENSION); - } - - @Override - public ReadStatus save(ReadStatus readStatus) { - Path path = resolvePath(readStatus.getId()); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(readStatus); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); - } - return readStatus; - } - - @Override - public Optional findById(UUID id) { - ReadStatus readStatusNullable = null; - Path path = resolvePath(id); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - if (Files.exists(path)) { - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - readStatusNullable = (ReadStatus) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); - } + + + public FileReadStatusRepository(@Value("${storage.location}") String storageLocation) { + DIRECTORY = Path.of(storageLocation,"ReadStatuses"); + createDirectory(DIRECTORY); } - return Optional.ofNullable(readStatusNullable); - } - - @Override - public List findAllByUserId(UUID userId) { - try (Stream paths = Files.list(DIRECTORY)) { - return paths - .filter(path -> path.toString().endsWith(EXTENSION)) - .map(path -> { - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - return (ReadStatus) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + + public void createDirectory(Path path) { + if (Files.notExists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new RuntimeException(e); } - }) - .filter(readStatus -> readStatus.getUserId().equals(userId)) - .toList(); - } catch (IOException e) { - throw new RuntimeException(e); + } + } + + @Override + public ReadStatus save(ReadStatus readStatus) { + if (readStatus.getId() == null) throw new NoSuchElementException("ReadStatus 객체를 찾을 수 없습니다."); + if (readStatus.getUserId() == null) throw new IllegalArgumentException("ReadStatus의 유저 정보가 누락되었습니다."); + if (readStatus.getChannelId() == null) throw new IllegalArgumentException("ReadStatus의 채널 정보가 누락되었습니다."); + + Path path = makePath(readStatus.getId()); + try (FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(readStatus); + return readStatus; + } catch (IOException e) { + return null; + } } - } - - @Override - public List findAllByChannelId(UUID channelId) { - try (Stream paths = Files.list(DIRECTORY)) { - return paths - .filter(path -> path.toString().endsWith(EXTENSION)) - .map(path -> { - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - return (ReadStatus) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + + public ReadStatus loadReadStatus (Path path) { + if(Files.notExists(path) || Files.isDirectory(path)) return null; + + try(FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Object obj = ois.readObject(); + if(!(obj instanceof ReadStatus)){ + throw new IllegalArgumentException("파일 내용이 ReadStatus가 아닙니다 : " + path); } - }) - .filter(readStatus -> readStatus.getChannelId().equals(channelId)) - .toList(); - } catch (IOException e) { - throw new RuntimeException(e); + return (ReadStatus) obj; + } catch (IOException | ClassNotFoundException e) { + System.err.println("ReadStatus 파일 로드 실패 : " + path); + return null; + } + } + + @Override + public List findAll() { + if(!Files.isDirectory(DIRECTORY)) return Collections.emptyList(); + try (Stream stream = Files.list(DIRECTORY)){ + return stream + .filter(path -> path.getFileName().toString().endsWith(EXTENSION)) + .map(this::loadReadStatus) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(ReadStatus::getUpdatedAt)) + .toList(); + } catch (IOException e) { + throw new NoSuchElementException("경로를 찾을 수 없습니다."); + } + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(loadReadStatus(makePath(id))); + } + + @Override + public List findByChannelId(UUID channelId) { + return findAll().stream() + .filter(status -> status.getChannelId().equals(channelId)) + .toList(); } - } - - @Override - public boolean existsById(UUID id) { - Path path = resolvePath(id); - return Files.exists(path); - } - - @Override - public void deleteById(UUID id) { - Path path = resolvePath(id); - try { - Files.delete(path); - } catch (IOException e) { - throw new RuntimeException(e); + + @Override + public List findByUserId(UUID userId) { + return findAll().stream() + .filter(status -> status.getUserId().equals(userId)) + .toList(); + } + + @Override + public Optional findByChannelIdAndUserId(UUID channelId, UUID userId) { + return findAll().stream() + .filter(status -> status.getChannelId().equals(channelId) && status.getUserId().equals(userId)) + .findFirst(); } - } - @Override - public void deleteAllByChannelId(UUID channelId) { - this.findAllByChannelId(channelId) - .forEach(readStatus -> this.deleteById(readStatus.getId())); - } + @Override + public void delete(UUID id) { + try{ + boolean deleted = Files.deleteIfExists(makePath(id)); + if(!deleted){ + System.out.println("삭제 실패 : 해당 ID의 파일이 존재하지 않습니다."); + } + } catch (IOException e) { + throw new RuntimeException("파일 삭제 중 오류 발생", e); + } + } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java index 2dc63b05..6c952545 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java @@ -2,148 +2,127 @@ import com.sprint.mission.discodeit.entity.User; import com.sprint.mission.discodeit.repository.UserRepository; -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 java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Repository; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Stream; + @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "file") public class FileUserRepository implements UserRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; - private final Path DIRECTORY; - private final String EXTENSION = ".ser"; - private final FileLockProvider fileLockProvider; - - public FileUserRepository( - @Value("${discodeit.repository.file-directory:data}") String fileDirectory, - FileLockProvider fileLockProvider - ) { - this.DIRECTORY = Paths.get(System.getProperty("user.dir"), fileDirectory, - User.class.getSimpleName()); - if (Files.notExists(DIRECTORY)) { - try { - Files.createDirectories(DIRECTORY); - } catch (IOException e) { - throw new RuntimeException(e); - } + public Path makePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); } - this.fileLockProvider = fileLockProvider; - } - - private Path resolvePath(UUID id) { - return DIRECTORY.resolve(id + EXTENSION); - } - - @Override - public User save(User user) { - Path path = resolvePath(user.getId()); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(user); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + + public FileUserRepository(@Value("${storage.location}") String storageLocation) { + DIRECTORY = Path.of(storageLocation, "Users"); + createDirectory(DIRECTORY); } - return user; - } - - @Override - public Optional findById(UUID id) { - User userNullable = null; - Path path = resolvePath(id); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - if (Files.exists(path)) { - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - userNullable = (User) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); - } + + public void createDirectory(Path path) { + if (Files.notExists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } - return Optional.ofNullable(userNullable); - } - - @Override - public Optional findByUsername(String username) { - return this.findAll().stream() - .filter(user -> user.getUsername().equals(username)) - .findFirst(); - } - - @Override - public List findAll() { - try (Stream paths = Files.list(DIRECTORY)) { - return paths - .filter(path -> path.toString().endsWith(EXTENSION)) - .map(path -> { - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - return (User) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + + @Override + public User save(User user) { + if (user == null) throw new NoSuchElementException("채널 객체를 찾을 수 없습니다."); + if (user.getId() == null) throw new IllegalArgumentException("User ID를 찾을 수 없습니다."); + + Path path = makePath(user.getId()); + try (FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(user); + return user; + } catch (IOException e) { + return null; + } + } + + public User loadUser(Path path) { + if (Files.notExists(path) || Files.isDirectory(path)) return null; + + try (FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Object obj = ois.readObject(); + if (!(obj instanceof User)) { + throw new IllegalArgumentException("파일 내용이 User가 아닙니다.: " + path); } - }) - .toList(); - } catch (IOException e) { - throw new RuntimeException(e); + return (User) obj; + } catch (IOException | ClassNotFoundException e) { + System.err.println("파일 로드 실패 : " + path); + return null; + } } - } - - @Override - public boolean existsById(UUID id) { - Path path = resolvePath(id); - return Files.exists(path); - } - - @Override - public void deleteById(UUID id) { - Path path = resolvePath(id); - try { - Files.delete(path); - } catch (IOException e) { - throw new RuntimeException(e); + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(loadUser(makePath(id))); + } + + @Override + public List findAll() { + if (!Files.isDirectory(DIRECTORY)) return Collections.emptyList(); + try (Stream stream = Files.list(DIRECTORY)) { + return stream + .filter(path -> path.getFileName().toString().endsWith(EXTENSION)) + .map(this::loadUser) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(User::getNickname)) + .toList(); + } catch (IOException e) { + return Collections.emptyList(); + } + } + + @Override + public boolean existsByNickname(String nickname) { + return findAll().stream() + .anyMatch(user -> user.getNickname().equals(nickname)); + } + + @Override + public boolean existsById(UUID id) { + return loadUser(makePath(id)) != null; + } + + @Override + public boolean existsByEmail(String email) { + return findAll().stream() + .anyMatch(user -> user.getEmail().equals(email)); + } + + @Override + public Optional findByNameAndPassword(String name, String password) { + return findAll().stream() + .filter(user -> user.getName().equals(name) && user.getPassword().equals(password)) + .findFirst(); + } + + @Override + public void deleteById(UUID id) { + try { + boolean deleted = Files.deleteIfExists(makePath(id)); + if (!deleted){ + System.out.println("삭제 실패 : 해당 ID의 user 파일이 존재하지 않습니다."); + } + } catch (IOException e) { + throw new RuntimeException("파일 삭제 중 오류 발생", e); + } } - } - - @Override - public boolean existsByEmail(String email) { - return this.findAll().stream() - .anyMatch(user -> user.getEmail().equals(email)); - } - - @Override - public boolean existsByUsername(String username) { - return this.findAll().stream() - .anyMatch(user -> user.getUsername().equals(username)); - } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java index c8daf8b9..fda8bb3c 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java @@ -9,132 +9,105 @@ import java.io.*; 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 java.util.concurrent.locks.ReentrantLock; +import java.util.*; import java.util.stream.Stream; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "file") public class FileUserStatusRepository implements UserStatusRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; - private final Path DIRECTORY; - private final String EXTENSION = ".ser"; - private final FileLockProvider fileLockProvider; - - public FileUserStatusRepository( - @Value("${discodeit.repository.file-directory:data}") String fileDirectory, - FileLockProvider fileLockProvider - ) { - this.DIRECTORY = Paths.get(System.getProperty("user.dir"), fileDirectory, - UserStatus.class.getSimpleName()); - if (Files.notExists(DIRECTORY)) { - try { - Files.createDirectories(DIRECTORY); - } catch (IOException e) { - throw new RuntimeException(e); - } + public Path makePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); } - this.fileLockProvider = fileLockProvider; - } - - private Path resolvePath(UUID id) { - return DIRECTORY.resolve(id + EXTENSION); - } - - @Override - public UserStatus save(UserStatus userStatus) { - Path path = resolvePath(userStatus.getId()); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - - try ( - FileOutputStream fos = new FileOutputStream(path.toFile()); - ObjectOutputStream oos = new ObjectOutputStream(fos) - ) { - oos.writeObject(userStatus); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + + + public FileUserStatusRepository(@Value("${storage.location}") String storageLocation) { + DIRECTORY = Path.of(storageLocation,"UserStatuses"); + createDirectory(DIRECTORY); } - return userStatus; - } - - @Override - public Optional findById(UUID id) { - UserStatus userStatusNullable = null; - Path path = resolvePath(id); - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - if (Files.exists(path)) { - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - userStatusNullable = (UserStatus) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); - } + + public void createDirectory(Path path) { + if (Files.notExists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } - return Optional.ofNullable(userStatusNullable); - } - - @Override - public Optional findByUserId(UUID userId) { - return findAll().stream() - .filter(userStatus -> userStatus.getUserId().equals(userId)) - .findFirst(); - } - - @Override - public List findAll() { - try (Stream paths = Files.list(DIRECTORY)) { - return paths - .filter(path -> path.toString().endsWith(EXTENSION)) - .map(path -> { - ReentrantLock lock = fileLockProvider.getLock(path); - lock.lock(); - try ( - FileInputStream fis = new FileInputStream(path.toFile()); - ObjectInputStream ois = new ObjectInputStream(fis) - ) { - return (UserStatus) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } finally { - lock.unlock(); + + @Override + public UserStatus save(UserStatus userStatus) { + if (userStatus.getId() == null) throw new NoSuchElementException("UserStatus 객체를 찾을 수 없습니다."); + if (userStatus.getUserId() == null) throw new IllegalArgumentException("UserStatus의 유저 정보가 누락되었습니다."); + + Path path = makePath(userStatus.getId()); + try (FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(userStatus); + return userStatus; + } catch (IOException e) { + return null; + } + } + + public UserStatus loadUserStatus(Path path) { + if (Files.notExists(path) || Files.isDirectory(path)) return null; + + try (FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Object obj = ois.readObject(); + if (!(obj instanceof UserStatus)) { + throw new IllegalArgumentException("파일 내용이 UserStatus가 아닙니다 : " + path); } - }) - .toList(); - } catch (IOException e) { - throw new RuntimeException(e); + return (UserStatus) obj; + } catch (IOException | ClassNotFoundException e) { + System.err.println("UserStatus 파일 로드 실패 : " + path); + return null; + } } - } - - @Override - public boolean existsById(UUID id) { - Path path = resolvePath(id); - return Files.exists(path); - } - - @Override - public void deleteById(UUID id) { - Path path = resolvePath(id); - try { - Files.delete(path); - } catch (IOException e) { - throw new RuntimeException(e); + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(loadUserStatus(makePath(id))); + } + + @Override + public Optional findByUserId(UUID userId) { + return findAll().stream() + .filter(status -> status.getUserId().equals(userId)) + .findFirst(); + } + + @Override + public List findAll() { + if(!Files.isDirectory(DIRECTORY)) return Collections.emptyList(); + + try (Stream stream = Files.list(DIRECTORY)){ + return stream + .filter(path -> path.getFileName().toString().endsWith(EXTENSION)) + .map(this::loadUserStatus) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(UserStatus::getUpdatedAt)) + .toList(); + } catch (IOException e) { + return Collections.emptyList(); + } } - } - @Override - public void deleteByUserId(UUID userId) { - this.findByUserId(userId) - .ifPresent(userStatus -> this.deleteById(userStatus.getId())); - } + @Override + public void delete(UUID id) { + try{ + boolean deleted = Files.deleteIfExists(makePath(id)); + if(!deleted){ + System.out.println("삭제 실패 : 해당 ID의 UserStatus 파일이 존재하지 않습니다."); + } + } catch (IOException e) { + throw new RuntimeException("파일 삭제 중 오류 발생", e); + } + } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java index e614e5ca..0d31ecdd 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java @@ -7,41 +7,34 @@ import java.util.*; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "jcf") public class JCFBinaryContentRepository implements BinaryContentRepository { - - private final Map data; - - public JCFBinaryContentRepository() { - this.data = new HashMap<>(); - } - - @Override - public BinaryContent save(BinaryContent binaryContent) { - this.data.put(binaryContent.getId(), binaryContent); - return binaryContent; - } - - @Override - public Optional findById(UUID id) { - return Optional.ofNullable(this.data.get(id)); - } - - @Override - public List findAllByIdIn(List ids) { - return this.data.values().stream() - .filter(content -> ids.contains(content.getId())) - .toList(); - } - - @Override - public boolean existsById(UUID id) { - return this.data.containsKey(id); - } - - @Override - public void deleteById(UUID id) { - this.data.remove(id); - } + private final Map data = new HashMap<>(); + + @Override + public BinaryContent save(BinaryContent content) { + if (content == null) throw new NoSuchElementException("Binary Content객체가 비어있습니다"); + if (content.getId() == null) throw new IllegalArgumentException("BinaryContent ID를 찾을 수 없습니다."); + data.put(content.getId(),content); + return data.get(content.getId()); + } + + @Override + public List findAllByIdIn(List ids) { + return ids.stream() + .map(data::get) + .filter(Objects::nonNull) + .toList(); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(data.get(id)); + } + + @Override + public void deleteById(UUID id) { + data.remove(id); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java index 1066ab48..3296863b 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java @@ -7,39 +7,36 @@ import java.util.*; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "jcf") public class JCFChannelRepository implements ChannelRepository { - - private final Map data; - - public JCFChannelRepository() { - this.data = new HashMap<>(); - } - - @Override - public Channel save(Channel channel) { - this.data.put(channel.getId(), channel); - return channel; - } - - @Override - public Optional findById(UUID id) { - return Optional.ofNullable(this.data.get(id)); - } - - @Override - public List findAll() { - return this.data.values().stream().toList(); - } - - @Override - public boolean existsById(UUID id) { - return this.data.containsKey(id); - } - - @Override - public void deleteById(UUID id) { - this.data.remove(id); - } + private final Map data = new HashMap<>(); + + @Override + public Channel save(Channel channel) { + if (channel == null) throw new NoSuchElementException("Channel 객체가 비어있습니다."); + if (channel.getId() == null) throw new IllegalArgumentException("Channel ID를 찾을 수 없습니다."); + data.put(channel.getId(), channel); + return data.get(channel.getId()); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(data.get(id)); + } + + @Override + public List findAll() { + return new ArrayList<>(data.values()); + } + + @Override + public void delete(UUID id) { + data.remove(id); + } + + @Override + public boolean existsById(UUID id) { + return data.get(id) != null; + } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java index 1d844828..6b36ef81 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java @@ -7,46 +7,43 @@ import java.util.*; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "jcf") public class JCFMessageRepository implements MessageRepository { - - private final Map data; - - public JCFMessageRepository() { - this.data = new HashMap<>(); - } - - @Override - public Message save(Message message) { - this.data.put(message.getId(), message); - return message; - } - - @Override - public Optional findById(UUID id) { - return Optional.ofNullable(this.data.get(id)); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return this.data.values().stream().filter(message -> message.getChannelId().equals(channelId)) - .toList(); - } - - @Override - public boolean existsById(UUID id) { - return this.data.containsKey(id); - } - - @Override - public void deleteById(UUID id) { - this.data.remove(id); - } - - @Override - public void deleteAllByChannelId(UUID channelId) { - this.findAllByChannelId(channelId) - .forEach(message -> this.deleteById(message.getId())); - } + private final Map data = new HashMap<>(); + + @Override + public Message save(Message message) { + if (message == null) throw new NoSuchElementException("Message 객체가 비어있습니다."); + if (message.getId() == null) throw new IllegalArgumentException("Message ID를 찾을 수 없습니다."); + data.put(message.getId(), message); + return data.get(message.getId()); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(data.get(id)); + } + + @Override + public List findAll() { + return new ArrayList<>(data.values()); + } + + @Override + public List findByChannelId(UUID id) { + return data.values().stream() + .filter(message -> message.getChannelId().equals(id)) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + return data.get(id) !=null; + } + + @Override + public void delete(UUID id) { + data.remove(id); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java index 0cf82c95..69c98f80 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java @@ -7,54 +7,57 @@ import java.util.*; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "jcf") public class JCFReadStatusRepository implements ReadStatusRepository { - - private final Map data; - - public JCFReadStatusRepository() { - this.data = new HashMap<>(); - } - - @Override - public ReadStatus save(ReadStatus readStatus) { - this.data.put(readStatus.getId(), readStatus); - return readStatus; - } - - @Override - public Optional findById(UUID id) { - return Optional.ofNullable(this.data.get(id)); - } - - @Override - public List findAllByUserId(UUID userId) { - return this.data.values().stream() - .filter(readStatus -> readStatus.getUserId().equals(userId)) - .toList(); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return this.data.values().stream() - .filter(readStatus -> readStatus.getChannelId().equals(channelId)) - .toList(); - } - - @Override - public boolean existsById(UUID id) { - return this.data.containsKey(id); - } - - @Override - public void deleteById(UUID id) { - this.data.remove(id); - } - - @Override - public void deleteAllByChannelId(UUID channelId) { - this.findAllByChannelId(channelId) - .forEach(readStatus -> this.deleteById(readStatus.getId())); - } + private final Map data = new HashMap<>(); + + @Override + public ReadStatus save(ReadStatus readStatus) { + if(readStatus == null){ + throw new NoSuchElementException("ReadStatus 객체가 비어있습니다."); + } + if(readStatus.getId() == null){ + throw new NoSuchElementException("ReadStatus ID를 찾을 수 없습니다."); + } + data.put(readStatus.getId(),readStatus); + return data.get(readStatus.getId()); + } + + @Override + public List findAll() { + return data.values().stream().toList(); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(data.get(id)); + } + + @Override + public List findByChannelId(UUID channelId) { + return data.values().stream() + .filter(status->status.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public List findByUserId(UUID userId) { + return data.values().stream() + .filter(status->status.getUserId().equals(userId)) + .toList(); + } + + @Override + public Optional findByChannelIdAndUserId(UUID channelId, UUID userId) { + return data.values().stream() + .filter(status-> + status.getChannelId().equals(channelId) && status.getUserId().equals(userId)) + .findFirst(); + } + + @Override + public void delete(UUID id) { + data.remove(id); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java index 71e7d8fe..1effdcf1 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java @@ -7,56 +7,67 @@ import java.util.*; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "jcf") public class JCFUserRepository implements UserRepository { + private final Map data = new HashMap<>(); - private final Map data; - - public JCFUserRepository() { - this.data = new HashMap<>(); - } - - @Override - public User save(User user) { - this.data.put(user.getId(), user); - return user; - } - - @Override - public Optional findById(UUID id) { - return Optional.ofNullable(this.data.get(id)); - } - - @Override - public Optional findByUsername(String username) { - return this.findAll().stream() - .filter(user -> user.getUsername().equals(username)) - .findFirst(); - } - - @Override - public List findAll() { - return this.data.values().stream().toList(); - } - - @Override - public boolean existsById(UUID id) { - return this.data.containsKey(id); - } - - @Override - public void deleteById(UUID id) { - this.data.remove(id); - } - - @Override - public boolean existsByEmail(String email) { - return this.findAll().stream().anyMatch(user -> user.getEmail().equals(email)); - } - - @Override - public boolean existsByUsername(String username) { - return this.findAll().stream().anyMatch(user -> user.getUsername().equals(username)); - } + @Override + public User save(User user) { + if (user == null) throw new NoSuchElementException("User 객체가 비어있습니다."); + if (user.getId() == null) throw new IllegalArgumentException("User ID를 찾을 수 없습니다."); + data.put(user.getId(), user); + return data.get(user.getId()); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(data.get(id)); + } + + @Override + public List findAll() { + return new ArrayList<>(data.values()); + } + + @Override + public boolean existsByNickname(String nickname) { + boolean result = false; + for(User user : data.values()){ + if (user.getNickname().equals(nickname)) { + result = true; + break; + } + } + return result; + } + + @Override + public boolean existsById(UUID id) { + return data.get(id) != null; + } + + @Override + public boolean existsByEmail(String email) { + boolean result = false; + for(User user : data.values()){ + if (user.getEmail().equals(email)) { + result = true; + break; + } + } + return result; + } + + @Override + public Optional findByNameAndPassword(String name, String password) { + return data.values().stream() + .filter(user -> user.getName().equals(name) && user.getPassword().equals(password)) + .findFirst(); + } + + @Override + public void deleteById(UUID id) { + data.remove(id); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java index d505497e..076174f9 100644 --- a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java @@ -7,52 +7,38 @@ import java.util.*; -@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) @Repository +@ConditionalOnProperty(name = "spring.service.type", havingValue = "jcf") public class JCFUserStatusRepository implements UserStatusRepository { - - private final Map data; - - public JCFUserStatusRepository() { - this.data = new HashMap<>(); - } - - @Override - public UserStatus save(UserStatus userStatus) { - this.data.put(userStatus.getId(), userStatus); - return userStatus; - } - - @Override - public Optional findById(UUID id) { - return Optional.ofNullable(this.data.get(id)); - } - - @Override - public Optional findByUserId(UUID userId) { - return this.findAll().stream() - .filter(userStatus -> userStatus.getUserId().equals(userId)) - .findFirst(); - } - - @Override - public List findAll() { - return this.data.values().stream().toList(); - } - - @Override - public boolean existsById(UUID id) { - return this.data.containsKey(id); - } - - @Override - public void deleteById(UUID id) { - this.data.remove(id); - } - - @Override - public void deleteByUserId(UUID userId) { - this.findByUserId(userId) - .ifPresent(userStatus -> this.deleteByUserId(userStatus.getId())); - } + private final Map data = new HashMap<>(); + + @Override + public UserStatus save(UserStatus userStatus) { + if(userStatus ==null) throw new NoSuchElementException("User Status객체가 비어있습니다"); + if(userStatus.getId() == null) throw new IllegalArgumentException("User Statis ID를 찾을 수 없습니다."); + data.put(userStatus.getId(),userStatus); + return data.get(userStatus.getId()); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(data.get(id)); + } + + @Override + public Optional findByUserId(UUID userId) { + return data.values().stream() + .filter(status -> status.getUserId().equals(userId)) + .findFirst(); + } + + @Override + public List findAll() { + return new ArrayList<>(data.values()); + } + + @Override + public void delete(UUID id) { + data.remove(id); + } } diff --git a/src/main/java/com/sprint/mission/discodeit/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/service/AuthService.java index c81cb51d..88bdd2af 100644 --- a/src/main/java/com/sprint/mission/discodeit/service/AuthService.java +++ b/src/main/java/com/sprint/mission/discodeit/service/AuthService.java @@ -1,9 +1,8 @@ package com.sprint.mission.discodeit.service; -import com.sprint.mission.discodeit.dto.request.LoginRequest; -import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.dto.auth.LoginRequest; +import com.sprint.mission.discodeit.dto.auth.LoginResponse; public interface AuthService { - - User login(LoginRequest loginRequest); + LoginResponse login(LoginRequest request); } diff --git a/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java index df448b89..fc7092b7 100644 --- a/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java +++ b/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java @@ -1,18 +1,14 @@ package com.sprint.mission.discodeit.service; -import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; -import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.dto.binarycontent.BinaryContentRequest; +import com.sprint.mission.discodeit.dto.binarycontent.BinaryContentResponse; import java.util.List; import java.util.UUID; public interface BinaryContentService { - - BinaryContent create(BinaryContentCreateRequest request); - - BinaryContent find(UUID binaryContentId); - - List findAllByIdIn(List binaryContentIds); - - void delete(UUID binaryContentId); + BinaryContentResponse create (BinaryContentRequest request); + BinaryContentResponse find (UUID id); + List findAllByIdIn (List ids); + void delete (UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java b/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java index b165ae1a..a51ea4d7 100644 --- a/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java +++ b/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java @@ -1,26 +1,20 @@ package com.sprint.mission.discodeit.service; -import com.sprint.mission.discodeit.dto.data.ChannelDto; -import com.sprint.mission.discodeit.dto.request.PrivateChannelCreateRequest; -import com.sprint.mission.discodeit.dto.request.PublicChannelCreateRequest; -import com.sprint.mission.discodeit.dto.request.PublicChannelUpdateRequest; -import com.sprint.mission.discodeit.entity.Channel; -import com.sprint.mission.discodeit.entity.ChannelType; +import com.sprint.mission.discodeit.dto.channel.ChannelCreateRequest; +import com.sprint.mission.discodeit.dto.channel.ChannelResponse; +import com.sprint.mission.discodeit.dto.channel.ChannelUpdateRequest; +import com.sprint.mission.discodeit.dto.channel.PrivateChannelCreateRequest; import java.util.List; import java.util.UUID; public interface ChannelService { - Channel create(PublicChannelCreateRequest request); - - Channel create(PrivateChannelCreateRequest request); - - ChannelDto find(UUID channelId); - - List findAllByUserId(UUID userId); - - Channel update(UUID channelId, PublicChannelUpdateRequest request); - - void delete(UUID channelId); -} \ No newline at end of file + ChannelResponse createPublicChannel(ChannelCreateRequest request); + ChannelResponse createPrivateChannel(PrivateChannelCreateRequest request); + ChannelResponse findById(UUID id); + List findAllByUserId(UUID userId); + List findAll(); + ChannelResponse update(ChannelUpdateRequest channel); + void delete(UUID id); +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/Impl/AuthServiceImpl.java b/src/main/java/com/sprint/mission/discodeit/service/Impl/AuthServiceImpl.java new file mode 100644 index 00000000..d01713f1 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/Impl/AuthServiceImpl.java @@ -0,0 +1,33 @@ +package com.sprint.mission.discodeit.service.Impl; + +import com.sprint.mission.discodeit.dto.auth.LoginRequest; +import com.sprint.mission.discodeit.dto.auth.LoginResponse; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.service.AuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.NoSuchElementException; + +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService{ + private final UserRepository userRepository; + + @Override + public LoginResponse login(LoginRequest request) { + User user = userRepository.findByNameAndPassword(request.name(),request.password()) + .orElseThrow(()-> new NoSuchElementException("로그인 실패")); + + return convertToResponse(user); + } + + public LoginResponse convertToResponse(User user) { + return new LoginResponse( + user.getId(), + Instant.now() + ); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/Impl/BinaryContentServiceImpl.java b/src/main/java/com/sprint/mission/discodeit/service/Impl/BinaryContentServiceImpl.java new file mode 100644 index 00000000..e48f0cce --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/Impl/BinaryContentServiceImpl.java @@ -0,0 +1,68 @@ +package com.sprint.mission.discodeit.service.Impl; + +import com.sprint.mission.discodeit.dto.binarycontent.BinaryContentRequest; +import com.sprint.mission.discodeit.dto.binarycontent.BinaryContentResponse; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.service.BinaryContentService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class BinaryContentServiceImpl implements BinaryContentService { + private final BinaryContentRepository binaryContentRepository; + + @Override + public BinaryContentResponse create(BinaryContentRequest request) { + BinaryContent content = new BinaryContent( + request.fileName(), + request.url() + ); + BinaryContent savedContent = binaryContentRepository.save(content); + return convertToResponse(savedContent); + } + + public BinaryContentResponse convertToResponse(BinaryContent content){ + return new BinaryContentResponse( + content.getId(), + content.getFileName(), + content.getUrl() + ); + } + + @Override + public BinaryContentResponse find(UUID id) { + BinaryContent content = binaryContentRepository.findById(id) + .orElseThrow(()-> new NoSuchElementException("존재하지 않는 컨텐츠입니다.")); + return convertToResponse(content); + } + + @Override + public List findAllByIdIn(List ids) { + if(ids == null || ids.isEmpty()){ + return Collections.emptyList(); + } + List contents = binaryContentRepository.findAllByIdIn(ids); + if(contents.isEmpty()) return Collections.emptyList(); + + if(contents.size() != ids.size()){ + throw new NoSuchElementException("요청한 ID 중 일부를 찾을 수 없습니다."); + } + return contents.stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public void delete(UUID id) { + binaryContentRepository.findById(id) + .orElseThrow(()-> new NoSuchElementException("존재하지 않는 컨텐츠입니다.")); + binaryContentRepository.deleteById(id); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/Impl/ChannelServiceImpl.java b/src/main/java/com/sprint/mission/discodeit/service/Impl/ChannelServiceImpl.java new file mode 100644 index 00000000..a798eb76 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/Impl/ChannelServiceImpl.java @@ -0,0 +1,158 @@ +package com.sprint.mission.discodeit.service.Impl; + +import com.sprint.mission.discodeit.dto.channel.ChannelCreateRequest; +import com.sprint.mission.discodeit.dto.channel.ChannelResponse; +import com.sprint.mission.discodeit.dto.channel.ChannelUpdateRequest; +import com.sprint.mission.discodeit.dto.channel.PrivateChannelCreateRequest; +import com.sprint.mission.discodeit.entity.*; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import com.sprint.mission.discodeit.repository.MessageRepository; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.service.ChannelService; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class ChannelServiceImpl implements ChannelService { + private final ChannelRepository channelRepository; + private final ReadStatusRepository readStatusRepository; + private final MessageRepository messageRepository; + + @Override + public ChannelResponse createPublicChannel(ChannelCreateRequest request) { + Channel channel = new Channel( + ChannelType.PUBLIC, + request.title(), + request.userId(), + request.description() + ); + return convertToResponse(channelRepository.save(channel), null); + } + + @Override + public ChannelResponse createPrivateChannel(PrivateChannelCreateRequest request) { + Channel channel = new Channel( + ChannelType.PRIVATE, + null, + request.userId(), + null + ); + Channel savedChannel = channelRepository.save(channel); + + request.memberIds().forEach(memberId -> { + ReadStatus readStatus = new ReadStatus(memberId, savedChannel.getId()); + readStatusRepository.save(readStatus); + }); + + return convertToResponse(savedChannel, request.memberIds()); + } + + public ChannelResponse convertToResponse(Channel channel, List memberIds) { + return new ChannelResponse( + channel.getId(), + channel.getUserId(), + channel.getTitle(), + channel.getDescription(), + channel.getType(), + channel.getUpdatedAt(), + memberIds + ); + } + + @Override + public ChannelResponse findById(UUID id) { + Channel channel = channelRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("채널을 찾을 수 없습니다.")); + + List memberIds = null; + if (channel.getType() == ChannelType.PRIVATE) { + memberIds = readStatusRepository.findByChannelId(id).stream() + .map(ReadStatus::getUserId) + .toList(); + } + return convertToResponse(channel, memberIds); + } + + @Override + public List findAllByUserId(UUID userId) { + List channels = channelRepository.findAll(); + List statuses = readStatusRepository.findAll(); + + return channels.stream() + .filter(channel -> { + // public은 전부 다 받음. + if (channel.getType() == ChannelType.PUBLIC) return true; + + return statuses.stream() + //해당 channel에 속해있는 status인지 확인 + .anyMatch(s -> s.getChannelId().equals(channel.getId()) + // 제시된 user와 id가 같은것만 추출. + && s.getUserId().equals(userId)); + }) + .map(channel -> { + List members = null; + if (channel.getType() == ChannelType.PRIVATE) { + members = statuses.stream() + .filter(s -> s.getChannelId().equals(channel.getId())) + .map(ReadStatus::getUserId) + .toList(); + } + return convertToResponse(channel, members); + }) + .toList(); + } + + @Override + public List findAll() { + List list = channelRepository.findAll(); + return list.stream() + .map(channel -> convertToResponse(channel,null)) + .toList(); + } + + @Override + public ChannelResponse update(ChannelUpdateRequest request) { + Channel channel = channelRepository.findById(request.id()). + orElseThrow(() -> new NoSuchElementException("해당 채널이 존재하지 않습니다")); + + if (channel.getType() == ChannelType.PRIVATE) { + throw new IllegalStateException("PRIVATE 채널은 수정할 수 없습니다."); + } + + channel.update(request.title(), request.description()); + + Channel updatedChannel = channelRepository.save(channel); + return convertToResponse(updatedChannel, null); + } + + @Override + public void delete(UUID id) { + if(!channelRepository.existsById(id)){ + throw new NoSuchElementException("해당 채널이 존재하지 않습니다."); + } + List messages = messageRepository.findByChannelId(id); + messages.forEach(message -> messageRepository.delete(message.getId())); + + List statuses = readStatusRepository.findByChannelId(id); + statuses.forEach(status -> readStatusRepository.delete(status.getId())); + + channelRepository.delete(id); + } +} + + + + + + + + + + + diff --git a/src/main/java/com/sprint/mission/discodeit/service/Impl/MessageServiceImpl.java b/src/main/java/com/sprint/mission/discodeit/service/Impl/MessageServiceImpl.java new file mode 100644 index 00000000..5a32615e --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/Impl/MessageServiceImpl.java @@ -0,0 +1,103 @@ +package com.sprint.mission.discodeit.service.Impl; + +import com.sprint.mission.discodeit.dto.message.MessageCreateRequest; +import com.sprint.mission.discodeit.dto.message.MessageResponse; +import com.sprint.mission.discodeit.dto.message.MessageUpdateRequest; +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import com.sprint.mission.discodeit.repository.MessageRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.service.MessageService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +@RequiredArgsConstructor +public class MessageServiceImpl implements MessageService { + private final MessageRepository messageRepository; + private final ChannelRepository channelRepository; + private final UserRepository userRepository; + private final BinaryContentRepository binaryContentRepository; + + @Override + public MessageResponse create(MessageCreateRequest request) { + + if(!channelRepository.existsById(request.channelId())){ + throw new NoSuchElementException("존재하지 않는 채널에 메시지를 작성할 수 없습니다."); + } + if(!userRepository.existsById(request.userId())){ + throw new NoSuchElementException("존재하지 않는 채널에 메시지를 작성할 수 없습니다."); + } + Message message = new Message( + request.channelId(), + request.userId(), + request.title(), + request.content(), + request.attachmentIds() + ); + return convertToResponse(messageRepository.save(message)); + } + + public MessageResponse convertToResponse(Message message){ + return new MessageResponse( + message.getId(), + message.getChannelId(), + message.getUserId(), + message.getTitle(), + message.getContent(), + message.getAttachmentIds() + ); + } + + @Override + public List findByChannelId(UUID id) { + List messages = messageRepository.findByChannelId(id); + if(messages.isEmpty()) return Collections.emptyList(); + + List messageResponses = new ArrayList<>(); + messages.forEach(message -> messageResponses.add(convertToResponse(message))); + + return messageResponses; + } + + @Override + public MessageResponse update(MessageUpdateRequest request) { + Message message = messageRepository.findById(request.id()) + .orElseThrow(() -> new NoSuchElementException("해당 Message가 존재하지 않습니다.")); + + message.update( + request.title(), + request.content() + ); + Message updatedMessage = messageRepository.save(message); + return convertToResponse(updatedMessage); + } + + @Override + public void delete(UUID id) { + if (!messageRepository.existsById(id)){ + throw new NoSuchElementException("해당 메시지가 존재하지 않습니다."); + } + List attachmentIds = messageRepository.findById(id) + .orElseThrow(() -> + new NoSuchElementException("해당 Message가 존재하지 않습니다.")) + .getAttachmentIds(); + attachmentIds.forEach(binaryContentRepository::deleteById); + + messageRepository.delete(id); + } +} + + + + + + + + + + + diff --git a/src/main/java/com/sprint/mission/discodeit/service/Impl/ReadStatusServiceImpl.java b/src/main/java/com/sprint/mission/discodeit/service/Impl/ReadStatusServiceImpl.java new file mode 100644 index 00000000..c4e69002 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/Impl/ReadStatusServiceImpl.java @@ -0,0 +1,87 @@ +package com.sprint.mission.discodeit.service.Impl; + +import com.sprint.mission.discodeit.dto.status.ReadStatusResponse; +import com.sprint.mission.discodeit.dto.status.ReadStatusUpdateRequest; +import com.sprint.mission.discodeit.dto.status.ReadStatuscreateRequest; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.service.ReadStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class ReadStatusServiceImpl implements ReadStatusService { + private final ReadStatusRepository readStatusRepository; + private final UserRepository userRepository; + private final ChannelRepository channelRepository; + + @Override + public ReadStatusResponse create(ReadStatuscreateRequest request) { + if (!userRepository.existsById(request.userId())) { + throw new NoSuchElementException("존재하지 않는 유저입니다."); + } + if (!channelRepository.existsById(request.channelId())) { + throw new NoSuchElementException("존재하지 않는 채널입니다."); + } + + readStatusRepository.findByChannelIdAndUserId(request.channelId(),request.userId()) + .orElseThrow(()-> new IllegalStateException("이미 해당 채널에 참여중인 유저입니다.")); + + ReadStatus readStatus = new ReadStatus(request.userId(), request.channelId()); + ReadStatus savedReadStatus = readStatusRepository.save(readStatus); + return convertToResponse(savedReadStatus); + } + + public ReadStatusResponse convertToResponse(ReadStatus readStatus) { + return new ReadStatusResponse( + readStatus.getId(), + readStatus.getUserId(), + readStatus.getChannelId(), + readStatus.getCreatedAt(), + readStatus.getUpdatedAt() + ); + } + + @Override + public ReadStatusResponse find(UUID id) { + ReadStatus status = readStatusRepository.findById(id) + .orElseThrow(()-> new NoSuchElementException("해당 유저가 존재하지 않습니다.")); // optional 사용? ReadStatus 사용? + return convertToResponse(status); + } + + @Override + public List findAllByUserId(UUID userId) { + List statuses = readStatusRepository.findByUserId(userId); + if(statuses.isEmpty()) return Collections.emptyList(); + + + return statuses.stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public ReadStatusResponse update(ReadStatusUpdateRequest request) { + ReadStatus status = readStatusRepository.findById(request.id()) + .orElseThrow(()-> new NoSuchElementException("참여 상태를 찾을 수 없습니다.")); + + status.updateReadTime(); + return convertToResponse(status); + } + + @Override + public void delete(UUID id) { + if (readStatusRepository.findById(id).isEmpty()) { + throw new NoSuchElementException("참여 상태를 찾을 수 없습니다."); + } + readStatusRepository.delete(id); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/Impl/UserServiceImpl.java b/src/main/java/com/sprint/mission/discodeit/service/Impl/UserServiceImpl.java new file mode 100644 index 00000000..15a8d46b --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/Impl/UserServiceImpl.java @@ -0,0 +1,124 @@ +package com.sprint.mission.discodeit.service.Impl; + +import com.sprint.mission.discodeit.dto.user.UserCreateRequest; +import com.sprint.mission.discodeit.dto.user.UserResponse; +import com.sprint.mission.discodeit.dto.user.UserUpdateRequest; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import com.sprint.mission.discodeit.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final UserStatusRepository userStatusRepository; + private final BinaryContentRepository binaryContentRepository; + + @Override + public UserResponse create(UserCreateRequest request) { + if (userRepository.existsByEmail(request.email())) + throw new IllegalStateException("이미 가입된 email 입니다."); + + if (userRepository.existsByNickname(request.nickname())) + throw new IllegalStateException("이미 사용중인 닉네임 입니다."); + + BinaryContent profile = null; + if (request.profileUrl() != null) { + profile = new BinaryContent(request.profileFileName(), request.profileUrl()); + binaryContentRepository.save(profile); + } + + User user = new User(profile, request.name(), request.email(), request.nickname(), request.password()); + UserStatus status = new UserStatus(user.getId()); + + userRepository.save(user); + userStatusRepository.save(status); + + return convertToResponse(user, status); + } + + public UserResponse convertToResponse(User user, UserStatus status) { + return new UserResponse( + user.getId(), + user.getName(), + user.getEmail(), + user.getNickname(), + user.getProfileId(), + status.isOnline() + ); + } + + @Override + public UserResponse findById(UUID id) { + User user = userRepository.findById(id) + .orElseThrow(()->new NoSuchElementException("유저를 찾을 수 없습니다.")); + UserStatus status = userStatusRepository.findByUserId(id) + .orElseThrow(()-> new NoSuchElementException("상태 정보를 찾을 수 없습니다.")); + return convertToResponse(user, status); + } + + @Override + public List findAll() { + List statuses = userStatusRepository.findAll(); + if(statuses.isEmpty()) return Collections.emptyList(); + + Map statusMap = statuses.stream() + .collect(Collectors.toMap(UserStatus::getUserId,status -> status)); + + return userRepository.findAll().stream() + .map(user ->{ + UserStatus status = statusMap.get(user.getId()); + return convertToResponse(user,status); + }) + .toList(); + } + + @Override + public UserResponse update(UserUpdateRequest request) { + User user = userRepository.findById(request.id()) + .orElseThrow(()-> new NoSuchElementException("해당 User가 존재하지 않습니다.")); + user.update( + request.name(), + request.profileId(), + request.password() + ); + + userRepository.save(user); + return findById(request.id()); + } + + @Override + public void delete(UUID id) { + User user = userRepository.findById(id) + .orElseThrow(()-> new NoSuchElementException("해당 유저를 찾을 수 없습니다.")); + + UserStatus status = userStatusRepository.findByUserId(id) + .orElseThrow(()-> new NoSuchElementException("해당 User Status를 찾을 수 없습니다.")); + userStatusRepository.delete(status.getId()); + + if (user.getProfileId() != null) { + binaryContentRepository.deleteById(user.getProfileId()); + } + userRepository.deleteById(id); + } +} + + + + + + + + + + + diff --git a/src/main/java/com/sprint/mission/discodeit/service/Impl/UserStatusServiceImpl.java b/src/main/java/com/sprint/mission/discodeit/service/Impl/UserStatusServiceImpl.java new file mode 100644 index 00000000..1d35b2e7 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/Impl/UserStatusServiceImpl.java @@ -0,0 +1,87 @@ +package com.sprint.mission.discodeit.service.Impl; + +import com.sprint.mission.discodeit.dto.status.UserStatusCreateRequest; +import com.sprint.mission.discodeit.dto.status.UserStatusResponse; +import com.sprint.mission.discodeit.dto.status.UserStatusUpdateRequest; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import com.sprint.mission.discodeit.service.UserStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class UserStatusServiceImpl implements UserStatusService { + private final UserStatusRepository userStatusRepository; + private final UserRepository userRepository; + + @Override + public UserStatusResponse create(UserStatusCreateRequest request) { + userRepository.findById(request.userId()) + .orElseThrow(()-> new NoSuchElementException("존재하지 않는 유저입니다.")); + + if (userStatusRepository.findByUserId(request.userId()).isPresent()) { + throw new IllegalStateException("이미 해당 유저의 상태정보가 존재합니다."); + } + + UserStatus status = new UserStatus(request.userId()); + UserStatus savedStatus = userStatusRepository.save(status); + + return convertToResponse(savedStatus); + } + + public UserStatusResponse convertToResponse(UserStatus status) { + return new UserStatusResponse( + status.getId(), + status.getUserId() + ); + } + + @Override + public UserStatusResponse find(UUID id) { + UserStatus status = userStatusRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("해당 유저상태가 존재하지 않습니다.")); + return convertToResponse(status); + } + + @Override + public List findAll() { + List statuses = userStatusRepository.findAll(); + if(statuses.isEmpty()) return Collections.emptyList(); + + + return statuses.stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public UserStatusResponse update(UserStatusUpdateRequest request) { + UserStatus status = userStatusRepository.findById(request.id()) + .orElseThrow(() -> new NoSuchElementException("상태 정보가 존재하지 않습니다")); + status.updateActiveTime(); + return convertToResponse(status); + } + + @Override + public UserStatusResponse updateByUserId(UUID userId, UserStatusUpdateRequest request) { + UserStatus status = userStatusRepository.findByUserId(userId) + .orElseThrow(() -> new NoSuchElementException("해당 유저에 대한 상태가 존재하지 않습니다")); + status.updateActiveTime(); + return convertToResponse(status); + } + + @Override + public void delete(UUID id) { + if (userStatusRepository.findById(id).isEmpty()) { + throw new NoSuchElementException("상태 정보가 존재하지 않습니다"); + } + userStatusRepository.delete(id); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/MessageService.java b/src/main/java/com/sprint/mission/discodeit/service/MessageService.java index cb47394e..7e817a38 100644 --- a/src/main/java/com/sprint/mission/discodeit/service/MessageService.java +++ b/src/main/java/com/sprint/mission/discodeit/service/MessageService.java @@ -1,23 +1,16 @@ package com.sprint.mission.discodeit.service; -import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; -import com.sprint.mission.discodeit.dto.request.MessageCreateRequest; -import com.sprint.mission.discodeit.dto.request.MessageUpdateRequest; -import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.dto.message.MessageCreateRequest; +import com.sprint.mission.discodeit.dto.message.MessageResponse; +import com.sprint.mission.discodeit.dto.message.MessageUpdateRequest; import java.util.List; import java.util.UUID; public interface MessageService { - Message create(MessageCreateRequest messageCreateRequest, - List binaryContentCreateRequests); - - Message find(UUID messageId); - - List findAllByChannelId(UUID channelId); - - Message update(UUID messageId, MessageUpdateRequest request); - - void delete(UUID messageId); + MessageResponse create(MessageCreateRequest request); + List findByChannelId(UUID id); + MessageResponse update(MessageUpdateRequest request); + void delete(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java index b0bd70c2..fb3d2b60 100644 --- a/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java @@ -1,21 +1,20 @@ package com.sprint.mission.discodeit.service; -import com.sprint.mission.discodeit.dto.request.ReadStatusCreateRequest; -import com.sprint.mission.discodeit.dto.request.ReadStatusUpdateRequest; -import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.dto.status.ReadStatusResponse; +import com.sprint.mission.discodeit.dto.status.ReadStatusUpdateRequest; +import com.sprint.mission.discodeit.dto.status.ReadStatuscreateRequest; import java.util.List; import java.util.UUID; public interface ReadStatusService { + ReadStatusResponse create(ReadStatuscreateRequest request); - ReadStatus create(ReadStatusCreateRequest request); + ReadStatusResponse find(UUID id); - ReadStatus find(UUID readStatusId); + List findAllByUserId(UUID userId); - List findAllByUserId(UUID userId); + ReadStatusResponse update(ReadStatusUpdateRequest request); - ReadStatus update(UUID readStatusId, ReadStatusUpdateRequest request); - - void delete(UUID readStatusId); + void delete(UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/service/UserService.java b/src/main/java/com/sprint/mission/discodeit/service/UserService.java index 6df78593..8b37f935 100644 --- a/src/main/java/com/sprint/mission/discodeit/service/UserService.java +++ b/src/main/java/com/sprint/mission/discodeit/service/UserService.java @@ -1,26 +1,18 @@ package com.sprint.mission.discodeit.service; -import com.sprint.mission.discodeit.dto.data.UserDto; -import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserUpdateRequest; -import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.dto.user.UserCreateRequest; +import com.sprint.mission.discodeit.dto.user.UserResponse; +import com.sprint.mission.discodeit.dto.user.UserUpdateRequest; import java.util.List; -import java.util.Optional; import java.util.UUID; public interface UserService { - User create(UserCreateRequest userCreateRequest, - Optional profileCreateRequest); + UserResponse create(UserCreateRequest request); + UserResponse findById(UUID id); + List findAll(); + UserResponse update(UserUpdateRequest request); + void delete(UUID id); - UserDto find(UUID userId); - - List findAll(); - - User update(UUID userId, UserUpdateRequest userUpdateRequest, - Optional profileCreateRequest); - - void delete(UUID userId); } diff --git a/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java index fbb37b0c..09f06f22 100644 --- a/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java +++ b/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java @@ -1,23 +1,17 @@ package com.sprint.mission.discodeit.service; -import com.sprint.mission.discodeit.dto.request.UserStatusCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserStatusUpdateRequest; -import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.dto.status.UserStatusCreateRequest; +import com.sprint.mission.discodeit.dto.status.UserStatusResponse; +import com.sprint.mission.discodeit.dto.status.UserStatusUpdateRequest; import java.util.List; import java.util.UUID; public interface UserStatusService { - - UserStatus create(UserStatusCreateRequest request); - - UserStatus find(UUID userStatusId); - - List findAll(); - - UserStatus update(UUID userStatusId, UserStatusUpdateRequest request); - - UserStatus updateByUserId(UUID userId, UserStatusUpdateRequest request); - - void delete(UUID userStatusId); + UserStatusResponse create (UserStatusCreateRequest request); + UserStatusResponse find (UUID id); + List findAll (); + UserStatusResponse update (UserStatusUpdateRequest request); + UserStatusResponse updateByUserId (UUID userId, UserStatusUpdateRequest request); + void delete (UUID id); } diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java deleted file mode 100644 index 83b11596..00000000 --- a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.sprint.mission.discodeit.service.basic; - -import com.sprint.mission.discodeit.dto.request.LoginRequest; -import com.sprint.mission.discodeit.entity.User; -import com.sprint.mission.discodeit.repository.UserRepository; -import com.sprint.mission.discodeit.service.AuthService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.NoSuchElementException; - -@RequiredArgsConstructor -@Service -public class BasicAuthService implements AuthService { - - private final UserRepository userRepository; - - @Override - public User login(LoginRequest loginRequest) { - String username = loginRequest.username(); - String password = loginRequest.password(); - - User user = userRepository.findByUsername(username) - .orElseThrow( - () -> new NoSuchElementException("User with username " + username + " not found")); - - if (!user.getPassword().equals(password)) { - throw new IllegalArgumentException("Wrong password"); - } - - return user; - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java deleted file mode 100644 index 335b6d67..00000000 --- a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.sprint.mission.discodeit.service.basic; - -import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; -import com.sprint.mission.discodeit.entity.BinaryContent; -import com.sprint.mission.discodeit.repository.BinaryContentRepository; -import com.sprint.mission.discodeit.service.BinaryContentService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.NoSuchElementException; -import java.util.UUID; - -@RequiredArgsConstructor -@Service -public class BasicBinaryContentService implements BinaryContentService { - - private final BinaryContentRepository binaryContentRepository; - - @Override - public BinaryContent create(BinaryContentCreateRequest request) { - String fileName = request.fileName(); - byte[] bytes = request.bytes(); - String contentType = request.contentType(); - BinaryContent binaryContent = new BinaryContent( - fileName, - (long) bytes.length, - contentType, - bytes - ); - return binaryContentRepository.save(binaryContent); - } - - @Override - public BinaryContent find(UUID binaryContentId) { - return binaryContentRepository.findById(binaryContentId) - .orElseThrow(() -> new NoSuchElementException( - "BinaryContent with id " + binaryContentId + " not found")); - } - - @Override - public List findAllByIdIn(List binaryContentIds) { - return binaryContentRepository.findAllByIdIn(binaryContentIds).stream() - .toList(); - } - - @Override - public void delete(UUID binaryContentId) { - if (!binaryContentRepository.existsById(binaryContentId)) { - throw new NoSuchElementException("BinaryContent with id " + binaryContentId + " not found"); - } - binaryContentRepository.deleteById(binaryContentId); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java deleted file mode 100644 index 54104b42..00000000 --- a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.sprint.mission.discodeit.service.basic; - -import com.sprint.mission.discodeit.dto.data.ChannelDto; -import com.sprint.mission.discodeit.dto.request.PrivateChannelCreateRequest; -import com.sprint.mission.discodeit.dto.request.PublicChannelCreateRequest; -import com.sprint.mission.discodeit.dto.request.PublicChannelUpdateRequest; -import com.sprint.mission.discodeit.entity.Channel; -import com.sprint.mission.discodeit.entity.ChannelType; -import com.sprint.mission.discodeit.entity.Message; -import com.sprint.mission.discodeit.entity.ReadStatus; -import com.sprint.mission.discodeit.repository.ChannelRepository; -import com.sprint.mission.discodeit.repository.MessageRepository; -import com.sprint.mission.discodeit.repository.ReadStatusRepository; -import com.sprint.mission.discodeit.service.ChannelService; -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 ReadStatusRepository readStatusRepository; - private final MessageRepository messageRepository; - - @Override - public Channel create(PublicChannelCreateRequest request) { - String name = request.name(); - String description = request.description(); - Channel channel = new Channel(ChannelType.PUBLIC, name, description); - - return channelRepository.save(channel); - } - - @Override - public Channel create(PrivateChannelCreateRequest request) { - Channel channel = new Channel(ChannelType.PRIVATE, null, null); - Channel createdChannel = channelRepository.save(channel); - - request.participantIds().stream() - .map(userId -> new ReadStatus(userId, createdChannel.getId(), Instant.MIN)) - .forEach(readStatusRepository::save); - - return createdChannel; - } - - @Override - public ChannelDto find(UUID channelId) { - return channelRepository.findById(channelId) - .map(this::toDto) - .orElseThrow( - () -> new NoSuchElementException("Channel with id " + channelId + " not found")); - } - - @Override - public List findAllByUserId(UUID userId) { - List mySubscribedChannelIds = readStatusRepository.findAllByUserId(userId).stream() - .map(ReadStatus::getChannelId) - .toList(); - - return channelRepository.findAll().stream() - .filter(channel -> - channel.getType().equals(ChannelType.PUBLIC) - || mySubscribedChannelIds.contains(channel.getId()) - ) - .map(this::toDto) - .toList(); - } - - @Override - public Channel update(UUID channelId, PublicChannelUpdateRequest request) { - String newName = request.newName(); - String newDescription = request.newDescription(); - Channel channel = channelRepository.findById(channelId) - .orElseThrow( - () -> new NoSuchElementException("Channel with id " + channelId + " not found")); - if (channel.getType().equals(ChannelType.PRIVATE)) { - throw new IllegalArgumentException("Private channel cannot be updated"); - } - channel.update(newName, newDescription); - return channelRepository.save(channel); - } - - @Override - public void delete(UUID channelId) { - Channel channel = channelRepository.findById(channelId) - .orElseThrow( - () -> new NoSuchElementException("Channel with id " + channelId + " not found")); - - messageRepository.deleteAllByChannelId(channel.getId()); - readStatusRepository.deleteAllByChannelId(channel.getId()); - - channelRepository.deleteById(channelId); - } - - private ChannelDto toDto(Channel channel) { - Instant lastMessageAt = messageRepository.findAllByChannelId(channel.getId()) - .stream() - .sorted(Comparator.comparing(Message::getCreatedAt).reversed()) - .map(Message::getCreatedAt) - .limit(1) - .findFirst() - .orElse(Instant.MIN); - - List participantIds = new ArrayList<>(); - if (channel.getType().equals(ChannelType.PRIVATE)) { - readStatusRepository.findAllByChannelId(channel.getId()) - .stream() - .map(ReadStatus::getUserId) - .forEach(participantIds::add); - } - - return new ChannelDto( - channel.getId(), - channel.getType(), - channel.getName(), - channel.getDescription(), - participantIds, - lastMessageAt - ); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java deleted file mode 100644 index e6fab413..00000000 --- a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.sprint.mission.discodeit.service.basic; - -import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; -import com.sprint.mission.discodeit.dto.request.MessageCreateRequest; -import com.sprint.mission.discodeit.dto.request.MessageUpdateRequest; -import com.sprint.mission.discodeit.entity.BinaryContent; -import com.sprint.mission.discodeit.entity.Message; -import com.sprint.mission.discodeit.repository.BinaryContentRepository; -import com.sprint.mission.discodeit.repository.ChannelRepository; -import com.sprint.mission.discodeit.repository.MessageRepository; -import com.sprint.mission.discodeit.repository.UserRepository; -import com.sprint.mission.discodeit.service.MessageService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.NoSuchElementException; -import java.util.UUID; - -@RequiredArgsConstructor -@Service -public class BasicMessageService implements MessageService { - - private final MessageRepository messageRepository; - // - private final ChannelRepository channelRepository; - private final UserRepository userRepository; - private final BinaryContentRepository binaryContentRepository; - - @Override - public Message create(MessageCreateRequest messageCreateRequest, - List binaryContentCreateRequests) { - UUID channelId = messageCreateRequest.channelId(); - UUID authorId = messageCreateRequest.authorId(); - - if (!channelRepository.existsById(channelId)) { - throw new NoSuchElementException("Channel with id " + channelId + " does not exist"); - } - if (!userRepository.existsById(authorId)) { - throw new NoSuchElementException("Author with id " + authorId + " does not exist"); - } - - List attachmentIds = binaryContentCreateRequests.stream() - .map(attachmentRequest -> { - String fileName = attachmentRequest.fileName(); - String contentType = attachmentRequest.contentType(); - byte[] bytes = attachmentRequest.bytes(); - - BinaryContent binaryContent = new BinaryContent(fileName, (long) bytes.length, - contentType, bytes); - BinaryContent createdBinaryContent = binaryContentRepository.save(binaryContent); - return createdBinaryContent.getId(); - }) - .toList(); - - String content = messageCreateRequest.content(); - Message message = new Message( - content, - channelId, - authorId, - attachmentIds - ); - return messageRepository.save(message); - } - - @Override - public Message find(UUID messageId) { - return messageRepository.findById(messageId) - .orElseThrow( - () -> new NoSuchElementException("Message with id " + messageId + " not found")); - } - - @Override - public List findAllByChannelId(UUID channelId) { - return messageRepository.findAllByChannelId(channelId).stream() - .toList(); - } - - @Override - public Message update(UUID messageId, MessageUpdateRequest request) { - String newContent = request.newContent(); - Message message = messageRepository.findById(messageId) - .orElseThrow( - () -> new NoSuchElementException("Message with id " + messageId + " not found")); - message.update(newContent); - return messageRepository.save(message); - } - - @Override - public void delete(UUID messageId) { - Message message = messageRepository.findById(messageId) - .orElseThrow( - () -> new NoSuchElementException("Message with id " + messageId + " not found")); - - message.getAttachmentIds() - .forEach(binaryContentRepository::deleteById); - - messageRepository.deleteById(messageId); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java deleted file mode 100644 index 8dc4fea9..00000000 --- a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.sprint.mission.discodeit.service.basic; - -import com.sprint.mission.discodeit.dto.request.ReadStatusCreateRequest; -import com.sprint.mission.discodeit.dto.request.ReadStatusUpdateRequest; -import com.sprint.mission.discodeit.entity.ReadStatus; -import com.sprint.mission.discodeit.repository.ChannelRepository; -import com.sprint.mission.discodeit.repository.ReadStatusRepository; -import com.sprint.mission.discodeit.repository.UserRepository; -import com.sprint.mission.discodeit.service.ReadStatusService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.time.Instant; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.UUID; - -@RequiredArgsConstructor -@Service -public class BasicReadStatusService implements ReadStatusService { - - private final ReadStatusRepository readStatusRepository; - private final UserRepository userRepository; - private final ChannelRepository channelRepository; - - @Override - public ReadStatus create(ReadStatusCreateRequest request) { - UUID userId = request.userId(); - UUID channelId = request.channelId(); - - if (!userRepository.existsById(userId)) { - throw new NoSuchElementException("User with id " + userId + " does not exist"); - } - if (!channelRepository.existsById(channelId)) { - throw new NoSuchElementException("Channel with id " + channelId + " does not exist"); - } - - return readStatusRepository.findAllByUserId(userId).stream() - .filter(readStatus -> readStatus.getChannelId().equals(channelId)) - .findFirst() - .orElseGet( - () -> { - Instant lastReadAt = request.lastReadAt(); - ReadStatus readStatus = new ReadStatus(userId, channelId, lastReadAt); - return readStatusRepository.save(readStatus); - } - ); - } - - @Override - public ReadStatus find(UUID readStatusId) { - return readStatusRepository.findById(readStatusId) - .orElseThrow( - () -> new NoSuchElementException("ReadStatus with id " + readStatusId + " not found")); - } - - @Override - public List findAllByUserId(UUID userId) { - return readStatusRepository.findAllByUserId(userId).stream() - .toList(); - } - - @Override - public ReadStatus update(UUID readStatusId, ReadStatusUpdateRequest request) { - Instant newLastReadAt = request.newLastReadAt(); - ReadStatus readStatus = readStatusRepository.findById(readStatusId) - .orElseThrow( - () -> new NoSuchElementException("ReadStatus with id " + readStatusId + " not found")); - readStatus.update(newLastReadAt); - return readStatusRepository.save(readStatus); - } - - @Override - public void delete(UUID readStatusId) { - if (!readStatusRepository.existsById(readStatusId)) { - throw new NoSuchElementException("ReadStatus with id " + readStatusId + " not found"); - } - readStatusRepository.deleteById(readStatusId); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java deleted file mode 100644 index 5a2ec717..00000000 --- a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.sprint.mission.discodeit.service.basic; - -import com.sprint.mission.discodeit.dto.data.UserDto; -import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserUpdateRequest; -import com.sprint.mission.discodeit.entity.BinaryContent; -import com.sprint.mission.discodeit.entity.User; -import com.sprint.mission.discodeit.entity.UserStatus; -import com.sprint.mission.discodeit.repository.BinaryContentRepository; -import com.sprint.mission.discodeit.repository.UserRepository; -import com.sprint.mission.discodeit.repository.UserStatusRepository; -import com.sprint.mission.discodeit.service.UserService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.time.Instant; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Optional; -import java.util.UUID; - -@RequiredArgsConstructor -@Service -public class BasicUserService implements UserService { - - private final UserRepository userRepository; - // - private final BinaryContentRepository binaryContentRepository; - private final UserStatusRepository userStatusRepository; - - @Override - public User create(UserCreateRequest userCreateRequest, - Optional optionalProfileCreateRequest) { - String username = userCreateRequest.username(); - String email = userCreateRequest.email(); - - if (userRepository.existsByEmail(email)) { - throw new IllegalArgumentException("User with email " + email + " already exists"); - } - if (userRepository.existsByUsername(username)) { - throw new IllegalArgumentException("User with username " + username + " already exists"); - } - - UUID nullableProfileId = optionalProfileCreateRequest - .map(profileRequest -> { - String fileName = profileRequest.fileName(); - String contentType = profileRequest.contentType(); - byte[] bytes = profileRequest.bytes(); - BinaryContent binaryContent = new BinaryContent(fileName, (long) bytes.length, - contentType, bytes); - return binaryContentRepository.save(binaryContent).getId(); - }) - .orElse(null); - String password = userCreateRequest.password(); - - User user = new User(username, email, password, nullableProfileId); - User createdUser = userRepository.save(user); - - Instant now = Instant.now(); - UserStatus userStatus = new UserStatus(createdUser.getId(), now); - userStatusRepository.save(userStatus); - - return createdUser; - } - - @Override - public UserDto find(UUID userId) { - return userRepository.findById(userId) - .map(this::toDto) - .orElseThrow(() -> new NoSuchElementException("User with id " + userId + " not found")); - } - - @Override - public List findAll() { - return userRepository.findAll() - .stream() - .map(this::toDto) - .toList(); - } - - @Override - public User update(UUID userId, UserUpdateRequest userUpdateRequest, - Optional optionalProfileCreateRequest) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new NoSuchElementException("User with id " + userId + " not found")); - - String newUsername = userUpdateRequest.newUsername(); - String newEmail = userUpdateRequest.newEmail(); - if (userRepository.existsByEmail(newEmail)) { - throw new IllegalArgumentException("User with email " + newEmail + " already exists"); - } - if (userRepository.existsByUsername(newUsername)) { - throw new IllegalArgumentException("User with username " + newUsername + " already exists"); - } - - UUID nullableProfileId = optionalProfileCreateRequest - .map(profileRequest -> { - Optional.ofNullable(user.getProfileId()) - .ifPresent(binaryContentRepository::deleteById); - - String fileName = profileRequest.fileName(); - String contentType = profileRequest.contentType(); - byte[] bytes = profileRequest.bytes(); - BinaryContent binaryContent = new BinaryContent(fileName, (long) bytes.length, - contentType, bytes); - return binaryContentRepository.save(binaryContent).getId(); - }) - .orElse(null); - - String newPassword = userUpdateRequest.newPassword(); - user.update(newUsername, newEmail, newPassword, nullableProfileId); - - return userRepository.save(user); - } - - @Override - public void delete(UUID userId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new NoSuchElementException("User with id " + userId + " not found")); - - Optional.ofNullable(user.getProfileId()) - .ifPresent(binaryContentRepository::deleteById); - userStatusRepository.deleteByUserId(userId); - - userRepository.deleteById(userId); - } - - private UserDto toDto(User user) { - Boolean online = userStatusRepository.findByUserId(user.getId()) - .map(UserStatus::isOnline) - .orElse(null); - - return new UserDto( - user.getId(), - user.getCreatedAt(), - user.getUpdatedAt(), - user.getUsername(), - user.getEmail(), - user.getProfileId(), - online - ); - } -} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java deleted file mode 100644 index 6680ad1a..00000000 --- a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.sprint.mission.discodeit.service.basic; - -import com.sprint.mission.discodeit.dto.request.UserStatusCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserStatusUpdateRequest; -import com.sprint.mission.discodeit.entity.UserStatus; -import com.sprint.mission.discodeit.repository.UserRepository; -import com.sprint.mission.discodeit.repository.UserStatusRepository; -import com.sprint.mission.discodeit.service.UserStatusService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.time.Instant; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.UUID; - -@RequiredArgsConstructor -@Service -public class BasicUserStatusService implements UserStatusService { - - private final UserStatusRepository userStatusRepository; - private final UserRepository userRepository; - - @Override - public UserStatus create(UserStatusCreateRequest request) { - UUID userId = request.userId(); - - if (!userRepository.existsById(userId)) { - throw new NoSuchElementException("User with id " + userId + " does not exist"); - } - if (userStatusRepository.findByUserId(userId).isPresent()) { - throw new IllegalArgumentException("UserStatus with id " + userId + " already exists"); - } - - Instant lastActiveAt = request.lastActiveAt(); - UserStatus userStatus = new UserStatus(userId, lastActiveAt); - return userStatusRepository.save(userStatus); - } - - @Override - public UserStatus find(UUID userStatusId) { - return userStatusRepository.findById(userStatusId) - .orElseThrow( - () -> new NoSuchElementException("UserStatus with id " + userStatusId + " not found")); - } - - @Override - public List findAll() { - return userStatusRepository.findAll().stream() - .toList(); - } - - @Override - public UserStatus update(UUID userStatusId, UserStatusUpdateRequest request) { - Instant newLastActiveAt = request.newLastActiveAt(); - - UserStatus userStatus = userStatusRepository.findById(userStatusId) - .orElseThrow( - () -> new NoSuchElementException("UserStatus with id " + userStatusId + " not found")); - userStatus.update(newLastActiveAt); - - return userStatusRepository.save(userStatus); - } - - @Override - public UserStatus updateByUserId(UUID userId, UserStatusUpdateRequest request) { - Instant newLastActiveAt = request.newLastActiveAt(); - - UserStatus userStatus = userStatusRepository.findByUserId(userId) - .orElseThrow( - () -> new NoSuchElementException("UserStatus with userId " + userId + " not found")); - userStatus.update(newLastActiveAt); - - return userStatusRepository.save(userStatus); - } - - @Override - public void delete(UUID userStatusId) { - if (!userStatusRepository.existsById(userStatusId)) { - throw new NoSuchElementException("UserStatus with id " + userStatusId + " not found"); - } - userStatusRepository.deleteById(userStatusId); - } -} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index af0f20c7..7ff10a47 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,13 +1,9 @@ spring: application: - name: discodeit - servlet: - multipart: - maxFileSize: 10MB # 파일 하나의 최대 크기 - maxRequestSize: 30MB # 한 번에 최대 업로드 가능 용량 + name : discodeit + service: + type : jcf #jcf | file -discodeit: - repository: - type: file # jcf | file - file-directory: .discodeit \ No newline at end of file +storage: + location: ./data \ No newline at end of file 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 ffeaa39b..00000000 --- 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,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";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 deleted file mode 100644 index 096eb411..00000000 --- a/src/main/resources/static/assets/index-kQJbKSsj.css +++ /dev/null @@ -1 +0,0 @@ -: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 deleted file mode 100644 index 479bed6a3da0a8dbdd08a51d81b30e4d4fabae89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html deleted file mode 100644 index 66e84975..00000000 --- a/src/main/resources/static/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - Discodeit - - - - - -

- - diff --git a/src/main/resources/static/script.js b/src/main/resources/static/script.js deleted file mode 100644 index e63118b8..00000000 --- a/src/main/resources/static/script.js +++ /dev/null @@ -1,67 +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 b45f4e70..00000000 --- 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 f3acfdb5..00000000 --- a/src/main/resources/static/user-list.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - 사용자 목록 - - - -
-

사용자 목록

-
- -
-
- - - \ No newline at end of file diff --git a/src/test/java/com/sprint/mission/discodeit/DiscodeitApplicationTests.java b/src/test/java/com/sprint/mission/discodeit/DiscodeitApplicationTests.java index 3a987a21..7ab8d98c 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() { + } }