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 a4b76b95..1b33c55b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ 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,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAw2SURBVHgB7d3PT1XpHcfxBy5g6hipSMolGViACThxJDbVRZ2FXejKlf9h/4GmC1fTRdkwC8fE0JgyJuICFkCjEA04GeZe6P0cPC0698I95zzPc57v5f1K6DSto3A8n/v9nufXGfrr338+dgBMGnYAzCLAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwbcTDvyuWh//33w1/1dexwMRBgYxTW5vVh9/vxYTcxPpR9jY0OffZrdt8fu82ttlvfbLv9j4R5kBHgxCmcE1eH3NfTDTc7PfxZte3lJNgjbmlxxK3+1HKrr1oOg4kAJ0pVdnG+4ZqTw7+psEUoxF91Qv/Di1+db/q+ZpvD7g+T6gb04XLyv6mF3//osuqvTmDn3RGdQCAEOCG6+W/ONdzNTnCrhPZLN2Yb2T99hVhdwOLcSOf37f7hknUN4yedgLoGeb3Rdv/qdAIE2S8CnIDzAuGDQrzXeTZee1OtndaHy9LCSOHvU3++vv693nLPX9LS+0KAa6QQLC2o4sb5a1A7rYGtMqPU+l7v3hpx85+qeVnfdH7W2c7z/Pcrh1RjD5gHromq2JOHY9HCK2Ojzk1dL1fhH90fqxzenDoO/X79DMjhbAQ4Mg1OPXl4KauGodrls6j6FaXKq+dZn/IQ13ENBgkBjiRvQR99V2/lmZos9lc+PxOuxdd1uL3gp6pfVDwDR6Ab9cG9Me9VLAZ1CiHpmXhz6yibakJxVODAZpoN9/iBzfCq+sboFkJ/SAwyrlxAujE1WJWSIiO/sYKlxSpTnbEBqnBxVOBA9LybWnjloM8An6ysitc1NCe5FcvgqgVw/85o1OmhItY32n39uqnJuC3/FAEuhavmmcLra77UN7XP2322qRNX494aqvgojqvmUcrhFa1+6tdXkae6tMiEhR3FEWBPNOCTcni1rZCli4OHAHuQ4mjzaewJHlxMI1Wked5Uw7v99ijbwqd/FnVQQ7WmQyiOAFegZ7a736ZzCU820h+7nbfHbnO7XSq4p3+vmHbfMwdcBgGuoO4dNQrZxtaR+08nqNueT73Y2D7qTIW5aLRXGcUR4JL03FtHeBXa9Y2jyhX2PHudiqg/K9ZuoY3t/uan8TkCXIKCG/u5V2Fae9N2a+vtKO2tjqfVnxfj5zw5O4sWugwCXIJa51hiB/e0tfVWdkZX6CrMCHl5BLigWDt0RCc6rrxo1XZQu6rw6qt2tq47FD0G9Lu8E79FgAvIWucIO3QU2B9ftpK4sVWFZ5rDQTYbqHUOcdztRcJCjgLUToauvrqpny4fJlWVlp/5P4BOH1IcbFcdAe6Tght6h5FeiaLwpnZTq5VW2HzN1eYfUoS3OgLcp9sL4cOrkKT6YrI8dFUHnDQYR3j94Rm4D9kLxQLuV009vKdpXbXae00vFdm8UWVZJ3ojwH3QcS+hnn1VifSMaemVoPqeVzqDT6rG2oivQS5dH33l70ZS262w7n04yhae8MrTMAhwH0KNPFsfyNH3vd+pxkwD1Ydn4HOodQ5VfTXHyrMgqiDA55ibCbNJX1VLc6xAFQT4HCEGr9Q6s3wQPhDgM4RqnzWVQusMHwjwGTS66puCS/WFLwT4DCHOKia88IkA96BjTkOcVbzDQgZ4RIB7CBFejTzz7AufCHAPWn3lGwse4BsB7uGa5wqcLS3k7XvwjAD3cOWy84pnX4RAgHvw/QzMLhyEQIC7CLF4Y4+DyxEAAe4iRIB3PzD6DP8IcBejnncPagCL/bAIgQB34fsc5P2PtM8IgwBHcMjJqQiEAHfBm+JhBQGO4IDlkwiEAHdx2PIbuFhv+MPFQ4C7ODx0Xo2OOiAIAhwBz9QIhQB34XvOlhYaoRDgLg5+dl7pcACqMEIgwF2EWDV1bZwAwz8C3IVOzfAd4omrXGr4x13Vg++jb6YmudTwj7uqh733fgOsM6YZzIJvBLiH3Q/+NyDMB3pNCy4u3k7Yw+57/wNZM9PDbu2NGwjqJiauDrmvpxufXiv6+f+v63fw8SjrZDgLLBwC3INO0NBAls+2V220jurZNXw6h8K6ODfibsye/UjQnNR/nnQcGk/IX/DNsbp+EeAetAVQVaQ56fe5dXGu4X54YTPASwsj7uZ8o/CHmkJ/Y7aRfb3eaBNkj3gGPsNOgNZPN7G1RR36fh8/uJS96LxqR6Kf/9H9MRa2eEKAz7C5FaZS3l6w0/goaArchMeFKPkHwrVxbr+quIJn0LNqiFZPVSjEmx98U7UNVS016PWXe6NU4ooI8DnWN8O8DuX+H0eTnxdeWgjb7uv3/vMd9lpWQYDPEep9Rrp5by+kOy+s7+/mfPhWXyPzFrqRVHHlzpFPgYTwTScg87NphjhmZdTgGMohwH1YexPupdx3b40mN5ij6tuMuHabKlweV60PGo0OdTB7ioM5WjEWW5PNHqVw1fq09ibcu33zqZpUQjzTjN/Ws1urHK5an9bWW0Ffj5JSiOv4HiaYEy6Fq9YnLa1cfRWuCku+wOHmXL2DOnUEmGOHyiHABagKh17Dqxv57rcj7k+3RpKfJ0b9CHBBKy/ivOhIU0yPH4xdqD3EV37HB1ZRBLignc6c8MZW2FY6p5ZSK7b0bNyMOM3CTiE7CHAJz1+2or7vV1Msj74by4IcoyKHOMygH4fhptsHFgEuQRXqx5fx7zYFWRX5ycNL2UqpUFV5512cDuNLvAS9ONawlaQ10jpSJsZ64S+d3iCvm3777XGntW9nx9fsfqh+JK5+Nq0Qi43WvTgCXMHqq5abma53g75Gqmen9fX/alz1CBtNmenfj7k6yvIxQ3Wiha5AN/r3K4fJtX55hVarvVTy8AB9OMV0GGdwf+AQ4IpU4f75LN27Tzt9HtwbKzynrNF2zXvHsvOWClwGAfZAN18dg1r9UnuthSFF6WeK1doS4HIIsCeqVrHbziLUUpdZornc6S5iDC5p8A3FEWCPVn9KO8RlTpVUeJ8u/xLsUAPR780UUjkE2LOUQ6x11jPN4n/l+WDdaqDznEOdO3YREOAAFOJUn4mrTA3p51KQNU/sM8g8/5bHPHAgeibWAND9O2mdtlF147yCm2/o0IeBXlyuAwDKfjDotBMWcJRHBQ5IlUUVa1Bv0O1squnkVSllvd5kAXQVBDiwfBAo5pyqFbo2od5+cVEQ4Ag0CKRnYrWedVfjlLqBlEfsrSDAEWnwJx8Eqsve+zQCrA+SOq/DoCDAkeWDQE+X63k23txKIzRUXz8IcE00Qv23f/wSta3Odim9q/+Zc6Pz3Ev19YNppJrpRtaXXrGinUMhp5zUvqfg+Uu2HvlCgBORB1nzqYtzDTc77ffoHC3CSGEAS4N5zPv6Q4ATo7lVfV253MoWXegMrKob6xWaFKax9PzNdJpfBDhRqlL7n6qy2mqFWeuY9QaDfttsfRCoXd1NYOS5rnPEBh0BNuB0mGVifOgk1Ncb2VJGbVLIdxnp12qqaHO7HXQHURH6ngZ5RVqdCLBBqqj62jCwiknbBJefEd5QCDCCUWgV3hRa+EFFgBEEbXMcBBjeabR55UWLUzYiIMDwRoHVK1iZKoqHAMMLqm49CDAqyxefID42MwCGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhhFgwDACDBhGgAHDCDBgGAEGDCPAgGEEGDCMAAOGEWDAMAIMGEaAAcMIMGAYAQYMI8CAYQQYMIwAA4YRYMAwAgwYRoABwwgwYBgBBgwjwIBhBBgwjAADhv0XZkN9IbEGbp4AAAAASUVORK5CYII=";function Md({channel:r,isActive:i,onClick:s,hasUnread:u}){const c=ut(x=>x.currentUserId),d=Dt(x=>x.users),p=Wt(x=>x.profileImages);if(r.type==="PUBLIC")return g.jsxs(yp,{$isActive:i,onClick:s,$hasUnread:u,children:["# ",r.name]});const m=r.participantIds.map(x=>d.find(E=>E.id===x)).filter(Boolean);if(m.length>2){const x=m.filter(E=>E.id!==c).map(E=>E.username).join(", ");return g.jsxs(yd,{$isActive:i,onClick:s,children:[g.jsx(uy,{children:m.filter(E=>E.id!==c).slice(0,2).map((E,j)=>g.jsx(cy,{src:E.profileId?p[E.profileId]:Qt,style:{position:"absolute",left:j*16,zIndex:2-j}},E.id))}),g.jsxs(xd,{children:[g.jsx(vd,{$hasUnread:u,children:x}),g.jsxs(ay,{children:["멤버 ",m.length,"명"]})]})]})}const v=m.filter(x=>x.id!==c)[0];return g.jsxs(yd,{$isActive:i,onClick:s,children:[g.jsxs(ly,{children:[g.jsx("img",{src:v.profileId?p[v.profileId]:Qt,alt:"profile"}),g.jsx(vp,{$online:v.online})]}),g.jsx(xd,{children:g.jsx(vd,{$hasUnread:u,children:v.username})})]})}function q0({isOpen:r,onClose:i,user:s,onSubmit:u}){const[c,d]=ue.useState(s.username),[p,m]=ue.useState(s.email),[v,x]=ue.useState(""),[E,j]=ue.useState(null),[O,P]=ue.useState(""),[I,R]=ue.useState(null),L=Wt(T=>T.profileImages),V=Wt(T=>T.fetchProfileImage),F=ut(T=>T.logout);ue.useEffect(()=>{s.profileId&&!L[s.profileId]&&V(s.profileId)},[s.profileId,L,V]);const W=()=>{d(s.username),m(s.email),x(""),j(null),R(null),P(""),i()},K=T=>{const H=T.target.files[0];if(H){j(H);const se=new FileReader;se.onloadend=()=>{R(se.result)},se.readAsDataURL(H)}},$=async T=>{T.preventDefault(),P("");try{const H=new FormData,se={};c!==s.username&&(se.newUsername=c),p!==s.email&&(se.newEmail=p),v&&(se.newPassword=v),(Object.keys(se).length>0||E)&&(H.append("userUpdateRequest",new Blob([JSON.stringify(se)],{type:"application/json"})),E&&H.append("profile",E),await u(H)),i()}catch{P("사용자 정보 수정에 실패했습니다.")}};return r?g.jsx(b0,{children:g.jsxs(G0,{children:[g.jsx("h2",{children:"프로필 수정"}),g.jsxs("form",{onSubmit:$,children:[g.jsxs(Mi,{children:[g.jsx(Ui,{children:"프로필 이미지"}),g.jsxs(K0,{children:[g.jsx(X0,{src:I||L[s.profileId]||Qt,alt:"profile"}),g.jsx(J0,{type:"file",accept:"image/*",onChange:K,id:"profile-image"}),g.jsx(Z0,{htmlFor:"profile-image",children:"이미지 변경"})]})]}),g.jsxs(Mi,{children:[g.jsxs(Ui,{children:["사용자명 ",g.jsx(Fd,{children:"*"})]}),g.jsx(xu,{type:"text",value:c,onChange:T=>d(T.target.value),required:!0})]}),g.jsxs(Mi,{children:[g.jsxs(Ui,{children:["이메일 ",g.jsx(Fd,{children:"*"})]}),g.jsx(xu,{type:"email",value:p,onChange:T=>m(T.target.value),required:!0})]}),g.jsxs(Mi,{children:[g.jsx(Ui,{children:"새 비밀번호"}),g.jsx(xu,{type:"password",placeholder:"변경하지 않으려면 비워두세요",value:v,onChange:T=>x(T.target.value)})]}),O&&g.jsx(Y0,{children:O}),g.jsxs(ev,{children:[g.jsx(Ud,{type:"button",onClick:W,$secondary:!0,children:"취소"}),g.jsx(Ud,{type:"submit",children:"저장"})]})]}),g.jsx(tv,{onClick:F,children:"로그아웃"})]})}):null}const b0=N.div` - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -`,G0=N.div` - background: ${({theme:r})=>r.colors.background.secondary}; - padding: 32px; - border-radius: 5px; - width: 100%; - max-width: 480px; - - h2 { - color: ${({theme:r})=>r.colors.text.primary}; - margin-bottom: 24px; - text-align: center; - font-size: 24px; - } -`,xu=N.input` - width: 100%; - padding: 10px; - margin-bottom: 10px; - border: none; - border-radius: 4px; - background: ${({theme:r})=>r.colors.background.input}; - color: ${({theme:r})=>r.colors.text.primary}; - - &::placeholder { - color: ${({theme:r})=>r.colors.text.muted}; - } - - &:focus { - outline: none; - box-shadow: 0 0 0 2px ${({theme:r})=>r.colors.brand.primary}; - } -`,Ud=N.button` - width: 100%; - padding: 10px; - border: none; - border-radius: 4px; - background: ${({$secondary:r,theme:i})=>r?"transparent":i.colors.brand.primary}; - color: ${({theme:r})=>r.colors.text.primary}; - cursor: pointer; - font-weight: 500; - - &:hover { - background: ${({$secondary:r,theme:i})=>r?i.colors.background.hover:i.colors.brand.hover}; - } -`,Y0=N.div` - color: ${({theme:r})=>r.colors.status.error}; - font-size: 14px; - margin-bottom: 10px; -`,K0=N.div` - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 20px; -`,X0=N.img` - width: 100px; - height: 100px; - border-radius: 50%; - margin-bottom: 10px; - object-fit: cover; -`,J0=N.input` - display: none; -`,Z0=N.label` - color: ${({theme:r})=>r.colors.brand.primary}; - cursor: pointer; - font-size: 14px; - - &:hover { - text-decoration: underline; - } -`,ev=N.div` - display: flex; - gap: 10px; - margin-top: 20px; -`,tv=N.button` - width: 100%; - padding: 10px; - margin-top: 16px; - border: none; - border-radius: 4px; - background: transparent; - color: ${({theme:r})=>r.colors.status.error}; - cursor: pointer; - font-weight: 500; - - &:hover { - background: ${({theme:r})=>r.colors.status.error}20; - } -`,Mi=N.div` - margin-bottom: 20px; -`,Ui=N.label` - display: block; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 12px; - font-weight: 700; - margin-bottom: 8px; -`,Fd=N.span` - color: ${({theme:r})=>r.colors.status.error}; -`,Wp=N.div` - position: absolute; - bottom: -3px; - right: -3px; - width: 16px; - height: 16px; - border-radius: 50%; - background: ${r=>r.$online?J.colors.status.online:J.colors.status.offline}; - border: 4px solid ${r=>r.$background||J.colors.background.secondary}; -`,nv=N.div` - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.5rem 0.75rem; - background-color: ${({theme:r})=>r.colors.background.tertiary}; - width: 100%; - height: 52px; -`,rv=N.div` - position: relative; - width: 32px; - height: 32px; - flex-shrink: 0; -`,ov=N.img` - width: 100%; - height: 100%; - border-radius: 50%; - object-fit: cover; -`,iv=N.div` - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - justify-content: center; -`,sv=N.div` - font-weight: 500; - color: ${({theme:r})=>r.colors.text.primary}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 0.875rem; - line-height: 1.2; -`,lv=N.div` - font-size: 0.75rem; - color: ${({theme:r})=>r.colors.text.secondary}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.2; -`,uv=N.div` - display: flex; - align-items: center; - flex-shrink: 0; -`,av=N.button` - background: none; - border: none; - padding: 0.25rem; - cursor: pointer; - color: ${({theme:r})=>r.colors.text.secondary}; - font-size: 18px; - - &:hover { - color: ${({theme:r})=>r.colors.text.primary}; - } -`;function cv({user:r}){const[i,s]=ue.useState(!1);ut(m=>m.logout);const u=ut(m=>m.updateUser),c=Wt(m=>m.profileImages),d=Wt(m=>m.fetchProfileImage);ue.useEffect(()=>{r.profileId&&!c[r.profileId]&&d(r.profileId)},[r.profileId,c,d]);const p=async m=>{await u(r.id,m)};return g.jsxs(g.Fragment,{children:[g.jsxs(nv,{children:[g.jsxs(rv,{children:[g.jsx(ov,{src:c[r.profileId]||Qt}),g.jsx(Wp,{$online:r.online,$background:J.colors.background.tertiary})]}),g.jsxs(iv,{children:[g.jsx(sv,{children:r.username}),g.jsx(lv,{children:r.email})]}),g.jsx(uv,{children:g.jsx(av,{onClick:()=>s(!0),children:"⚙️"})})]}),g.jsx(q0,{isOpen:i,onClose:()=>s(!1),user:r,onSubmit:p})]})}const fv=N.div` - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.85); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -`,dv=N.div` - background: ${J.colors.background.primary}; - border-radius: 4px; - width: 440px; - max-width: 90%; -`,pv=N.div` - padding: 16px; - display: flex; - justify-content: space-between; - align-items: center; -`,hv=N.h2` - color: ${J.colors.text.primary}; - font-size: 20px; - font-weight: 600; - margin: 0; -`,mv=N.div` - padding: 0 16px 16px; -`,gv=N.form` - display: flex; - flex-direction: column; - gap: 16px; -`,Su=N.div` - display: flex; - flex-direction: column; - gap: 8px; -`,ku=N.label` - color: ${J.colors.text.primary}; - font-size: 12px; - font-weight: 600; - text-transform: uppercase; -`,yv=N.p` - color: ${J.colors.text.muted}; - font-size: 14px; - margin: -4px 0 0; -`,Fu=N.input` - padding: 10px; - background: ${J.colors.background.tertiary}; - border: none; - border-radius: 3px; - color: ${J.colors.text.primary}; - font-size: 16px; - - &:focus { - outline: none; - box-shadow: 0 0 0 2px ${J.colors.status.online}; - } - - &::placeholder { - color: ${J.colors.text.muted}; - } -`,vv=N.button` - margin-top: 8px; - padding: 12px; - background: ${J.colors.status.online}; - color: white; - border: none; - border-radius: 3px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s; - - &:hover { - background: #3ca374; - } -`,wv=N.button` - background: none; - border: none; - color: ${J.colors.text.muted}; - font-size: 24px; - cursor: pointer; - padding: 4px; - line-height: 1; - - &:hover { - color: ${J.colors.text.primary}; - } -`,xv=N(Fu)` - margin-bottom: 8px; -`,Sv=N.div` - max-height: 300px; - overflow-y: auto; - background: ${J.colors.background.tertiary}; - border-radius: 4px; -`,kv=N.div` - display: flex; - align-items: center; - padding: 8px 12px; - cursor: pointer; - transition: background 0.2s; - - &:hover { - background: ${J.colors.background.hover}; - } - - & + & { - border-top: 1px solid ${J.colors.border.primary}; - } -`,Ev=N.input` - margin-right: 12px; - width: 16px; - height: 16px; - cursor: pointer; -`,Bd=N.img` - width: 32px; - height: 32px; - border-radius: 50%; - margin-right: 12px; -`,Cv=N.div` - flex: 1; - min-width: 0; -`,Av=N.div` - color: ${J.colors.text.primary}; - font-size: 14px; - font-weight: 500; -`,Rv=N.div` - color: ${J.colors.text.muted}; - font-size: 12px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`,Pv=N.div` - padding: 16px; - text-align: center; - color: ${J.colors.text.muted}; -`,jv=N.div` - color: ${J.colors.status.error}; - font-size: 14px; - padding: 8px 0; - text-align: center; - background-color: ${({theme:r})=>r.colors.background.tertiary}; - border-radius: 4px; - margin-bottom: 8px; -`,Bn=bn((r,i)=>({channels:[],pollingInterval:null,fetchChannels:async s=>{try{const u=await he.get(`${Xe.apiBaseUrl}/channels`,{params:{userId:s}});return r({channels:u.data}),u.data}catch(u){console.error("채널 목록 조회 실패:",u)}},startPolling:s=>{i().pollingInterval&&clearInterval(i().pollingInterval);const u=setInterval(()=>{i().fetchChannels(s)},3e3);r({pollingInterval:u})},stopPolling:()=>{i().pollingInterval&&(clearInterval(i().pollingInterval),r({pollingInterval:null}))},createPublicChannel:async s=>{try{const u=await he.post(`${Xe.apiBaseUrl}/channels/public`,s),c={...u.data,participantIds:[],lastMessageAt:u.data.createdAt};return r(d=>({channels:[...d.channels,c]})),c}catch(u){throw console.error("공개 채널 생성 실패:",u),u}},createPrivateChannel:async s=>{try{const u=await he.post(`${Xe.apiBaseUrl}/channels/private`,{participantIds:s}),c={...u.data,participantIds:s,lastMessageAt:u.data.createdAt};return r(d=>({channels:[...d.channels,c]})),c}catch(u){throw console.error("비공개 채널 생성 실패:",u),u}}}));function Iv({isOpen:r,type:i,onClose:s,onCreateSuccess:u}){const[c,d]=ue.useState({name:"",description:""}),[p,m]=ue.useState(""),[v,x]=ue.useState([]),[E,j]=ue.useState(""),O=Dt($=>$.users),P=Wt($=>$.profileImages),I=ut($=>$.currentUserId),R=ue.useMemo(()=>O.filter($=>$.id!==I).filter($=>$.username.toLowerCase().includes(p.toLowerCase())||$.email.toLowerCase().includes(p.toLowerCase())),[p,O]),L=Bn($=>$.createPublicChannel),V=Bn($=>$.createPrivateChannel),F=$=>{const{name:T,value:H}=$.target;d(se=>({...se,[T]:H}))},W=$=>{x(T=>T.includes($)?T.filter(H=>H!==$):[...T,$])},K=async $=>{var T,H;$.preventDefault(),j("");try{if(i==="PUBLIC"){if(!c.name.trim()){j("채널 이름을 입력해주세요.");return}await L({name:c.name,description:c.description})}else{if(v.length===0){j("대화 상대를 선택해주세요.");return}const se=[...v,I];await V(se)}u()}catch(se){console.error("채널 생성 실패:",se),j(((H=(T=se.response)==null?void 0:T.data)==null?void 0:H.message)||"채널 생성에 실패했습니다. 다시 시도해주세요.")}};return r?g.jsx(fv,{onClick:s,children:g.jsxs(dv,{onClick:$=>$.stopPropagation(),children:[g.jsxs(pv,{children:[g.jsx(hv,{children:i==="PUBLIC"?"채널 만들기":"개인 메시지 시작하기"}),g.jsx(wv,{onClick:s,children:"×"})]}),g.jsx(mv,{children:g.jsxs(gv,{onSubmit:K,children:[E&&g.jsx(jv,{children:E}),i==="PUBLIC"?g.jsxs(g.Fragment,{children:[g.jsxs(Su,{children:[g.jsx(ku,{children:"채널 이름"}),g.jsx(Fu,{name:"name",value:c.name,onChange:F,placeholder:"새로운-채널",required:!0})]}),g.jsxs(Su,{children:[g.jsx(ku,{children:"채널 설명"}),g.jsx(yv,{children:"이 채널의 주제를 설명해주세요."}),g.jsx(Fu,{name:"description",value:c.description,onChange:F,placeholder:"채널 설명을 입력하세요"})]})]}):g.jsxs(Su,{children:[g.jsx(ku,{children:"사용자 검색"}),g.jsx(xv,{type:"text",value:p,onChange:$=>m($.target.value),placeholder:"사용자명 또는 이메일로 검색"}),g.jsx(Sv,{children:R.length>0?R.map($=>g.jsxs(kv,{children:[g.jsx(Ev,{type:"checkbox",checked:v.includes($.id),onChange:()=>W($.id)}),$.profileId?g.jsx(Bd,{src:P[$.profileId]}):g.jsx(Bd,{src:Qt}),g.jsxs(Cv,{children:[g.jsx(Av,{children:$.username}),g.jsx(Rv,{children:$.email})]})]},$.id)):g.jsx(Pv,{children:"검색 결과가 없습니다."})})]}),g.jsx(vv,{type:"submit",children:i==="PUBLIC"?"채널 만들기":"대화 시작하기"})]})})]})}):null}const yo=bn((r,i)=>({readStatuses:{},fetchReadStatuses:async()=>{try{const s=ut.getState().currentUserId;if(!s)return;const c=(await he.get(`${Xe.apiBaseUrl}/readStatuses`,{params:{userId:s}})).data.reduce((d,p)=>(d[p.channelId]={id:p.id,lastReadAt:p.lastReadAt},d),{});r({readStatuses:c})}catch(s){console.error("읽음 상태 조회 실패:",s)}},updateReadStatus:async s=>{try{const u=ut.getState().currentUserId;if(!u)return;const c=i().readStatuses[s];let d;c?d=await he.patch(`${Xe.apiBaseUrl}/readStatuses/${c.id}`,{newLastReadAt:new Date().toISOString()}):d=await he.post(`${Xe.apiBaseUrl}/readStatuses`,{userId:u,channelId:s,lastReadAt:new Date().toISOString()}),r(p=>({readStatuses:{...p.readStatuses,[s]:{id:d.data.id,lastReadAt:d.data.lastReadAt}}}))}catch(u){console.error("읽음 상태 업데이트 실패:",u)}},hasUnreadMessages:(s,u)=>{const c=i().readStatuses[s],d=c==null?void 0:c.lastReadAt;return!d||new Date(u)>new Date(d)}}));function _v({currentUser:r,activeChannel:i,onChannelSelect:s}){var K,$;const[u,c]=ue.useState({PUBLIC:!1,PRIVATE:!1}),[d,p]=ue.useState({isOpen:!1,type:null}),m=Bn(T=>T.channels),v=Bn(T=>T.fetchChannels),x=Bn(T=>T.startPolling),E=Bn(T=>T.stopPolling);yo(T=>T.readStatuses);const j=yo(T=>T.fetchReadStatuses),O=yo(T=>T.updateReadStatus),P=yo(T=>T.hasUnreadMessages);ue.useEffect(()=>{if(r)return v(r.id),j(),x(r.id),()=>{E()}},[r,v,j,x,E]);const I=T=>{c(H=>({...H,[T]:!H[T]}))},R=(T,H)=>{H.stopPropagation(),p({isOpen:!0,type:T})},L=()=>{p({isOpen:!1,type:null})},V=async T=>{try{await v(r.id),L()}catch(H){console.error("채널 생성 실패:",H)}},F=T=>{s(T),O(T.id)},W=m.reduce((T,H)=>(T[H.type]||(T[H.type]=[]),T[H.type].push(H),T),{});return g.jsxs(oy,{children:[g.jsx(fy,{}),g.jsxs(iy,{children:[g.jsxs(hd,{children:[g.jsxs(Nu,{onClick:()=>I("PUBLIC"),children:[g.jsx(md,{$folded:u.PUBLIC,children:"▼"}),g.jsx("span",{children:"일반 채널"}),g.jsx(wd,{onClick:T=>R("PUBLIC",T),children:"+"})]}),g.jsx(gd,{$folded:u.PUBLIC,children:(K=W.PUBLIC)==null?void 0:K.map(T=>g.jsx(Md,{channel:T,isActive:(i==null?void 0:i.id)===T.id,hasUnread:P(T.id,T.lastMessageAt),onClick:()=>F(T)},T.id))})]}),g.jsxs(hd,{children:[g.jsxs(Nu,{onClick:()=>I("PRIVATE"),children:[g.jsx(md,{$folded:u.PRIVATE,children:"▼"}),g.jsx("span",{children:"개인 메시지"}),g.jsx(wd,{onClick:T=>R("PRIVATE",T),children:"+"})]}),g.jsx(gd,{$folded:u.PRIVATE,children:($=W.PRIVATE)==null?void 0:$.map(T=>g.jsx(Md,{channel:T,isActive:(i==null?void 0:i.id)===T.id,hasUnread:P(T.id,T.lastMessageAt),onClick:()=>F(T)},T.id))})]})]}),g.jsx(Nv,{children:g.jsx(cv,{user:r})}),g.jsx(Iv,{isOpen:d.isOpen,type:d.type,onClose:L,onCreateSuccess:V})]})}const Nv=N.div` - margin-top: auto; - border-top: 1px solid ${({theme:r})=>r.colors.border.primary}; - background-color: ${({theme:r})=>r.colors.background.tertiary}; -`,Ov=N.div` - flex: 1; - display: flex; - flex-direction: column; - background: ${({theme:r})=>r.colors.background.primary}; -`,Tv=N.div` - display: flex; - flex-direction: column; - height: 100%; - background: ${({theme:r})=>r.colors.background.primary}; -`,Lv=N(Tv)` - justify-content: center; - align-items: center; - flex: 1; - padding: 0 20px; -`,Dv=N.div` - text-align: center; - max-width: 400px; - padding: 20px; - margin-bottom: 80px; -`,zv=N.div` - font-size: 48px; - margin-bottom: 16px; - animation: wave 2s infinite; - transform-origin: 70% 70%; - - @keyframes wave { - 0% { transform: rotate(0deg); } - 10% { transform: rotate(14deg); } - 20% { transform: rotate(-8deg); } - 30% { transform: rotate(14deg); } - 40% { transform: rotate(-4deg); } - 50% { transform: rotate(10deg); } - 60% { transform: rotate(0deg); } - 100% { transform: rotate(0deg); } - } -`,Mv=N.h2` - color: ${({theme:r})=>r.colors.text.primary}; - font-size: 28px; - font-weight: 700; - margin-bottom: 16px; -`,Uv=N.p` - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 16px; - line-height: 1.6; - word-break: keep-all; -`,$d=N.div` - height: 48px; - padding: 0 16px; - background: ${J.colors.background.primary}; - border-bottom: 1px solid ${J.colors.border.primary}; - display: flex; - align-items: center; -`,Hd=N.div` - display: flex; - align-items: center; - gap: 8px; - height: 100%; -`,Fv=N.div` - display: flex; - align-items: center; - gap: 12px; - height: 100%; -`,Bv=N.div` - position: relative; - width: 24px; - height: 24px; - flex-shrink: 0; -`,Vd=N.img` - width: 24px; - height: 24px; - border-radius: 50%; -`,$v=N.div` - position: relative; - width: 40px; - height: 24px; - flex-shrink: 0; -`,Hv=N.div` - position: absolute; - bottom: -2px; - right: -2px; - width: 14px; - height: 14px; - border-radius: 50%; - background: ${r=>r.online?J.colors.status.online:J.colors.status.offline}; - border: 3px solid ${J.colors.background.secondary}; -`,Vv=N(Hv)` - border-color: ${J.colors.background.primary}; - bottom: -3px; - right: -3px; -`,Wv=N.div` - font-size: 12px; - color: ${J.colors.text.muted}; - line-height: 13px; -`,Wd=N.div` - font-weight: bold; - color: ${J.colors.text.primary}; - line-height: 20px; - font-size: 16px; -`,Qv=N.div` - flex: 1; - display: flex; - flex-direction: column-reverse; - overflow-y: auto; -`,qv=N.div` - padding: 16px; - display: flex; - flex-direction: column; -`,bv=N.div` - margin-bottom: 16px; - display: flex; - align-items: flex-start; -`,Gv=N.div` - position: relative; - margin-right: 16px; - flex-shrink: 0; -`,Yv=N.img` - width: 40px; - height: 40px; - border-radius: 50%; -`,Kv=N.div` - display: flex; - align-items: center; - margin-bottom: 4px; -`,Xv=N.span` - font-weight: bold; - color: ${J.colors.text.primary}; - margin-right: 8px; -`,Jv=N.span` - font-size: 0.75rem; - color: ${J.colors.text.muted}; -`,Zv=N.div` - color: ${J.colors.text.secondary}; - margin-top: 4px; -`,e1=N.form` - display: flex; - align-items: center; - gap: 8px; - padding: 16px; - background: ${({theme:r})=>r.colors.background.secondary}; -`,t1=N.textarea` - flex: 1; - padding: 12px; - background: ${({theme:r})=>r.colors.background.tertiary}; - border: none; - border-radius: 4px; - color: ${({theme:r})=>r.colors.text.primary}; - font-size: 14px; - resize: none; - min-height: 44px; - max-height: 144px; - - &:focus { - outline: none; - } - - &::placeholder { - color: ${({theme:r})=>r.colors.text.muted}; - } -`,n1=N.button` - background: none; - border: none; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 24px; - cursor: pointer; - padding: 4px 8px; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - color: ${({theme:r})=>r.colors.text.primary}; - } -`;N.div` - flex: 1; - display: flex; - align-items: center; - justify-content: center; - color: ${J.colors.text.muted}; - font-size: 16px; - font-weight: 500; - padding: 20px; - text-align: center; -`;const Qd=N.div` - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 8px; - width: 100%; -`,r1=N.a` - display: block; - border-radius: 4px; - overflow: hidden; - max-width: 300px; - - img { - width: 100%; - height: auto; - display: block; - } -`,o1=N.a` - display: flex; - align-items: center; - gap: 12px; - padding: 12px; - background: ${({theme:r})=>r.colors.background.tertiary}; - border-radius: 8px; - text-decoration: none; - width: fit-content; - - &:hover { - background: ${({theme:r})=>r.colors.background.hover}; - } -`,i1=N.div` - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - font-size: 40px; - color: #0B93F6; -`,s1=N.div` - display: flex; - flex-direction: column; - gap: 2px; -`,l1=N.span` - font-size: 14px; - color: #0B93F6; - font-weight: 500; -`,u1=N.span` - font-size: 13px; - color: ${({theme:r})=>r.colors.text.muted}; -`,a1=N.div` - display: flex; - flex-wrap: wrap; - gap: 8px; - padding: 8px 0; -`,Qp=N.div` - position: relative; - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - background: ${({theme:r})=>r.colors.background.tertiary}; - border-radius: 4px; - max-width: 300px; -`,c1=N(Qp)` - padding: 0; - overflow: hidden; - width: 200px; - height: 120px; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } -`,f1=N.div` - color: #0B93F6; - font-size: 20px; -`,d1=N.div` - font-size: 13px; - color: ${({theme:r})=>r.colors.text.primary}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`,qd=N.button` - position: absolute; - top: -6px; - right: -6px; - width: 20px; - height: 20px; - border-radius: 50%; - background: ${({theme:r})=>r.colors.background.secondary}; - border: none; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 16px; - line-height: 1; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - padding: 0; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - - &:hover { - color: ${({theme:r})=>r.colors.text.primary}; - } -`,vo=bn((r,i)=>({messages:[],pollingIntervals:{},lastMessageId:null,fetchMessages:async s=>{try{const u=await he.get(`${Xe.apiBaseUrl}/messages`,{params:{channelId:s}}),c=u.data[u.data.length-1],d=(c==null?void 0:c.id)!==i().lastMessageId;return r({messages:u.data,lastMessageId:c==null?void 0:c.id}),d}catch(u){return console.error("메시지 목록 조회 실패:",u),!1}},startPolling:s=>{const u=i();u.pollingIntervals[s]&&clearTimeout(u.pollingIntervals[s]);let c=300;const d=3e3;r(m=>({pollingIntervals:{...m.pollingIntervals,[s]:!0}}));const p=async()=>{const m=i();if(!m.pollingIntervals[s])return;if(await m.fetchMessages(s)?c=300:c=Math.min(c*1.5,d),i().pollingIntervals[s]){const x=setTimeout(p,c);r(E=>({pollingIntervals:{...E.pollingIntervals,[s]:x}}))}};p()},stopPolling:s=>{const{pollingIntervals:u}=i();if(u[s]){const c=u[s];typeof c=="number"&&clearTimeout(c),r(d=>{const p={...d.pollingIntervals};return delete p[s],{pollingIntervals:p}})}},createMessage:async s=>{try{const u=new FormData;u.append("messageCreateRequest",new Blob([JSON.stringify({content:s.content,channelId:s.channelId,authorId:s.authorId})],{type:"application/json"})),s.attachments&&s.attachments.forEach(p=>{u.append("attachments",p)});const c=await he.post(`${Xe.apiBaseUrl}/messages`,u,{headers:{"Content-Type":"multipart/form-data"}}),d=yo.getState().updateReadStatus;return await d(s.channelId),r(p=>({messages:[...p.messages,c.data]})),c.data}catch(u){throw console.error("메시지 생성 실패:",u),u}}})),p1=bn((r,i)=>({attachments:{},fetchAttachment:async s=>{if(i().attachments[s])return i().attachments[s];try{const u=await he.get(`${Xe.apiBaseUrl}/binaryContents/${s}`),{bytes:c,contentType:d,fileName:p,size:m}=u.data,x={url:`data:${d};base64,${c}`,contentType:d,originalName:p,size:m};return r(E=>({attachments:{...E.attachments,[s]:x}})),x}catch(u){return console.error("첨부파일 정보 조회 실패:",u),null}}})),h1=r=>r<1024?r+" B":r<1024*1024?(r/1024).toFixed(2)+" KB":r<1024*1024*1024?(r/(1024*1024)).toFixed(2)+" MB":(r/(1024*1024*1024)).toFixed(2)+" GB";function m1({channel:r}){const i=vo(P=>P.messages),s=vo(P=>P.fetchMessages),u=vo(P=>P.startPolling),c=vo(P=>P.stopPolling),d=Wt(P=>P.profileImages),p=Dt(P=>P.users),{attachments:m,fetchAttachment:v}=p1();ue.useEffect(()=>{if(r!=null&&r.id)return s(r.id),u(r.id),()=>{c(r.id)}},[r==null?void 0:r.id,s,u,c]),ue.useEffect(()=>{i.forEach(P=>{var I;(I=P.attachmentIds)==null||I.forEach(R=>{m[R]||v(R)})})},[i,m,v]);const x=async(P,I)=>{try{const R=await he.get(`${Xe.apiBaseUrl}/binaryContents/${P}`,{responseType:"blob"}),L=new Blob([R.data],{type:R.headers["content-type"]}),V=window.URL.createObjectURL(L),F=document.createElement("a");F.href=V,F.download=I,F.style.display="none",document.body.appendChild(F);try{const K=await(await window.showSaveFilePicker({suggestedName:I,types:[{description:"Files",accept:{"*/*":[".txt",".pdf",".doc",".docx",".xls",".xlsx",".jpg",".jpeg",".png",".gif"]}}]})).createWritable();await K.write(L),await K.close()}catch(W){W.name!=="AbortError"&&F.click()}document.body.removeChild(F),window.URL.revokeObjectURL(V)}catch(R){console.error("파일 다운로드 실패:",R)}},E=P=>P!=null&&P.length?P.map(I=>{const R=m[I];return R?R.contentType.startsWith("image/")?g.jsx(Qd,{children:g.jsx(r1,{href:"#",onClick:V=>{V.preventDefault(),x(I,R.originalName)},children:g.jsx("img",{src:R.url,alt:R.originalName})})},I):g.jsx(Qd,{children:g.jsxs(o1,{href:"#",onClick:V=>{V.preventDefault(),x(I,R.originalName)},children:[g.jsx(i1,{children:g.jsxs("svg",{width:"40",height:"40",viewBox:"0 0 40 40",fill:"none",children:[g.jsx("path",{d:"M8 3C8 1.89543 8.89543 1 10 1H22L32 11V37C32 38.1046 31.1046 39 30 39H10C8.89543 39 8 38.1046 8 37V3Z",fill:"#0B93F6",fillOpacity:"0.1"}),g.jsx("path",{d:"M22 1L32 11H24C22.8954 11 22 10.1046 22 9V1Z",fill:"#0B93F6",fillOpacity:"0.3"}),g.jsx("path",{d:"M13 19H27M13 25H27M13 31H27",stroke:"#0B93F6",strokeWidth:"2",strokeLinecap:"round"})]})}),g.jsxs(s1,{children:[g.jsx(l1,{children:R.originalName}),g.jsx(u1,{children:h1(R.size)})]})]})},I):null}):null,j=P=>new Date(P).toLocaleTimeString(),O=[...i].sort((P,I)=>P.createdAt.localeCompare(I.createdAt));return g.jsx(Qv,{children:g.jsx(qv,{children:O.map(P=>{const I=p.find(R=>R.id===P.authorId);return g.jsxs(bv,{children:[g.jsx(Gv,{children:g.jsx(Yv,{src:I&&I.profileId?d[I.profileId]:Qt,alt:I&&I.username||"알 수 없음"})}),g.jsxs("div",{children:[g.jsxs(Kv,{children:[g.jsx(Xv,{children:I&&I.username||"알 수 없음"}),g.jsx(Jv,{children:j(P.createdAt)})]}),g.jsx(Zv,{children:P.content}),E(P.attachmentIds)]})]},P.id)})})})}function g1({channel:r}){const[i,s]=ue.useState(""),[u,c]=ue.useState([]),d=vo(O=>O.createMessage),p=ut(O=>O.currentUserId),m=async O=>{if(O.preventDefault(),!(!i.trim()&&u.length===0))try{await d({content:i.trim(),channelId:r.id,authorId:p,attachments:u}),s(""),c([])}catch(P){console.error("메시지 전송 실패:",P)}},v=O=>{const P=Array.from(O.target.files);c(I=>[...I,...P]),O.target.value=""},x=O=>{c(P=>P.filter((I,R)=>R!==O))},E=O=>{O.key==="Enter"&&!O.shiftKey&&(O.preventDefault(),m(O))},j=(O,P)=>O.type.startsWith("image/")?g.jsxs(c1,{children:[g.jsx("img",{src:URL.createObjectURL(O),alt:O.name}),g.jsx(qd,{onClick:()=>x(P),children:"×"})]},P):g.jsxs(Qp,{children:[g.jsx(f1,{children:"📎"}),g.jsx(d1,{children:O.name}),g.jsx(qd,{onClick:()=>x(P),children:"×"})]},P);return ue.useEffect(()=>()=>{u.forEach(O=>{O.type.startsWith("image/")&&URL.revokeObjectURL(O)})},[u]),r?g.jsxs(g.Fragment,{children:[u.length>0&&g.jsx(a1,{children:u.map((O,P)=>j(O,P))}),g.jsxs(e1,{onSubmit:m,children:[g.jsxs(n1,{as:"label",children:["+",g.jsx("input",{type:"file",multiple:!0,onChange:v,style:{display:"none"}})]}),g.jsx(t1,{value:i,onChange:O=>s(O.target.value),onKeyPress:E,placeholder:r.type==="PUBLIC"?`#${r.name}에 메시지 보내기`:"메시지 보내기"})]})]}):null}function y1({channel:r}){const i=ut(v=>v.currentUserId),s=Dt(v=>v.users),u=Wt(v=>v.profileImages);if(!r)return null;if(r.type==="PUBLIC")return g.jsx($d,{children:g.jsx(Hd,{children:g.jsxs(Wd,{children:["# ",r.name]})})});const c=r.participantIds.map(v=>s.find(x=>x.id===v)).filter(Boolean),d=c.filter(v=>v.id!==i),p=c.length>2,m=c.filter(v=>v.id!==i).map(v=>v.username).join(", ");return g.jsx($d,{children:g.jsx(Hd,{children:g.jsxs(Fv,{children:[p?g.jsx($v,{children:d.slice(0,2).map((v,x)=>g.jsx(Vd,{src:v.profileId?u[v.profileId]:Qt,style:{position:"absolute",left:x*16,zIndex:2-x}},v.id))}):g.jsxs(Bv,{children:[g.jsx(Vd,{src:d[0].profileId?u[d[0].profileId]:Qt}),g.jsx(Vv,{online:d[0].online})]}),g.jsxs("div",{children:[g.jsx(Wd,{children:m}),p&&g.jsxs(Wv,{children:["멤버 ",c.length,"명"]})]})]})})})}function v1({channel:r}){return r?g.jsxs(Ov,{children:[g.jsx(y1,{channel:r}),g.jsx(m1,{channel:r}),g.jsx(g1,{channel:r})]}):g.jsx(Lv,{children:g.jsxs(Dv,{children:[g.jsx(zv,{children:"👋"}),g.jsx(Mv,{children:"채널을 선택해주세요"}),g.jsxs(Uv,{children:["왼쪽의 채널 목록에서 채널을 선택하여",g.jsx("br",{}),"대화를 시작하세요."]})]})})}const w1=N.div` - width: 240px; - background: ${J.colors.background.secondary}; - border-left: 1px solid ${J.colors.border.primary}; -`,x1=N.div` - padding: 16px; - font-size: 14px; - font-weight: bold; - color: ${J.colors.text.muted}; - text-transform: uppercase; -`,S1=N.div` - padding: 8px 16px; - display: flex; - align-items: center; - color: ${J.colors.text.muted}; -`,k1=N.div` - position: relative; - width: 32px; - height: 32px; - margin-right: 12px; -`,bd=N.img` - width: 100%; - height: 100%; - border-radius: 50%; -`,E1=N.div` - display: flex; - align-items: center; -`;N.div` - width: 8px; - height: 8px; - border-radius: 50%; - margin-right: 8px; - background: ${r=>J.colors.status[r.status]}; -`;function C1({member:r}){const i=Wt(u=>u.profileImages),s=Wt(u=>u.fetchProfileImage);return ue.useEffect(()=>{r.profileId&&!i[r.profileId]&&s(r.profileId)},[r.profileId,i,s]),g.jsxs(S1,{children:[g.jsxs(k1,{children:[i[r.profileId]?g.jsx(bd,{src:i[r.profileId]}):g.jsx(bd,{src:Qt}),g.jsx(Wp,{$online:r.online})]}),g.jsx(E1,{children:r.username})]})}function A1(){const r=Dt(c=>c.users),i=Dt(c=>c.fetchUsers),s=ut(c=>c.currentUserId);ue.useEffect(()=>{i()},[i]);const u=[...r].sort((c,d)=>c.id===s?-1:d.id===s?1:c.online&&!d.online?-1:!c.online&&d.online?1:c.username.localeCompare(d.username));return g.jsxs(w1,{children:[g.jsxs(x1,{children:["멤버 목록 - ",r.length]}),u.map(c=>g.jsx(C1,{member:c},c.id))]})}const qp=N.div` - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -`,bp=N.div` - background: ${J.colors.background.primary}; - padding: 32px; - border-radius: 8px; - width: 440px; - - h2 { - color: ${J.colors.text.primary}; - margin-bottom: 24px; - font-size: 24px; - font-weight: bold; - } - - form { - display: flex; - flex-direction: column; - gap: 16px; - } -`,xo=N.input` - width: 100%; - padding: 10px; - border-radius: 4px; - background: ${J.colors.background.input}; - border: none; - color: ${J.colors.text.primary}; - font-size: 16px; - - &::placeholder { - color: ${J.colors.text.muted}; - } - - &:focus { - outline: none; - } -`,Gp=N.button` - width: 100%; - padding: 12px; - border-radius: 4px; - background: ${J.colors.brand.primary}; - color: white; - font-size: 16px; - font-weight: 500; - border: none; - cursor: pointer; - transition: background-color 0.2s; - - &:hover { - background: ${J.colors.brand.hover}; - } -`,Yp=N.div` - color: ${J.colors.status.error}; - font-size: 14px; - text-align: center; -`,R1=N.p` - text-align: center; - margin-top: 16px; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 14px; -`,P1=N.span` - color: ${({theme:r})=>r.colors.brand.primary}; - cursor: pointer; - - &:hover { - text-decoration: underline; - } -`;function j1({isOpen:r,onClose:i}){const[s,u]=ue.useState(""),[c,d]=ue.useState(""),[p,m]=ue.useState(""),[v,x]=ue.useState(null),[E,j]=ue.useState(null),[O,P]=ue.useState(""),I=ut(V=>V.setCurrentUser),R=V=>{const F=V.target.files[0];if(F){x(F);const W=new FileReader;W.onloadend=()=>{j(W.result)},W.readAsDataURL(F)}},L=async V=>{V.preventDefault(),P("");try{const F=new FormData;F.append("userCreateRequest",new Blob([JSON.stringify({email:s,username:c,password:p})],{type:"application/json"})),v&&F.append("profile",v);const W=await he.post(`${Xe.apiBaseUrl}/users`,F);I(W.data),i()}catch{P("회원가입에 실패했습니다.")}};return r?g.jsx(qp,{children:g.jsxs(bp,{children:[g.jsx("h2",{children:"계정 만들기"}),g.jsxs("form",{onSubmit:L,children:[g.jsxs(Fi,{children:[g.jsxs(Bi,{children:["이메일 ",g.jsx(Eu,{children:"*"})]}),g.jsx(xo,{type:"email",value:s,onChange:V=>u(V.target.value),required:!0})]}),g.jsxs(Fi,{children:[g.jsxs(Bi,{children:["사용자명 ",g.jsx(Eu,{children:"*"})]}),g.jsx(xo,{type:"text",value:c,onChange:V=>d(V.target.value),required:!0})]}),g.jsxs(Fi,{children:[g.jsxs(Bi,{children:["비밀번호 ",g.jsx(Eu,{children:"*"})]}),g.jsx(xo,{type:"password",value:p,onChange:V=>m(V.target.value),required:!0})]}),g.jsxs(Fi,{children:[g.jsx(Bi,{children:"프로필 이미지"}),g.jsxs(I1,{children:[g.jsx(_1,{src:E||Qt,alt:"profile"}),g.jsx(N1,{type:"file",accept:"image/*",onChange:R,id:"profile-image"}),g.jsx(O1,{htmlFor:"profile-image",children:"이미지 변경"})]})]}),O&&g.jsx(Yp,{children:O}),g.jsx(Gp,{type:"submit",children:"계속하기"}),g.jsx(L1,{onClick:i,children:"이미 계정이 있으신가요?"})]})]})}):null}const Fi=N.div` - margin-bottom: 20px; -`,Bi=N.label` - display: block; - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 12px; - font-weight: 700; - margin-bottom: 8px; -`,Eu=N.span` - color: ${({theme:r})=>r.colors.status.error}; -`,I1=N.div` - display: flex; - flex-direction: column; - align-items: center; - margin: 10px 0; -`,_1=N.img` - width: 80px; - height: 80px; - border-radius: 50%; - margin-bottom: 10px; - object-fit: cover; -`,N1=N.input` - display: none; -`,O1=N.label` - color: ${({theme:r})=>r.colors.brand.primary}; - cursor: pointer; - font-size: 14px; - - &:hover { - text-decoration: underline; - } -`;N.p` - color: ${({theme:r})=>r.colors.text.muted}; - font-size: 12px; - margin-top: 16px; - text-align: center; -`;const T1=N.span` - color: ${({theme:r})=>r.colors.brand.primary}; - cursor: pointer; - - &:hover { - text-decoration: underline; - } -`,L1=N(T1)` - display: block; - text-align: center; - margin-top: 16px; -`,D1=({isOpen:r,onClose:i})=>{const[s,u]=ue.useState(""),[c,d]=ue.useState(""),[p,m]=ue.useState(""),[v,x]=ue.useState(!1),E=ut(P=>P.setCurrentUser),{fetchUsers:j}=Dt(),O=async()=>{var P;try{const I=await he.post(`${Xe.apiBaseUrl}/auth/login`,{username:s,password:c});I.status===200&&(await j(),E(I.data),m(""),i())}catch(I){console.error("로그인 에러:",I),((P=I.response)==null?void 0:P.status)===401?m("아이디 또는 비밀번호가 올바르지 않습니다."):m("로그인에 실패했습니다.")}};return r?g.jsxs(g.Fragment,{children:[g.jsx(qp,{children:g.jsxs(bp,{children:[g.jsx("h2",{children:"돌아오신 것을 환영해요!"}),g.jsxs("form",{onSubmit:P=>{P.preventDefault(),O()},children:[g.jsx(xo,{type:"text",placeholder:"사용자 이름",value:s,onChange:P=>u(P.target.value)}),g.jsx(xo,{type:"password",placeholder:"비밀번호",value:c,onChange:P=>d(P.target.value)}),p&&g.jsx(Yp,{children:p}),g.jsx(Gp,{type:"submit",children:"로그인"})]}),g.jsxs(R1,{children:["계정이 필요한가요? ",g.jsx(P1,{onClick:()=>x(!0),children:"가입하기"})]})]})}),g.jsx(j1,{isOpen:v,onClose:()=>x(!1)})]}):null};function z1(){const r=ut(v=>v.currentUserId),i=Dt(v=>v.users),{fetchUsers:s,updateUserStatus:u}=Dt(),[c,d]=ue.useState(null),p=Bn(v=>v.channels),m=r?i.find(v=>v.id===r):null;return ue.useEffect(()=>{let v;if(r){s(),u(r),v=setInterval(()=>{u(r)},3e4);const x=setInterval(()=>{s()},6e4);return()=>{clearInterval(v),clearInterval(x)}}},[r,s,u]),g.jsx(ty,{theme:J,children:m?g.jsxs(M1,{children:[g.jsx(_v,{channels:p,currentUser:m,activeChannel:c,onChannelSelect:d}),g.jsx(v1,{channel:c}),g.jsx(A1,{})]}):g.jsx(D1,{isOpen:!0,onClose:()=>{}})})}const M1=N.div` - display: flex; - height: 100vh; - width: 100vw; - position: relative; -`;eg.createRoot(document.getElementById("root")).render(g.jsx(ue.StrictMode,{children:g.jsx(z1,{})})); diff --git a/src/main/resources/static/assets/index-kQJbKSsj.css b/src/main/resources/static/assets/index-kQJbKSsj.css 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 479bed6a..00000000 Binary files a/src/main/resources/static/favicon.ico and /dev/null differ 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() { + } }