From 51450a3abd15cb403ed291517e0af54d0d27083e Mon Sep 17 00:00:00 2001 From: coehgns Date: Fri, 11 Jul 2025 22:10:43 +0900 Subject: [PATCH 01/49] =?UTF-8?q?chore=20(=20#9=20)=20:=20build-logic=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-logic/build.gradle.kts | 11 ----------- build-logic/settings.gradle.kts | 17 ----------------- .../main/kotlin/io/casper/build/TestClass.kt | 15 --------------- 3 files changed, 43 deletions(-) delete mode 100644 build-logic/build.gradle.kts delete mode 100644 build-logic/settings.gradle.kts delete mode 100644 build-logic/src/main/kotlin/io/casper/build/TestClass.kt diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts deleted file mode 100644 index a6e17cc..0000000 --- a/build-logic/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - `kotlin-dsl` - id("casper.documentation-convention") -} - -group = "io.casper.build" -version = "1.0.0" - -repositories { - mavenCentral() -} \ No newline at end of file diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts deleted file mode 100644 index 2af0ef2..0000000 --- a/build-logic/settings.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -rootProject.name = "build-logic" - -pluginManagement { - repositories { - gradlePluginPortal() - mavenCentral() - } - - // convention 모듈 참조 - includeBuild("../casper-convention") -} - -dependencyResolutionManagement { - repositories { - mavenCentral() - } -} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/io/casper/build/TestClass.kt b/build-logic/src/main/kotlin/io/casper/build/TestClass.kt deleted file mode 100644 index 6a5992d..0000000 --- a/build-logic/src/main/kotlin/io/casper/build/TestClass.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.casper.build - -/** - * 이 클래스는 KDoc 주석 검사 테스트를 위한 용도입니다. - * 이제 올바른 KDoc 주석 형식을 사용합니다. - */ -class TestClass { - - /** - * 이 함수는 빌드 로직에서 사용하는 테스트 함수입니다. - */ - fun testFunction() { - println("이 함수는 문서화 검사를 테스트하기 위한 용도입니다.") - } -} \ No newline at end of file From d014d3a65bf53414ac974084cd48074caa47aa40 Mon Sep 17 00:00:00 2001 From: coehgns Date: Fri, 11 Jul 2025 22:11:53 +0900 Subject: [PATCH 02/49] build ( #9 ) : DependencyVersion.kt --- buildSrc/src/main/kotlin/DependencyVersion.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 buildSrc/src/main/kotlin/DependencyVersion.kt diff --git a/buildSrc/src/main/kotlin/DependencyVersion.kt b/buildSrc/src/main/kotlin/DependencyVersion.kt new file mode 100644 index 0000000..17d8889 --- /dev/null +++ b/buildSrc/src/main/kotlin/DependencyVersion.kt @@ -0,0 +1,20 @@ +object DependencyVersion { + const val KOTLIN = "1.9.25" + const val SPRING_BOOT = "3.4.4" + const val SPRING_DEPENDENCY_MANAGEMENT = "1.1.7" + const val DETEKT = "1.23.6" + const val KTLINT = "12.1.1" + + const val JWT = "0.11.5" + const val ORG_JSON = "20230227" + const val MAPSTRUCT = "1.6.0" + + const val GRPC = "1.61.1" + const val GRPC_KOTLIN = "1.4.1" + const val PROTOBUF = "3.25.3" + + const val SWAGGER_VERSION = "2.5.0" + const val AWS = "1.12.281" + + const val OPEN_FEIGN_VERSION = "3.1.4" +} From 7439092b773d87d7b606cf9c261de8af679c2abd Mon Sep 17 00:00:00 2001 From: coehgns Date: Fri, 11 Jul 2025 22:12:02 +0900 Subject: [PATCH 03/49] build ( #9 ) : Dependencies.kt --- buildSrc/src/main/kotlin/Dependencies.kt | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 buildSrc/src/main/kotlin/Dependencies.kt diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt new file mode 100644 index 0000000..1aae290 --- /dev/null +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -0,0 +1,54 @@ +object Dependencies { + // Spring Boot + const val SPRING_BOOT_STARTER = "org.springframework.boot:spring-boot-starter" + const val SPRING_BOOT_STARTER_WEB = "org.springframework.boot:spring-boot-starter-web" + const val SPRING_BOOT_STARTER_DATA_JPA = "org.springframework.boot:spring-boot-starter-data-jpa" + const val SPRING_BOOT_STARTER_DATA_REDIS = "org.springframework.boot:spring-boot-starter-data-redis" + const val SPRING_BOOT_STARTER_SECURITY = "org.springframework.boot:spring-boot-starter-security" + const val SPRING_BOOT_STARTER_VALIDATION = "org.springframework.boot:spring-boot-starter-validation" + const val SPRING_BOOT_STARTER_TEST = "org.springframework.boot:spring-boot-starter-test" + + // Kotlin + const val KOTLIN_REFLECT = "org.jetbrains.kotlin:kotlin-reflect" + const val KOTLIN_TEST_JUNIT5 = "org.jetbrains.kotlin:kotlin-test-junit5" + + // Database + const val MYSQL_CONNECTOR = "com.mysql:mysql-connector-j" + + // JSON + const val JACKSON_MODULE_KOTLIN = "com.fasterxml.jackson.module:jackson-module-kotlin" + const val ORG_JSON = "org.json:json:${DependencyVersion.ORG_JSON}" + + // JWT + const val JWT_API = "io.jsonwebtoken:jjwt-api:${DependencyVersion.JWT}" + const val JWT_IMPL = "io.jsonwebtoken:jjwt-impl:${DependencyVersion.JWT}" + const val JWT_JACKSON = "io.jsonwebtoken:jjwt-jackson:${DependencyVersion.JWT}" + + // MapStruct + const val MAPSTRUCT = "org.mapstruct:mapstruct:${DependencyVersion.MAPSTRUCT}" + const val MAPSTRUCT_PROCESSOR = "org.mapstruct:mapstruct-processor:${DependencyVersion.MAPSTRUCT}" + + // Test + const val JUNIT_PLATFORM_LAUNCHER = "org.junit.platform:junit-platform-launcher" + + // gRPC + const val GRPC_NETTY_SHADED = "io.grpc:grpc-netty-shaded:${DependencyVersion.GRPC}" + const val GRPC_PROTOBUF = "io.grpc:grpc-protobuf:${DependencyVersion.GRPC}" + const val GRPC_STUB = "io.grpc:grpc-stub:${DependencyVersion.GRPC}" + const val GRPC_KOTLIN_STUB = "io.grpc:grpc-kotlin-stub:${DependencyVersion.GRPC_KOTLIN}" + const val PROTOBUF_KOTLIN = "com.google.protobuf:protobuf-kotlin:${DependencyVersion.PROTOBUF}" + const val GRPC_TESTING = "io.grpc:grpc-testing:${DependencyVersion.GRPC}" + + + // swagger + const val SWAGGER = "org.springdoc:springdoc-openapi-starter-webmvc-ui:${DependencyVersion.SWAGGER_VERSION}" + + // AWS + const val AWS = "com.amazonaws:aws-java-sdk-s3:${DependencyVersion.AWS}" + + // open feign + const val OPEN_FEIGN = "org.springframework.cloud:spring-cloud-starter-openfeign:${DependencyVersion.OPEN_FEIGN_VERSION}" + + // Kafka + const val KAFKA = "org.springframework.kafka:spring-kafka" +} From 20885c3ce24af0b126cdf94c36cecc823461f7f6 Mon Sep 17 00:00:00 2001 From: coehgns Date: Fri, 11 Jul 2025 22:12:36 +0900 Subject: [PATCH 04/49] build ( #9 ) : PluginVersion --- buildSrc/src/main/kotlin/PluginVersion.kt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 buildSrc/src/main/kotlin/PluginVersion.kt diff --git a/buildSrc/src/main/kotlin/PluginVersion.kt b/buildSrc/src/main/kotlin/PluginVersion.kt new file mode 100644 index 0000000..9b16506 --- /dev/null +++ b/buildSrc/src/main/kotlin/PluginVersion.kt @@ -0,0 +1,8 @@ +object PluginVersion { + const val KOTLIN_VERSION = "1.9.25" + const val SPRING_BOOT_VERSION = "3.4.4" + const val SPRING_DEPENDENCY_MANAGEMENT_VERSION = "1.1.7" + const val DETEKT_VERSION = "1.23.6" + const val KTLINT_VERSION = "12.1.1" + const val PROTOBUF_VERSION = "0.9.4" +} \ No newline at end of file From 817422156c0023663286d9ac09f0072957ee1e11 Mon Sep 17 00:00:00 2001 From: coehgns Date: Fri, 11 Jul 2025 22:12:40 +0900 Subject: [PATCH 05/49] build ( #9 ) : Plugin --- buildSrc/src/main/kotlin/Plugin.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 buildSrc/src/main/kotlin/Plugin.kt diff --git a/buildSrc/src/main/kotlin/Plugin.kt b/buildSrc/src/main/kotlin/Plugin.kt new file mode 100644 index 0000000..84e3329 --- /dev/null +++ b/buildSrc/src/main/kotlin/Plugin.kt @@ -0,0 +1,11 @@ +object Plugin { + const val KOTLIN_JVM = "org.jetbrains.kotlin.jvm" + const val KOTLIN_SPRING = "org.jetbrains.kotlin.plugin.spring" + const val KOTLIN_KAPT = "org.jetbrains.kotlin.kapt" + const val SPRING_BOOT = "org.springframework.boot" + const val SPRING_DEPENDENCY_MANAGEMENT = "io.spring.dependency-management" + const val DETEKT = "io.gitlab.arturbosch.detekt" + const val KTLINT = "org.jlleitschuh.gradle.ktlint" + const val CASPER_DOCUMENTATION = "casper.documentation-convention" + const val PROTOBUF = "com.google.protobuf" +} \ No newline at end of file From 2016fc9f82b80dc75f5be92225a8ee2f9052d4e5 Mon Sep 17 00:00:00 2001 From: coehgns Date: Fri, 11 Jul 2025 22:13:35 +0900 Subject: [PATCH 06/49] build ( #9 ) : build.gradle.kts - buildSrc --- buildSrc/build.gradle.kts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 buildSrc/build.gradle.kts diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..b22ed73 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} \ No newline at end of file From 1eecb7ba9c2ca2059d8dedef622b759c0f6e6deb Mon Sep 17 00:00:00 2001 From: coehgns Date: Fri, 11 Jul 2025 22:15:22 +0900 Subject: [PATCH 07/49] =?UTF-8?q?build=20(=20#9=20)=20:=20settings.gradle.?= =?UTF-8?q?kts=EC=97=90=EC=84=9C=20build-logic=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- settings.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index fc3189a..e455e48 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,6 @@ rootProject.name = "Casper-Feed" pluginManagement { includeBuild("casper-convention") - includeBuild("build-logic") repositories { gradlePluginPortal() mavenCentral() From 2b8e7ca52c54d7688d69d5b8ae418d4bd6642d2f Mon Sep 17 00:00:00 2001 From: coehgns Date: Fri, 11 Jul 2025 22:16:28 +0900 Subject: [PATCH 08/49] chore ( #7 ) : .editorconfig --- .editorconfig | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a314622 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +ktlint_disabled_rules = no-wildcard-imports,import-ordering,comment-spacing \ No newline at end of file From 2fe0fe0e9c899ad0031492be871598811b4fa2c2 Mon Sep 17 00:00:00 2001 From: coehgns Date: Fri, 11 Jul 2025 22:16:57 +0900 Subject: [PATCH 09/49] =?UTF-8?q?feat=20(=20#7=20)=20:=20Equus-Feed=20?= =?UTF-8?q?=EB=A0=88=EA=B1=B0=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 92 ++++++++++++++++++- gradlew | 0 .../hs/kr/entrydsm/feed/domain/BaseEntity.kt | 20 ++++ .../kr/entrydsm/feed/domain/BaseTimeEntity.kt | 17 ++++ .../kr/entrydsm/feed/domain/BaseUUIDEntity.kt | 19 ++++ .../domain/attachFile/domain/AttachFile.kt | 11 +++ .../domain/repository/AttachFileRepository.kt | 12 +++ .../presentation/AttachFileController.kt | 19 ++++ .../dto/response/CreateAttachFileResponse.kt | 6 ++ .../service/CreateAttachFileService.kt | 36 ++++++++ .../kr/entrydsm/feed/domain/faq/domain/Faq.kt | 33 +++++++ .../faq/domain/repository/FaqRepository.kt | 12 +++ .../feed/domain/faq/domain/type/FaqType.kt | 9 ++ .../faq/exception/FaqNotFoundException.kt | 8 ++ .../domain/faq/presentation/FaqController.kt | 71 ++++++++++++++ .../dto/request/CreateFaqRequest.kt | 17 ++++ .../dto/request/UpdateFaqRequest.kt | 17 ++++ .../dto/response/FaqDetailsResponse.kt | 11 +++ .../faq/presentation/dto/response/FaqDto.kt | 13 +++ .../dto/response/FaqListResponse.kt | 5 + .../dto/response/FaqTitleAndTypeResponse.kt | 10 ++ .../dto/response/FaqTitleResponse.kt | 9 ++ .../domain/faq/service/CreateFaqService.kt | 25 +++++ .../domain/faq/service/DeleteFaqService.kt | 19 ++++ .../faq/service/QueryFaqDetailsService.kt | 25 +++++ .../faq/service/QueryFaqListByTypeService.kt | 27 ++++++ .../domain/faq/service/QueryFaqListService.kt | 26 ++++++ .../service/QueryFaqTitleAndTypeService.kt | 24 +++++ .../domain/faq/service/QueryTopFaqService.kt | 23 +++++ .../domain/faq/service/UpdateFaqService.kt | 29 ++++++ .../feed/domain/notice/domain/Notice.kt | 60 ++++++++++++ .../domain/repository/NoticeRepository.kt | 10 ++ .../domain/notice/domain/type/NoticeType.kt | 5 + .../exception/AttachFileNotFoundException.kt | 8 ++ .../exception/NoticeNotFoundException.kt | 8 ++ .../notice/presentation/NoticeController.kt | 79 ++++++++++++++++ .../dto/request/CreateNoticeRequest.kt | 27 ++++++ .../dto/request/UpdateNoticeRequest.kt | 26 ++++++ .../dto/response/NoticeResponse.kt | 13 +++ .../response/QueryDetailsNoticeResponse.kt | 20 ++++ .../dto/response/QueryListNoticeResponse.kt | 5 + .../dto/response/QueryNoticeTitleResponse.kt | 10 ++ .../dto/response/UploadNoticeImageResponse.kt | 6 ++ .../notice/service/CreateNoticeService.kt | 43 +++++++++ .../notice/service/DeleteNoticeService.kt | 19 ++++ .../service/QueryDetailsNoticeService.kt | 48 ++++++++++ .../service/QueryNoticeListByTypeService.kt | 37 ++++++++ .../notice/service/QueryNoticeTitleService.kt | 24 +++++ .../notice/service/UpdateNoticeService.kt | 57 ++++++++++++ .../service/UploadNoticeImageService.kt | 17 ++++ .../feed/domain/question/domain/Question.kt | 45 +++++++++ .../domain/repository/QuestionRepository.kt | 13 +++ .../AccessDeniedQuestionException.kt | 8 ++ .../exception/FeedWriterMismatchException.kt | 8 ++ .../exception/QuestionNotFoundException.kt | 8 ++ .../presentation/QuestionController.kt | 71 ++++++++++++++ .../dto/request/CreateQuestionRequest.kt | 18 ++++ .../dto/request/UpdateQuestionRequest.kt | 19 ++++ .../presentation/dto/response/QuestionDTO.kt | 13 +++ .../dto/response/QuestionDetailsResponse.kt | 17 ++++ .../dto/response/QuestionListResponse.kt | 5 + .../question/service/CreateQuestionService.kt | 26 ++++++ .../question/service/DeleteQuestionService.kt | 24 +++++ .../service/QueryMyQuestionService.kt | 32 +++++++ .../service/QueryQuestionDetailsService.kt | 71 ++++++++++++++ .../service/QueryQuestionListService.kt | 30 ++++++ .../question/service/UpdateQuestionService.kt | 31 +++++++ .../feed/domain/reply/domain/Reply.kt | 28 ++++++ .../domain/repository/ReplyRepository.kt | 9 ++ .../exception/AccessDeniedReplyException.kt | 8 ++ .../reply/exception/ReplyExistsException.kt | 8 ++ .../reply/exception/ReplyNotFoundException.kt | 8 ++ .../reply/presentation/ReplyController.kt | 48 ++++++++++ .../dto/request/CreateReplyRequest.kt | 15 +++ .../dto/request/UpdateReplyRequest.kt | 15 +++ .../presentation/dto/response/ReplyDto.kt | 9 ++ .../reply/service/CreateReplyService.kt | 41 +++++++++ .../reply/service/DeleteReplyService.kt | 23 +++++ .../reply/service/UpdateReplyService.kt | 26 ++++++ .../reserve/presentation/ReserveController.kt | 17 ++++ .../reserve/service/GetReserveLinkService.kt | 13 +++ .../feed/domain/screen/domain/Screen.kt | 22 +++++ .../domain/repository/ScreenRepository.kt | 7 ++ .../exception/ScreenNotFoundException.kt | 8 ++ .../screen/presentation/ScreenController.kt | 45 +++++++++ .../dto/response/QueryScreenResponse.kt | 11 +++ .../dto/response/ScreenResponse.kt | 5 + .../screen/service/CreateScreenService.kt | 33 +++++++ .../screen/service/QueryScreenService.kt | 26 ++++++ .../screen/service/UpdateScreenService.kt | 30 ++++++ .../entrydsm/feed/global/config/AwsConfig.kt | 29 ++++++ .../feed/global/config/FilterConfig.kt | 23 +++++ .../feed/global/config/SecurityConfig.kt | 66 +++++++++++++ .../feed/global/error/ErrorResponse.kt | 6 ++ .../global/error/GlobalExceptionFilter.kt | 42 +++++++++ .../global/error/GlobalExceptionHandler.kt | 35 +++++++ .../global/error/exception/EquusException.kt | 7 ++ .../feed/global/error/exception/ErrorCode.kt | 39 ++++++++ .../global/exception/ExpiredTokenException.kt | 8 ++ .../exception/InternalServerErrorException.kt | 8 ++ .../global/exception/InvalidTokenException.kt | 8 ++ .../feed/global/security/jwt/JwtFilter.kt | 44 +++++++++ .../feed/global/security/jwt/JwtProperties.kt | 12 +++ .../feed/global/utils/admin/AdminUtils.kt | 22 +++++ .../feed/global/utils/user/UserUtils.kt | 22 +++++ .../feign/FeignClientErrorDecoder.kt | 24 +++++ .../feign/client/user/UserFeignClient.kt | 17 ++++ .../feign/client/user/model/Admin.kt | 7 ++ .../feign/client/user/model/User.kt | 13 +++ .../feign/configuration/FeignConfig.kt | 16 ++++ .../configuration/HeaderConfiguration.kt | 14 +++ .../exception/FeignBadRequestException.kt | 8 ++ .../exception/FeignForbiddenException.kt | 8 ++ .../feign/exception/FeignServerError.kt | 8 ++ .../exception/FeignUnAuthorizedException.kt | 8 ++ .../configuration/KafkaConsumerConfig.kt | 47 ++++++++++ .../kafka/configuration/KafkaProperty.kt | 12 +++ .../kafka/configuration/KafkaTopics.kt | 6 ++ .../kafka/consumer/DeleteFaqTableConsumer.kt | 20 ++++ .../consumer/DeleteQuestionTableConsumer.kt | 20 ++++ .../consumer/DeleteReplyTableConsumer.kt | 21 +++++ .../consumer/DeleteUserQuestionConsumer.kt | 24 +++++ .../dto/response/DeletedUserInfoResponse.kt | 7 ++ .../feed/infrastructure/s3/PathList.kt | 7 ++ .../s3/exception/BadFileExtensionException.kt | 8 ++ .../s3/exception/EmptyFileException.kt | 8 ++ .../feed/infrastructure/s3/util/FileUtil.kt | 90 ++++++++++++++++++ 127 files changed, 2820 insertions(+), 5 deletions(-) mode change 100644 => 100755 gradlew create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseEntity.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseTimeEntity.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseUUIDEntity.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/AttachFile.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/repository/AttachFileRepository.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/AttachFileController.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/dto/response/CreateAttachFileResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/service/CreateAttachFileService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/Faq.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/repository/FaqRepository.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/type/FaqType.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/exception/FaqNotFoundException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/FaqController.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/CreateFaqRequest.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/UpdateFaqRequest.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDetailsResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDto.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqListResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleAndTypeResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/CreateFaqService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/DeleteFaqService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqDetailsService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListByTypeService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqTitleAndTypeService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryTopFaqService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/UpdateFaqService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/Notice.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/repository/NoticeRepository.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/type/NoticeType.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/AttachFileNotFoundException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/NoticeNotFoundException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/NoticeController.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/CreateNoticeRequest.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/UpdateNoticeRequest.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/NoticeResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryDetailsNoticeResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryListNoticeResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryNoticeTitleResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/UploadNoticeImageResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/CreateNoticeService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/DeleteNoticeService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryDetailsNoticeService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeListByTypeService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeTitleService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UpdateNoticeService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UploadNoticeImageService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/Question.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/repository/QuestionRepository.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/AccessDeniedQuestionException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/FeedWriterMismatchException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/QuestionNotFoundException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/QuestionController.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/CreateQuestionRequest.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/UpdateQuestionRequest.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDTO.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDetailsResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionListResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/CreateQuestionService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/DeleteQuestionService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryMyQuestionService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionDetailsService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionListService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/UpdateQuestionService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/Reply.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/repository/ReplyRepository.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/AccessDeniedReplyException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyExistsException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyNotFoundException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/ReplyController.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/CreateReplyRequest.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/UpdateReplyRequest.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/response/ReplyDto.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/CreateReplyService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/DeleteReplyService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/UpdateReplyService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/presentation/ReserveController.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/service/GetReserveLinkService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/Screen.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/repository/ScreenRepository.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/exception/ScreenNotFoundException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/ScreenController.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/QueryScreenResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/ScreenResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/CreateScreenService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/QueryScreenService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/UpdateScreenService.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/config/AwsConfig.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/config/FilterConfig.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/config/SecurityConfig.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/ErrorResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionFilter.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionHandler.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/EquusException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/ErrorCode.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/exception/ExpiredTokenException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InternalServerErrorException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InvalidTokenException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtFilter.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtProperties.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/utils/admin/AdminUtils.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/utils/user/UserUtils.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/FeignClientErrorDecoder.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/UserFeignClient.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/Admin.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/User.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/FeignConfig.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/HeaderConfiguration.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignBadRequestException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignForbiddenException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignServerError.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignUnAuthorizedException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaConsumerConfig.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaProperty.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaTopics.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteFaqTableConsumer.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteQuestionTableConsumer.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteReplyTableConsumer.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteUserQuestionConsumer.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/dto/response/DeletedUserInfoResponse.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/PathList.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/BadFileExtensionException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/EmptyFileException.kt create mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/util/FileUtil.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7822ed8..a28cb1c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,11 +5,14 @@ import kotlin.collections.plus plugins { kotlin("jvm") version "1.9.23" kotlin("plugin.spring") version "1.9.23" + kotlin("plugin.jpa") version "1.9.23" + kotlin("kapt") version PluginVersion.KOTLIN_VERSION id("org.springframework.boot") version "3.4.4" id("io.spring.dependency-management") version "1.1.7" id("org.jlleitschuh.gradle.ktlint").version("12.1.1") id("io.gitlab.arturbosch.detekt") version "1.23.6" id("casper.documentation-convention") + id("com.google.protobuf") version "0.9.4" } // 서브프로젝트 설정 @@ -40,12 +43,91 @@ group = "hs.kr.entrydsm" version = "0.0.1-SNAPSHOT" dependencies { + // 스프링 부트 기본 기능 + implementation(Dependencies.SPRING_BOOT_STARTER) - implementation("org.springframework.boot:spring-boot-starter") - implementation("org.jetbrains.kotlin:kotlin-reflect") - testImplementation("org.springframework.boot:spring-boot-starter-test") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") + // 코틀린 리플렉션 + implementation(Dependencies.KOTLIN_REFLECT) + + // 스프링 부트 테스트 도구 + testImplementation(Dependencies.SPRING_BOOT_STARTER_TEST) + + // 코틀린 + JUnit5 테스트 + testImplementation(Dependencies.KOTLIN_TEST_JUNIT5) + + // JUnit5 실행 런처 + testRuntimeOnly(Dependencies.JUNIT_PLATFORM_LAUNCHER) + + // 웹 관련 + implementation(Dependencies.SPRING_BOOT_STARTER_WEB) + + // 데이터베이스 + implementation(Dependencies.SPRING_BOOT_STARTER_DATA_JPA) + implementation(Dependencies.SPRING_BOOT_STARTER_DATA_REDIS) + runtimeOnly(Dependencies.MYSQL_CONNECTOR) + + // 보안 + implementation(Dependencies.SPRING_BOOT_STARTER_SECURITY) + + // 검증 + implementation(Dependencies.SPRING_BOOT_STARTER_VALIDATION) + + // JSON 처리 + implementation(Dependencies.JACKSON_MODULE_KOTLIN) + implementation(Dependencies.ORG_JSON) + + // JWT + implementation(Dependencies.JWT_API) + implementation(Dependencies.JWT_IMPL) + runtimeOnly(Dependencies.JWT_JACKSON) + + implementation(Dependencies.MAPSTRUCT) + kapt(Dependencies.MAPSTRUCT_PROCESSOR) + + // grpc + implementation(Dependencies.GRPC_NETTY_SHADED) + implementation(Dependencies.GRPC_PROTOBUF) + implementation(Dependencies.GRPC_STUB) + implementation(Dependencies.GRPC_KOTLIN_STUB) + implementation(Dependencies.PROTOBUF_KOTLIN) + testImplementation(Dependencies.GRPC_TESTING) + implementation("net.devh:grpc-server-spring-boot-starter:2.12.0.RELEASE") + + + // swagger + implementation(Dependencies.SWAGGER) + + // aws + implementation(Dependencies.AWS) + + // feign + implementation(Dependencies.OPEN_FEIGN) + + // Kafka + implementation(Dependencies.KAFKA) +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${DependencyVersion.PROTOBUF}" + } + plugins { + create("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:${DependencyVersion.GRPC}" + } + create("grpckt") { + artifact = "io.grpc:protoc-gen-grpc-kotlin:${DependencyVersion.GRPC_KOTLIN}:jdk8@jar" + } + } + generateProtoTasks { + all().forEach { + it.plugins { + create("grpc") + create("grpckt") + } + } + + } } kotlin { diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseEntity.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseEntity.kt new file mode 100644 index 0000000..d470db8 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseEntity.kt @@ -0,0 +1,20 @@ +package hs.kr.entrydsm.feed.domain + +import jakarta.persistence.Column +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id +import jakarta.persistence.MappedSuperclass +import org.hibernate.annotations.GenericGenerator +import java.util.* + +@MappedSuperclass +abstract class BaseEntity( + @Id + @GeneratedValue(generator = "uuid2") + @GenericGenerator(name = "uuid2", strategy = "uuid2") + @Column( + columnDefinition = "BINARY(16)", + nullable = false + ) + val id: UUID? +) : BaseTimeEntity() diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseTimeEntity.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseTimeEntity.kt new file mode 100644 index 0000000..71b6947 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseTimeEntity.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.domain + +import jakarta.persistence.EntityListeners +import jakarta.persistence.MappedSuperclass +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.LocalDateTime + +@MappedSuperclass +@EntityListeners(AuditingEntityListener::class) +abstract class BaseTimeEntity( + @CreatedDate + val createdAt: LocalDateTime = LocalDateTime.now(), + @LastModifiedDate + val modifiedAt: LocalDateTime = LocalDateTime.now() +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseUUIDEntity.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseUUIDEntity.kt new file mode 100644 index 0000000..ed7d6e8 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseUUIDEntity.kt @@ -0,0 +1,19 @@ +package hs.kr.entrydsm.feed.domain + +import jakarta.persistence.Column +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id +import jakarta.persistence.MappedSuperclass +import org.hibernate.annotations.GenericGenerator +import java.util.* + +@MappedSuperclass +abstract class BaseUUIDEntity( + id: UUID? +) { + @Id + @GeneratedValue(generator = "uuid2") + @GenericGenerator(name = "uuid2", strategy = "uuid2") + @Column(columnDefinition = "BINARY(16)", nullable = false) + val id: UUID? = if (id == UUID(0, 0)) null else id +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/AttachFile.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/AttachFile.kt new file mode 100644 index 0000000..f41a22b --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/AttachFile.kt @@ -0,0 +1,11 @@ +package hs.kr.entrydsm.feed.domain.attachFile.domain + +import jakarta.persistence.Entity +import jakarta.persistence.Id + +@Entity(name = "tbl_attach_file") +class AttachFile( + @Id + val uploadedFileName: String, //aws s3에 올라가는 fileName + var originalAttachFileName: String // 인코딩 되기 전 첨부파일 이름 ex): 서프수행.hwp +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/repository/AttachFileRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/repository/AttachFileRepository.kt new file mode 100644 index 0000000..cb44fbd --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/repository/AttachFileRepository.kt @@ -0,0 +1,12 @@ +package hs.kr.entrydsm.feed.domain.attachFile.domain.repository + +import hs.kr.entrydsm.feed.domain.attachFile.domain.AttachFile +import org.springframework.data.jpa.repository.JpaRepository + +interface AttachFileRepository : JpaRepository { + fun findByOriginalAttachFileName(attachFileName: String): List? + + fun deleteByOriginalAttachFileName(attachFileName: String) + + fun existsByOriginalAttachFileName(attachFileName: String): Boolean +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/AttachFileController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/AttachFileController.kt new file mode 100644 index 0000000..f953da6 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/AttachFileController.kt @@ -0,0 +1,19 @@ +package hs.kr.entrydsm.feed.domain.attachFile.presentation + +import hs.kr.entrydsm.feed.domain.attachFile.service.CreateAttachFileService +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestPart +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile + +@RestController +@RequestMapping("/attach-file") +class AttachFileController( + private val createAttachFileService: CreateAttachFileService +) { + @PostMapping + fun createAttachFile( + @RequestPart(value = "attach_file") attachFile: List + ) = createAttachFileService.execute(attachFile) +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/dto/response/CreateAttachFileResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/dto/response/CreateAttachFileResponse.kt new file mode 100644 index 0000000..66be980 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/dto/response/CreateAttachFileResponse.kt @@ -0,0 +1,6 @@ +package hs.kr.entrydsm.feed.domain.attachFile.presentation.dto.response + +class CreateAttachFileResponse( + val fileName: String, + val url: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/service/CreateAttachFileService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/service/CreateAttachFileService.kt new file mode 100644 index 0000000..8acd471 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/service/CreateAttachFileService.kt @@ -0,0 +1,36 @@ +package hs.kr.entrydsm.feed.domain.attachFile.service + +import hs.kr.entrydsm.feed.domain.attachFile.domain.AttachFile +import hs.kr.entrydsm.feed.domain.attachFile.domain.repository.AttachFileRepository +import hs.kr.entrydsm.feed.domain.attachFile.presentation.dto.response.CreateAttachFileResponse +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile + +@Transactional +@Service +class CreateAttachFileService( + private val attachFileRepository: AttachFileRepository, + private val fileUtil: FileUtil +) { + fun execute(attachFile: List): List { + val attachFileResponses = mutableListOf() + + attachFile.forEach { file -> + if (attachFileRepository.existsByOriginalAttachFileName(file.originalFilename!!)) { + attachFileRepository.deleteByOriginalAttachFileName(file.originalFilename!!) + } + val uploadedFilename = fileUtil.upload(file, PathList.ATTACH_FILE) + val attachFileEntity = AttachFile( + uploadedFileName = uploadedFilename, + originalAttachFileName = file.originalFilename!! + ) + attachFileRepository.save(attachFileEntity) + val url = fileUtil.generateObjectUrl(uploadedFilename, PathList.ATTACH_FILE) + attachFileResponses.add(CreateAttachFileResponse(file.originalFilename!!, url)) + } + return attachFileResponses + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/Faq.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/Faq.kt new file mode 100644 index 0000000..dbb00ef --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/Faq.kt @@ -0,0 +1,33 @@ +package hs.kr.entrydsm.feed.domain.faq.domain + +import hs.kr.entrydsm.feed.domain.BaseEntity +import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import java.util.UUID + +@Entity(name = "tbl_faq") +class Faq( + id: UUID? = null, + + @Column(name = "title", length = 100, nullable = false) + var title: String, + + @Column(name = "content", length = 5000, nullable = false) + var content: String, + + @Enumerated(EnumType.STRING) + var faqType: FaqType, + + @Column(name = "admin_id", columnDefinition = "BINARY(16)", nullable = false) + var adminId: UUID +) : BaseEntity(id) { + fun updateFaq(title: String, content: String, faqType: FaqType, adminId: UUID) { + this.title = title + this.content = content + this.faqType = faqType + this.adminId = adminId + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/repository/FaqRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/repository/FaqRepository.kt new file mode 100644 index 0000000..66cc3ab --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/repository/FaqRepository.kt @@ -0,0 +1,12 @@ +package hs.kr.entrydsm.feed.domain.faq.domain.repository + +import hs.kr.entrydsm.feed.domain.faq.domain.Faq +import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType +import org.springframework.data.jpa.repository.JpaRepository +import java.util.UUID + +interface FaqRepository : JpaRepository { + fun findAllByFaqType(faqType: FaqType): List + + fun findTop5ByOrderByCreatedAtDesc(): List +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/type/FaqType.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/type/FaqType.kt new file mode 100644 index 0000000..d54b692 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/type/FaqType.kt @@ -0,0 +1,9 @@ +package hs.kr.entrydsm.feed.domain.faq.domain.type + +enum class FaqType { + ADMISSION, + COURSE, + SCHOOL_LIFE, + DORMITORY, + OTHER +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/exception/FaqNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/exception/FaqNotFoundException.kt new file mode 100644 index 0000000..1a4fd6b --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/exception/FaqNotFoundException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.faq.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object FaqNotFoundException : EquusException( + ErrorCode.FAQ_NOT_FOUND +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/FaqController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/FaqController.kt new file mode 100644 index 0000000..cfc691f --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/FaqController.kt @@ -0,0 +1,71 @@ +package hs.kr.entrydsm.feed.domain.faq.presentation + +import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.request.CreateFaqRequest +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.request.UpdateFaqRequest +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqDetailsResponse +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqListResponse +import hs.kr.entrydsm.feed.domain.faq.service.CreateFaqService +import hs.kr.entrydsm.feed.domain.faq.service.DeleteFaqService +import hs.kr.entrydsm.feed.domain.faq.service.QueryFaqDetailsService +import hs.kr.entrydsm.feed.domain.faq.service.QueryFaqListByTypeService +import hs.kr.entrydsm.feed.domain.faq.service.QueryFaqListService +import hs.kr.entrydsm.feed.domain.faq.service.QueryTopFaqService +import hs.kr.entrydsm.feed.domain.faq.service.UpdateFaqService +import org.springframework.http.HttpStatus +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController +import java.util.UUID + +@RequestMapping("/faq") +@RestController +class FaqController( + private val createFaqService: CreateFaqService, + private val queryFaqDetailsService: QueryFaqDetailsService, + private val queryFaqListByTypeService: QueryFaqListByTypeService, + private val queryFaqListService: QueryFaqListService, + private val queryTopFaqService: QueryTopFaqService, + private val updateFaqService: UpdateFaqService, + private val deleteFaqService: DeleteFaqService +) { + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + fun createFaq( + @RequestBody @Validated + createFaqRequest: CreateFaqRequest + ) = createFaqService.execute(createFaqRequest) + + @GetMapping("/{faq-id}") + fun queryFaqDetails(@PathVariable("faq-id") faqId: UUID): FaqDetailsResponse = + queryFaqDetailsService.execute(faqId) + + @GetMapping + fun queryFaqListByType(@RequestParam("type") faqType: FaqType): FaqListResponse = + queryFaqListByTypeService.execute(faqType) + + @GetMapping("/all") + fun queryFaqList(): FaqListResponse = queryFaqListService.execute() + + @GetMapping("/recently") + fun queryTopFaq() = queryTopFaqService.execute() + + @PatchMapping("/{faq-id}") + fun updateFaq( + @PathVariable("faq-id") faqId: UUID, + @RequestBody @Validated + updateFaqRequest: UpdateFaqRequest + ) = + updateFaqService.execute(faqId, updateFaqRequest) + + @DeleteMapping("/{faq-id}") + fun deleteFaq(@PathVariable("faq-id") faqId: UUID) = deleteFaqService.execute(faqId) +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/CreateFaqRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/CreateFaqRequest.kt new file mode 100644 index 0000000..cdf5329 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/CreateFaqRequest.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.domain.faq.presentation.dto.request + +import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + +data class CreateFaqRequest( + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") + val content: String, + + val faqType: FaqType +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/UpdateFaqRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/UpdateFaqRequest.kt new file mode 100644 index 0000000..9737030 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/UpdateFaqRequest.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.domain.faq.presentation.dto.request + +import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + +data class UpdateFaqRequest( + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") + val content: String, + + val faqType: FaqType +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDetailsResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDetailsResponse.kt new file mode 100644 index 0000000..f109f43 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDetailsResponse.kt @@ -0,0 +1,11 @@ +package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response + +import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType +import java.time.LocalDateTime + +data class FaqDetailsResponse( + val title: String, + val content: String, + val createdAt: LocalDateTime, + val faqType: FaqType +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDto.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDto.kt new file mode 100644 index 0000000..4e29b4a --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDto.kt @@ -0,0 +1,13 @@ +package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response + +import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType +import java.time.LocalDateTime +import java.util.UUID + +data class FaqDto( + val id: UUID, + val title: String, + val content: String, + val createdAt: LocalDateTime, + val faqType: FaqType +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqListResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqListResponse.kt new file mode 100644 index 0000000..eb981a8 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqListResponse.kt @@ -0,0 +1,5 @@ +package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response + +data class FaqListResponse( + val faqs: List +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleAndTypeResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleAndTypeResponse.kt new file mode 100644 index 0000000..64ea24c --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleAndTypeResponse.kt @@ -0,0 +1,10 @@ +package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response + +import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType +import java.util.UUID + +data class FaqTitleAndTypeResponse( + val id: UUID, + val type: FaqType, + val title: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleResponse.kt new file mode 100644 index 0000000..b65b622 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleResponse.kt @@ -0,0 +1,9 @@ +package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response + +import java.util.UUID + +data class FaqTitleResponse( + val id: UUID, + val title: String, + val content: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/CreateFaqService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/CreateFaqService.kt new file mode 100644 index 0000000..88138f2 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/CreateFaqService.kt @@ -0,0 +1,25 @@ +package hs.kr.entrydsm.feed.domain.faq.service + +import hs.kr.entrydsm.feed.domain.faq.domain.Faq +import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.request.CreateFaqRequest +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class CreateFaqService( + private val faqRepository: FaqRepository, + private val userUtils: UserUtils +) { + @Transactional + fun execute(createFaqRequest: CreateFaqRequest) { + val faq = Faq( + title = createFaqRequest.title, + content = createFaqRequest.content, + faqType = createFaqRequest.faqType, + adminId = userUtils.getCurrentUserId() + ) + faqRepository.save(faq) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/DeleteFaqService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/DeleteFaqService.kt new file mode 100644 index 0000000..637f798 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/DeleteFaqService.kt @@ -0,0 +1,19 @@ +package hs.kr.entrydsm.feed.domain.faq.service + +import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository +import hs.kr.entrydsm.feed.domain.faq.exception.FaqNotFoundException +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class DeleteFaqService( + private val faqRepository: FaqRepository +) { + @Transactional + fun execute(faqId: UUID) { + val faq = faqRepository.findByIdOrNull(faqId) ?: throw FaqNotFoundException + faqRepository.delete(faq) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqDetailsService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqDetailsService.kt new file mode 100644 index 0000000..05f4ebf --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqDetailsService.kt @@ -0,0 +1,25 @@ +package hs.kr.entrydsm.feed.domain.faq.service + +import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository +import hs.kr.entrydsm.feed.domain.faq.exception.FaqNotFoundException +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqDetailsResponse +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class QueryFaqDetailsService( + private val faqRepository: FaqRepository +) { + @Transactional + fun execute(faqId: UUID): FaqDetailsResponse { + val faq = faqRepository.findByIdOrNull(faqId) ?: throw FaqNotFoundException + return FaqDetailsResponse( + title = faq.title, + content = faq.content, + createdAt = faq.createdAt, + faqType = faq.faqType + ) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListByTypeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListByTypeService.kt new file mode 100644 index 0000000..3943ace --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListByTypeService.kt @@ -0,0 +1,27 @@ +package hs.kr.entrydsm.feed.domain.faq.service + +import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository +import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqDto +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqListResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class QueryFaqListByTypeService( + private val faqRepository: FaqRepository +) { + @Transactional + fun execute(faqType: FaqType): FaqListResponse { + val faqs = faqRepository.findAllByFaqType(faqType).map { + FaqDto( + id = it.id!!, + title = it.title, + content = it.content, + createdAt = it.createdAt, + faqType = it.faqType + ) + } + return FaqListResponse(faqs) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListService.kt new file mode 100644 index 0000000..a281263 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListService.kt @@ -0,0 +1,26 @@ +package hs.kr.entrydsm.feed.domain.faq.service + +import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqDto +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqListResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class QueryFaqListService( + private val faqRepository: FaqRepository +) { + @Transactional + fun execute(): FaqListResponse { + val faqs = faqRepository.findAll().map { + FaqDto( + id = it.id!!, + title = it.title, + content = it.content, + createdAt = it.createdAt, + faqType = it.faqType + ) + } + return FaqListResponse(faqs) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqTitleAndTypeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqTitleAndTypeService.kt new file mode 100644 index 0000000..d3d4c6a --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqTitleAndTypeService.kt @@ -0,0 +1,24 @@ +package hs.kr.entrydsm.feed.domain.faq.service + +import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqTitleAndTypeResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class QueryFaqTitleAndTypeService( + private val faqRepository: FaqRepository +) { + + @Transactional(readOnly = true) + fun execute() = + faqRepository.findAll() + .map { + it -> + FaqTitleAndTypeResponse( + it.id!!, + it.faqType, + it.title + ) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryTopFaqService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryTopFaqService.kt new file mode 100644 index 0000000..f370b31 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryTopFaqService.kt @@ -0,0 +1,23 @@ +package hs.kr.entrydsm.feed.domain.faq.service + +import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqTitleResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class QueryTopFaqService( + private val faqRepository: FaqRepository +) { + + @Transactional(readOnly = true) + fun execute(): List = + faqRepository.findTop5ByOrderByCreatedAtDesc() + .map { + FaqTitleResponse( + it.id!!, + it.title, + it.content + ) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/UpdateFaqService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/UpdateFaqService.kt new file mode 100644 index 0000000..69ae2a7 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/UpdateFaqService.kt @@ -0,0 +1,29 @@ +package hs.kr.entrydsm.feed.domain.faq.service + +import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository +import hs.kr.entrydsm.feed.domain.faq.exception.FaqNotFoundException +import hs.kr.entrydsm.feed.domain.faq.presentation.dto.request.UpdateFaqRequest +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class UpdateFaqService( + private val faqRepository: FaqRepository, + private val userUtils: UserUtils +) { + @Transactional + fun execute(faqId: UUID, updateFaqRequest: UpdateFaqRequest) { + val faq = faqRepository.findByIdOrNull(faqId) ?: throw FaqNotFoundException + updateFaqRequest.run { + faq.updateFaq( + title = title, + content = content, + faqType = faqType, + adminId = userUtils.getCurrentUserId() + ) + } + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/Notice.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/Notice.kt new file mode 100644 index 0000000..877ba6c --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/Notice.kt @@ -0,0 +1,60 @@ +package hs.kr.entrydsm.feed.domain.notice.domain + +import hs.kr.entrydsm.feed.domain.BaseEntity +import hs.kr.entrydsm.feed.domain.attachFile.domain.AttachFile +import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.OneToMany +import java.util.* + +@Entity(name = "tbl_notice") +class Notice( + id: UUID? = null, + + @Column(name = "title", length = 100, nullable = false) + var title: String, + + @Column(name = "content", length = 5000, nullable = false) + var content: String, + + @Column(name = "file_name", nullable = true) + var fileName: String? = null, + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn(name = "noticeId") + var attachFile: List? = emptyList(), + + @Column(name = "admin_id", nullable = false, columnDefinition = "BINARY(16)") + var adminId: UUID, + + @Column(nullable = false) + var isPinned: Boolean, + + @Column(nullable = false) + @Enumerated(value = EnumType.STRING) + var type: NoticeType + +) : BaseEntity(id) { + fun modifyNotice( + title: String, + content: String, + isPinned: Boolean, + adminId: UUID, + fileName: String?, + type: NoticeType, + attachFile: List? + ) { + this.title = title + this.content = content + this.type = type + this.isPinned = isPinned + this.adminId = adminId + this.fileName = fileName + this.attachFile = attachFile + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/repository/NoticeRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/repository/NoticeRepository.kt new file mode 100644 index 0000000..a27cc13 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/repository/NoticeRepository.kt @@ -0,0 +1,10 @@ +package hs.kr.entrydsm.feed.domain.notice.domain.repository + +import hs.kr.entrydsm.feed.domain.notice.domain.Notice +import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType +import org.springframework.data.jpa.repository.JpaRepository +import java.util.UUID + +interface NoticeRepository : JpaRepository { + fun findAllByType(type: NoticeType): List +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/type/NoticeType.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/type/NoticeType.kt new file mode 100644 index 0000000..a57d0d2 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/type/NoticeType.kt @@ -0,0 +1,5 @@ +package hs.kr.entrydsm.feed.domain.notice.domain.type + +enum class NoticeType { + GUIDE, NOTICE +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/AttachFileNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/AttachFileNotFoundException.kt new file mode 100644 index 0000000..f5a1629 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/AttachFileNotFoundException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.notice.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object AttachFileNotFoundException : EquusException( + ErrorCode.ATTACH_FILE_NOT_FOUND +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/NoticeNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/NoticeNotFoundException.kt new file mode 100644 index 0000000..4a2cf34 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/NoticeNotFoundException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.notice.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object NoticeNotFoundException : EquusException( + ErrorCode.NOTICE_NOT_FOUND +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/NoticeController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/NoticeController.kt new file mode 100644 index 0000000..9e29e25 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/NoticeController.kt @@ -0,0 +1,79 @@ +package hs.kr.entrydsm.feed.domain.notice.presentation + +import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.request.CreateNoticeRequest +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryListNoticeResponse +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.request.UpdateNoticeRequest +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryDetailsNoticeResponse +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryNoticeTitleResponse +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.UploadNoticeImageResponse +import hs.kr.entrydsm.feed.domain.notice.service.CreateNoticeService +import hs.kr.entrydsm.feed.domain.notice.service.DeleteNoticeService +import hs.kr.entrydsm.feed.domain.notice.service.QueryDetailsNoticeService +import hs.kr.entrydsm.feed.domain.notice.service.QueryNoticeListByTypeService +import hs.kr.entrydsm.feed.domain.notice.service.QueryNoticeTitleService +import hs.kr.entrydsm.feed.domain.notice.service.UpdateNoticeService +import hs.kr.entrydsm.feed.domain.notice.service.UploadNoticeImageService +import jakarta.validation.Valid +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile +import java.util.UUID + +@RestController +@RequestMapping("/notice") +class NoticeController( + private val createNoticeService: CreateNoticeService, + private val uploadNoticeImageService: UploadNoticeImageService, + private val updateNoticeService: UpdateNoticeService, + private val queryNoticeTitleService: QueryNoticeTitleService, + private val queryNoticeListByTypeService: QueryNoticeListByTypeService, + private val queryDetailsNoticeService: QueryDetailsNoticeService, + private val deleteNoticeService: DeleteNoticeService +) { + + @ResponseStatus(value = HttpStatus.CREATED) + @PostMapping + fun createNotice( + @RequestBody @Valid + createNoticeRequest: CreateNoticeRequest + ) { + createNoticeService.execute(createNoticeRequest) + } + + @PatchMapping("/{notice-id}") + fun modifyNotice( + @PathVariable(name = "notice-id") id: UUID, + @RequestBody updateNoticeRequest: UpdateNoticeRequest + ): ResponseEntity = + updateNoticeService.execute(id, updateNoticeRequest) + + @PostMapping("/image") + fun uploadImage( + @RequestPart(name = "photo") image: MultipartFile + ): UploadNoticeImageResponse = + uploadNoticeImageService.execute(image) + + @GetMapping("/title") + fun queryTitle(): List = queryNoticeTitleService.execute() + + @GetMapping("/{notice-id}") + fun getNotice( + @PathVariable(name = "notice-id", required = true) + noticeId: UUID + ): QueryDetailsNoticeResponse = queryDetailsNoticeService.execute(noticeId) + + @GetMapping + fun getNoticeListByType( + @RequestParam("type") type: NoticeType? + ): QueryListNoticeResponse = + queryNoticeListByTypeService.execute(type) + + @ResponseStatus(value = HttpStatus.NO_CONTENT) + @DeleteMapping("/{notice-id}") + fun deleteNotice( + @PathVariable(name = "notice-id")id: UUID + ) = + deleteNoticeService.execute(id) +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/CreateNoticeRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/CreateNoticeRequest.kt new file mode 100644 index 0000000..d128fd6 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/CreateNoticeRequest.kt @@ -0,0 +1,27 @@ +package hs.kr.entrydsm.feed.domain.notice.presentation.dto.request + +import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size + +data class CreateNoticeRequest( + + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @Size(max = 5000, message = "content은 최대 5000자까지 가능합니다.") + val content: String, + + @field:NotNull(message = "Pinned는 null일수가 없습니다") + val isPinned: Boolean, + + @field:NotNull(message = "type은 null일수가 없습니다") + val type: NoticeType, + + val fileName: String? = null, + + val attachFileName: List? = null +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/UpdateNoticeRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/UpdateNoticeRequest.kt new file mode 100644 index 0000000..e9dc961 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/UpdateNoticeRequest.kt @@ -0,0 +1,26 @@ +package hs.kr.entrydsm.feed.domain.notice.presentation.dto.request + +import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size + +data class UpdateNoticeRequest( + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @Size(max = 5000, message = "content은 최대 5000자까지 가능합니다.") + val content: String, + + @field:NotNull(message = "Pinned은 null일수가 없습니다") + val isPinned: Boolean, + + @field:NotNull(message = "type은 null일수가 없습니다") + val type: NoticeType, + + val fileName: String? = null, + + val attachFileName: List? = emptyList() +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/NoticeResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/NoticeResponse.kt new file mode 100644 index 0000000..8f8e764 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/NoticeResponse.kt @@ -0,0 +1,13 @@ +package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response + +import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType +import java.time.LocalDateTime +import java.util.* + +data class NoticeResponse( + val id: UUID, + val title: String, + val type: NoticeType, + val isPinned: Boolean, + val createdAt: LocalDateTime +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryDetailsNoticeResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryDetailsNoticeResponse.kt new file mode 100644 index 0000000..236f8bf --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryDetailsNoticeResponse.kt @@ -0,0 +1,20 @@ +package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response + +import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType +import java.time.LocalDateTime + +data class QueryDetailsNoticeResponse( + val title: String, + val content: String, + val createdAt: LocalDateTime, + val type: NoticeType, + val imageURL: String?, + val imageName: String?, + val attachFiles: List = emptyList(), + val isPinned: Boolean +) + +data class AttachFile( + val attachFileUrl: String, + val attachFileName: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryListNoticeResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryListNoticeResponse.kt new file mode 100644 index 0000000..e153698 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryListNoticeResponse.kt @@ -0,0 +1,5 @@ +package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response + +data class QueryListNoticeResponse( + val notices: List +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryNoticeTitleResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryNoticeTitleResponse.kt new file mode 100644 index 0000000..9eac953 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryNoticeTitleResponse.kt @@ -0,0 +1,10 @@ +package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response + +import java.time.LocalDateTime +import java.util.UUID + +data class QueryNoticeTitleResponse( + val id: UUID, + val title: String, + val createdAt: LocalDateTime +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/UploadNoticeImageResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/UploadNoticeImageResponse.kt new file mode 100644 index 0000000..ac951c6 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/UploadNoticeImageResponse.kt @@ -0,0 +1,6 @@ +package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response + +data class UploadNoticeImageResponse( + val fileUrl: String, + val fileName: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/CreateNoticeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/CreateNoticeService.kt new file mode 100644 index 0000000..6101731 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/CreateNoticeService.kt @@ -0,0 +1,43 @@ +package hs.kr.entrydsm.feed.domain.notice.service + +import hs.kr.entrydsm.feed.domain.notice.domain.Notice +import hs.kr.entrydsm.feed.domain.attachFile.domain.repository.AttachFileRepository +import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository +import hs.kr.entrydsm.feed.domain.notice.exception.AttachFileNotFoundException +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.request.CreateNoticeRequest +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class CreateNoticeService( + private val noticeRepository: NoticeRepository, + private val attachFileRepository: AttachFileRepository, + private val adminUtils: UserUtils +) { + + @Transactional + fun execute( + request: CreateNoticeRequest + ) { + val admin = adminUtils.getCurrentUserId() + val attachFiles = request.attachFileName?.let { fileNames -> + fileNames.flatMap { fileName -> + val files = attachFileRepository.findByOriginalAttachFileName(fileName) + files ?: throw AttachFileNotFoundException + } + } ?: emptyList() + + noticeRepository.save( + Notice( + title = request.title, + content = request.content, + type = request.type, + isPinned = request.isPinned, + adminId = admin, + fileName = request.fileName, + attachFile = attachFiles + ) + ) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/DeleteNoticeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/DeleteNoticeService.kt new file mode 100644 index 0000000..2940634 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/DeleteNoticeService.kt @@ -0,0 +1,19 @@ +package hs.kr.entrydsm.feed.domain.notice.service + +import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository +import hs.kr.entrydsm.feed.domain.notice.exception.NoticeNotFoundException +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class DeleteNoticeService( + private val noticeRepository: NoticeRepository +) { + @Transactional + fun execute(noticeId: UUID) { + val notice = noticeRepository.findByIdOrNull(noticeId) ?: throw NoticeNotFoundException + noticeRepository.deleteById(notice.id!!) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryDetailsNoticeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryDetailsNoticeService.kt new file mode 100644 index 0000000..8d99868 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryDetailsNoticeService.kt @@ -0,0 +1,48 @@ +package hs.kr.entrydsm.feed.domain.notice.service + +import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository +import hs.kr.entrydsm.feed.domain.notice.exception.NoticeNotFoundException +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.AttachFile +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryDetailsNoticeResponse +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.* + +@Transactional(readOnly = true) +@Service +class QueryDetailsNoticeService( + private val noticeRepository: NoticeRepository, + private val fileUtil: FileUtil +) { + fun execute(noticeId: UUID): QueryDetailsNoticeResponse { + val notice = noticeRepository.findByIdOrNull(noticeId) ?: throw NoticeNotFoundException + val imageURL = notice.fileName?.let { getUrl(it, PathList.NOTICE) } + + val attachFile = + notice.attachFile?.map { + // 첨부파일 이름과 url을 묶어서 여러개 반환하기때문에 List로 묶는다 + AttachFile( + attachFileUrl = getUrl(it.uploadedFileName, PathList.ATTACH_FILE), + attachFileName = it.originalAttachFileName + ) + } + + return notice.run { + QueryDetailsNoticeResponse( + title = title, + content = content, + createdAt = createdAt, + type = type, + imageURL = imageURL, + imageName = fileName, + attachFiles = attachFile!!, + isPinned = isPinned + ) + } + } + + private fun getUrl(file: String, path: String) = fileUtil.generateObjectUrl(file, path) +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeListByTypeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeListByTypeService.kt new file mode 100644 index 0000000..4b9d967 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeListByTypeService.kt @@ -0,0 +1,37 @@ +package hs.kr.entrydsm.feed.domain.notice.service + +import hs.kr.entrydsm.feed.domain.notice.domain.Notice +import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository +import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.NoticeResponse +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryListNoticeResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class QueryNoticeListByTypeService( + private val noticeRepository: NoticeRepository +) { + + @Transactional(readOnly = true) + fun execute(type: NoticeType?): QueryListNoticeResponse { + val notices = getNoticeList(type).map { it -> + NoticeResponse( + id = it.id!!, + title = it.title, + isPinned = it.isPinned, + type = it.type, + createdAt = it.createdAt + ) + }.sortedWith( + compareByDescending { it.isPinned } + .thenByDescending { it.createdAt } + ) + + return QueryListNoticeResponse(notices) + } + + private fun getNoticeList(type: NoticeType?): List { + return type?.let { noticeRepository.findAllByType(it) } ?: noticeRepository.findAll() + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeTitleService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeTitleService.kt new file mode 100644 index 0000000..998e1ff --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeTitleService.kt @@ -0,0 +1,24 @@ +package hs.kr.entrydsm.feed.domain.notice.service + +import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryNoticeTitleResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class QueryNoticeTitleService( + private val noticeRepository: NoticeRepository +) { + + @Transactional(readOnly = true) + fun execute(): List = + noticeRepository.findAll() + .map { + it -> + QueryNoticeTitleResponse( + id = it.id!!, + title = it.title, + it.createdAt + ) + }.sortedByDescending { it.createdAt } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UpdateNoticeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UpdateNoticeService.kt new file mode 100644 index 0000000..5cb79f9 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UpdateNoticeService.kt @@ -0,0 +1,57 @@ +package hs.kr.entrydsm.feed.domain.notice.service + +import hs.kr.entrydsm.feed.domain.attachFile.domain.AttachFile +import hs.kr.entrydsm.feed.domain.attachFile.domain.repository.AttachFileRepository +import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository +import hs.kr.entrydsm.feed.domain.notice.exception.AttachFileNotFoundException +import hs.kr.entrydsm.feed.domain.notice.exception.NoticeNotFoundException +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.request.UpdateNoticeRequest +import hs.kr.entrydsm.feed.global.utils.admin.AdminUtils +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import org.springframework.data.repository.findByIdOrNull +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class UpdateNoticeService( + private val noticeRepository: NoticeRepository, + private val adminUtils: AdminUtils, + private val fileUtil: FileUtil, + private val attachFileRepository: AttachFileRepository +) { + @Transactional + fun execute(id: UUID, request: UpdateNoticeRequest): ResponseEntity { + val adminId = adminUtils.getCurrentAdminId() + + val notice = noticeRepository.findByIdOrNull(id) ?: throw NoticeNotFoundException + val fileName = request.fileName + val attachFiles = findAttachFiles(request.attachFileName) + + request.run { + notice.modifyNotice( + title = title, + content = content, + isPinned = isPinned, + type = type, + fileName = fileName, + adminId = adminId, + attachFile = attachFiles + ) + } + + return fileName?.let { + ResponseEntity.ok(fileUtil.generateObjectUrl(it, PathList.NOTICE)) + } ?: ResponseEntity(HttpStatus.NO_CONTENT) + } + + private fun findAttachFiles(fileNameList: List?): List { + return fileNameList?.flatMap { fileName -> + val fileList = attachFileRepository.findByOriginalAttachFileName(fileName) + fileList ?: throw AttachFileNotFoundException + } ?: emptyList() + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UploadNoticeImageService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UploadNoticeImageService.kt new file mode 100644 index 0000000..adbe8a1 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UploadNoticeImageService.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.domain.notice.service + +import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.UploadNoticeImageResponse +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile + +@Service +class UploadNoticeImageService( + private val fileUtil: FileUtil +) { + fun execute(image: MultipartFile): UploadNoticeImageResponse { + val fileName = fileUtil.upload(image, PathList.NOTICE) + return UploadNoticeImageResponse(fileUtil.generateObjectUrl(fileName, PathList.NOTICE), fileName) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/Question.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/Question.kt new file mode 100644 index 0000000..bbf4cf3 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/Question.kt @@ -0,0 +1,45 @@ +package hs.kr.entrydsm.feed.domain.question.domain + +import hs.kr.entrydsm.feed.domain.BaseEntity +import hs.kr.entrydsm.feed.domain.question.exception.FeedWriterMismatchException +import jakarta.persistence.Column +import jakarta.persistence.Entity +import java.util.UUID + +@Entity(name = "tbl_question") +class Question( + id: UUID? = null, + + @Column(name = "title", columnDefinition = "varchar(100)", nullable = false) + var title: String, + + @Column(name = "content", columnDefinition = "varchar(1000)", nullable = false) + var content: String, + + @Column(name = "is_public", columnDefinition = "BIT(1) default 0", nullable = false) + var isPublic: Boolean, + + @Column(name = "is_replied", columnDefinition = "BIT(1) default 0", nullable = false) + var isReplied: Boolean, + + @Column(name = "user_id", columnDefinition = "BINARY(16)", nullable = false) + val userId: UUID +) : BaseEntity(id) { + fun validateWriter(userid: UUID) { + if (this.userId != userid) { + throw FeedWriterMismatchException + } + } + + fun updateQuestion(userId: UUID, content: String, title: String, isPublic: Boolean) { + validateWriter(userId) + + this.title = content + this.content = title + this.isPublic = isPublic + } + + fun updateIsReplied(isReplied: Boolean) { + this.isReplied = isReplied + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/repository/QuestionRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/repository/QuestionRepository.kt new file mode 100644 index 0000000..f11bc9d --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/repository/QuestionRepository.kt @@ -0,0 +1,13 @@ +package hs.kr.entrydsm.feed.domain.question.domain.repository + +import hs.kr.entrydsm.feed.domain.question.domain.Question +import org.springframework.data.domain.Pageable +import org.springframework.data.jpa.repository.JpaRepository +import java.util.UUID + +interface QuestionRepository : JpaRepository { + fun findAllByUserId(userId: UUID): List + fun deleteAllByUserId(userId: UUID) + + fun findAllByOrderByCreatedAtDesc(pageable: Pageable): List +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/AccessDeniedQuestionException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/AccessDeniedQuestionException.kt new file mode 100644 index 0000000..7d74d05 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/AccessDeniedQuestionException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.question.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object AccessDeniedQuestionException : EquusException( + ErrorCode.ACCESS_DENIED_QUESTION +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/FeedWriterMismatchException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/FeedWriterMismatchException.kt new file mode 100644 index 0000000..33d8561 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/FeedWriterMismatchException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.question.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object FeedWriterMismatchException : EquusException( + ErrorCode.FEED_WRITER_MISMATCH +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/QuestionNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/QuestionNotFoundException.kt new file mode 100644 index 0000000..dc8ea59 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/QuestionNotFoundException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.question.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object QuestionNotFoundException : EquusException( + ErrorCode.QUESTION_NOT_FOUND +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/QuestionController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/QuestionController.kt new file mode 100644 index 0000000..3d40bc1 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/QuestionController.kt @@ -0,0 +1,71 @@ +package hs.kr.entrydsm.feed.domain.question.presentation + +import hs.kr.entrydsm.feed.domain.question.presentation.dto.request.CreateQuestionRequest +import hs.kr.entrydsm.feed.domain.question.presentation.dto.request.UpdateQuestionRequest +import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionDetailsResponse +import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionListResponse +import hs.kr.entrydsm.feed.domain.question.service.CreateQuestionService +import hs.kr.entrydsm.feed.domain.question.service.QueryMyQuestionService +import hs.kr.entrydsm.feed.domain.question.service.QueryQuestionDetailsService +import hs.kr.entrydsm.feed.domain.question.service.QueryQuestionListService +import hs.kr.entrydsm.feed.domain.question.service.UpdateQuestionService +import hs.kr.entrydsm.feed.domain.question.service.DeleteQuestionService +import org.springframework.data.domain.Pageable +import org.springframework.data.web.PageableDefault +import org.springframework.http.HttpStatus +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PatchMapping +import java.util.UUID + +@RequestMapping("/question") +@RestController +class QuestionController( + private val createQuestionService: CreateQuestionService, + private val queryQuestionListService: QueryQuestionListService, + private val queryQuestionDetailsService: QueryQuestionDetailsService, + private val updateQuestionService: UpdateQuestionService, + private val queryMyQuestionService: QueryMyQuestionService, + private val deleteQuestionService: DeleteQuestionService +) { + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + fun createQuestion( + @RequestBody @Validated + createQuestionRequest: CreateQuestionRequest + ) { + createQuestionService.execute(createQuestionRequest) + } + + @GetMapping("/all") + fun getQuestionList( + @PageableDefault(size = 10) pageable: Pageable + ): QuestionListResponse = + queryQuestionListService.execute(pageable) + + @GetMapping("/{questionId}") + fun getQuestionDetails(@PathVariable("questionId") questionId: UUID): QuestionDetailsResponse = + queryQuestionDetailsService.execute(questionId) + + @ResponseStatus(HttpStatus.NO_CONTENT) + @PatchMapping("/{questionId}") + fun updateQuestion( + @PathVariable questionId: UUID, + @Validated @RequestBody + updateQuestionRequest: UpdateQuestionRequest + ) = updateQuestionService.execute(questionId, updateQuestionRequest) + + @GetMapping + fun getMyQuestionList(): QuestionListResponse = queryMyQuestionService.execute() + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{questionId}") + fun deleteQuestion(@PathVariable("questionId") questionId: UUID) = deleteQuestionService.execute(questionId) +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/CreateQuestionRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/CreateQuestionRequest.kt new file mode 100644 index 0000000..7561222 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/CreateQuestionRequest.kt @@ -0,0 +1,18 @@ +package hs.kr.entrydsm.feed.domain.question.presentation.dto.request + +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size + +data class CreateQuestionRequest( + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") + val content: String, + + @NotNull + val isPublic: Boolean +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/UpdateQuestionRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/UpdateQuestionRequest.kt new file mode 100644 index 0000000..a6f9dc3 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/UpdateQuestionRequest.kt @@ -0,0 +1,19 @@ +package hs.kr.entrydsm.feed.domain.question.presentation.dto.request + +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size + + +data class UpdateQuestionRequest( + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") + val content: String, + + @field:NotNull + val isPublic: Boolean +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDTO.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDTO.kt new file mode 100644 index 0000000..80eef43 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDTO.kt @@ -0,0 +1,13 @@ +package hs.kr.entrydsm.feed.domain.question.presentation.dto.response + +import java.time.LocalDateTime +import java.util.UUID + +data class QuestionDTO( + val id: UUID, + val title: String, + val createdAt: LocalDateTime, + val username: String, + val isReplied: Boolean, + val isPublic: Boolean +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDetailsResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDetailsResponse.kt new file mode 100644 index 0000000..15d2390 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDetailsResponse.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.domain.question.presentation.dto.response + +import hs.kr.entrydsm.feed.domain.reply.presentation.dto.response.ReplyDto +import java.time.LocalDateTime +import java.util.UUID + +data class QuestionDetailsResponse( + val id: UUID, + val title: String, + val content: String, + val username: String, + val isReplied: Boolean, + val isMine: Boolean, + val isPublic: Boolean, + val createdAt: LocalDateTime, + val reply: ReplyDto? +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionListResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionListResponse.kt new file mode 100644 index 0000000..bdd843a --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionListResponse.kt @@ -0,0 +1,5 @@ +package hs.kr.entrydsm.feed.domain.question.presentation.dto.response + +data class QuestionListResponse( + val questions: List +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/CreateQuestionService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/CreateQuestionService.kt new file mode 100644 index 0000000..832e6f7 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/CreateQuestionService.kt @@ -0,0 +1,26 @@ +package hs.kr.entrydsm.feed.domain.question.service + +import hs.kr.entrydsm.feed.domain.question.domain.Question +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.domain.question.presentation.dto.request.CreateQuestionRequest +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class CreateQuestionService( + private val questionRepository: QuestionRepository, + private val userUtils: UserUtils +) { + @Transactional + fun execute(createQuestionRequest: CreateQuestionRequest) { + val question = Question( + title = createQuestionRequest.title, + content = createQuestionRequest.content, + isPublic = createQuestionRequest.isPublic, + isReplied = false, + userId = userUtils.getCurrentUserId() + ) + questionRepository.save(question) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/DeleteQuestionService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/DeleteQuestionService.kt new file mode 100644 index 0000000..1321332 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/DeleteQuestionService.kt @@ -0,0 +1,24 @@ +package hs.kr.entrydsm.feed.domain.question.service + +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class DeleteQuestionService( + private val userUtils: UserUtils, + private val questionRepository: QuestionRepository +) { + @Transactional + fun execute(questionId: UUID) { + val question = questionRepository.findByIdOrNull(questionId) ?: throw QuestionNotFoundException + val userId = userUtils.getCurrentUserId() + + question.validateWriter(userId) + questionRepository.delete(question) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryMyQuestionService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryMyQuestionService.kt new file mode 100644 index 0000000..8d03590 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryMyQuestionService.kt @@ -0,0 +1,32 @@ +package hs.kr.entrydsm.feed.domain.question.service + +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionDTO +import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionListResponse +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class QueryMyQuestionService( + private val userUtils: UserUtils, + private val questionRepository: QuestionRepository, + private val userFeignClient: UserFeignClient +) { + @Transactional(readOnly = true) + fun execute(): QuestionListResponse { + val userId = userUtils.getCurrentUserId() + val questionList = questionRepository.findAllByUserId(userId).map { it -> + QuestionDTO( + id = it.id!!, + title = it.title, + createdAt = it.createdAt, + username = userFeignClient.getUserByUUID(it.userId).name, + isReplied = it.isReplied, + isPublic = it.isPublic + ) + } + return QuestionListResponse(questionList) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionDetailsService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionDetailsService.kt new file mode 100644 index 0000000..912801e --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionDetailsService.kt @@ -0,0 +1,71 @@ +package hs.kr.entrydsm.feed.domain.question.service + +import hs.kr.entrydsm.feed.domain.question.domain.Question +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.domain.question.exception.AccessDeniedQuestionException +import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException +import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionDetailsResponse +import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository +import hs.kr.entrydsm.feed.domain.reply.presentation.dto.response.ReplyDto +import hs.kr.entrydsm.feed.global.security.jwt.UserRole +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient +import hs.kr.equus.feed.infrastructure.feign.client.user.model.User +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.* + +@Service +class QueryQuestionDetailsService( + private val questionRepository: QuestionRepository, + private val replyRepository: ReplyRepository, + private val userUtils: UserUtils, + private val userFeignClient: UserFeignClient +) { + @Transactional(readOnly = true) + fun execute(questionId: UUID): QuestionDetailsResponse { + val question = questionRepository.findByIdOrNull(questionId) ?: throw QuestionNotFoundException + val user = userUtils.getCurrentUser() + + validateAccessPermission(question, user) + + return createResponse(question, user) + } + + private fun validateAccessPermission(question: Question, user: User) { + if (!question.isPublic) { + if (question.userId != user.id) { + throw AccessDeniedQuestionException + } + if (user.role.toString() != "ROLE_${UserRole.ADMIN}" && question.userId != user.id) { + throw AccessDeniedQuestionException + } + } + } + + private fun createResponse(question: Question, user: User): QuestionDetailsResponse { + return QuestionDetailsResponse( + id = question.id!!, + title = question.title, + content = question.content, + username = userFeignClient.getUserByUUID(question.userId).name, + isReplied = question.isReplied, + isMine = (question.userId == user.id), + isPublic = question.isPublic, + createdAt = question.createdAt, + reply = getReplyDto(question.id) + ) + } + + private fun getReplyDto(questionId: UUID): ReplyDto? { + val reply = replyRepository.findByQuestionId(questionId) + return reply?.run { + ReplyDto( + title = title, + content = content, + createdAt = createdAt + ) + } + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionListService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionListService.kt new file mode 100644 index 0000000..c3dd1ab --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionListService.kt @@ -0,0 +1,30 @@ +package hs.kr.entrydsm.feed.domain.question.service + +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionDTO +import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionListResponse +import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient +import org.springframework.data.domain.Pageable +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class QueryQuestionListService( + private val questionRepository: QuestionRepository, + private val userFeignClient: UserFeignClient +) { + @Transactional(readOnly = true) + fun execute(pageable: Pageable): QuestionListResponse { + val questions = questionRepository.findAllByOrderByCreatedAtDesc(pageable).map { it -> + QuestionDTO( + id = it.id!!, + title = it.title, + createdAt = it.createdAt, + username = userFeignClient.getUserByUUID(it.userId).name, + isReplied = it.isReplied, + isPublic = it.isPublic + ) + } + return QuestionListResponse(questions = questions) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/UpdateQuestionService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/UpdateQuestionService.kt new file mode 100644 index 0000000..664bded --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/UpdateQuestionService.kt @@ -0,0 +1,31 @@ +package hs.kr.entrydsm.feed.domain.question.service + +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException +import hs.kr.entrydsm.feed.domain.question.presentation.dto.request.UpdateQuestionRequest +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class UpdateQuestionService( + private val questionRepository: QuestionRepository, + private val userUtils: UserUtils +) { + @Transactional + fun execute(questionId: UUID, updateQuestionRequest: UpdateQuestionRequest) { + val userId = userUtils.getCurrentUserId() + val question = questionRepository.findByIdOrNull(questionId) ?: throw QuestionNotFoundException + + updateQuestionRequest.run { + question.updateQuestion( + userId = userId, + title = title, + content = content, + isPublic = isPublic + ) + } + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/Reply.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/Reply.kt new file mode 100644 index 0000000..1517e25 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/Reply.kt @@ -0,0 +1,28 @@ +package hs.kr.entrydsm.feed.domain.reply.domain + +import hs.kr.entrydsm.feed.domain.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import java.util.* + +@Entity(name = "tbl_reply") +class Reply( + id: UUID? = null, + + @Column(name = "title", length = 150, nullable = false) + var title: String, + + @Column(name = "content", length = 5000, nullable = false) + var content: String, + + @Column(name = "question_id", columnDefinition = "BINARY(16)", nullable = false) + val questionId: UUID, + + @Column(name = "admin_id", columnDefinition = "BINARY(16)", nullable = false) + val adminId: UUID +) : BaseEntity(id) { + fun updateReply(title: String, content: String) { + this.title = title + this.content = content + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/repository/ReplyRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/repository/ReplyRepository.kt new file mode 100644 index 0000000..9753948 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/repository/ReplyRepository.kt @@ -0,0 +1,9 @@ +package hs.kr.entrydsm.feed.domain.reply.domain.repository + +import hs.kr.entrydsm.feed.domain.reply.domain.Reply +import org.springframework.data.jpa.repository.JpaRepository +import java.util.UUID + +interface ReplyRepository : JpaRepository { + fun findByQuestionId(questionId: UUID): Reply? +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/AccessDeniedReplyException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/AccessDeniedReplyException.kt new file mode 100644 index 0000000..dec61d1 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/AccessDeniedReplyException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.reply.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object AccessDeniedReplyException : EquusException( + ErrorCode.ACCESS_DENIED_REPLY +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyExistsException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyExistsException.kt new file mode 100644 index 0000000..6bfc6b2 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyExistsException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.reply.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object ReplyExistsException : EquusException( + ErrorCode.REPLY_EXISTS +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyNotFoundException.kt new file mode 100644 index 0000000..1215ac4 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyNotFoundException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.reply.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object ReplyNotFoundException : EquusException( + ErrorCode.REPLY_NOT_FOUND +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/ReplyController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/ReplyController.kt new file mode 100644 index 0000000..8ff31ec --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/ReplyController.kt @@ -0,0 +1,48 @@ +package hs.kr.entrydsm.feed.domain.reply.presentation + +import hs.kr.entrydsm.feed.domain.reply.presentation.dto.request.CreateReplyRequest +import hs.kr.entrydsm.feed.domain.reply.presentation.dto.request.UpdateReplyRequest +import hs.kr.entrydsm.feed.domain.reply.service.CreateReplyService +import hs.kr.entrydsm.feed.domain.reply.service.DeleteReplyService +import hs.kr.entrydsm.feed.domain.reply.service.UpdateReplyService +import org.springframework.http.HttpStatus +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController +import java.util.UUID + +@RequestMapping("/reply") +@RestController +class ReplyController( + private val createReplyService: CreateReplyService, + private val updateReplyService: UpdateReplyService, + private val deleteReplyService: DeleteReplyService +) { + @ResponseStatus(HttpStatus.CREATED) + @PostMapping("/{questionId}") + fun createReply( + @RequestBody @Validated + createReplyRequest: CreateReplyRequest, + @PathVariable("questionId") questionId: UUID + ) = createReplyService.execute(createReplyRequest, questionId) + + @ResponseStatus(HttpStatus.NO_CONTENT) + @PatchMapping("/{replyId}") + fun updateReply( + @RequestBody @Validated + updateReplyRequest: UpdateReplyRequest, + @PathVariable("replyId") replyId: UUID + ) = updateReplyService.execute(updateReplyRequest, replyId) + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{replyId}") + fun deleteReply( + @PathVariable("replyId") replyId: UUID + ) = deleteReplyService.execute(replyId) +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/CreateReplyRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/CreateReplyRequest.kt new file mode 100644 index 0000000..56df969 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/CreateReplyRequest.kt @@ -0,0 +1,15 @@ +package hs.kr.entrydsm.feed.domain.reply.presentation.dto.request + +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + + +data class CreateReplyRequest( + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") + val content: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/UpdateReplyRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/UpdateReplyRequest.kt new file mode 100644 index 0000000..f78d6e9 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/UpdateReplyRequest.kt @@ -0,0 +1,15 @@ +package hs.kr.entrydsm.feed.domain.reply.presentation.dto.request + +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + + +data class UpdateReplyRequest( + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") + val content: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/response/ReplyDto.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/response/ReplyDto.kt new file mode 100644 index 0000000..38837c0 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/response/ReplyDto.kt @@ -0,0 +1,9 @@ +package hs.kr.entrydsm.feed.domain.reply.presentation.dto.response + +import java.time.LocalDateTime + +data class ReplyDto( + val title: String, + val content: String, + val createdAt: LocalDateTime +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/CreateReplyService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/CreateReplyService.kt new file mode 100644 index 0000000..f1207ff --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/CreateReplyService.kt @@ -0,0 +1,41 @@ +package hs.kr.entrydsm.feed.domain.reply.service + +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException +import hs.kr.entrydsm.feed.domain.reply.domain.Reply +import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository +import hs.kr.entrydsm.feed.domain.reply.exception.ReplyExistsException +import hs.kr.entrydsm.feed.domain.reply.presentation.dto.request.CreateReplyRequest +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.* + +@Service +class CreateReplyService( + private val userUtils: UserUtils, + private val replyRepository: ReplyRepository, + private val questionRepository: QuestionRepository +) { + @Transactional + fun execute(createReplyRequest: CreateReplyRequest, questionId: UUID) { + val question = questionRepository.findByIdOrNull(questionId) ?: throw QuestionNotFoundException + if (question.isReplied) { + throw ReplyExistsException + } + val user = userUtils.getCurrentUser() + + createReplyRequest.run { + replyRepository.save( + Reply( + title = title, + content = content, + questionId = question.id!!, + adminId = user.id + ) + ) + } + question.updateIsReplied(true) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/DeleteReplyService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/DeleteReplyService.kt new file mode 100644 index 0000000..d70513c --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/DeleteReplyService.kt @@ -0,0 +1,23 @@ +package hs.kr.entrydsm.feed.domain.reply.service + +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException +import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository +import hs.kr.entrydsm.feed.domain.reply.exception.ReplyNotFoundException +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.* + +@Service +class DeleteReplyService( + private val replyRepository: ReplyRepository, + private val questionRepository: QuestionRepository +) { + @Transactional + fun execute(replyId: UUID) { + val reply = replyRepository.findByIdOrNull(replyId) ?: throw ReplyNotFoundException + questionRepository.findByIdOrNull(reply.questionId) ?.updateIsReplied(false) ?: throw QuestionNotFoundException + replyRepository.delete(reply) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/UpdateReplyService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/UpdateReplyService.kt new file mode 100644 index 0000000..94b315c --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/UpdateReplyService.kt @@ -0,0 +1,26 @@ +package hs.kr.entrydsm.feed.domain.reply.service + +import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository +import hs.kr.entrydsm.feed.domain.reply.exception.ReplyNotFoundException +import hs.kr.entrydsm.feed.domain.reply.presentation.dto.request.UpdateReplyRequest +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class UpdateReplyService( + private val replyRepository: ReplyRepository +) { + @Transactional + fun execute(updateReplyRequest: UpdateReplyRequest, replyId: UUID) { + val reply = replyRepository.findByIdOrNull(replyId) ?: throw ReplyNotFoundException + + updateReplyRequest.run { + reply.updateReply( + title = title, + content = content + ) + } + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/presentation/ReserveController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/presentation/ReserveController.kt new file mode 100644 index 0000000..40474e2 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/presentation/ReserveController.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.domain.reserve.presentation + +import hs.kr.entrydsm.feed.domain.reserve.service.GetReserveLinkService +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/reserve") +class ReserveController( + private val getReserveLinkService: GetReserveLinkService +) { + + @GetMapping + fun reserveLink(): String = + getReserveLinkService.execute() +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/service/GetReserveLinkService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/service/GetReserveLinkService.kt new file mode 100644 index 0000000..2ea926c --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/service/GetReserveLinkService.kt @@ -0,0 +1,13 @@ +package hs.kr.entrydsm.feed.domain.reserve.service + +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +@Service +class GetReserveLinkService( + @Value("\${reserve.link}") + private val reserveLink: String +) { + fun execute() = + reserveLink +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/Screen.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/Screen.kt new file mode 100644 index 0000000..cabbff7 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/Screen.kt @@ -0,0 +1,22 @@ +package hs.kr.entrydsm.feed.domain.screen.domain + +import hs.kr.entrydsm.feed.domain.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import java.util.UUID + +@Entity(name = "tbl_screen") +class Screen( + + id: UUID? = null, + + var image: String, + + @Column(columnDefinition = "BINARY(16)") + val adminId: UUID +) : BaseEntity(id) { + + fun updateImage(image: String) { + this.image = image + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/repository/ScreenRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/repository/ScreenRepository.kt new file mode 100644 index 0000000..1abdd85 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/repository/ScreenRepository.kt @@ -0,0 +1,7 @@ +package hs.kr.entrydsm.feed.domain.screen.domain.repository + +import hs.kr.entrydsm.feed.domain.screen.domain.Screen +import org.springframework.data.jpa.repository.JpaRepository +import java.util.UUID + +interface ScreenRepository : JpaRepository diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/exception/ScreenNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/exception/ScreenNotFoundException.kt new file mode 100644 index 0000000..b98cb42 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/exception/ScreenNotFoundException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.domain.screen.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object ScreenNotFoundException : EquusException( + ErrorCode.SCREEN_NOT_FOUND +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/ScreenController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/ScreenController.kt new file mode 100644 index 0000000..8bcb01c --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/ScreenController.kt @@ -0,0 +1,45 @@ +package hs.kr.entrydsm.feed.domain.screen.presentation + +import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.QueryScreenResponse +import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.ScreenResponse +import hs.kr.entrydsm.feed.domain.screen.service.CreateScreenService +import hs.kr.entrydsm.feed.domain.screen.service.QueryScreenService +import hs.kr.entrydsm.feed.domain.screen.service.UpdateScreenService +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestPart +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile +import java.util.UUID + +@RestController +@RequestMapping("/screen") +class ScreenController( + private val createScreenService: CreateScreenService, + private val updateScreenService: UpdateScreenService, + private val queryScreenService: QueryScreenService +) { + + @ResponseStatus(value = HttpStatus.CREATED) + @PostMapping + fun createScreen( + @RequestPart(name = "image") image: MultipartFile + ): ScreenResponse = + createScreenService.execute(image) + + @PatchMapping("/{screen-id}") + fun updateScreen( + @PathVariable(name = "screen-id") id: UUID, + @RequestPart(name = "image") image: MultipartFile + ): ScreenResponse = + updateScreenService.execute(id, image) + + @GetMapping + fun queryScreen(): List = + queryScreenService.execute() +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/QueryScreenResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/QueryScreenResponse.kt new file mode 100644 index 0000000..46998b8 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/QueryScreenResponse.kt @@ -0,0 +1,11 @@ +package hs.kr.entrydsm.feed.domain.screen.presentation.dto.response + +import java.time.LocalDateTime +import java.util.UUID + +data class QueryScreenResponse( + val id: UUID, + val image: String, + val createAt: LocalDateTime, + val modifyAt: LocalDateTime +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/ScreenResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/ScreenResponse.kt new file mode 100644 index 0000000..01c7813 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/ScreenResponse.kt @@ -0,0 +1,5 @@ +package hs.kr.entrydsm.feed.domain.screen.presentation.dto.response + +data class ScreenResponse( + val image: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/CreateScreenService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/CreateScreenService.kt new file mode 100644 index 0000000..53a3acd --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/CreateScreenService.kt @@ -0,0 +1,33 @@ +package hs.kr.entrydsm.feed.domain.screen.service + +import hs.kr.entrydsm.feed.domain.screen.domain.Screen +import hs.kr.entrydsm.feed.domain.screen.domain.repository.ScreenRepository +import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.ScreenResponse +import hs.kr.entrydsm.feed.global.utils.user.UserUtils +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile + +@Service +class CreateScreenService( + private val screenRepository: ScreenRepository, + private val fileUtil: FileUtil, + private val userUtils: UserUtils +) { + + @Transactional + fun execute(file: MultipartFile): ScreenResponse { + val adminId = userUtils.getCurrentUser().id + + val fileName = fileUtil.upload(file, PathList.SCREEN) + screenRepository.save( + Screen( + image = fileName, + adminId = adminId + ) + ) + return ScreenResponse(fileName) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/QueryScreenService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/QueryScreenService.kt new file mode 100644 index 0000000..a493049 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/QueryScreenService.kt @@ -0,0 +1,26 @@ +package hs.kr.entrydsm.feed.domain.screen.service + +import hs.kr.entrydsm.feed.domain.screen.domain.repository.ScreenRepository +import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.QueryScreenResponse +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class QueryScreenService( + private val screenRepository: ScreenRepository, + private val fileUtil: FileUtil +) { + @Transactional(readOnly = true) + fun execute(): List = + screenRepository.findAll() + .map { it -> + QueryScreenResponse( + it.id!!, + fileUtil.generateObjectUrl(it.image, PathList.SCREEN), + it.createdAt, + it.modifiedAt + ) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/UpdateScreenService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/UpdateScreenService.kt new file mode 100644 index 0000000..7117392 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/UpdateScreenService.kt @@ -0,0 +1,30 @@ +package hs.kr.entrydsm.feed.domain.screen.service + +import hs.kr.entrydsm.feed.domain.screen.domain.repository.ScreenRepository +import hs.kr.entrydsm.feed.domain.screen.exception.ScreenNotFoundException +import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.ScreenResponse +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile +import java.util.UUID + +@Service +class UpdateScreenService( + private val screenRepository: ScreenRepository, + private val fileUtil: FileUtil +) { + + @Transactional + fun execute(id: UUID, file: MultipartFile): ScreenResponse { + val screen = screenRepository.findByIdOrNull(id) + ?: throw ScreenNotFoundException + val fileName = fileUtil.upload(file, PathList.SCREEN) + fileUtil.delete(screen.image, PathList.SCREEN) + screen.updateImage(fileName) + + return ScreenResponse(fileUtil.generateObjectUrl(fileName, PathList.SCREEN)) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/config/AwsConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/config/AwsConfig.kt new file mode 100644 index 0000000..d11c259 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/config/AwsConfig.kt @@ -0,0 +1,29 @@ +package hs.kr.entrydsm.feed.global.config + +import com.amazonaws.auth.AWSCredentials +import com.amazonaws.auth.AWSStaticCredentialsProvider +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.AmazonS3ClientBuilder +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class AwsConfig( + @Value("\${cloud.aws.credentials.accessKey}") + private val accessKey: String, + @Value("\${cloud.aws.credentials.secretKey}") + private val secretKey: String, + @Value("\${cloud.aws.region.static}") + private val region: String +) { + @Bean + fun amazonS3(): AmazonS3 { + val awsCredentials: AWSCredentials = BasicAWSCredentials(accessKey, secretKey) + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(AWSStaticCredentialsProvider(awsCredentials)) + .build() + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/config/FilterConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/config/FilterConfig.kt new file mode 100644 index 0000000..0b353e1 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/config/FilterConfig.kt @@ -0,0 +1,23 @@ +package hs.kr.entrydsm.feed.global.config + +import com.fasterxml.jackson.databind.ObjectMapper +import hs.kr.entrydsm.feed.global.error.GlobalExceptionFilter +import hs.kr.entrydsm.feed.global.security.jwt.JwtFilter +import org.springframework.security.config.annotation.SecurityConfigurerAdapter +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.web.DefaultSecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter +import org.springframework.stereotype.Component + +@Component +class FilterConfig( + private val objectMapper: ObjectMapper +) : SecurityConfigurerAdapter() { + override fun configure(builder: HttpSecurity) { + builder.addFilterBefore( + JwtFilter(), + UsernamePasswordAuthenticationFilter::class.java + ) + builder.addFilterBefore(GlobalExceptionFilter(objectMapper), JwtFilter::class.java) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/config/SecurityConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/config/SecurityConfig.kt new file mode 100644 index 0000000..0639cdf --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/config/SecurityConfig.kt @@ -0,0 +1,66 @@ +package hs.kr.entrydsm.feed.global.config + +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.web.SecurityFilterChain + +@Configuration +class SecurityConfig( + private val objectMapper: ObjectMapper +) { + companion object { + private const val ADMIN_ROLE = "ADMIN" + } + + @Bean + protected fun filterChain(http: HttpSecurity): SecurityFilterChain { + http + .csrf { it.disable() } + .cors { } + .formLogin { it.disable() } + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .authorizeHttpRequests { auth -> + auth + .requestMatchers("/") + .permitAll() + .requestMatchers("/reply/**") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.POST, "/faq/**") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.PATCH, "/faq/**") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.DELETE, "/faq/**") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.GET, "/faq/all/title-type") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.GET, "/faq/**") + .permitAll() + .requestMatchers(HttpMethod.GET, "/reserve/**") + .permitAll() + .requestMatchers(HttpMethod.POST, "/notice") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.PATCH, "/notice/**") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.POST, "/notice/image") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.POST, "/screen") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.POST, "/attach-file") + .hasRole(ADMIN_ROLE) + .requestMatchers(HttpMethod.GET, "/notice/**") + .permitAll() + .anyRequest() + .authenticated() + } + .with(FilterConfig(objectMapper)) { } + .build() + + return http.build() + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/ErrorResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/ErrorResponse.kt new file mode 100644 index 0000000..7e7c138 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/ErrorResponse.kt @@ -0,0 +1,6 @@ +package hs.kr.entrydsm.feed.global.error + +data class ErrorResponse( + val status: Int, + val message: String? +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionFilter.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionFilter.kt new file mode 100644 index 0000000..62ee587 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionFilter.kt @@ -0,0 +1,42 @@ +package hs.kr.entrydsm.feed.global.error + +import com.fasterxml.jackson.databind.ObjectMapper +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.MediaType +import org.springframework.web.filter.OncePerRequestFilter +import java.io.IOException +import java.nio.charset.StandardCharsets + +class GlobalExceptionFilter( + private val objectMapper: ObjectMapper +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + try { + filterChain.doFilter(request, response) + } catch (e: EquusException) { + println(e.errorCode) + writerErrorCode(response, e.errorCode) + } catch (e: Exception) { + e.printStackTrace() + writerErrorCode(response, ErrorCode.INTERNAL_SERVER_ERROR) + } + } + + @Throws(IOException::class) + private fun writerErrorCode(response: HttpServletResponse, errorCode: ErrorCode) { + val errorResponse = ErrorResponse(errorCode.status, errorCode.message) + response.status = errorCode.status + response.characterEncoding = StandardCharsets.UTF_8.name() + response.contentType = MediaType.APPLICATION_JSON_VALUE + response.writer.write(objectMapper.writeValueAsString(errorResponse)) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionHandler.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionHandler.kt new file mode 100644 index 0000000..8bc3ae0 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionHandler.kt @@ -0,0 +1,35 @@ +package hs.kr.entrydsm.feed.global.error + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import org.springframework.context.MessageSource +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice + +@RestControllerAdvice +class GlobalExceptionHandler( + private val messageSource: MessageSource +) { + + @ExceptionHandler(EquusException::class) + fun handlingEquusException(e: EquusException): ResponseEntity { + val code = e.errorCode + return ResponseEntity( + ErrorResponse(code.status, code.message), + HttpStatus.valueOf(code.status) + ) + } + + @ExceptionHandler(MethodArgumentNotValidException::class) + fun validatorExceptionHandler(e: MethodArgumentNotValidException): ResponseEntity { + return ResponseEntity( + ErrorResponse( + 400, + e.bindingResult.allErrors[0].defaultMessage + ), + HttpStatus.BAD_REQUEST + ) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/EquusException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/EquusException.kt new file mode 100644 index 0000000..39d4f75 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/EquusException.kt @@ -0,0 +1,7 @@ +package hs.kr.entrydsm.feed.global.error.exception + +import java.lang.RuntimeException + +abstract class EquusException( + val errorCode: ErrorCode +) : RuntimeException() diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/ErrorCode.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/ErrorCode.kt new file mode 100644 index 0000000..69d78dd --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/ErrorCode.kt @@ -0,0 +1,39 @@ +package hs.kr.entrydsm.feed.global.error.exception + +enum class ErrorCode( + val status: Int, + val message: String +) { + // Feign + FEIGN_BAD_REQUEST(400, "Feign Bad Request"), + FEIGN_UNAUTHORIZED(401, "Feign UnAuthorized"), + FEIGN_FORBIDDEN(403, "Feign Forbidden"), + FEIGN_SERVER_ERROR(500, "Feign Server Error"), + + // Internal Server Error + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), + + // UnAuthorization + INVALID_TOKEN(401, "Invalid Token"), + EXPIRED_TOKEN(401, "Expired Token"), + + // Forbidden + ACCESS_DENIED_QUESTION(403, "No Permission To Access Question"), + ACCESS_DENIED_REPLY(403, "No Permission To Comment Question"), + FEED_WRITER_MISMATCH(403, "Feed Writer Mismatch"), + + // Not Found + QUESTION_NOT_FOUND(404, "Question Not Found"), + REPLY_NOT_FOUND(404, "Reply Not Found"), + FAQ_NOT_FOUND(404, "Faq Not Found"), + NOTICE_NOT_FOUND(404, "Notice Not Found"), + SCREEN_NOT_FOUND(404, "Screen Not Found"), + ATTACH_FILE_NOT_FOUND(404, "Attach Not Found"), + + // Bad Request + FILE_IS_EMPTY(400, "File does not exist"), + BAD_FILE_EXTENSION(400, "File Extension is invalid"), + + // Conflict + REPLY_EXISTS(409, "Reply Already Exists") +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/ExpiredTokenException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/ExpiredTokenException.kt new file mode 100644 index 0000000..cae9206 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/ExpiredTokenException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.global.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object ExpiredTokenException : EquusException( + ErrorCode.EXPIRED_TOKEN +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InternalServerErrorException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InternalServerErrorException.kt new file mode 100644 index 0000000..8738854 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InternalServerErrorException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.global.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object InternalServerErrorException : EquusException( + ErrorCode.INTERNAL_SERVER_ERROR +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InvalidTokenException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InvalidTokenException.kt new file mode 100644 index 0000000..d244577 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InvalidTokenException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.global.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object InvalidTokenException : EquusException( + ErrorCode.INVALID_TOKEN +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtFilter.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtFilter.kt new file mode 100644 index 0000000..a77ccd3 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtFilter.kt @@ -0,0 +1,44 @@ +package hs.kr.entrydsm.feed.global.security.jwt + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.core.context.SecurityContextHolder.clearContext +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.web.filter.OncePerRequestFilter + +class JwtFilter : OncePerRequestFilter() { + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val userId: String? = request.getHeader("Request-User-Id") + val role: UserRole? = request.getHeader("Request-User-Role")?.let { UserRole.valueOf(it) } + + if ((userId == null) || (role == null)) { + filterChain.doFilter(request, response) + return + } + + val authorities = mutableListOf(SimpleGrantedAuthority("ROLE_${role.name}")) + val userDetails: UserDetails = User(userId, "", authorities) + val authentication: Authentication = + UsernamePasswordAuthenticationToken(userDetails, "", userDetails.authorities) + + clearContext() + SecurityContextHolder.getContext().authentication = authentication + filterChain.doFilter(request, response) + } +} + +enum class UserRole { + ROOT, + ADMIN, + USER +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtProperties.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtProperties.kt new file mode 100644 index 0000000..747d1a1 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtProperties.kt @@ -0,0 +1,12 @@ +package hs.kr.entrydsm.feed.global.security.jwt + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.bind.ConstructorBinding + +@ConstructorBinding +@ConfigurationProperties("auth.jwt") +class JwtProperties( + val secretKey: String, + val header: String, + val prefix: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/admin/AdminUtils.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/admin/AdminUtils.kt new file mode 100644 index 0000000..0e17d8b --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/admin/AdminUtils.kt @@ -0,0 +1,22 @@ +package hs.kr.entrydsm.feed.global.utils.admin + +import hs.kr.entrydsm.feed.global.exception.InvalidTokenException +import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient +import hs.kr.equus.feed.infrastructure.feign.client.user.model.Admin +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Component +import java.util.* + +@Component +class AdminUtils( + private val userFeignClient: UserFeignClient +) { + fun getCurrentAdminId(): UUID { + try { + return UUID.fromString(SecurityContextHolder.getContext().authentication.name) + } catch (e: IllegalArgumentException) { + throw InvalidTokenException + } + } + fun getCurrentAdmin(): Admin = userFeignClient.getAdminByUUID(getCurrentAdminId()) +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/user/UserUtils.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/user/UserUtils.kt new file mode 100644 index 0000000..9668bf7 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/user/UserUtils.kt @@ -0,0 +1,22 @@ +package hs.kr.entrydsm.feed.global.utils.user + +import hs.kr.entrydsm.feed.global.exception.InvalidTokenException +import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient +import hs.kr.equus.feed.infrastructure.feign.client.user.model.User +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Component +import java.util.UUID + +@Component +class UserUtils( + private val userFeignClient: UserFeignClient +) { + fun getCurrentUserId(): UUID { + try { + return UUID.fromString(SecurityContextHolder.getContext().authentication.name) + } catch (e: IllegalArgumentException) { + throw InvalidTokenException + } + } + fun getCurrentUser(): User = userFeignClient.getUserByUUID(getCurrentUserId()) +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/FeignClientErrorDecoder.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/FeignClientErrorDecoder.kt new file mode 100644 index 0000000..6e294a2 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/FeignClientErrorDecoder.kt @@ -0,0 +1,24 @@ +package hs.kr.entrydsm.feed.infrastructure.feign + +import feign.FeignException +import feign.Response +import feign.codec.ErrorDecoder +import hs.kr.entrydsm.feed.infrastructure.feign.exception.FeignBadRequestException +import hs.kr.entrydsm.feed.infrastructure.feign.exception.FeignForbiddenException +import hs.kr.entrydsm.feed.infrastructure.feign.exception.FeignServerError +import hs.kr.entrydsm.feed.infrastructure.feign.exception.FeignUnAuthorizedException + +class FeignClientErrorDecoder : ErrorDecoder { + override fun decode(methodKey: String?, response: Response?): Exception { + print("feign error : ${response?.reason()}") + if (response!!.status() >= 400) { + when (response.status()) { + 400 -> throw FeignBadRequestException + 401 -> throw FeignUnAuthorizedException + 403 -> throw FeignForbiddenException + else -> throw FeignServerError + } + } + return FeignException.errorStatus(methodKey, response) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/UserFeignClient.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/UserFeignClient.kt new file mode 100644 index 0000000..20802f4 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/UserFeignClient.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.infrastructure.feign.client.user + +import hs.kr.equus.feed.infrastructure.feign.client.user.model.Admin +import hs.kr.equus.feed.infrastructure.feign.client.user.model.User +import org.springframework.cloud.openfeign.FeignClient +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import java.util.UUID + +@FeignClient(name = "UserClient", url = "\${url.user}") +interface UserFeignClient { + @GetMapping("/user/{userId}") + fun getUserByUUID(@PathVariable userId: UUID): User + + @GetMapping("/admin/{adminId}") + fun getAdminByUUID(@PathVariable adminId: UUID): Admin +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/Admin.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/Admin.kt new file mode 100644 index 0000000..eb00317 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/Admin.kt @@ -0,0 +1,7 @@ +package hs.kr.equus.feed.infrastructure.feign.client.user.model + +import java.util.UUID + +data class Admin( + val id: UUID +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/User.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/User.kt new file mode 100644 index 0000000..c90e1fe --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/User.kt @@ -0,0 +1,13 @@ +package hs.kr.equus.feed.infrastructure.feign.client.user.model + +import hs.kr.entrydsm.feed.global.security.jwt.UserRole +import java.util.UUID + +data class User( + val id: UUID, + val phoneNumber: String, + val name: String, + val isParent: Boolean, + val receiptCode: UUID?, + val role: UserRole +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/FeignConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/FeignConfig.kt new file mode 100644 index 0000000..b0badc1 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/FeignConfig.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.feed.infrastructure.feign.configuration + +import feign.codec.ErrorDecoder +import hs.kr.entrydsm.feed.infrastructure.feign.FeignClientErrorDecoder +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.cloud.openfeign.EnableFeignClients +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@EnableFeignClients(basePackages = ["hs.kr.entrydsm.feed"]) +@Configuration +class FeignConfig { + @Bean + @ConditionalOnMissingBean(value = [ErrorDecoder::class]) + fun commonFeignErrorDecoder(): FeignClientErrorDecoder = FeignClientErrorDecoder() +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/HeaderConfiguration.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/HeaderConfiguration.kt new file mode 100644 index 0000000..bb89e37 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/HeaderConfiguration.kt @@ -0,0 +1,14 @@ +package hs.kr.entrydsm.feed.infrastructure.feign.configuration + +import feign.RequestInterceptor +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class HeaderConfiguration { + @Bean + fun requestInterceptor(): RequestInterceptor = RequestInterceptor { template -> + template.header("Request-User-Id", "\${feign.userId}") + template.header("Request-User-Role", "ROOT") + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignBadRequestException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignBadRequestException.kt new file mode 100644 index 0000000..419a349 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignBadRequestException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.infrastructure.feign.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object FeignBadRequestException : EquusException( + ErrorCode.FEIGN_BAD_REQUEST +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignForbiddenException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignForbiddenException.kt new file mode 100644 index 0000000..5d30347 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignForbiddenException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.infrastructure.feign.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object FeignForbiddenException : EquusException( + ErrorCode.FEIGN_FORBIDDEN +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignServerError.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignServerError.kt new file mode 100644 index 0000000..a9791cb --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignServerError.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.infrastructure.feign.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object FeignServerError : EquusException( + ErrorCode.FEIGN_SERVER_ERROR +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignUnAuthorizedException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignUnAuthorizedException.kt new file mode 100644 index 0000000..56f78d4 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignUnAuthorizedException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.infrastructure.feign.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object FeignUnAuthorizedException : EquusException( + ErrorCode.FEIGN_UNAUTHORIZED +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaConsumerConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaConsumerConfig.kt new file mode 100644 index 0000000..dc718ad --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaConsumerConfig.kt @@ -0,0 +1,47 @@ +package hs.kr.entrydsm.feed.infrastructure.kafka.configuration + +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.common.serialization.StringDeserializer +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.kafka.annotation.EnableKafka +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory +import org.springframework.kafka.core.DefaultKafkaConsumerFactory +import org.springframework.kafka.support.converter.StringJsonMessageConverter +import org.springframework.kafka.support.serializer.JsonDeserializer + +@EnableKafka +@Configuration +class KafkaConsumerConfig( + private val kafkaProperty: KafkaProperty +) { + + @Bean + fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory { + return ConcurrentKafkaListenerContainerFactory().apply { + setConcurrency(2) + consumerFactory = DefaultKafkaConsumerFactory(consumerFactoryConfig()) + setMessageConverter(StringJsonMessageConverter()) + containerProperties.pollTimeout = 500 + } + } + + private fun consumerFactoryConfig(): Map { + return mapOf( + ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to kafkaProperty.serverAddress, + ConsumerConfig.ISOLATION_LEVEL_CONFIG to "read_committed", + ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java, + ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG to "false", + ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to "latest", + ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to JsonDeserializer::class.java, + ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG to 5000, + JsonDeserializer.TRUSTED_PACKAGES to "*", + "security.protocol" to "SASL_PLAINTEXT", + "sasl.mechanism" to "SCRAM-SHA-512", + "sasl.jaas.config" to + "org.apache.kafka.common.security.scram.ScramLoginModule required " + + "username=\"${kafkaProperty.confluentApiKey}\" " + + "password=\"${kafkaProperty.confluentApiSecret}\";" + ) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaProperty.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaProperty.kt new file mode 100644 index 0000000..9805cb7 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaProperty.kt @@ -0,0 +1,12 @@ +package hs.kr.entrydsm.feed.infrastructure.kafka.configuration + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.bind.ConstructorBinding + +@ConstructorBinding +@ConfigurationProperties("kafka") +class KafkaProperty( + val serverAddress: String, + val confluentApiKey: String, + val confluentApiSecret: String +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaTopics.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaTopics.kt new file mode 100644 index 0000000..626a2a5 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaTopics.kt @@ -0,0 +1,6 @@ +package hs.kr.entrydsm.feed.infrastructure.kafka.configuration + +object KafkaTopics { + const val DELETE_ALL_TABLE = "delete-all-table" + const val DELETE_USER = "delete-user" +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteFaqTableConsumer.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteFaqTableConsumer.kt new file mode 100644 index 0000000..3159bf3 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteFaqTableConsumer.kt @@ -0,0 +1,20 @@ +package hs.kr.entrydsm.feed.infrastructure.kafka.consumer + +import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository +import hs.kr.entrydsm.feed.infrastructure.kafka.configuration.KafkaTopics +import org.springframework.kafka.annotation.KafkaListener +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class DeleteFaqTableConsumer( + private val faqRepository: FaqRepository +) { + @KafkaListener( + topics = [KafkaTopics.DELETE_ALL_TABLE], + groupId = "delete-all-table-faq", + containerFactory = "kafkaListenerContainerFactory" + ) + @Transactional + fun execute() = faqRepository.deleteAll() +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteQuestionTableConsumer.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteQuestionTableConsumer.kt new file mode 100644 index 0000000..42f37f3 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteQuestionTableConsumer.kt @@ -0,0 +1,20 @@ +package hs.kr.entrydsm.feed.infrastructure.kafka.consumer + +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.infrastructure.kafka.configuration.KafkaTopics +import org.springframework.kafka.annotation.KafkaListener +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class DeleteQuestionTableConsumer( + private val questionRepository: QuestionRepository +) { + @KafkaListener( + topics = [KafkaTopics.DELETE_ALL_TABLE], + groupId = "delete-all-table-question", + containerFactory = "kafkaListenerContainerFactory" + ) + @Transactional + fun execute() = questionRepository.deleteAll() +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteReplyTableConsumer.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteReplyTableConsumer.kt new file mode 100644 index 0000000..5b8f40b --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteReplyTableConsumer.kt @@ -0,0 +1,21 @@ +package hs.kr.entrydsm.feed.infrastructure.kafka.consumer + +import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository +import hs.kr.entrydsm.feed.infrastructure.kafka.configuration.KafkaTopics +import org.springframework.kafka.annotation.KafkaListener +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class DeleteReplyTableConsumer( + private val replyRepository: ReplyRepository +) { + + @KafkaListener( + topics = [KafkaTopics.DELETE_ALL_TABLE], + groupId = "delete-all-table-reply", + containerFactory = "kafkaListenerContainerFactory" + ) + @Transactional + fun execute() = replyRepository.deleteAll() +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteUserQuestionConsumer.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteUserQuestionConsumer.kt new file mode 100644 index 0000000..f78d6ff --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteUserQuestionConsumer.kt @@ -0,0 +1,24 @@ +package hs.kr.entrydsm.feed.infrastructure.kafka.consumer + +import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository +import hs.kr.entrydsm.feed.infrastructure.kafka.configuration.KafkaTopics +import org.springframework.kafka.annotation.KafkaListener +import org.springframework.messaging.handler.annotation.Payload +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class DeleteUserQuestionConsumer( + private val questionRepository: QuestionRepository +) { + @KafkaListener( + topics = [KafkaTopics.DELETE_USER], + groupId = "delete-user-question", + containerFactory = "kafkaListenerContainerFactory" + ) + @Transactional + fun execute(@Payload deletedUserInfo: UUID) { + questionRepository.deleteAllByUserId(deletedUserInfo) + } +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/dto/response/DeletedUserInfoResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/dto/response/DeletedUserInfoResponse.kt new file mode 100644 index 0000000..8d082ad --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/dto/response/DeletedUserInfoResponse.kt @@ -0,0 +1,7 @@ +package hs.kr.entrydsm.feed.infrastructure.kafka.dto.response + +import java.util.UUID + +data class DeletedUserInfoResponse( + val userId: UUID +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/PathList.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/PathList.kt new file mode 100644 index 0000000..b4983bc --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/PathList.kt @@ -0,0 +1,7 @@ +package hs.kr.entrydsm.feed.infrastructure.s3 + +object PathList { + const val NOTICE = "notice/" + const val SCREEN = "screen/" + const val ATTACH_FILE = "attach_file/" +} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/BadFileExtensionException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/BadFileExtensionException.kt new file mode 100644 index 0000000..83ae914 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/BadFileExtensionException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.infrastructure.s3.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object BadFileExtensionException : EquusException( + ErrorCode.BAD_FILE_EXTENSION +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/EmptyFileException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/EmptyFileException.kt new file mode 100644 index 0000000..7b6e1d6 --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/EmptyFileException.kt @@ -0,0 +1,8 @@ +package hs.kr.entrydsm.feed.infrastructure.s3.exception + +import hs.kr.entrydsm.feed.global.error.exception.EquusException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +object EmptyFileException : EquusException( + ErrorCode.FILE_IS_EMPTY +) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/util/FileUtil.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/util/FileUtil.kt new file mode 100644 index 0000000..848b89f --- /dev/null +++ b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/util/FileUtil.kt @@ -0,0 +1,90 @@ +package hs.kr.entrydsm.feed.infrastructure.s3.util + +import com.amazonaws.HttpMethod +import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.model.CannedAccessControlList +import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest +import com.amazonaws.services.s3.model.ObjectMetadata +import com.amazonaws.services.s3.model.PutObjectRequest +import hs.kr.entrydsm.feed.infrastructure.s3.exception.BadFileExtensionException +import hs.kr.entrydsm.feed.infrastructure.s3.exception.EmptyFileException +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.MediaType +import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.util.* + +@Service +class FileUtil( + private val amazonS3: AmazonS3 +) { + @Value("\${cloud.aws.s3.bucket}") + lateinit var bucketName: String + + companion object { + const val EXP_TIME = 10000 * 60 * 2 + } + + fun upload(file: MultipartFile, path: String): String { + val ext = verificationFile(file) + + val randomName = UUID.randomUUID().toString() + val filename = "$randomName.$ext" + val inputStream: InputStream = ByteArrayInputStream(file.bytes) + + val metadata = ObjectMetadata().apply { + contentType = when (ext) { + "pdf" -> MediaType.APPLICATION_PDF_VALUE + else -> MediaType.IMAGE_PNG_VALUE + } + contentLength = file.size + contentDisposition = "inline" + } + + inputStream.use { + amazonS3.putObject( + PutObjectRequest(bucketName, "${path}$filename", it, metadata) + .withCannedAcl(CannedAccessControlList.AuthenticatedRead) + ) + } + + return filename + } + + fun delete(objectName: String, path: String) { + amazonS3.deleteObject(bucketName, path + objectName) + } + + fun generateObjectUrl(fileName: String, path: String): String { + val expiration = Date().apply { + time += EXP_TIME + } + return amazonS3.generatePresignedUrl( + GeneratePresignedUrlRequest( + bucketName, + "${path}$fileName" + ).withMethod(HttpMethod.GET).withExpiration(expiration) + ).toString() + } + + private fun verificationFile(file: MultipartFile): String { + if (file.isEmpty || file.originalFilename == null) throw EmptyFileException + val originalFilename = file.originalFilename!! + val ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).lowercase(Locale.getDefault()) + + if (!( + ext == "jpg" || ext == "jpeg" || + ext == "png" || ext == "heic" || + ext == "hwp" || ext == "pptx" || + ext == "pdf" || ext == "xls" || + ext == "xlsx" + ) + ) { + throw BadFileExtensionException + } + + return ext + } +} From 050f5c16c7b7b03963bd1c79daa8d1119902c3d2 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:14:55 +0900 Subject: [PATCH 10/49] =?UTF-8?q?chore=20(=20#14=20)=20:=20casper-feed=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 99 ++----------------- .../kr/entrydsm/feed/CasperFeedApplication.kt | 24 +++++ settings.gradle.kts | 1 + .../kr/entrydsm/feed/CasperFeedApplication.kt | 11 --- .../hs/kr/entrydsm/feed/domain/BaseEntity.kt | 20 ---- .../kr/entrydsm/feed/domain/BaseTimeEntity.kt | 17 ---- .../kr/entrydsm/feed/domain/BaseUUIDEntity.kt | 19 ---- .../domain/attachFile/domain/AttachFile.kt | 11 --- .../domain/repository/AttachFileRepository.kt | 12 --- .../presentation/AttachFileController.kt | 19 ---- .../dto/response/CreateAttachFileResponse.kt | 6 -- .../service/CreateAttachFileService.kt | 36 ------- .../kr/entrydsm/feed/domain/faq/domain/Faq.kt | 33 ------- .../faq/domain/repository/FaqRepository.kt | 12 --- .../feed/domain/faq/domain/type/FaqType.kt | 9 -- .../faq/exception/FaqNotFoundException.kt | 8 -- .../domain/faq/presentation/FaqController.kt | 71 ------------- .../dto/request/CreateFaqRequest.kt | 17 ---- .../dto/request/UpdateFaqRequest.kt | 17 ---- .../dto/response/FaqDetailsResponse.kt | 11 --- .../faq/presentation/dto/response/FaqDto.kt | 13 --- .../dto/response/FaqListResponse.kt | 5 - .../dto/response/FaqTitleAndTypeResponse.kt | 10 -- .../dto/response/FaqTitleResponse.kt | 9 -- .../domain/faq/service/CreateFaqService.kt | 25 ----- .../domain/faq/service/DeleteFaqService.kt | 19 ---- .../faq/service/QueryFaqDetailsService.kt | 25 ----- .../faq/service/QueryFaqListByTypeService.kt | 27 ----- .../domain/faq/service/QueryFaqListService.kt | 26 ----- .../service/QueryFaqTitleAndTypeService.kt | 24 ----- .../domain/faq/service/QueryTopFaqService.kt | 23 ----- .../domain/faq/service/UpdateFaqService.kt | 29 ------ .../feed/domain/notice/domain/Notice.kt | 60 ----------- .../domain/repository/NoticeRepository.kt | 10 -- .../domain/notice/domain/type/NoticeType.kt | 5 - .../exception/AttachFileNotFoundException.kt | 8 -- .../exception/NoticeNotFoundException.kt | 8 -- .../notice/presentation/NoticeController.kt | 79 --------------- .../dto/request/CreateNoticeRequest.kt | 27 ----- .../dto/request/UpdateNoticeRequest.kt | 26 ----- .../dto/response/NoticeResponse.kt | 13 --- .../response/QueryDetailsNoticeResponse.kt | 20 ---- .../dto/response/QueryListNoticeResponse.kt | 5 - .../dto/response/QueryNoticeTitleResponse.kt | 10 -- .../dto/response/UploadNoticeImageResponse.kt | 6 -- .../notice/service/CreateNoticeService.kt | 43 -------- .../notice/service/DeleteNoticeService.kt | 19 ---- .../service/QueryDetailsNoticeService.kt | 48 --------- .../service/QueryNoticeListByTypeService.kt | 37 ------- .../notice/service/QueryNoticeTitleService.kt | 24 ----- .../notice/service/UpdateNoticeService.kt | 57 ----------- .../service/UploadNoticeImageService.kt | 17 ---- .../feed/domain/question/domain/Question.kt | 45 --------- .../domain/repository/QuestionRepository.kt | 13 --- .../exception/FeedWriterMismatchException.kt | 8 -- .../exception/QuestionNotFoundException.kt | 8 -- .../presentation/QuestionController.kt | 71 ------------- .../dto/request/CreateQuestionRequest.kt | 18 ---- .../dto/request/UpdateQuestionRequest.kt | 19 ---- .../presentation/dto/response/QuestionDTO.kt | 13 --- .../dto/response/QuestionDetailsResponse.kt | 17 ---- .../dto/response/QuestionListResponse.kt | 5 - .../question/service/CreateQuestionService.kt | 26 ----- .../question/service/DeleteQuestionService.kt | 24 ----- .../service/QueryMyQuestionService.kt | 32 ------ .../service/QueryQuestionDetailsService.kt | 71 ------------- .../service/QueryQuestionListService.kt | 30 ------ .../question/service/UpdateQuestionService.kt | 31 ------ .../feed/domain/reply/domain/Reply.kt | 28 ------ .../domain/repository/ReplyRepository.kt | 9 -- .../reply/exception/ReplyExistsException.kt | 8 -- .../reply/exception/ReplyNotFoundException.kt | 8 -- .../reply/presentation/ReplyController.kt | 48 --------- .../dto/request/CreateReplyRequest.kt | 15 --- .../dto/request/UpdateReplyRequest.kt | 15 --- .../presentation/dto/response/ReplyDto.kt | 9 -- .../reply/service/CreateReplyService.kt | 41 -------- .../reply/service/DeleteReplyService.kt | 23 ----- .../reply/service/UpdateReplyService.kt | 26 ----- .../reserve/presentation/ReserveController.kt | 17 ---- .../reserve/service/GetReserveLinkService.kt | 13 --- .../feed/domain/screen/domain/Screen.kt | 22 ----- .../domain/repository/ScreenRepository.kt | 7 -- .../exception/ScreenNotFoundException.kt | 8 -- .../screen/presentation/ScreenController.kt | 45 --------- .../dto/response/QueryScreenResponse.kt | 11 --- .../dto/response/ScreenResponse.kt | 5 - .../screen/service/CreateScreenService.kt | 33 ------- .../screen/service/QueryScreenService.kt | 26 ----- .../screen/service/UpdateScreenService.kt | 30 ------ .../global/error/GlobalExceptionHandler.kt | 35 ------- .../global/error/exception/EquusException.kt | 7 -- .../global/exception/ExpiredTokenException.kt | 8 -- .../exception/InternalServerErrorException.kt | 8 -- .../global/exception/InvalidTokenException.kt | 8 -- .../feed/global/security/jwt/JwtProperties.kt | 12 --- .../feed/global/utils/user/UserUtils.kt | 22 ----- .../feign/client/user/UserFeignClient.kt | 17 ---- .../configuration/HeaderConfiguration.kt | 14 --- .../exception/FeignBadRequestException.kt | 8 -- .../exception/FeignForbiddenException.kt | 8 -- .../feign/exception/FeignServerError.kt | 8 -- .../exception/FeignUnAuthorizedException.kt | 8 -- .../configuration/KafkaConsumerConfig.kt | 47 --------- .../kafka/configuration/KafkaProperty.kt | 12 --- .../kafka/configuration/KafkaTopics.kt | 6 -- .../kafka/consumer/DeleteFaqTableConsumer.kt | 20 ---- .../consumer/DeleteQuestionTableConsumer.kt | 20 ---- .../consumer/DeleteReplyTableConsumer.kt | 21 ---- .../consumer/DeleteUserQuestionConsumer.kt | 24 ----- .../dto/response/DeletedUserInfoResponse.kt | 7 -- .../feed/infrastructure/s3/PathList.kt | 7 -- .../s3/exception/BadFileExtensionException.kt | 8 -- .../s3/exception/EmptyFileException.kt | 8 -- .../feed/infrastructure/s3/util/FileUtil.kt | 90 ----------------- src/main/resources/application.properties | 1 - .../feed/CasperFeedApplicationTests.kt | 11 --- 117 files changed, 35 insertions(+), 2498 deletions(-) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/CasperFeedApplication.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/CasperFeedApplication.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseEntity.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseTimeEntity.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseUUIDEntity.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/AttachFile.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/repository/AttachFileRepository.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/AttachFileController.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/dto/response/CreateAttachFileResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/service/CreateAttachFileService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/Faq.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/repository/FaqRepository.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/type/FaqType.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/exception/FaqNotFoundException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/FaqController.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/CreateFaqRequest.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/UpdateFaqRequest.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDetailsResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDto.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqListResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleAndTypeResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/CreateFaqService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/DeleteFaqService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqDetailsService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListByTypeService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqTitleAndTypeService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryTopFaqService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/UpdateFaqService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/Notice.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/repository/NoticeRepository.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/type/NoticeType.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/AttachFileNotFoundException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/NoticeNotFoundException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/NoticeController.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/CreateNoticeRequest.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/UpdateNoticeRequest.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/NoticeResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryDetailsNoticeResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryListNoticeResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryNoticeTitleResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/UploadNoticeImageResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/CreateNoticeService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/DeleteNoticeService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryDetailsNoticeService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeListByTypeService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeTitleService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UpdateNoticeService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UploadNoticeImageService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/Question.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/repository/QuestionRepository.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/FeedWriterMismatchException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/QuestionNotFoundException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/QuestionController.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/CreateQuestionRequest.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/UpdateQuestionRequest.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDTO.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDetailsResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionListResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/CreateQuestionService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/DeleteQuestionService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryMyQuestionService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionDetailsService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionListService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/UpdateQuestionService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/Reply.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/repository/ReplyRepository.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyExistsException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyNotFoundException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/ReplyController.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/CreateReplyRequest.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/UpdateReplyRequest.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/response/ReplyDto.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/CreateReplyService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/DeleteReplyService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/UpdateReplyService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/presentation/ReserveController.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/service/GetReserveLinkService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/Screen.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/repository/ScreenRepository.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/exception/ScreenNotFoundException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/ScreenController.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/QueryScreenResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/ScreenResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/CreateScreenService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/QueryScreenService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/UpdateScreenService.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionHandler.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/EquusException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/exception/ExpiredTokenException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InternalServerErrorException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InvalidTokenException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtProperties.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/utils/user/UserUtils.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/UserFeignClient.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/HeaderConfiguration.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignBadRequestException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignForbiddenException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignServerError.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignUnAuthorizedException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaConsumerConfig.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaProperty.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaTopics.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteFaqTableConsumer.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteQuestionTableConsumer.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteReplyTableConsumer.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteUserQuestionConsumer.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/dto/response/DeletedUserInfoResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/PathList.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/BadFileExtensionException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/EmptyFileException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/util/FileUtil.kt delete mode 100644 src/main/resources/application.properties delete mode 100644 src/test/kotlin/hs/kr/entrydsm/feed/CasperFeedApplicationTests.kt diff --git a/build.gradle.kts b/build.gradle.kts index a28cb1c..ab8e23c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,14 +5,11 @@ import kotlin.collections.plus plugins { kotlin("jvm") version "1.9.23" kotlin("plugin.spring") version "1.9.23" - kotlin("plugin.jpa") version "1.9.23" - kotlin("kapt") version PluginVersion.KOTLIN_VERSION id("org.springframework.boot") version "3.4.4" id("io.spring.dependency-management") version "1.1.7" - id("org.jlleitschuh.gradle.ktlint").version("12.1.1") + id("org.jlleitschuh.gradle.ktlint").version("11.5.1") id("io.gitlab.arturbosch.detekt") version "1.23.6" id("casper.documentation-convention") - id("com.google.protobuf") version "0.9.4" } // 서브프로젝트 설정 @@ -43,91 +40,11 @@ group = "hs.kr.entrydsm" version = "0.0.1-SNAPSHOT" dependencies { - // 스프링 부트 기본 기능 - implementation(Dependencies.SPRING_BOOT_STARTER) - - // 코틀린 리플렉션 - implementation(Dependencies.KOTLIN_REFLECT) - - // 스프링 부트 테스트 도구 - testImplementation(Dependencies.SPRING_BOOT_STARTER_TEST) - - // 코틀린 + JUnit5 테스트 - testImplementation(Dependencies.KOTLIN_TEST_JUNIT5) - - // JUnit5 실행 런처 - testRuntimeOnly(Dependencies.JUNIT_PLATFORM_LAUNCHER) - - // 웹 관련 - implementation(Dependencies.SPRING_BOOT_STARTER_WEB) - - // 데이터베이스 - implementation(Dependencies.SPRING_BOOT_STARTER_DATA_JPA) - implementation(Dependencies.SPRING_BOOT_STARTER_DATA_REDIS) - runtimeOnly(Dependencies.MYSQL_CONNECTOR) - - // 보안 - implementation(Dependencies.SPRING_BOOT_STARTER_SECURITY) - - // 검증 - implementation(Dependencies.SPRING_BOOT_STARTER_VALIDATION) - - // JSON 처리 - implementation(Dependencies.JACKSON_MODULE_KOTLIN) - implementation(Dependencies.ORG_JSON) - - // JWT - implementation(Dependencies.JWT_API) - implementation(Dependencies.JWT_IMPL) - runtimeOnly(Dependencies.JWT_JACKSON) - - implementation(Dependencies.MAPSTRUCT) - kapt(Dependencies.MAPSTRUCT_PROCESSOR) - - // grpc - implementation(Dependencies.GRPC_NETTY_SHADED) - implementation(Dependencies.GRPC_PROTOBUF) - implementation(Dependencies.GRPC_STUB) - implementation(Dependencies.GRPC_KOTLIN_STUB) - implementation(Dependencies.PROTOBUF_KOTLIN) - testImplementation(Dependencies.GRPC_TESTING) - implementation("net.devh:grpc-server-spring-boot-starter:2.12.0.RELEASE") - - - // swagger - implementation(Dependencies.SWAGGER) - - // aws - implementation(Dependencies.AWS) - - // feign - implementation(Dependencies.OPEN_FEIGN) - - // Kafka - implementation(Dependencies.KAFKA) -} - -protobuf { - protoc { - artifact = "com.google.protobuf:protoc:${DependencyVersion.PROTOBUF}" - } - plugins { - create("grpc") { - artifact = "io.grpc:protoc-gen-grpc-java:${DependencyVersion.GRPC}" - } - create("grpckt") { - artifact = "io.grpc:protoc-gen-grpc-kotlin:${DependencyVersion.GRPC_KOTLIN}:jdk8@jar" - } - } - generateProtoTasks { - all().forEach { - it.plugins { - create("grpc") - create("grpckt") - } - } - - } + implementation("org.springframework.boot:spring-boot-starter") + implementation("org.jetbrains.kotlin:kotlin-reflect") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } kotlin { @@ -158,3 +75,7 @@ tasks.withType().configureEach { jvmTarget = ("17") // Detekt가 사용하는 JVM 타겟을 Java 17로 지정 } + +tasks.withType { + mainClass.set("hs.kr.entrydsm.feed.CasperFeedApplication") +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/CasperFeedApplication.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/CasperFeedApplication.kt new file mode 100644 index 0000000..dafa47b --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/CasperFeedApplication.kt @@ -0,0 +1,24 @@ +package hs.kr.entrydsm.feed + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.context.properties.ConfigurationPropertiesScan +import org.springframework.boot.runApplication + +/** + * CasperFeed 애플리케이션의 메인 클래스입니다. + * + * 이 클래스는 스프링 부트 애플리케이션을 시작하고 자동 구성을 활성화합니다. + * `@ConfigurationPropertiesScan` 어노테이션을 통해 설정 프로퍼티 클래스들을 스캔합니다. + */ +@SpringBootApplication +@ConfigurationPropertiesScan +class CasperFeedApplication + +/** + * 애플리케이션의 진입점입니다. + * + * @param args 명령행 인자 배열 + */ +fun main(args: Array) { + runApplication(*args) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e455e48..88a27fd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,3 +14,4 @@ dependencyResolutionManagement { } } +include("casper-feed") diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/CasperFeedApplication.kt b/src/main/kotlin/hs/kr/entrydsm/feed/CasperFeedApplication.kt deleted file mode 100644 index 8f4e864..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/CasperFeedApplication.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hs.kr.entrydsm.feed - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.runApplication - -@SpringBootApplication -class CasperFeedApplication - -fun main(args: Array) { - runApplication(*args) -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseEntity.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseEntity.kt deleted file mode 100644 index d470db8..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseEntity.kt +++ /dev/null @@ -1,20 +0,0 @@ -package hs.kr.entrydsm.feed.domain - -import jakarta.persistence.Column -import jakarta.persistence.GeneratedValue -import jakarta.persistence.Id -import jakarta.persistence.MappedSuperclass -import org.hibernate.annotations.GenericGenerator -import java.util.* - -@MappedSuperclass -abstract class BaseEntity( - @Id - @GeneratedValue(generator = "uuid2") - @GenericGenerator(name = "uuid2", strategy = "uuid2") - @Column( - columnDefinition = "BINARY(16)", - nullable = false - ) - val id: UUID? -) : BaseTimeEntity() diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseTimeEntity.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseTimeEntity.kt deleted file mode 100644 index 71b6947..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseTimeEntity.kt +++ /dev/null @@ -1,17 +0,0 @@ -package hs.kr.entrydsm.feed.domain - -import jakarta.persistence.EntityListeners -import jakarta.persistence.MappedSuperclass -import org.springframework.data.annotation.CreatedDate -import org.springframework.data.annotation.LastModifiedDate -import org.springframework.data.jpa.domain.support.AuditingEntityListener -import java.time.LocalDateTime - -@MappedSuperclass -@EntityListeners(AuditingEntityListener::class) -abstract class BaseTimeEntity( - @CreatedDate - val createdAt: LocalDateTime = LocalDateTime.now(), - @LastModifiedDate - val modifiedAt: LocalDateTime = LocalDateTime.now() -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseUUIDEntity.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseUUIDEntity.kt deleted file mode 100644 index ed7d6e8..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/BaseUUIDEntity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package hs.kr.entrydsm.feed.domain - -import jakarta.persistence.Column -import jakarta.persistence.GeneratedValue -import jakarta.persistence.Id -import jakarta.persistence.MappedSuperclass -import org.hibernate.annotations.GenericGenerator -import java.util.* - -@MappedSuperclass -abstract class BaseUUIDEntity( - id: UUID? -) { - @Id - @GeneratedValue(generator = "uuid2") - @GenericGenerator(name = "uuid2", strategy = "uuid2") - @Column(columnDefinition = "BINARY(16)", nullable = false) - val id: UUID? = if (id == UUID(0, 0)) null else id -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/AttachFile.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/AttachFile.kt deleted file mode 100644 index f41a22b..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/AttachFile.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hs.kr.entrydsm.feed.domain.attachFile.domain - -import jakarta.persistence.Entity -import jakarta.persistence.Id - -@Entity(name = "tbl_attach_file") -class AttachFile( - @Id - val uploadedFileName: String, //aws s3에 올라가는 fileName - var originalAttachFileName: String // 인코딩 되기 전 첨부파일 이름 ex): 서프수행.hwp -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/repository/AttachFileRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/repository/AttachFileRepository.kt deleted file mode 100644 index cb44fbd..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/domain/repository/AttachFileRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package hs.kr.entrydsm.feed.domain.attachFile.domain.repository - -import hs.kr.entrydsm.feed.domain.attachFile.domain.AttachFile -import org.springframework.data.jpa.repository.JpaRepository - -interface AttachFileRepository : JpaRepository { - fun findByOriginalAttachFileName(attachFileName: String): List? - - fun deleteByOriginalAttachFileName(attachFileName: String) - - fun existsByOriginalAttachFileName(attachFileName: String): Boolean -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/AttachFileController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/AttachFileController.kt deleted file mode 100644 index f953da6..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/AttachFileController.kt +++ /dev/null @@ -1,19 +0,0 @@ -package hs.kr.entrydsm.feed.domain.attachFile.presentation - -import hs.kr.entrydsm.feed.domain.attachFile.service.CreateAttachFileService -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestPart -import org.springframework.web.bind.annotation.RestController -import org.springframework.web.multipart.MultipartFile - -@RestController -@RequestMapping("/attach-file") -class AttachFileController( - private val createAttachFileService: CreateAttachFileService -) { - @PostMapping - fun createAttachFile( - @RequestPart(value = "attach_file") attachFile: List - ) = createAttachFileService.execute(attachFile) -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/dto/response/CreateAttachFileResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/dto/response/CreateAttachFileResponse.kt deleted file mode 100644 index 66be980..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/presentation/dto/response/CreateAttachFileResponse.kt +++ /dev/null @@ -1,6 +0,0 @@ -package hs.kr.entrydsm.feed.domain.attachFile.presentation.dto.response - -class CreateAttachFileResponse( - val fileName: String, - val url: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/service/CreateAttachFileService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/service/CreateAttachFileService.kt deleted file mode 100644 index 8acd471..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/attachFile/service/CreateAttachFileService.kt +++ /dev/null @@ -1,36 +0,0 @@ -package hs.kr.entrydsm.feed.domain.attachFile.service - -import hs.kr.entrydsm.feed.domain.attachFile.domain.AttachFile -import hs.kr.entrydsm.feed.domain.attachFile.domain.repository.AttachFileRepository -import hs.kr.entrydsm.feed.domain.attachFile.presentation.dto.response.CreateAttachFileResponse -import hs.kr.entrydsm.feed.infrastructure.s3.PathList -import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.multipart.MultipartFile - -@Transactional -@Service -class CreateAttachFileService( - private val attachFileRepository: AttachFileRepository, - private val fileUtil: FileUtil -) { - fun execute(attachFile: List): List { - val attachFileResponses = mutableListOf() - - attachFile.forEach { file -> - if (attachFileRepository.existsByOriginalAttachFileName(file.originalFilename!!)) { - attachFileRepository.deleteByOriginalAttachFileName(file.originalFilename!!) - } - val uploadedFilename = fileUtil.upload(file, PathList.ATTACH_FILE) - val attachFileEntity = AttachFile( - uploadedFileName = uploadedFilename, - originalAttachFileName = file.originalFilename!! - ) - attachFileRepository.save(attachFileEntity) - val url = fileUtil.generateObjectUrl(uploadedFilename, PathList.ATTACH_FILE) - attachFileResponses.add(CreateAttachFileResponse(file.originalFilename!!, url)) - } - return attachFileResponses - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/Faq.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/Faq.kt deleted file mode 100644 index dbb00ef..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/Faq.kt +++ /dev/null @@ -1,33 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.domain - -import hs.kr.entrydsm.feed.domain.BaseEntity -import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.EnumType -import jakarta.persistence.Enumerated -import java.util.UUID - -@Entity(name = "tbl_faq") -class Faq( - id: UUID? = null, - - @Column(name = "title", length = 100, nullable = false) - var title: String, - - @Column(name = "content", length = 5000, nullable = false) - var content: String, - - @Enumerated(EnumType.STRING) - var faqType: FaqType, - - @Column(name = "admin_id", columnDefinition = "BINARY(16)", nullable = false) - var adminId: UUID -) : BaseEntity(id) { - fun updateFaq(title: String, content: String, faqType: FaqType, adminId: UUID) { - this.title = title - this.content = content - this.faqType = faqType - this.adminId = adminId - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/repository/FaqRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/repository/FaqRepository.kt deleted file mode 100644 index 66cc3ab..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/repository/FaqRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.domain.repository - -import hs.kr.entrydsm.feed.domain.faq.domain.Faq -import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType -import org.springframework.data.jpa.repository.JpaRepository -import java.util.UUID - -interface FaqRepository : JpaRepository { - fun findAllByFaqType(faqType: FaqType): List - - fun findTop5ByOrderByCreatedAtDesc(): List -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/type/FaqType.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/type/FaqType.kt deleted file mode 100644 index d54b692..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/domain/type/FaqType.kt +++ /dev/null @@ -1,9 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.domain.type - -enum class FaqType { - ADMISSION, - COURSE, - SCHOOL_LIFE, - DORMITORY, - OTHER -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/exception/FaqNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/exception/FaqNotFoundException.kt deleted file mode 100644 index 1a4fd6b..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/exception/FaqNotFoundException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object FaqNotFoundException : EquusException( - ErrorCode.FAQ_NOT_FOUND -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/FaqController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/FaqController.kt deleted file mode 100644 index cfc691f..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/FaqController.kt +++ /dev/null @@ -1,71 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.presentation - -import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.request.CreateFaqRequest -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.request.UpdateFaqRequest -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqDetailsResponse -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqListResponse -import hs.kr.entrydsm.feed.domain.faq.service.CreateFaqService -import hs.kr.entrydsm.feed.domain.faq.service.DeleteFaqService -import hs.kr.entrydsm.feed.domain.faq.service.QueryFaqDetailsService -import hs.kr.entrydsm.feed.domain.faq.service.QueryFaqListByTypeService -import hs.kr.entrydsm.feed.domain.faq.service.QueryFaqListService -import hs.kr.entrydsm.feed.domain.faq.service.QueryTopFaqService -import hs.kr.entrydsm.feed.domain.faq.service.UpdateFaqService -import org.springframework.http.HttpStatus -import org.springframework.validation.annotation.Validated -import org.springframework.web.bind.annotation.DeleteMapping -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PatchMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.ResponseStatus -import org.springframework.web.bind.annotation.RestController -import java.util.UUID - -@RequestMapping("/faq") -@RestController -class FaqController( - private val createFaqService: CreateFaqService, - private val queryFaqDetailsService: QueryFaqDetailsService, - private val queryFaqListByTypeService: QueryFaqListByTypeService, - private val queryFaqListService: QueryFaqListService, - private val queryTopFaqService: QueryTopFaqService, - private val updateFaqService: UpdateFaqService, - private val deleteFaqService: DeleteFaqService -) { - @ResponseStatus(HttpStatus.CREATED) - @PostMapping - fun createFaq( - @RequestBody @Validated - createFaqRequest: CreateFaqRequest - ) = createFaqService.execute(createFaqRequest) - - @GetMapping("/{faq-id}") - fun queryFaqDetails(@PathVariable("faq-id") faqId: UUID): FaqDetailsResponse = - queryFaqDetailsService.execute(faqId) - - @GetMapping - fun queryFaqListByType(@RequestParam("type") faqType: FaqType): FaqListResponse = - queryFaqListByTypeService.execute(faqType) - - @GetMapping("/all") - fun queryFaqList(): FaqListResponse = queryFaqListService.execute() - - @GetMapping("/recently") - fun queryTopFaq() = queryTopFaqService.execute() - - @PatchMapping("/{faq-id}") - fun updateFaq( - @PathVariable("faq-id") faqId: UUID, - @RequestBody @Validated - updateFaqRequest: UpdateFaqRequest - ) = - updateFaqService.execute(faqId, updateFaqRequest) - - @DeleteMapping("/{faq-id}") - fun deleteFaq(@PathVariable("faq-id") faqId: UUID) = deleteFaqService.execute(faqId) -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/CreateFaqRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/CreateFaqRequest.kt deleted file mode 100644 index cdf5329..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/CreateFaqRequest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.presentation.dto.request - -import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.Size - -data class CreateFaqRequest( - @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") - val title: String, - - @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") - val content: String, - - val faqType: FaqType -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/UpdateFaqRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/UpdateFaqRequest.kt deleted file mode 100644 index 9737030..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/request/UpdateFaqRequest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.presentation.dto.request - -import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.Size - -data class UpdateFaqRequest( - @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") - val title: String, - - @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") - val content: String, - - val faqType: FaqType -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDetailsResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDetailsResponse.kt deleted file mode 100644 index f109f43..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDetailsResponse.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response - -import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType -import java.time.LocalDateTime - -data class FaqDetailsResponse( - val title: String, - val content: String, - val createdAt: LocalDateTime, - val faqType: FaqType -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDto.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDto.kt deleted file mode 100644 index 4e29b4a..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqDto.kt +++ /dev/null @@ -1,13 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response - -import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType -import java.time.LocalDateTime -import java.util.UUID - -data class FaqDto( - val id: UUID, - val title: String, - val content: String, - val createdAt: LocalDateTime, - val faqType: FaqType -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqListResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqListResponse.kt deleted file mode 100644 index eb981a8..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqListResponse.kt +++ /dev/null @@ -1,5 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response - -data class FaqListResponse( - val faqs: List -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleAndTypeResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleAndTypeResponse.kt deleted file mode 100644 index 64ea24c..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleAndTypeResponse.kt +++ /dev/null @@ -1,10 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response - -import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType -import java.util.UUID - -data class FaqTitleAndTypeResponse( - val id: UUID, - val type: FaqType, - val title: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleResponse.kt deleted file mode 100644 index b65b622..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/presentation/dto/response/FaqTitleResponse.kt +++ /dev/null @@ -1,9 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.presentation.dto.response - -import java.util.UUID - -data class FaqTitleResponse( - val id: UUID, - val title: String, - val content: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/CreateFaqService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/CreateFaqService.kt deleted file mode 100644 index 88138f2..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/CreateFaqService.kt +++ /dev/null @@ -1,25 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.service - -import hs.kr.entrydsm.feed.domain.faq.domain.Faq -import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.request.CreateFaqRequest -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class CreateFaqService( - private val faqRepository: FaqRepository, - private val userUtils: UserUtils -) { - @Transactional - fun execute(createFaqRequest: CreateFaqRequest) { - val faq = Faq( - title = createFaqRequest.title, - content = createFaqRequest.content, - faqType = createFaqRequest.faqType, - adminId = userUtils.getCurrentUserId() - ) - faqRepository.save(faq) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/DeleteFaqService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/DeleteFaqService.kt deleted file mode 100644 index 637f798..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/DeleteFaqService.kt +++ /dev/null @@ -1,19 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.service - -import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository -import hs.kr.entrydsm.feed.domain.faq.exception.FaqNotFoundException -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.UUID - -@Service -class DeleteFaqService( - private val faqRepository: FaqRepository -) { - @Transactional - fun execute(faqId: UUID) { - val faq = faqRepository.findByIdOrNull(faqId) ?: throw FaqNotFoundException - faqRepository.delete(faq) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqDetailsService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqDetailsService.kt deleted file mode 100644 index 05f4ebf..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqDetailsService.kt +++ /dev/null @@ -1,25 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.service - -import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository -import hs.kr.entrydsm.feed.domain.faq.exception.FaqNotFoundException -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqDetailsResponse -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.UUID - -@Service -class QueryFaqDetailsService( - private val faqRepository: FaqRepository -) { - @Transactional - fun execute(faqId: UUID): FaqDetailsResponse { - val faq = faqRepository.findByIdOrNull(faqId) ?: throw FaqNotFoundException - return FaqDetailsResponse( - title = faq.title, - content = faq.content, - createdAt = faq.createdAt, - faqType = faq.faqType - ) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListByTypeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListByTypeService.kt deleted file mode 100644 index 3943ace..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListByTypeService.kt +++ /dev/null @@ -1,27 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.service - -import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository -import hs.kr.entrydsm.feed.domain.faq.domain.type.FaqType -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqDto -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqListResponse -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class QueryFaqListByTypeService( - private val faqRepository: FaqRepository -) { - @Transactional - fun execute(faqType: FaqType): FaqListResponse { - val faqs = faqRepository.findAllByFaqType(faqType).map { - FaqDto( - id = it.id!!, - title = it.title, - content = it.content, - createdAt = it.createdAt, - faqType = it.faqType - ) - } - return FaqListResponse(faqs) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListService.kt deleted file mode 100644 index a281263..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqListService.kt +++ /dev/null @@ -1,26 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.service - -import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqDto -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqListResponse -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class QueryFaqListService( - private val faqRepository: FaqRepository -) { - @Transactional - fun execute(): FaqListResponse { - val faqs = faqRepository.findAll().map { - FaqDto( - id = it.id!!, - title = it.title, - content = it.content, - createdAt = it.createdAt, - faqType = it.faqType - ) - } - return FaqListResponse(faqs) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqTitleAndTypeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqTitleAndTypeService.kt deleted file mode 100644 index d3d4c6a..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryFaqTitleAndTypeService.kt +++ /dev/null @@ -1,24 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.service - -import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqTitleAndTypeResponse -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class QueryFaqTitleAndTypeService( - private val faqRepository: FaqRepository -) { - - @Transactional(readOnly = true) - fun execute() = - faqRepository.findAll() - .map { - it -> - FaqTitleAndTypeResponse( - it.id!!, - it.faqType, - it.title - ) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryTopFaqService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryTopFaqService.kt deleted file mode 100644 index f370b31..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/QueryTopFaqService.kt +++ /dev/null @@ -1,23 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.service - -import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.response.FaqTitleResponse -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class QueryTopFaqService( - private val faqRepository: FaqRepository -) { - - @Transactional(readOnly = true) - fun execute(): List = - faqRepository.findTop5ByOrderByCreatedAtDesc() - .map { - FaqTitleResponse( - it.id!!, - it.title, - it.content - ) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/UpdateFaqService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/UpdateFaqService.kt deleted file mode 100644 index 69ae2a7..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/faq/service/UpdateFaqService.kt +++ /dev/null @@ -1,29 +0,0 @@ -package hs.kr.entrydsm.feed.domain.faq.service - -import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository -import hs.kr.entrydsm.feed.domain.faq.exception.FaqNotFoundException -import hs.kr.entrydsm.feed.domain.faq.presentation.dto.request.UpdateFaqRequest -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.UUID - -@Service -class UpdateFaqService( - private val faqRepository: FaqRepository, - private val userUtils: UserUtils -) { - @Transactional - fun execute(faqId: UUID, updateFaqRequest: UpdateFaqRequest) { - val faq = faqRepository.findByIdOrNull(faqId) ?: throw FaqNotFoundException - updateFaqRequest.run { - faq.updateFaq( - title = title, - content = content, - faqType = faqType, - adminId = userUtils.getCurrentUserId() - ) - } - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/Notice.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/Notice.kt deleted file mode 100644 index 877ba6c..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/Notice.kt +++ /dev/null @@ -1,60 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.domain - -import hs.kr.entrydsm.feed.domain.BaseEntity -import hs.kr.entrydsm.feed.domain.attachFile.domain.AttachFile -import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.EnumType -import jakarta.persistence.Enumerated -import jakarta.persistence.FetchType -import jakarta.persistence.JoinColumn -import jakarta.persistence.OneToMany -import java.util.* - -@Entity(name = "tbl_notice") -class Notice( - id: UUID? = null, - - @Column(name = "title", length = 100, nullable = false) - var title: String, - - @Column(name = "content", length = 5000, nullable = false) - var content: String, - - @Column(name = "file_name", nullable = true) - var fileName: String? = null, - - @OneToMany(fetch = FetchType.LAZY) - @JoinColumn(name = "noticeId") - var attachFile: List? = emptyList(), - - @Column(name = "admin_id", nullable = false, columnDefinition = "BINARY(16)") - var adminId: UUID, - - @Column(nullable = false) - var isPinned: Boolean, - - @Column(nullable = false) - @Enumerated(value = EnumType.STRING) - var type: NoticeType - -) : BaseEntity(id) { - fun modifyNotice( - title: String, - content: String, - isPinned: Boolean, - adminId: UUID, - fileName: String?, - type: NoticeType, - attachFile: List? - ) { - this.title = title - this.content = content - this.type = type - this.isPinned = isPinned - this.adminId = adminId - this.fileName = fileName - this.attachFile = attachFile - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/repository/NoticeRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/repository/NoticeRepository.kt deleted file mode 100644 index a27cc13..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/repository/NoticeRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.domain.repository - -import hs.kr.entrydsm.feed.domain.notice.domain.Notice -import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType -import org.springframework.data.jpa.repository.JpaRepository -import java.util.UUID - -interface NoticeRepository : JpaRepository { - fun findAllByType(type: NoticeType): List -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/type/NoticeType.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/type/NoticeType.kt deleted file mode 100644 index a57d0d2..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/domain/type/NoticeType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.domain.type - -enum class NoticeType { - GUIDE, NOTICE -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/AttachFileNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/AttachFileNotFoundException.kt deleted file mode 100644 index f5a1629..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/AttachFileNotFoundException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object AttachFileNotFoundException : EquusException( - ErrorCode.ATTACH_FILE_NOT_FOUND -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/NoticeNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/NoticeNotFoundException.kt deleted file mode 100644 index 4a2cf34..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/exception/NoticeNotFoundException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object NoticeNotFoundException : EquusException( - ErrorCode.NOTICE_NOT_FOUND -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/NoticeController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/NoticeController.kt deleted file mode 100644 index 9e29e25..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/NoticeController.kt +++ /dev/null @@ -1,79 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.presentation - -import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.request.CreateNoticeRequest -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryListNoticeResponse -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.request.UpdateNoticeRequest -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryDetailsNoticeResponse -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryNoticeTitleResponse -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.UploadNoticeImageResponse -import hs.kr.entrydsm.feed.domain.notice.service.CreateNoticeService -import hs.kr.entrydsm.feed.domain.notice.service.DeleteNoticeService -import hs.kr.entrydsm.feed.domain.notice.service.QueryDetailsNoticeService -import hs.kr.entrydsm.feed.domain.notice.service.QueryNoticeListByTypeService -import hs.kr.entrydsm.feed.domain.notice.service.QueryNoticeTitleService -import hs.kr.entrydsm.feed.domain.notice.service.UpdateNoticeService -import hs.kr.entrydsm.feed.domain.notice.service.UploadNoticeImageService -import jakarta.validation.Valid -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* -import org.springframework.web.multipart.MultipartFile -import java.util.UUID - -@RestController -@RequestMapping("/notice") -class NoticeController( - private val createNoticeService: CreateNoticeService, - private val uploadNoticeImageService: UploadNoticeImageService, - private val updateNoticeService: UpdateNoticeService, - private val queryNoticeTitleService: QueryNoticeTitleService, - private val queryNoticeListByTypeService: QueryNoticeListByTypeService, - private val queryDetailsNoticeService: QueryDetailsNoticeService, - private val deleteNoticeService: DeleteNoticeService -) { - - @ResponseStatus(value = HttpStatus.CREATED) - @PostMapping - fun createNotice( - @RequestBody @Valid - createNoticeRequest: CreateNoticeRequest - ) { - createNoticeService.execute(createNoticeRequest) - } - - @PatchMapping("/{notice-id}") - fun modifyNotice( - @PathVariable(name = "notice-id") id: UUID, - @RequestBody updateNoticeRequest: UpdateNoticeRequest - ): ResponseEntity = - updateNoticeService.execute(id, updateNoticeRequest) - - @PostMapping("/image") - fun uploadImage( - @RequestPart(name = "photo") image: MultipartFile - ): UploadNoticeImageResponse = - uploadNoticeImageService.execute(image) - - @GetMapping("/title") - fun queryTitle(): List = queryNoticeTitleService.execute() - - @GetMapping("/{notice-id}") - fun getNotice( - @PathVariable(name = "notice-id", required = true) - noticeId: UUID - ): QueryDetailsNoticeResponse = queryDetailsNoticeService.execute(noticeId) - - @GetMapping - fun getNoticeListByType( - @RequestParam("type") type: NoticeType? - ): QueryListNoticeResponse = - queryNoticeListByTypeService.execute(type) - - @ResponseStatus(value = HttpStatus.NO_CONTENT) - @DeleteMapping("/{notice-id}") - fun deleteNotice( - @PathVariable(name = "notice-id")id: UUID - ) = - deleteNoticeService.execute(id) -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/CreateNoticeRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/CreateNoticeRequest.kt deleted file mode 100644 index d128fd6..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/CreateNoticeRequest.kt +++ /dev/null @@ -1,27 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.presentation.dto.request - -import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.NotNull -import jakarta.validation.constraints.Size - -data class CreateNoticeRequest( - - @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @Size(max = 100, message = "title은 최대 100자까지 가능합니다.") - val title: String, - - @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @Size(max = 5000, message = "content은 최대 5000자까지 가능합니다.") - val content: String, - - @field:NotNull(message = "Pinned는 null일수가 없습니다") - val isPinned: Boolean, - - @field:NotNull(message = "type은 null일수가 없습니다") - val type: NoticeType, - - val fileName: String? = null, - - val attachFileName: List? = null -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/UpdateNoticeRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/UpdateNoticeRequest.kt deleted file mode 100644 index e9dc961..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/request/UpdateNoticeRequest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.presentation.dto.request - -import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.NotNull -import jakarta.validation.constraints.Size - -data class UpdateNoticeRequest( - @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @Size(max = 100, message = "title은 최대 100자까지 가능합니다.") - val title: String, - - @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @Size(max = 5000, message = "content은 최대 5000자까지 가능합니다.") - val content: String, - - @field:NotNull(message = "Pinned은 null일수가 없습니다") - val isPinned: Boolean, - - @field:NotNull(message = "type은 null일수가 없습니다") - val type: NoticeType, - - val fileName: String? = null, - - val attachFileName: List? = emptyList() -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/NoticeResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/NoticeResponse.kt deleted file mode 100644 index 8f8e764..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/NoticeResponse.kt +++ /dev/null @@ -1,13 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response - -import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType -import java.time.LocalDateTime -import java.util.* - -data class NoticeResponse( - val id: UUID, - val title: String, - val type: NoticeType, - val isPinned: Boolean, - val createdAt: LocalDateTime -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryDetailsNoticeResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryDetailsNoticeResponse.kt deleted file mode 100644 index 236f8bf..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryDetailsNoticeResponse.kt +++ /dev/null @@ -1,20 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response - -import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType -import java.time.LocalDateTime - -data class QueryDetailsNoticeResponse( - val title: String, - val content: String, - val createdAt: LocalDateTime, - val type: NoticeType, - val imageURL: String?, - val imageName: String?, - val attachFiles: List = emptyList(), - val isPinned: Boolean -) - -data class AttachFile( - val attachFileUrl: String, - val attachFileName: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryListNoticeResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryListNoticeResponse.kt deleted file mode 100644 index e153698..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryListNoticeResponse.kt +++ /dev/null @@ -1,5 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response - -data class QueryListNoticeResponse( - val notices: List -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryNoticeTitleResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryNoticeTitleResponse.kt deleted file mode 100644 index 9eac953..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/QueryNoticeTitleResponse.kt +++ /dev/null @@ -1,10 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response - -import java.time.LocalDateTime -import java.util.UUID - -data class QueryNoticeTitleResponse( - val id: UUID, - val title: String, - val createdAt: LocalDateTime -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/UploadNoticeImageResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/UploadNoticeImageResponse.kt deleted file mode 100644 index ac951c6..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/presentation/dto/response/UploadNoticeImageResponse.kt +++ /dev/null @@ -1,6 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.presentation.dto.response - -data class UploadNoticeImageResponse( - val fileUrl: String, - val fileName: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/CreateNoticeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/CreateNoticeService.kt deleted file mode 100644 index 6101731..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/CreateNoticeService.kt +++ /dev/null @@ -1,43 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.service - -import hs.kr.entrydsm.feed.domain.notice.domain.Notice -import hs.kr.entrydsm.feed.domain.attachFile.domain.repository.AttachFileRepository -import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository -import hs.kr.entrydsm.feed.domain.notice.exception.AttachFileNotFoundException -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.request.CreateNoticeRequest -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class CreateNoticeService( - private val noticeRepository: NoticeRepository, - private val attachFileRepository: AttachFileRepository, - private val adminUtils: UserUtils -) { - - @Transactional - fun execute( - request: CreateNoticeRequest - ) { - val admin = adminUtils.getCurrentUserId() - val attachFiles = request.attachFileName?.let { fileNames -> - fileNames.flatMap { fileName -> - val files = attachFileRepository.findByOriginalAttachFileName(fileName) - files ?: throw AttachFileNotFoundException - } - } ?: emptyList() - - noticeRepository.save( - Notice( - title = request.title, - content = request.content, - type = request.type, - isPinned = request.isPinned, - adminId = admin, - fileName = request.fileName, - attachFile = attachFiles - ) - ) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/DeleteNoticeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/DeleteNoticeService.kt deleted file mode 100644 index 2940634..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/DeleteNoticeService.kt +++ /dev/null @@ -1,19 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.service - -import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository -import hs.kr.entrydsm.feed.domain.notice.exception.NoticeNotFoundException -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.UUID - -@Service -class DeleteNoticeService( - private val noticeRepository: NoticeRepository -) { - @Transactional - fun execute(noticeId: UUID) { - val notice = noticeRepository.findByIdOrNull(noticeId) ?: throw NoticeNotFoundException - noticeRepository.deleteById(notice.id!!) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryDetailsNoticeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryDetailsNoticeService.kt deleted file mode 100644 index 8d99868..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryDetailsNoticeService.kt +++ /dev/null @@ -1,48 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.service - -import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository -import hs.kr.entrydsm.feed.domain.notice.exception.NoticeNotFoundException -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.AttachFile -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryDetailsNoticeResponse -import hs.kr.entrydsm.feed.infrastructure.s3.PathList -import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.* - -@Transactional(readOnly = true) -@Service -class QueryDetailsNoticeService( - private val noticeRepository: NoticeRepository, - private val fileUtil: FileUtil -) { - fun execute(noticeId: UUID): QueryDetailsNoticeResponse { - val notice = noticeRepository.findByIdOrNull(noticeId) ?: throw NoticeNotFoundException - val imageURL = notice.fileName?.let { getUrl(it, PathList.NOTICE) } - - val attachFile = - notice.attachFile?.map { - // 첨부파일 이름과 url을 묶어서 여러개 반환하기때문에 List로 묶는다 - AttachFile( - attachFileUrl = getUrl(it.uploadedFileName, PathList.ATTACH_FILE), - attachFileName = it.originalAttachFileName - ) - } - - return notice.run { - QueryDetailsNoticeResponse( - title = title, - content = content, - createdAt = createdAt, - type = type, - imageURL = imageURL, - imageName = fileName, - attachFiles = attachFile!!, - isPinned = isPinned - ) - } - } - - private fun getUrl(file: String, path: String) = fileUtil.generateObjectUrl(file, path) -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeListByTypeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeListByTypeService.kt deleted file mode 100644 index 4b9d967..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeListByTypeService.kt +++ /dev/null @@ -1,37 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.service - -import hs.kr.entrydsm.feed.domain.notice.domain.Notice -import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository -import hs.kr.entrydsm.feed.domain.notice.domain.type.NoticeType -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.NoticeResponse -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryListNoticeResponse -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class QueryNoticeListByTypeService( - private val noticeRepository: NoticeRepository -) { - - @Transactional(readOnly = true) - fun execute(type: NoticeType?): QueryListNoticeResponse { - val notices = getNoticeList(type).map { it -> - NoticeResponse( - id = it.id!!, - title = it.title, - isPinned = it.isPinned, - type = it.type, - createdAt = it.createdAt - ) - }.sortedWith( - compareByDescending { it.isPinned } - .thenByDescending { it.createdAt } - ) - - return QueryListNoticeResponse(notices) - } - - private fun getNoticeList(type: NoticeType?): List { - return type?.let { noticeRepository.findAllByType(it) } ?: noticeRepository.findAll() - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeTitleService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeTitleService.kt deleted file mode 100644 index 998e1ff..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/QueryNoticeTitleService.kt +++ /dev/null @@ -1,24 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.service - -import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.QueryNoticeTitleResponse -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class QueryNoticeTitleService( - private val noticeRepository: NoticeRepository -) { - - @Transactional(readOnly = true) - fun execute(): List = - noticeRepository.findAll() - .map { - it -> - QueryNoticeTitleResponse( - id = it.id!!, - title = it.title, - it.createdAt - ) - }.sortedByDescending { it.createdAt } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UpdateNoticeService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UpdateNoticeService.kt deleted file mode 100644 index 5cb79f9..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UpdateNoticeService.kt +++ /dev/null @@ -1,57 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.service - -import hs.kr.entrydsm.feed.domain.attachFile.domain.AttachFile -import hs.kr.entrydsm.feed.domain.attachFile.domain.repository.AttachFileRepository -import hs.kr.entrydsm.feed.domain.notice.domain.repository.NoticeRepository -import hs.kr.entrydsm.feed.domain.notice.exception.AttachFileNotFoundException -import hs.kr.entrydsm.feed.domain.notice.exception.NoticeNotFoundException -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.request.UpdateNoticeRequest -import hs.kr.entrydsm.feed.global.utils.admin.AdminUtils -import hs.kr.entrydsm.feed.infrastructure.s3.PathList -import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil -import org.springframework.data.repository.findByIdOrNull -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.UUID - -@Service -class UpdateNoticeService( - private val noticeRepository: NoticeRepository, - private val adminUtils: AdminUtils, - private val fileUtil: FileUtil, - private val attachFileRepository: AttachFileRepository -) { - @Transactional - fun execute(id: UUID, request: UpdateNoticeRequest): ResponseEntity { - val adminId = adminUtils.getCurrentAdminId() - - val notice = noticeRepository.findByIdOrNull(id) ?: throw NoticeNotFoundException - val fileName = request.fileName - val attachFiles = findAttachFiles(request.attachFileName) - - request.run { - notice.modifyNotice( - title = title, - content = content, - isPinned = isPinned, - type = type, - fileName = fileName, - adminId = adminId, - attachFile = attachFiles - ) - } - - return fileName?.let { - ResponseEntity.ok(fileUtil.generateObjectUrl(it, PathList.NOTICE)) - } ?: ResponseEntity(HttpStatus.NO_CONTENT) - } - - private fun findAttachFiles(fileNameList: List?): List { - return fileNameList?.flatMap { fileName -> - val fileList = attachFileRepository.findByOriginalAttachFileName(fileName) - fileList ?: throw AttachFileNotFoundException - } ?: emptyList() - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UploadNoticeImageService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UploadNoticeImageService.kt deleted file mode 100644 index adbe8a1..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/notice/service/UploadNoticeImageService.kt +++ /dev/null @@ -1,17 +0,0 @@ -package hs.kr.entrydsm.feed.domain.notice.service - -import hs.kr.entrydsm.feed.domain.notice.presentation.dto.response.UploadNoticeImageResponse -import hs.kr.entrydsm.feed.infrastructure.s3.PathList -import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil -import org.springframework.stereotype.Service -import org.springframework.web.multipart.MultipartFile - -@Service -class UploadNoticeImageService( - private val fileUtil: FileUtil -) { - fun execute(image: MultipartFile): UploadNoticeImageResponse { - val fileName = fileUtil.upload(image, PathList.NOTICE) - return UploadNoticeImageResponse(fileUtil.generateObjectUrl(fileName, PathList.NOTICE), fileName) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/Question.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/Question.kt deleted file mode 100644 index bbf4cf3..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/Question.kt +++ /dev/null @@ -1,45 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.domain - -import hs.kr.entrydsm.feed.domain.BaseEntity -import hs.kr.entrydsm.feed.domain.question.exception.FeedWriterMismatchException -import jakarta.persistence.Column -import jakarta.persistence.Entity -import java.util.UUID - -@Entity(name = "tbl_question") -class Question( - id: UUID? = null, - - @Column(name = "title", columnDefinition = "varchar(100)", nullable = false) - var title: String, - - @Column(name = "content", columnDefinition = "varchar(1000)", nullable = false) - var content: String, - - @Column(name = "is_public", columnDefinition = "BIT(1) default 0", nullable = false) - var isPublic: Boolean, - - @Column(name = "is_replied", columnDefinition = "BIT(1) default 0", nullable = false) - var isReplied: Boolean, - - @Column(name = "user_id", columnDefinition = "BINARY(16)", nullable = false) - val userId: UUID -) : BaseEntity(id) { - fun validateWriter(userid: UUID) { - if (this.userId != userid) { - throw FeedWriterMismatchException - } - } - - fun updateQuestion(userId: UUID, content: String, title: String, isPublic: Boolean) { - validateWriter(userId) - - this.title = content - this.content = title - this.isPublic = isPublic - } - - fun updateIsReplied(isReplied: Boolean) { - this.isReplied = isReplied - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/repository/QuestionRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/repository/QuestionRepository.kt deleted file mode 100644 index f11bc9d..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/domain/repository/QuestionRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.domain.repository - -import hs.kr.entrydsm.feed.domain.question.domain.Question -import org.springframework.data.domain.Pageable -import org.springframework.data.jpa.repository.JpaRepository -import java.util.UUID - -interface QuestionRepository : JpaRepository { - fun findAllByUserId(userId: UUID): List - fun deleteAllByUserId(userId: UUID) - - fun findAllByOrderByCreatedAtDesc(pageable: Pageable): List -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/FeedWriterMismatchException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/FeedWriterMismatchException.kt deleted file mode 100644 index 33d8561..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/FeedWriterMismatchException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object FeedWriterMismatchException : EquusException( - ErrorCode.FEED_WRITER_MISMATCH -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/QuestionNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/QuestionNotFoundException.kt deleted file mode 100644 index dc8ea59..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/QuestionNotFoundException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object QuestionNotFoundException : EquusException( - ErrorCode.QUESTION_NOT_FOUND -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/QuestionController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/QuestionController.kt deleted file mode 100644 index 3d40bc1..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/QuestionController.kt +++ /dev/null @@ -1,71 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.presentation - -import hs.kr.entrydsm.feed.domain.question.presentation.dto.request.CreateQuestionRequest -import hs.kr.entrydsm.feed.domain.question.presentation.dto.request.UpdateQuestionRequest -import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionDetailsResponse -import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionListResponse -import hs.kr.entrydsm.feed.domain.question.service.CreateQuestionService -import hs.kr.entrydsm.feed.domain.question.service.QueryMyQuestionService -import hs.kr.entrydsm.feed.domain.question.service.QueryQuestionDetailsService -import hs.kr.entrydsm.feed.domain.question.service.QueryQuestionListService -import hs.kr.entrydsm.feed.domain.question.service.UpdateQuestionService -import hs.kr.entrydsm.feed.domain.question.service.DeleteQuestionService -import org.springframework.data.domain.Pageable -import org.springframework.data.web.PageableDefault -import org.springframework.http.HttpStatus -import org.springframework.validation.annotation.Validated -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import org.springframework.web.bind.annotation.ResponseStatus -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.DeleteMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PatchMapping -import java.util.UUID - -@RequestMapping("/question") -@RestController -class QuestionController( - private val createQuestionService: CreateQuestionService, - private val queryQuestionListService: QueryQuestionListService, - private val queryQuestionDetailsService: QueryQuestionDetailsService, - private val updateQuestionService: UpdateQuestionService, - private val queryMyQuestionService: QueryMyQuestionService, - private val deleteQuestionService: DeleteQuestionService -) { - @ResponseStatus(HttpStatus.CREATED) - @PostMapping - fun createQuestion( - @RequestBody @Validated - createQuestionRequest: CreateQuestionRequest - ) { - createQuestionService.execute(createQuestionRequest) - } - - @GetMapping("/all") - fun getQuestionList( - @PageableDefault(size = 10) pageable: Pageable - ): QuestionListResponse = - queryQuestionListService.execute(pageable) - - @GetMapping("/{questionId}") - fun getQuestionDetails(@PathVariable("questionId") questionId: UUID): QuestionDetailsResponse = - queryQuestionDetailsService.execute(questionId) - - @ResponseStatus(HttpStatus.NO_CONTENT) - @PatchMapping("/{questionId}") - fun updateQuestion( - @PathVariable questionId: UUID, - @Validated @RequestBody - updateQuestionRequest: UpdateQuestionRequest - ) = updateQuestionService.execute(questionId, updateQuestionRequest) - - @GetMapping - fun getMyQuestionList(): QuestionListResponse = queryMyQuestionService.execute() - - @ResponseStatus(HttpStatus.NO_CONTENT) - @DeleteMapping("/{questionId}") - fun deleteQuestion(@PathVariable("questionId") questionId: UUID) = deleteQuestionService.execute(questionId) -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/CreateQuestionRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/CreateQuestionRequest.kt deleted file mode 100644 index 7561222..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/CreateQuestionRequest.kt +++ /dev/null @@ -1,18 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.presentation.dto.request - -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.NotNull -import jakarta.validation.constraints.Size - -data class CreateQuestionRequest( - @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") - val title: String, - - @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") - val content: String, - - @NotNull - val isPublic: Boolean -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/UpdateQuestionRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/UpdateQuestionRequest.kt deleted file mode 100644 index a6f9dc3..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/request/UpdateQuestionRequest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.presentation.dto.request - -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.NotNull -import jakarta.validation.constraints.Size - - -data class UpdateQuestionRequest( - @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") - val title: String, - - @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") - val content: String, - - @field:NotNull - val isPublic: Boolean -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDTO.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDTO.kt deleted file mode 100644 index 80eef43..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDTO.kt +++ /dev/null @@ -1,13 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.presentation.dto.response - -import java.time.LocalDateTime -import java.util.UUID - -data class QuestionDTO( - val id: UUID, - val title: String, - val createdAt: LocalDateTime, - val username: String, - val isReplied: Boolean, - val isPublic: Boolean -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDetailsResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDetailsResponse.kt deleted file mode 100644 index 15d2390..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionDetailsResponse.kt +++ /dev/null @@ -1,17 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.presentation.dto.response - -import hs.kr.entrydsm.feed.domain.reply.presentation.dto.response.ReplyDto -import java.time.LocalDateTime -import java.util.UUID - -data class QuestionDetailsResponse( - val id: UUID, - val title: String, - val content: String, - val username: String, - val isReplied: Boolean, - val isMine: Boolean, - val isPublic: Boolean, - val createdAt: LocalDateTime, - val reply: ReplyDto? -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionListResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionListResponse.kt deleted file mode 100644 index bdd843a..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/presentation/dto/response/QuestionListResponse.kt +++ /dev/null @@ -1,5 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.presentation.dto.response - -data class QuestionListResponse( - val questions: List -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/CreateQuestionService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/CreateQuestionService.kt deleted file mode 100644 index 832e6f7..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/CreateQuestionService.kt +++ /dev/null @@ -1,26 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.service - -import hs.kr.entrydsm.feed.domain.question.domain.Question -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.domain.question.presentation.dto.request.CreateQuestionRequest -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class CreateQuestionService( - private val questionRepository: QuestionRepository, - private val userUtils: UserUtils -) { - @Transactional - fun execute(createQuestionRequest: CreateQuestionRequest) { - val question = Question( - title = createQuestionRequest.title, - content = createQuestionRequest.content, - isPublic = createQuestionRequest.isPublic, - isReplied = false, - userId = userUtils.getCurrentUserId() - ) - questionRepository.save(question) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/DeleteQuestionService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/DeleteQuestionService.kt deleted file mode 100644 index 1321332..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/DeleteQuestionService.kt +++ /dev/null @@ -1,24 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.service - -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.UUID - -@Service -class DeleteQuestionService( - private val userUtils: UserUtils, - private val questionRepository: QuestionRepository -) { - @Transactional - fun execute(questionId: UUID) { - val question = questionRepository.findByIdOrNull(questionId) ?: throw QuestionNotFoundException - val userId = userUtils.getCurrentUserId() - - question.validateWriter(userId) - questionRepository.delete(question) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryMyQuestionService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryMyQuestionService.kt deleted file mode 100644 index 8d03590..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryMyQuestionService.kt +++ /dev/null @@ -1,32 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.service - -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionDTO -import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionListResponse -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class QueryMyQuestionService( - private val userUtils: UserUtils, - private val questionRepository: QuestionRepository, - private val userFeignClient: UserFeignClient -) { - @Transactional(readOnly = true) - fun execute(): QuestionListResponse { - val userId = userUtils.getCurrentUserId() - val questionList = questionRepository.findAllByUserId(userId).map { it -> - QuestionDTO( - id = it.id!!, - title = it.title, - createdAt = it.createdAt, - username = userFeignClient.getUserByUUID(it.userId).name, - isReplied = it.isReplied, - isPublic = it.isPublic - ) - } - return QuestionListResponse(questionList) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionDetailsService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionDetailsService.kt deleted file mode 100644 index 912801e..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionDetailsService.kt +++ /dev/null @@ -1,71 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.service - -import hs.kr.entrydsm.feed.domain.question.domain.Question -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.domain.question.exception.AccessDeniedQuestionException -import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException -import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionDetailsResponse -import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository -import hs.kr.entrydsm.feed.domain.reply.presentation.dto.response.ReplyDto -import hs.kr.entrydsm.feed.global.security.jwt.UserRole -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient -import hs.kr.equus.feed.infrastructure.feign.client.user.model.User -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.* - -@Service -class QueryQuestionDetailsService( - private val questionRepository: QuestionRepository, - private val replyRepository: ReplyRepository, - private val userUtils: UserUtils, - private val userFeignClient: UserFeignClient -) { - @Transactional(readOnly = true) - fun execute(questionId: UUID): QuestionDetailsResponse { - val question = questionRepository.findByIdOrNull(questionId) ?: throw QuestionNotFoundException - val user = userUtils.getCurrentUser() - - validateAccessPermission(question, user) - - return createResponse(question, user) - } - - private fun validateAccessPermission(question: Question, user: User) { - if (!question.isPublic) { - if (question.userId != user.id) { - throw AccessDeniedQuestionException - } - if (user.role.toString() != "ROLE_${UserRole.ADMIN}" && question.userId != user.id) { - throw AccessDeniedQuestionException - } - } - } - - private fun createResponse(question: Question, user: User): QuestionDetailsResponse { - return QuestionDetailsResponse( - id = question.id!!, - title = question.title, - content = question.content, - username = userFeignClient.getUserByUUID(question.userId).name, - isReplied = question.isReplied, - isMine = (question.userId == user.id), - isPublic = question.isPublic, - createdAt = question.createdAt, - reply = getReplyDto(question.id) - ) - } - - private fun getReplyDto(questionId: UUID): ReplyDto? { - val reply = replyRepository.findByQuestionId(questionId) - return reply?.run { - ReplyDto( - title = title, - content = content, - createdAt = createdAt - ) - } - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionListService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionListService.kt deleted file mode 100644 index c3dd1ab..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/QueryQuestionListService.kt +++ /dev/null @@ -1,30 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.service - -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionDTO -import hs.kr.entrydsm.feed.domain.question.presentation.dto.response.QuestionListResponse -import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient -import org.springframework.data.domain.Pageable -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class QueryQuestionListService( - private val questionRepository: QuestionRepository, - private val userFeignClient: UserFeignClient -) { - @Transactional(readOnly = true) - fun execute(pageable: Pageable): QuestionListResponse { - val questions = questionRepository.findAllByOrderByCreatedAtDesc(pageable).map { it -> - QuestionDTO( - id = it.id!!, - title = it.title, - createdAt = it.createdAt, - username = userFeignClient.getUserByUUID(it.userId).name, - isReplied = it.isReplied, - isPublic = it.isPublic - ) - } - return QuestionListResponse(questions = questions) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/UpdateQuestionService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/UpdateQuestionService.kt deleted file mode 100644 index 664bded..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/service/UpdateQuestionService.kt +++ /dev/null @@ -1,31 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.service - -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException -import hs.kr.entrydsm.feed.domain.question.presentation.dto.request.UpdateQuestionRequest -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.UUID - -@Service -class UpdateQuestionService( - private val questionRepository: QuestionRepository, - private val userUtils: UserUtils -) { - @Transactional - fun execute(questionId: UUID, updateQuestionRequest: UpdateQuestionRequest) { - val userId = userUtils.getCurrentUserId() - val question = questionRepository.findByIdOrNull(questionId) ?: throw QuestionNotFoundException - - updateQuestionRequest.run { - question.updateQuestion( - userId = userId, - title = title, - content = content, - isPublic = isPublic - ) - } - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/Reply.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/Reply.kt deleted file mode 100644 index 1517e25..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/Reply.kt +++ /dev/null @@ -1,28 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.domain - -import hs.kr.entrydsm.feed.domain.BaseEntity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import java.util.* - -@Entity(name = "tbl_reply") -class Reply( - id: UUID? = null, - - @Column(name = "title", length = 150, nullable = false) - var title: String, - - @Column(name = "content", length = 5000, nullable = false) - var content: String, - - @Column(name = "question_id", columnDefinition = "BINARY(16)", nullable = false) - val questionId: UUID, - - @Column(name = "admin_id", columnDefinition = "BINARY(16)", nullable = false) - val adminId: UUID -) : BaseEntity(id) { - fun updateReply(title: String, content: String) { - this.title = title - this.content = content - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/repository/ReplyRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/repository/ReplyRepository.kt deleted file mode 100644 index 9753948..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/domain/repository/ReplyRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.domain.repository - -import hs.kr.entrydsm.feed.domain.reply.domain.Reply -import org.springframework.data.jpa.repository.JpaRepository -import java.util.UUID - -interface ReplyRepository : JpaRepository { - fun findByQuestionId(questionId: UUID): Reply? -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyExistsException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyExistsException.kt deleted file mode 100644 index 6bfc6b2..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyExistsException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object ReplyExistsException : EquusException( - ErrorCode.REPLY_EXISTS -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyNotFoundException.kt deleted file mode 100644 index 1215ac4..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/ReplyNotFoundException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object ReplyNotFoundException : EquusException( - ErrorCode.REPLY_NOT_FOUND -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/ReplyController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/ReplyController.kt deleted file mode 100644 index 8ff31ec..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/ReplyController.kt +++ /dev/null @@ -1,48 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.presentation - -import hs.kr.entrydsm.feed.domain.reply.presentation.dto.request.CreateReplyRequest -import hs.kr.entrydsm.feed.domain.reply.presentation.dto.request.UpdateReplyRequest -import hs.kr.entrydsm.feed.domain.reply.service.CreateReplyService -import hs.kr.entrydsm.feed.domain.reply.service.DeleteReplyService -import hs.kr.entrydsm.feed.domain.reply.service.UpdateReplyService -import org.springframework.http.HttpStatus -import org.springframework.validation.annotation.Validated -import org.springframework.web.bind.annotation.DeleteMapping -import org.springframework.web.bind.annotation.PatchMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.ResponseStatus -import org.springframework.web.bind.annotation.RestController -import java.util.UUID - -@RequestMapping("/reply") -@RestController -class ReplyController( - private val createReplyService: CreateReplyService, - private val updateReplyService: UpdateReplyService, - private val deleteReplyService: DeleteReplyService -) { - @ResponseStatus(HttpStatus.CREATED) - @PostMapping("/{questionId}") - fun createReply( - @RequestBody @Validated - createReplyRequest: CreateReplyRequest, - @PathVariable("questionId") questionId: UUID - ) = createReplyService.execute(createReplyRequest, questionId) - - @ResponseStatus(HttpStatus.NO_CONTENT) - @PatchMapping("/{replyId}") - fun updateReply( - @RequestBody @Validated - updateReplyRequest: UpdateReplyRequest, - @PathVariable("replyId") replyId: UUID - ) = updateReplyService.execute(updateReplyRequest, replyId) - - @ResponseStatus(HttpStatus.NO_CONTENT) - @DeleteMapping("/{replyId}") - fun deleteReply( - @PathVariable("replyId") replyId: UUID - ) = deleteReplyService.execute(replyId) -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/CreateReplyRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/CreateReplyRequest.kt deleted file mode 100644 index 56df969..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/CreateReplyRequest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.presentation.dto.request - -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.Size - - -data class CreateReplyRequest( - @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") - val title: String, - - @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") - val content: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/UpdateReplyRequest.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/UpdateReplyRequest.kt deleted file mode 100644 index f78d6e9..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/request/UpdateReplyRequest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.presentation.dto.request - -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.Size - - -data class UpdateReplyRequest( - @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.") - val title: String, - - @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") - @field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.") - val content: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/response/ReplyDto.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/response/ReplyDto.kt deleted file mode 100644 index 38837c0..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/presentation/dto/response/ReplyDto.kt +++ /dev/null @@ -1,9 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.presentation.dto.response - -import java.time.LocalDateTime - -data class ReplyDto( - val title: String, - val content: String, - val createdAt: LocalDateTime -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/CreateReplyService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/CreateReplyService.kt deleted file mode 100644 index f1207ff..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/CreateReplyService.kt +++ /dev/null @@ -1,41 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.service - -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException -import hs.kr.entrydsm.feed.domain.reply.domain.Reply -import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository -import hs.kr.entrydsm.feed.domain.reply.exception.ReplyExistsException -import hs.kr.entrydsm.feed.domain.reply.presentation.dto.request.CreateReplyRequest -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.* - -@Service -class CreateReplyService( - private val userUtils: UserUtils, - private val replyRepository: ReplyRepository, - private val questionRepository: QuestionRepository -) { - @Transactional - fun execute(createReplyRequest: CreateReplyRequest, questionId: UUID) { - val question = questionRepository.findByIdOrNull(questionId) ?: throw QuestionNotFoundException - if (question.isReplied) { - throw ReplyExistsException - } - val user = userUtils.getCurrentUser() - - createReplyRequest.run { - replyRepository.save( - Reply( - title = title, - content = content, - questionId = question.id!!, - adminId = user.id - ) - ) - } - question.updateIsReplied(true) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/DeleteReplyService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/DeleteReplyService.kt deleted file mode 100644 index d70513c..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/DeleteReplyService.kt +++ /dev/null @@ -1,23 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.service - -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.domain.question.exception.QuestionNotFoundException -import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository -import hs.kr.entrydsm.feed.domain.reply.exception.ReplyNotFoundException -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.* - -@Service -class DeleteReplyService( - private val replyRepository: ReplyRepository, - private val questionRepository: QuestionRepository -) { - @Transactional - fun execute(replyId: UUID) { - val reply = replyRepository.findByIdOrNull(replyId) ?: throw ReplyNotFoundException - questionRepository.findByIdOrNull(reply.questionId) ?.updateIsReplied(false) ?: throw QuestionNotFoundException - replyRepository.delete(reply) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/UpdateReplyService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/UpdateReplyService.kt deleted file mode 100644 index 94b315c..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/service/UpdateReplyService.kt +++ /dev/null @@ -1,26 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.service - -import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository -import hs.kr.entrydsm.feed.domain.reply.exception.ReplyNotFoundException -import hs.kr.entrydsm.feed.domain.reply.presentation.dto.request.UpdateReplyRequest -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.UUID - -@Service -class UpdateReplyService( - private val replyRepository: ReplyRepository -) { - @Transactional - fun execute(updateReplyRequest: UpdateReplyRequest, replyId: UUID) { - val reply = replyRepository.findByIdOrNull(replyId) ?: throw ReplyNotFoundException - - updateReplyRequest.run { - reply.updateReply( - title = title, - content = content - ) - } - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/presentation/ReserveController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/presentation/ReserveController.kt deleted file mode 100644 index 40474e2..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/presentation/ReserveController.kt +++ /dev/null @@ -1,17 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reserve.presentation - -import hs.kr.entrydsm.feed.domain.reserve.service.GetReserveLinkService -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController - -@RestController -@RequestMapping("/reserve") -class ReserveController( - private val getReserveLinkService: GetReserveLinkService -) { - - @GetMapping - fun reserveLink(): String = - getReserveLinkService.execute() -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/service/GetReserveLinkService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/service/GetReserveLinkService.kt deleted file mode 100644 index 2ea926c..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reserve/service/GetReserveLinkService.kt +++ /dev/null @@ -1,13 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reserve.service - -import org.springframework.beans.factory.annotation.Value -import org.springframework.stereotype.Service - -@Service -class GetReserveLinkService( - @Value("\${reserve.link}") - private val reserveLink: String -) { - fun execute() = - reserveLink -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/Screen.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/Screen.kt deleted file mode 100644 index cabbff7..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/Screen.kt +++ /dev/null @@ -1,22 +0,0 @@ -package hs.kr.entrydsm.feed.domain.screen.domain - -import hs.kr.entrydsm.feed.domain.BaseEntity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import java.util.UUID - -@Entity(name = "tbl_screen") -class Screen( - - id: UUID? = null, - - var image: String, - - @Column(columnDefinition = "BINARY(16)") - val adminId: UUID -) : BaseEntity(id) { - - fun updateImage(image: String) { - this.image = image - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/repository/ScreenRepository.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/repository/ScreenRepository.kt deleted file mode 100644 index 1abdd85..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/domain/repository/ScreenRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package hs.kr.entrydsm.feed.domain.screen.domain.repository - -import hs.kr.entrydsm.feed.domain.screen.domain.Screen -import org.springframework.data.jpa.repository.JpaRepository -import java.util.UUID - -interface ScreenRepository : JpaRepository diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/exception/ScreenNotFoundException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/exception/ScreenNotFoundException.kt deleted file mode 100644 index b98cb42..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/exception/ScreenNotFoundException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.screen.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object ScreenNotFoundException : EquusException( - ErrorCode.SCREEN_NOT_FOUND -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/ScreenController.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/ScreenController.kt deleted file mode 100644 index 8bcb01c..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/ScreenController.kt +++ /dev/null @@ -1,45 +0,0 @@ -package hs.kr.entrydsm.feed.domain.screen.presentation - -import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.QueryScreenResponse -import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.ScreenResponse -import hs.kr.entrydsm.feed.domain.screen.service.CreateScreenService -import hs.kr.entrydsm.feed.domain.screen.service.QueryScreenService -import hs.kr.entrydsm.feed.domain.screen.service.UpdateScreenService -import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PatchMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestPart -import org.springframework.web.bind.annotation.ResponseStatus -import org.springframework.web.bind.annotation.RestController -import org.springframework.web.multipart.MultipartFile -import java.util.UUID - -@RestController -@RequestMapping("/screen") -class ScreenController( - private val createScreenService: CreateScreenService, - private val updateScreenService: UpdateScreenService, - private val queryScreenService: QueryScreenService -) { - - @ResponseStatus(value = HttpStatus.CREATED) - @PostMapping - fun createScreen( - @RequestPart(name = "image") image: MultipartFile - ): ScreenResponse = - createScreenService.execute(image) - - @PatchMapping("/{screen-id}") - fun updateScreen( - @PathVariable(name = "screen-id") id: UUID, - @RequestPart(name = "image") image: MultipartFile - ): ScreenResponse = - updateScreenService.execute(id, image) - - @GetMapping - fun queryScreen(): List = - queryScreenService.execute() -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/QueryScreenResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/QueryScreenResponse.kt deleted file mode 100644 index 46998b8..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/QueryScreenResponse.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hs.kr.entrydsm.feed.domain.screen.presentation.dto.response - -import java.time.LocalDateTime -import java.util.UUID - -data class QueryScreenResponse( - val id: UUID, - val image: String, - val createAt: LocalDateTime, - val modifyAt: LocalDateTime -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/ScreenResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/ScreenResponse.kt deleted file mode 100644 index 01c7813..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/presentation/dto/response/ScreenResponse.kt +++ /dev/null @@ -1,5 +0,0 @@ -package hs.kr.entrydsm.feed.domain.screen.presentation.dto.response - -data class ScreenResponse( - val image: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/CreateScreenService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/CreateScreenService.kt deleted file mode 100644 index 53a3acd..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/CreateScreenService.kt +++ /dev/null @@ -1,33 +0,0 @@ -package hs.kr.entrydsm.feed.domain.screen.service - -import hs.kr.entrydsm.feed.domain.screen.domain.Screen -import hs.kr.entrydsm.feed.domain.screen.domain.repository.ScreenRepository -import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.ScreenResponse -import hs.kr.entrydsm.feed.global.utils.user.UserUtils -import hs.kr.entrydsm.feed.infrastructure.s3.PathList -import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.multipart.MultipartFile - -@Service -class CreateScreenService( - private val screenRepository: ScreenRepository, - private val fileUtil: FileUtil, - private val userUtils: UserUtils -) { - - @Transactional - fun execute(file: MultipartFile): ScreenResponse { - val adminId = userUtils.getCurrentUser().id - - val fileName = fileUtil.upload(file, PathList.SCREEN) - screenRepository.save( - Screen( - image = fileName, - adminId = adminId - ) - ) - return ScreenResponse(fileName) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/QueryScreenService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/QueryScreenService.kt deleted file mode 100644 index a493049..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/QueryScreenService.kt +++ /dev/null @@ -1,26 +0,0 @@ -package hs.kr.entrydsm.feed.domain.screen.service - -import hs.kr.entrydsm.feed.domain.screen.domain.repository.ScreenRepository -import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.QueryScreenResponse -import hs.kr.entrydsm.feed.infrastructure.s3.PathList -import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class QueryScreenService( - private val screenRepository: ScreenRepository, - private val fileUtil: FileUtil -) { - @Transactional(readOnly = true) - fun execute(): List = - screenRepository.findAll() - .map { it -> - QueryScreenResponse( - it.id!!, - fileUtil.generateObjectUrl(it.image, PathList.SCREEN), - it.createdAt, - it.modifiedAt - ) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/UpdateScreenService.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/UpdateScreenService.kt deleted file mode 100644 index 7117392..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/screen/service/UpdateScreenService.kt +++ /dev/null @@ -1,30 +0,0 @@ -package hs.kr.entrydsm.feed.domain.screen.service - -import hs.kr.entrydsm.feed.domain.screen.domain.repository.ScreenRepository -import hs.kr.entrydsm.feed.domain.screen.exception.ScreenNotFoundException -import hs.kr.entrydsm.feed.domain.screen.presentation.dto.response.ScreenResponse -import hs.kr.entrydsm.feed.infrastructure.s3.PathList -import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.multipart.MultipartFile -import java.util.UUID - -@Service -class UpdateScreenService( - private val screenRepository: ScreenRepository, - private val fileUtil: FileUtil -) { - - @Transactional - fun execute(id: UUID, file: MultipartFile): ScreenResponse { - val screen = screenRepository.findByIdOrNull(id) - ?: throw ScreenNotFoundException - val fileName = fileUtil.upload(file, PathList.SCREEN) - fileUtil.delete(screen.image, PathList.SCREEN) - screen.updateImage(fileName) - - return ScreenResponse(fileUtil.generateObjectUrl(fileName, PathList.SCREEN)) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionHandler.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionHandler.kt deleted file mode 100644 index 8bc3ae0..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionHandler.kt +++ /dev/null @@ -1,35 +0,0 @@ -package hs.kr.entrydsm.feed.global.error - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import org.springframework.context.MessageSource -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.MethodArgumentNotValidException -import org.springframework.web.bind.annotation.ExceptionHandler -import org.springframework.web.bind.annotation.RestControllerAdvice - -@RestControllerAdvice -class GlobalExceptionHandler( - private val messageSource: MessageSource -) { - - @ExceptionHandler(EquusException::class) - fun handlingEquusException(e: EquusException): ResponseEntity { - val code = e.errorCode - return ResponseEntity( - ErrorResponse(code.status, code.message), - HttpStatus.valueOf(code.status) - ) - } - - @ExceptionHandler(MethodArgumentNotValidException::class) - fun validatorExceptionHandler(e: MethodArgumentNotValidException): ResponseEntity { - return ResponseEntity( - ErrorResponse( - 400, - e.bindingResult.allErrors[0].defaultMessage - ), - HttpStatus.BAD_REQUEST - ) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/EquusException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/EquusException.kt deleted file mode 100644 index 39d4f75..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/EquusException.kt +++ /dev/null @@ -1,7 +0,0 @@ -package hs.kr.entrydsm.feed.global.error.exception - -import java.lang.RuntimeException - -abstract class EquusException( - val errorCode: ErrorCode -) : RuntimeException() diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/ExpiredTokenException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/ExpiredTokenException.kt deleted file mode 100644 index cae9206..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/ExpiredTokenException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.global.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object ExpiredTokenException : EquusException( - ErrorCode.EXPIRED_TOKEN -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InternalServerErrorException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InternalServerErrorException.kt deleted file mode 100644 index 8738854..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InternalServerErrorException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.global.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object InternalServerErrorException : EquusException( - ErrorCode.INTERNAL_SERVER_ERROR -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InvalidTokenException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InvalidTokenException.kt deleted file mode 100644 index d244577..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/exception/InvalidTokenException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.global.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object InvalidTokenException : EquusException( - ErrorCode.INVALID_TOKEN -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtProperties.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtProperties.kt deleted file mode 100644 index 747d1a1..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtProperties.kt +++ /dev/null @@ -1,12 +0,0 @@ -package hs.kr.entrydsm.feed.global.security.jwt - -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.context.properties.bind.ConstructorBinding - -@ConstructorBinding -@ConfigurationProperties("auth.jwt") -class JwtProperties( - val secretKey: String, - val header: String, - val prefix: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/user/UserUtils.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/user/UserUtils.kt deleted file mode 100644 index 9668bf7..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/user/UserUtils.kt +++ /dev/null @@ -1,22 +0,0 @@ -package hs.kr.entrydsm.feed.global.utils.user - -import hs.kr.entrydsm.feed.global.exception.InvalidTokenException -import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient -import hs.kr.equus.feed.infrastructure.feign.client.user.model.User -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.stereotype.Component -import java.util.UUID - -@Component -class UserUtils( - private val userFeignClient: UserFeignClient -) { - fun getCurrentUserId(): UUID { - try { - return UUID.fromString(SecurityContextHolder.getContext().authentication.name) - } catch (e: IllegalArgumentException) { - throw InvalidTokenException - } - } - fun getCurrentUser(): User = userFeignClient.getUserByUUID(getCurrentUserId()) -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/UserFeignClient.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/UserFeignClient.kt deleted file mode 100644 index 20802f4..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/UserFeignClient.kt +++ /dev/null @@ -1,17 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.feign.client.user - -import hs.kr.equus.feed.infrastructure.feign.client.user.model.Admin -import hs.kr.equus.feed.infrastructure.feign.client.user.model.User -import org.springframework.cloud.openfeign.FeignClient -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import java.util.UUID - -@FeignClient(name = "UserClient", url = "\${url.user}") -interface UserFeignClient { - @GetMapping("/user/{userId}") - fun getUserByUUID(@PathVariable userId: UUID): User - - @GetMapping("/admin/{adminId}") - fun getAdminByUUID(@PathVariable adminId: UUID): Admin -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/HeaderConfiguration.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/HeaderConfiguration.kt deleted file mode 100644 index bb89e37..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/HeaderConfiguration.kt +++ /dev/null @@ -1,14 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.feign.configuration - -import feign.RequestInterceptor -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration -class HeaderConfiguration { - @Bean - fun requestInterceptor(): RequestInterceptor = RequestInterceptor { template -> - template.header("Request-User-Id", "\${feign.userId}") - template.header("Request-User-Role", "ROOT") - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignBadRequestException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignBadRequestException.kt deleted file mode 100644 index 419a349..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignBadRequestException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.feign.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object FeignBadRequestException : EquusException( - ErrorCode.FEIGN_BAD_REQUEST -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignForbiddenException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignForbiddenException.kt deleted file mode 100644 index 5d30347..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignForbiddenException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.feign.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object FeignForbiddenException : EquusException( - ErrorCode.FEIGN_FORBIDDEN -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignServerError.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignServerError.kt deleted file mode 100644 index a9791cb..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignServerError.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.feign.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object FeignServerError : EquusException( - ErrorCode.FEIGN_SERVER_ERROR -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignUnAuthorizedException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignUnAuthorizedException.kt deleted file mode 100644 index 56f78d4..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/exception/FeignUnAuthorizedException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.feign.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object FeignUnAuthorizedException : EquusException( - ErrorCode.FEIGN_UNAUTHORIZED -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaConsumerConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaConsumerConfig.kt deleted file mode 100644 index dc718ad..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaConsumerConfig.kt +++ /dev/null @@ -1,47 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.kafka.configuration - -import org.apache.kafka.clients.consumer.ConsumerConfig -import org.apache.kafka.common.serialization.StringDeserializer -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.kafka.annotation.EnableKafka -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory -import org.springframework.kafka.core.DefaultKafkaConsumerFactory -import org.springframework.kafka.support.converter.StringJsonMessageConverter -import org.springframework.kafka.support.serializer.JsonDeserializer - -@EnableKafka -@Configuration -class KafkaConsumerConfig( - private val kafkaProperty: KafkaProperty -) { - - @Bean - fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory { - return ConcurrentKafkaListenerContainerFactory().apply { - setConcurrency(2) - consumerFactory = DefaultKafkaConsumerFactory(consumerFactoryConfig()) - setMessageConverter(StringJsonMessageConverter()) - containerProperties.pollTimeout = 500 - } - } - - private fun consumerFactoryConfig(): Map { - return mapOf( - ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to kafkaProperty.serverAddress, - ConsumerConfig.ISOLATION_LEVEL_CONFIG to "read_committed", - ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java, - ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG to "false", - ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to "latest", - ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to JsonDeserializer::class.java, - ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG to 5000, - JsonDeserializer.TRUSTED_PACKAGES to "*", - "security.protocol" to "SASL_PLAINTEXT", - "sasl.mechanism" to "SCRAM-SHA-512", - "sasl.jaas.config" to - "org.apache.kafka.common.security.scram.ScramLoginModule required " + - "username=\"${kafkaProperty.confluentApiKey}\" " + - "password=\"${kafkaProperty.confluentApiSecret}\";" - ) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaProperty.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaProperty.kt deleted file mode 100644 index 9805cb7..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaProperty.kt +++ /dev/null @@ -1,12 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.kafka.configuration - -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.context.properties.bind.ConstructorBinding - -@ConstructorBinding -@ConfigurationProperties("kafka") -class KafkaProperty( - val serverAddress: String, - val confluentApiKey: String, - val confluentApiSecret: String -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaTopics.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaTopics.kt deleted file mode 100644 index 626a2a5..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/configuration/KafkaTopics.kt +++ /dev/null @@ -1,6 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.kafka.configuration - -object KafkaTopics { - const val DELETE_ALL_TABLE = "delete-all-table" - const val DELETE_USER = "delete-user" -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteFaqTableConsumer.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteFaqTableConsumer.kt deleted file mode 100644 index 3159bf3..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteFaqTableConsumer.kt +++ /dev/null @@ -1,20 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.kafka.consumer - -import hs.kr.entrydsm.feed.domain.faq.domain.repository.FaqRepository -import hs.kr.entrydsm.feed.infrastructure.kafka.configuration.KafkaTopics -import org.springframework.kafka.annotation.KafkaListener -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class DeleteFaqTableConsumer( - private val faqRepository: FaqRepository -) { - @KafkaListener( - topics = [KafkaTopics.DELETE_ALL_TABLE], - groupId = "delete-all-table-faq", - containerFactory = "kafkaListenerContainerFactory" - ) - @Transactional - fun execute() = faqRepository.deleteAll() -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteQuestionTableConsumer.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteQuestionTableConsumer.kt deleted file mode 100644 index 42f37f3..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteQuestionTableConsumer.kt +++ /dev/null @@ -1,20 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.kafka.consumer - -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.infrastructure.kafka.configuration.KafkaTopics -import org.springframework.kafka.annotation.KafkaListener -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class DeleteQuestionTableConsumer( - private val questionRepository: QuestionRepository -) { - @KafkaListener( - topics = [KafkaTopics.DELETE_ALL_TABLE], - groupId = "delete-all-table-question", - containerFactory = "kafkaListenerContainerFactory" - ) - @Transactional - fun execute() = questionRepository.deleteAll() -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteReplyTableConsumer.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteReplyTableConsumer.kt deleted file mode 100644 index 5b8f40b..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteReplyTableConsumer.kt +++ /dev/null @@ -1,21 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.kafka.consumer - -import hs.kr.entrydsm.feed.domain.reply.domain.repository.ReplyRepository -import hs.kr.entrydsm.feed.infrastructure.kafka.configuration.KafkaTopics -import org.springframework.kafka.annotation.KafkaListener -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -class DeleteReplyTableConsumer( - private val replyRepository: ReplyRepository -) { - - @KafkaListener( - topics = [KafkaTopics.DELETE_ALL_TABLE], - groupId = "delete-all-table-reply", - containerFactory = "kafkaListenerContainerFactory" - ) - @Transactional - fun execute() = replyRepository.deleteAll() -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteUserQuestionConsumer.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteUserQuestionConsumer.kt deleted file mode 100644 index f78d6ff..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/consumer/DeleteUserQuestionConsumer.kt +++ /dev/null @@ -1,24 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.kafka.consumer - -import hs.kr.entrydsm.feed.domain.question.domain.repository.QuestionRepository -import hs.kr.entrydsm.feed.infrastructure.kafka.configuration.KafkaTopics -import org.springframework.kafka.annotation.KafkaListener -import org.springframework.messaging.handler.annotation.Payload -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.util.UUID - -@Service -class DeleteUserQuestionConsumer( - private val questionRepository: QuestionRepository -) { - @KafkaListener( - topics = [KafkaTopics.DELETE_USER], - groupId = "delete-user-question", - containerFactory = "kafkaListenerContainerFactory" - ) - @Transactional - fun execute(@Payload deletedUserInfo: UUID) { - questionRepository.deleteAllByUserId(deletedUserInfo) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/dto/response/DeletedUserInfoResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/dto/response/DeletedUserInfoResponse.kt deleted file mode 100644 index 8d082ad..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/kafka/dto/response/DeletedUserInfoResponse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.kafka.dto.response - -import java.util.UUID - -data class DeletedUserInfoResponse( - val userId: UUID -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/PathList.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/PathList.kt deleted file mode 100644 index b4983bc..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/PathList.kt +++ /dev/null @@ -1,7 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.s3 - -object PathList { - const val NOTICE = "notice/" - const val SCREEN = "screen/" - const val ATTACH_FILE = "attach_file/" -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/BadFileExtensionException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/BadFileExtensionException.kt deleted file mode 100644 index 83ae914..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/BadFileExtensionException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.s3.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object BadFileExtensionException : EquusException( - ErrorCode.BAD_FILE_EXTENSION -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/EmptyFileException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/EmptyFileException.kt deleted file mode 100644 index 7b6e1d6..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/exception/EmptyFileException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.s3.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object EmptyFileException : EquusException( - ErrorCode.FILE_IS_EMPTY -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/util/FileUtil.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/util/FileUtil.kt deleted file mode 100644 index 848b89f..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/s3/util/FileUtil.kt +++ /dev/null @@ -1,90 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.s3.util - -import com.amazonaws.HttpMethod -import com.amazonaws.services.s3.AmazonS3 -import com.amazonaws.services.s3.model.CannedAccessControlList -import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest -import com.amazonaws.services.s3.model.ObjectMetadata -import com.amazonaws.services.s3.model.PutObjectRequest -import hs.kr.entrydsm.feed.infrastructure.s3.exception.BadFileExtensionException -import hs.kr.entrydsm.feed.infrastructure.s3.exception.EmptyFileException -import org.springframework.beans.factory.annotation.Value -import org.springframework.http.MediaType -import org.springframework.stereotype.Service -import org.springframework.web.multipart.MultipartFile -import java.io.ByteArrayInputStream -import java.io.InputStream -import java.util.* - -@Service -class FileUtil( - private val amazonS3: AmazonS3 -) { - @Value("\${cloud.aws.s3.bucket}") - lateinit var bucketName: String - - companion object { - const val EXP_TIME = 10000 * 60 * 2 - } - - fun upload(file: MultipartFile, path: String): String { - val ext = verificationFile(file) - - val randomName = UUID.randomUUID().toString() - val filename = "$randomName.$ext" - val inputStream: InputStream = ByteArrayInputStream(file.bytes) - - val metadata = ObjectMetadata().apply { - contentType = when (ext) { - "pdf" -> MediaType.APPLICATION_PDF_VALUE - else -> MediaType.IMAGE_PNG_VALUE - } - contentLength = file.size - contentDisposition = "inline" - } - - inputStream.use { - amazonS3.putObject( - PutObjectRequest(bucketName, "${path}$filename", it, metadata) - .withCannedAcl(CannedAccessControlList.AuthenticatedRead) - ) - } - - return filename - } - - fun delete(objectName: String, path: String) { - amazonS3.deleteObject(bucketName, path + objectName) - } - - fun generateObjectUrl(fileName: String, path: String): String { - val expiration = Date().apply { - time += EXP_TIME - } - return amazonS3.generatePresignedUrl( - GeneratePresignedUrlRequest( - bucketName, - "${path}$fileName" - ).withMethod(HttpMethod.GET).withExpiration(expiration) - ).toString() - } - - private fun verificationFile(file: MultipartFile): String { - if (file.isEmpty || file.originalFilename == null) throw EmptyFileException - val originalFilename = file.originalFilename!! - val ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).lowercase(Locale.getDefault()) - - if (!( - ext == "jpg" || ext == "jpeg" || - ext == "png" || ext == "heic" || - ext == "hwp" || ext == "pptx" || - ext == "pdf" || ext == "xls" || - ext == "xlsx" - ) - ) { - throw BadFileExtensionException - } - - return ext - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index e5a9199..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=Casper-Feed diff --git a/src/test/kotlin/hs/kr/entrydsm/feed/CasperFeedApplicationTests.kt b/src/test/kotlin/hs/kr/entrydsm/feed/CasperFeedApplicationTests.kt deleted file mode 100644 index f496918..0000000 --- a/src/test/kotlin/hs/kr/entrydsm/feed/CasperFeedApplicationTests.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hs.kr.entrydsm.feed - -import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -class CasperFeedApplicationTests { - @Test - fun contextLoads() { - } -} From 72e3c5f1462ee2313a3de2f0536dcebf9554c5e5 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:16:20 +0900 Subject: [PATCH 11/49] =?UTF-8?q?chore=20(=20#14=20)=20:=20casper-feed=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AccessDeniedQuestionException.kt | 8 ------- .../exception/AccessDeniedReplyException.kt | 8 ------- .../feed/global/utils/admin/AdminUtils.kt | 22 ------------------- .../feign/client/user/model/Admin.kt | 7 ------ 4 files changed, 45 deletions(-) delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/AccessDeniedQuestionException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/AccessDeniedReplyException.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/utils/admin/AdminUtils.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/Admin.kt diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/AccessDeniedQuestionException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/AccessDeniedQuestionException.kt deleted file mode 100644 index 7d74d05..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/question/exception/AccessDeniedQuestionException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.question.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object AccessDeniedQuestionException : EquusException( - ErrorCode.ACCESS_DENIED_QUESTION -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/AccessDeniedReplyException.kt b/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/AccessDeniedReplyException.kt deleted file mode 100644 index dec61d1..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/domain/reply/exception/AccessDeniedReplyException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hs.kr.entrydsm.feed.domain.reply.exception - -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode - -object AccessDeniedReplyException : EquusException( - ErrorCode.ACCESS_DENIED_REPLY -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/admin/AdminUtils.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/admin/AdminUtils.kt deleted file mode 100644 index 0e17d8b..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/utils/admin/AdminUtils.kt +++ /dev/null @@ -1,22 +0,0 @@ -package hs.kr.entrydsm.feed.global.utils.admin - -import hs.kr.entrydsm.feed.global.exception.InvalidTokenException -import hs.kr.entrydsm.feed.infrastructure.feign.client.user.UserFeignClient -import hs.kr.equus.feed.infrastructure.feign.client.user.model.Admin -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.stereotype.Component -import java.util.* - -@Component -class AdminUtils( - private val userFeignClient: UserFeignClient -) { - fun getCurrentAdminId(): UUID { - try { - return UUID.fromString(SecurityContextHolder.getContext().authentication.name) - } catch (e: IllegalArgumentException) { - throw InvalidTokenException - } - } - fun getCurrentAdmin(): Admin = userFeignClient.getAdminByUUID(getCurrentAdminId()) -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/Admin.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/Admin.kt deleted file mode 100644 index eb00317..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/Admin.kt +++ /dev/null @@ -1,7 +0,0 @@ -package hs.kr.equus.feed.infrastructure.feign.client.user.model - -import java.util.UUID - -data class Admin( - val id: UUID -) From 7daf22e120ba6949f8d809f6ca71d71b35371453 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:18:04 +0900 Subject: [PATCH 12/49] feat ( #8 ) : CreateAttachFileResponse --- .../dto/response/CreateAttachFileResponse.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/dto/response/CreateAttachFileResponse.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/dto/response/CreateAttachFileResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/dto/response/CreateAttachFileResponse.kt new file mode 100644 index 0000000..6d0d165 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/dto/response/CreateAttachFileResponse.kt @@ -0,0 +1,12 @@ +package hs.kr.entrydsm.feed.adapter.`in`.attachFile.dto.response + +/** + * 첨부 파일 생성 응답을 위한 데이터 클래스입니다. + * + * @property fileName 원본 파일 이름 + * @property url 업로드된 파일에 접근할 수 있는 URL + */ +class CreateAttachFileResponse( + val fileName: String, + val url: String, +) From 018847611c3234d71183a745abb4d69995188dbf Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:18:19 +0900 Subject: [PATCH 13/49] feat ( #8 ) : AttachFileWebAdapter --- .../in/attachFile/AttachFileWebAdapter.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/AttachFileWebAdapter.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/AttachFileWebAdapter.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/AttachFileWebAdapter.kt new file mode 100644 index 0000000..14313aa --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/AttachFileWebAdapter.kt @@ -0,0 +1,27 @@ +package hs.kr.entrydsm.feed.adapter.`in`.attachFile + +import hs.kr.entrydsm.feed.application.attachFile.service.CreateAttachFileService +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestPart +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile + +/** + * 첨부 파일 관련 HTTP 요청을 처리하는 웹 어댑터 클래스입니다. + * + * 이 클래스는 첨부 파일 업로드와 관련된 HTTP 엔드포인트를 제공하며, + * 클라이언트의 요청을 적절한 서비스 메서드로 라우팅합니다. + * + * @property createAttachFileService 첨부 파일 비즈니스 로직을 처리하는 서비스 + */ +@RestController +@RequestMapping("/attach-file") +class AttachFileWebAdapter( + private val createAttachFileUseCase: CreateAttachFileService, +) { + @PostMapping + fun createAttachFile( + @RequestPart(value = "attach_file") attachFile: List, + ) = createAttachFileUseCase.execute(attachFile) +} From b62362f87528aa5c51cc7f27c4f9672029a71f55 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:18:41 +0900 Subject: [PATCH 14/49] feat ( #8 ) : AttachFileJpaEntity --- .../entity/attachFile/AttachFileJpaEntity.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/entity/attachFile/AttachFileJpaEntity.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/entity/attachFile/AttachFileJpaEntity.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/entity/attachFile/AttachFileJpaEntity.kt new file mode 100644 index 0000000..528210c --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/entity/attachFile/AttachFileJpaEntity.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.adapter.out.entity.attachFile + +import jakarta.persistence.Entity +import jakarta.persistence.Id + +/** + * 첨부 파일 정보를 데이터베이스에 저장하기 위한 JPA 엔티티 클래스입니다. + * + * @property uploadedFileName AWS S3에 업로드된 파일명 (UUID 등으로 인코딩된 파일명) + * @property originalAttachFileName 원본 첨부 파일명 (사용자가 업로드한 원본 파일명) + */ +@Entity(name = "tbl_attach_file") +class AttachFileJpaEntity( + @Id + val uploadedFileName: String, // aws s3에 올라가는 fileName + var originalAttachFileName: String, // 인코딩 되기 전 첨부파일 이름 ex): 서프수행.hwp +) From 19d4ce8195d798fc10b2fb772621dd8b823d2852 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:18:52 +0900 Subject: [PATCH 15/49] feat ( #8 ) : AttachFileMapper --- .../out/mapper/attachFile/AttachFileMapper.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/mapper/attachFile/AttachFileMapper.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/mapper/attachFile/AttachFileMapper.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/mapper/attachFile/AttachFileMapper.kt new file mode 100644 index 0000000..2919113 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/mapper/attachFile/AttachFileMapper.kt @@ -0,0 +1,28 @@ +package hs.kr.entrydsm.feed.adapter.out.mapper.attachFile + +import hs.kr.entrydsm.feed.adapter.out.entity.attachFile.AttachFileJpaEntity +import hs.kr.entrydsm.feed.model.attachFile.AttachFile +import org.mapstruct.Mapper + +/** + * 첨부 파일(AttachFile) 도메인 모델과 JPA 엔티티 간의 변환을 담당하는 매퍼 인터페이스입니다. + * MapStruct를 사용하여 구현체가 자동으로 생성됩니다. + */ +@Mapper(componentModel = "spring") +interface AttachFileMapper { + /** + * JPA 엔티티를 AttachFile 도메인 모델로 변환합니다. + * + * @param entity 변환할 AttachFileJpaEntity 인스턴스 + * @return 변환된 AttachFile 도메인 모델 + */ + fun toModel(entity: AttachFileJpaEntity): AttachFile + + /** + * AttachFile 도메인 모델을 JPA 엔티티로 변환합니다. + * + * @param model 변환할 AttachFile 도메인 모델 + * @return 변환된 AttachFileJpaEntity 인스턴스 + */ + fun toEntity(model: AttachFile): AttachFileJpaEntity +} From 702c7a01a9230f319f23a910c510eacd11107ae2 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:19:10 +0900 Subject: [PATCH 16/49] feat ( #8 ) : AttachFileRepository --- .../repository/AttachFileRepository.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/repository/AttachFileRepository.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/repository/AttachFileRepository.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/repository/AttachFileRepository.kt new file mode 100644 index 0000000..fddde5b --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/repository/AttachFileRepository.kt @@ -0,0 +1,33 @@ +package hs.kr.entrydsm.feed.adapter.out.persistence.attachFile.repository + +import hs.kr.entrydsm.feed.adapter.out.entity.attachFile.AttachFileJpaEntity +import org.springframework.data.jpa.repository.JpaRepository + +/** + * 첨부 파일 데이터에 접근하기 위한 JPA Repository 인터페이스입니다. + * 첨부 파일 엔티티의 CRUD 및 커스텀 쿼리 메서드를 제공합니다. + */ +interface AttachFileRepository : JpaRepository { + /** + * 원본 파일명으로 첨부 파일 엔티티를 조회합니다. + * + * @param attachFileName 조회할 원본 파일명 + * @return 조회된 첨부 파일 엔티티 목록 (없을 경우 null 반환) + */ + fun findByOriginalAttachFileName(attachFileName: String): List? + + /** + * 원본 파일명에 해당하는 모든 첨부 파일 엔티티를 삭제합니다. + * + * @param attachFileName 삭제할 원본 파일명 + */ + fun deleteByOriginalAttachFileName(attachFileName: String) + + /** + * 주어진 원본 파일명을 가진 첨부 파일이 존재하는지 확인합니다. + * + * @param attachFileName 확인할 원본 파일명 + * @return 파일이 존재하면 true, 그렇지 않으면 false + */ + fun existsByOriginalAttachFileName(attachFileName: String): Boolean +} From 871eacd70e079734749e3c3c6caa42b897f01dba Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:19:29 +0900 Subject: [PATCH 17/49] feat ( #8 ) : AttachFilePersistenceAdapter --- .../AttachFilePersistenceAdapter.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/AttachFilePersistenceAdapter.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/AttachFilePersistenceAdapter.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/AttachFilePersistenceAdapter.kt new file mode 100644 index 0000000..a912daf --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/AttachFilePersistenceAdapter.kt @@ -0,0 +1,39 @@ +package hs.kr.entrydsm.feed.adapter.out.persistence.attachFile + +import hs.kr.entrydsm.feed.adapter.out.mapper.attachFile.AttachFileMapper +import hs.kr.entrydsm.feed.adapter.out.persistence.attachFile.repository.AttachFileRepository +import hs.kr.entrydsm.feed.application.attachFile.port.out.DeleteAttachFilePort +import hs.kr.entrydsm.feed.application.attachFile.port.out.ExistsAttachFilePort +import hs.kr.entrydsm.feed.application.attachFile.port.out.FindAttachFilePort +import hs.kr.entrydsm.feed.application.attachFile.port.out.SaveAttachFilePort +import hs.kr.entrydsm.feed.model.attachFile.AttachFile +import org.springframework.stereotype.Component + +/** + * 첨부 파일 도메인과 데이터베이스 간의 상호작용을 담당하는 어댑터 클래스입니다. + * 첨부 파일의 CRUD 작업을 처리하며, 파일명을 통한 조회 및 삭제 기능을 제공합니다. + * + * @property attachFileRepository 첨부 파일 엔티티를 데이터베이스에서 조작하기 위한 리포지토리 + * @property attachFileMapper 첨부 파일 도메인 객체와 엔티티 간의 변환을 담당하는 매퍼 + */ +@Component +class AttachFilePersistenceAdapter( + private val attachFileRepository: AttachFileRepository, + private val attachFileMapper: AttachFileMapper, +) : ExistsAttachFilePort, DeleteAttachFilePort, SaveAttachFilePort, FindAttachFilePort { + override fun existsByOriginalAttachFileName(attachFileName: String): Boolean { + return attachFileRepository.existsByOriginalAttachFileName(attachFileName) + } + + override fun deleteByOriginalAttachFileName(attachFileName: String) { + attachFileRepository.deleteByOriginalAttachFileName(attachFileName) + } + + override fun save(attachFile: AttachFile): AttachFile { + return attachFileMapper.toModel(attachFileRepository.save(attachFileMapper.toEntity(attachFile))) + } + + override fun findByOriginalAttachFileName(attachFileName: String): List? { + return attachFileRepository.findByOriginalAttachFileName(attachFileName)?.map { attachFileMapper.toModel(it) } + } +} From 84e0d92e86fccc93c86fd6a5e14736d3856f7719 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:20:04 +0900 Subject: [PATCH 18/49] feat ( #8 ) : CreateAttachFileUseCase --- .../port/in/CreateAttachFileUseCase.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/in/CreateAttachFileUseCase.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/in/CreateAttachFileUseCase.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/in/CreateAttachFileUseCase.kt new file mode 100644 index 0000000..240ad88 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/in/CreateAttachFileUseCase.kt @@ -0,0 +1,18 @@ +package hs.kr.entrydsm.feed.application.attachFile.port.`in` + +import hs.kr.entrydsm.feed.adapter.`in`.attachFile.dto.response.CreateAttachFileResponse +import org.springframework.web.multipart.MultipartFile + +/** + * 첨부 파일 관련 비즈니스 로직을 정의한 인터페이스입니다. + * 첨부 파일 업로드 기능을 제공합니다. + */ +interface CreateAttachFileUseCase { + /** + * 하나 이상의 첨부 파일을 업로드합니다. + * + * @param attachFile 업로드할 첨부 파일 목록 + * @return 생성된 첨부 파일 정보 응답 목록 + */ + fun execute(attachFile: List): List +} From dfd95f6a7d178a8e8c69158960903cbea04db1b8 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:20:33 +0900 Subject: [PATCH 19/49] feat ( #8 ) : DeleteAttachFilePort --- .../attachFile/port/out/DeleteAttachFilePort.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/DeleteAttachFilePort.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/DeleteAttachFilePort.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/DeleteAttachFilePort.kt new file mode 100644 index 0000000..2c0b8a1 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/DeleteAttachFilePort.kt @@ -0,0 +1,14 @@ +package hs.kr.entrydsm.feed.application.attachFile.port.out + +/** + * 첨부 파일 삭제를 위한 포트 인터페이스입니다. + * 첨부 파일을 삭제하는 메서드를 정의합니다. + */ +interface DeleteAttachFilePort { + /** + * 원본 첨부 파일명으로 첨부 파일을 삭제합니다. + * + * @param attachFileName 삭제할 첨부 파일명 + */ + fun deleteByOriginalAttachFileName(attachFileName: String) +} From d91050c689b9ef2be8378970c47a97952f1d9fad Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:20:46 +0900 Subject: [PATCH 20/49] feat ( #8 ) : ExistsAttachFilePort --- .../attachFile/port/out/ExistsAttachFilePort.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/ExistsAttachFilePort.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/ExistsAttachFilePort.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/ExistsAttachFilePort.kt new file mode 100644 index 0000000..8e0ec12 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/ExistsAttachFilePort.kt @@ -0,0 +1,15 @@ +package hs.kr.entrydsm.feed.application.attachFile.port.out + +/** + * 첨부 파일 존재 여부 확인을 위한 포트 인터페이스입니다. + * 첨부 파일의 존재 여부를 확인하는 메서드를 정의합니다. + */ +interface ExistsAttachFilePort { + /** + * 원본 첨부 파일명으로 첨부 파일의 존재 여부를 확인합니다. + * + * @param attachFileName 확인할 첨부 파일명 + * @return 파일이 존재하면 true, 그렇지 않으면 false + */ + fun existsByOriginalAttachFileName(attachFileName: String): Boolean +} From 046f418f80a4bdddee20b9ab7a1090730c143522 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:20:55 +0900 Subject: [PATCH 21/49] feat ( #8 ) : FindAttachFilePort --- .../attachFile/port/out/FindAttachFilePort.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/FindAttachFilePort.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/FindAttachFilePort.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/FindAttachFilePort.kt new file mode 100644 index 0000000..5c2c2ae --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/FindAttachFilePort.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.application.attachFile.port.out + +import hs.kr.entrydsm.feed.model.attachFile.AttachFile + +/** + * 첨부 파일 조회를 위한 포트 인터페이스입니다. + * 첨부 파일 도메인 객체를 조회하는 메서드를 정의합니다. + */ +interface FindAttachFilePort { + /** + * 원본 첨부 파일명으로 첨부 파일 목록을 조회합니다. + * + * @param attachFileName 조회할 첨부 파일명 + * @return 조회된 첨부 파일 목록, 없을 경우 null + */ + fun findByOriginalAttachFileName(attachFileName: String): List? +} From c4f52917547239b1e0582ce5c043afd075c87e38 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:21:04 +0900 Subject: [PATCH 22/49] feat ( #8 ) : SaveAttachFilePort --- .../attachFile/port/out/SaveAttachFilePort.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/SaveAttachFilePort.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/SaveAttachFilePort.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/SaveAttachFilePort.kt new file mode 100644 index 0000000..e9f8724 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/port/out/SaveAttachFilePort.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.feed.application.attachFile.port.out + +import hs.kr.entrydsm.feed.model.attachFile.AttachFile + +/** + * 첨부 파일 저장을 위한 포트 인터페이스입니다. + * 첨부 파일 도메인 객체를 저장하는 메서드를 정의합니다. + */ +interface SaveAttachFilePort { + /** + * 첨부 파일을 저장하거나 업데이트합니다. + * + * @param attachFile 저장할 첨부 파일 도메인 객체 + * @return 저장된 첨부 파일 도메인 객체 (ID가 할당됨) + */ + fun save(attachFile: AttachFile): AttachFile +} From 451bf43b1a86b6392d6cbaf1735bf5e5707b40d7 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:21:24 +0900 Subject: [PATCH 23/49] feat ( #8 ) : CreateAttachFileService --- .../service/CreateAttachFileService.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt new file mode 100644 index 0000000..3dc551b --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt @@ -0,0 +1,51 @@ +package hs.kr.entrydsm.feed.application.attachFile.service + +import hs.kr.entrydsm.feed.adapter.`in`.attachFile.dto.response.CreateAttachFileResponse +import hs.kr.entrydsm.feed.application.attachFile.port.`in`.CreateAttachFileUseCase +import hs.kr.entrydsm.feed.application.attachFile.port.out.DeleteAttachFilePort +import hs.kr.entrydsm.feed.application.attachFile.port.out.ExistsAttachFilePort +import hs.kr.entrydsm.feed.application.attachFile.port.out.SaveAttachFilePort +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import hs.kr.entrydsm.feed.model.attachFile.AttachFile +import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile + +/** + * 첨부 파일 도메인에 대한 비즈니스 로직을 처리하는 서비스 클래스입니다. + * + * 이 클래스는 첨부 파일과 관련된 모든 비즈니스 로직을 캡슐화하며, + * 파일 업로드, 조회, 삭제 등의 기능을 제공합니다. + * + * @property fileUtil 파일 업로드/다운로드와 관련된 유틸리티 + * @property existsAttachFilePort 첨부 파일 존재 여부 확인을 위한 포트 + * @property deleteAttachFilePort 첨부 파일 삭제를 위한 포트 + * @property saveAttachFilePort 첨부 파일 저장을 위한 포트 + */ +@Service +class CreateAttachFileService( + private val fileUtil: FileUtil, + private val existsAttachFilePort: ExistsAttachFilePort, + private val deleteAttachFilePort: DeleteAttachFilePort, + private val saveAttachFilePort: SaveAttachFilePort, +) : CreateAttachFileUseCase { + override fun execute(attachFile: List): List { + val attachFileResponses = mutableListOf() + + attachFile.forEach { file -> + if (existsAttachFilePort.existsByOriginalAttachFileName(file.originalFilename!!)) { + deleteAttachFilePort.deleteByOriginalAttachFileName(file.originalFilename!!) + } + val uploadedFilename = fileUtil.upload(file, PathList.ATTACH_FILE) + val attachFile = + AttachFile( + uploadedFileName = uploadedFilename, + originalAttachFileName = file.originalFilename!!, + ) + saveAttachFilePort.save(attachFile) + val url = fileUtil.generateObjectUrl(uploadedFilename, PathList.ATTACH_FILE) + attachFileResponses.add(CreateAttachFileResponse(file.originalFilename!!, url)) + } + return attachFileResponses + } +} From 36718377509c945c8a2beae6d525caad6eb8d379 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:22:29 +0900 Subject: [PATCH 24/49] feat ( #8 ) : AttachFileNotFoundException --- .../exception/AttachFileNotFoundException.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/exception/AttachFileNotFoundException.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/exception/AttachFileNotFoundException.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/exception/AttachFileNotFoundException.kt new file mode 100644 index 0000000..b9b4d0b --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/exception/AttachFileNotFoundException.kt @@ -0,0 +1,14 @@ +package hs.kr.entrydsm.feed.application.notice.exception + +import hs.kr.entrydsm.feed.global.error.exception.CasperException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +/** + * 첨부 파일을 찾을 수 없을 때 발생하는 예외 클래스입니다. + * + * @property status HTTP 상태 코드 (404) + * @property message 에러 메시지 + */ +object AttachFileNotFoundException : CasperException( + ErrorCode.ATTACH_FILE_NOT_FOUND, +) From faa3ac112c4e6fe21744f606491a36ab408ca894 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:22:52 +0900 Subject: [PATCH 25/49] feat ( #8 ) : NoticeNotFoundException --- .../notice/exception/NoticeNotFoundException.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/exception/NoticeNotFoundException.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/exception/NoticeNotFoundException.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/exception/NoticeNotFoundException.kt new file mode 100644 index 0000000..266b0aa --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/exception/NoticeNotFoundException.kt @@ -0,0 +1,14 @@ +package hs.kr.entrydsm.feed.application.notice.exception + +import hs.kr.entrydsm.feed.global.error.exception.CasperException +import hs.kr.entrydsm.feed.global.error.exception.ErrorCode + +/** + * 공지사항을 찾을 수 없을 때 발생하는 예외 클래스입니다. + * + * @property status HTTP 상태 코드 (404) + * @property message 에러 메시지 + */ +object NoticeNotFoundException : CasperException( + ErrorCode.NOTICE_NOT_FOUND, +) From 00580bfd921d73ab1e2a02a24e6547377c602d99 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:23:34 +0900 Subject: [PATCH 26/49] feat ( #8 ) : CreateNoticeRequest --- .../notice/dto/request/CreateNoticeRequest.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/CreateNoticeRequest.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/CreateNoticeRequest.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/CreateNoticeRequest.kt new file mode 100644 index 0000000..0e5b4a7 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/CreateNoticeRequest.kt @@ -0,0 +1,21 @@ +package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.request + +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size + +data class CreateNoticeRequest( + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @Size(max = 5000, message = "content은 최대 5000자까지 가능합니다.") + val content: String, + @field:NotNull(message = "Pinned는 null일수가 없습니다") + val isPinned: Boolean, + @field:NotNull(message = "type은 null일수가 없습니다") + val type: NoticeType, + val fileName: String? = null, + val attachFileName: List? = null, +) From e4543a93665c72a80420a74620a79b02e493f214 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:23:47 +0900 Subject: [PATCH 27/49] feat ( #8 ) : UpdateNoticeRequest --- .../notice/dto/request/UpdateNoticeRequest.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/UpdateNoticeRequest.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/UpdateNoticeRequest.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/UpdateNoticeRequest.kt new file mode 100644 index 0000000..d572f03 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/UpdateNoticeRequest.kt @@ -0,0 +1,21 @@ +package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.request + +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size + +data class UpdateNoticeRequest( + @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @Size(max = 100, message = "title은 최대 100자까지 가능합니다.") + val title: String, + @field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.") + @Size(max = 5000, message = "content은 최대 5000자까지 가능합니다.") + val content: String, + @field:NotNull(message = "Pinned은 null일수가 없습니다") + val isPinned: Boolean, + @field:NotNull(message = "type은 null일수가 없습니다") + val type: NoticeType, + val fileName: String? = null, + val attachFileName: List? = emptyList(), +) From 29dd89b3f828534950a29ce2fdcb033f725b6d2d Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:24:26 +0900 Subject: [PATCH 28/49] feat ( #8 ) : NoticeResponse --- .../in/notice/dto/response/NoticeResponse.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/NoticeResponse.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/NoticeResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/NoticeResponse.kt new file mode 100644 index 0000000..811d830 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/NoticeResponse.kt @@ -0,0 +1,13 @@ +package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response + +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import java.time.LocalDateTime +import java.util.* + +data class NoticeResponse( + val id: UUID, + val title: String, + val type: NoticeType, + val isPinned: Boolean, + val createdAt: LocalDateTime, +) From 864d931cb2fcbcbb0809571dd76f3b0931fa8def Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:24:40 +0900 Subject: [PATCH 29/49] feat ( #8 ) : QueryDetailsNoticeResponse --- .../response/QueryDetailsNoticeResponse.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryDetailsNoticeResponse.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryDetailsNoticeResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryDetailsNoticeResponse.kt new file mode 100644 index 0000000..cf0fc78 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryDetailsNoticeResponse.kt @@ -0,0 +1,20 @@ +package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response + +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import java.time.LocalDateTime + +data class QueryDetailsNoticeResponse( + val title: String, + val content: String, + val createdAt: LocalDateTime, + val type: NoticeType, + val imageURL: String?, + val imageName: String?, + val attachFiles: List = emptyList(), + val isPinned: Boolean, +) + +data class AttachFileElement( + val attachFileUrl: String, + val attachFileName: String, +) From 015e3540ee28e70097ac877df72ef3cd99ba010e Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:24:55 +0900 Subject: [PATCH 30/49] feat ( #8 ) : QueryListNoticeResponse --- .../in/notice/dto/response/QueryListNoticeResponse.kt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryListNoticeResponse.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryListNoticeResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryListNoticeResponse.kt new file mode 100644 index 0000000..eac8d8f --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryListNoticeResponse.kt @@ -0,0 +1,5 @@ +package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response + +data class QueryListNoticeResponse( + val notices: List, +) From 7c2babe2a36fc0aaf03e0cba6c4faa5def8d96c5 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:25:12 +0900 Subject: [PATCH 31/49] feat ( #8 ) : QueryNoticeTitleResponse --- .../in/notice/dto/response/QueryNoticeTitleResponse.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryNoticeTitleResponse.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryNoticeTitleResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryNoticeTitleResponse.kt new file mode 100644 index 0000000..03e94a8 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryNoticeTitleResponse.kt @@ -0,0 +1,10 @@ +package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response + +import java.time.LocalDateTime +import java.util.UUID + +data class QueryNoticeTitleResponse( + val id: UUID, + val title: String, + val createdAt: LocalDateTime, +) From 3bcf9676e86d1fb606db0de5d99319a392127402 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:25:21 +0900 Subject: [PATCH 32/49] feat ( #8 ) : UploadNoticeImageResponse --- .../in/notice/dto/response/UploadNoticeImageResponse.kt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/UploadNoticeImageResponse.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/UploadNoticeImageResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/UploadNoticeImageResponse.kt new file mode 100644 index 0000000..a10321d --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/UploadNoticeImageResponse.kt @@ -0,0 +1,6 @@ +package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response + +data class UploadNoticeImageResponse( + val fileUrl: String, + val fileName: String, +) From e7a56ba3a6449464aa613a757a6f7859bcaada43 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:25:29 +0900 Subject: [PATCH 33/49] feat ( #8 ) : NoticeWebAdapter --- .../adapter/in/notice/NoticeWebAdapter.kt | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/NoticeWebAdapter.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/NoticeWebAdapter.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/NoticeWebAdapter.kt new file mode 100644 index 0000000..19a128e --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/NoticeWebAdapter.kt @@ -0,0 +1,122 @@ +package hs.kr.entrydsm.feed.adapter.`in`.notice + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.request.CreateNoticeRequest +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.request.UpdateNoticeRequest +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.QueryDetailsNoticeResponse +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.QueryListNoticeResponse +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.QueryNoticeTitleResponse +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.UploadNoticeImageResponse +import hs.kr.entrydsm.feed.application.notice.port.`in`.CreateNoticeUseCase +import hs.kr.entrydsm.feed.application.notice.port.`in`.DeleteNoticeUseCase +import hs.kr.entrydsm.feed.application.notice.port.`in`.QueryDetailsNoticeUseCase +import hs.kr.entrydsm.feed.application.notice.port.`in`.QueryNoticeListByTypeUseCase +import hs.kr.entrydsm.feed.application.notice.port.`in`.QueryNoticeTitleUseCase +import hs.kr.entrydsm.feed.application.notice.port.`in`.UpdateNoticeUseCase +import hs.kr.entrydsm.feed.application.notice.port.`in`.UploadNoticeImageUseCase +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import jakarta.validation.Valid +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile +import java.util.UUID + +/** + * 공지사항 관련 HTTP 요청을 처리하는 웹 어댑터 클래스입니다. + * + * 이 클래스는 공지사항과 관련된 모든 HTTP 엔드포인트를 제공하며, + * 클라이언트의 요청을 적절한 서비스 메서드로 라우팅합니다. + * + * @property noticeService 공지사항 비즈니스 로직을 처리하는 서비스 + */ +@RestController +@RequestMapping("/notice") +class NoticeWebAdapter( + private val createNoticeUseCase: CreateNoticeUseCase, + private val updateNoticeUseCase: UpdateNoticeUseCase, + private val deleteNoticeUseCase: DeleteNoticeUseCase, + private val queryDetailsNoticeUseCase: QueryDetailsNoticeUseCase, + private val queryNoticeTitleUseCase: QueryNoticeTitleUseCase, + private val uploadNoticeImageUseCase: UploadNoticeImageUseCase, + private val queryListNoticeListByTypeUseCase: QueryNoticeListByTypeUseCase, +) { + /** + * 새로운 공지사항을 생성합니다. + * + * @param createNoticeRequest 공지사항 생성 요청 데이터 + */ + @ResponseStatus(value = HttpStatus.CREATED) + @PostMapping + fun createNotice( + @RequestBody @Valid + createNoticeRequest: CreateNoticeRequest, + ) { + createNoticeUseCase.execute(createNoticeRequest) + } + + /** + * 기존 공지사항을 수정합니다. + * + * @param id 수정할 공지사항의 고유 식별자 + * @param updateNoticeRequest 공지사항 수정 요청 데이터 + * @return 수정 결과에 대한 응답 엔티티 + */ + @PatchMapping("/{notice-id}") + fun updateNotice( + @PathVariable(name = "notice-id") id: UUID, + @RequestBody updateNoticeRequest: UpdateNoticeRequest, + ): ResponseEntity = updateNoticeUseCase.execute(id, updateNoticeRequest) + + /** + * 공지사항에 첨부할 이미지를 업로드합니다. + * + * @param image 업로드할 이미지 파일 + * @return 업로드된 이미지 정보가 포함된 응답 객체 + */ + @PostMapping("/image") + fun uploadImage( + @RequestPart(name = "photo") image: MultipartFile, + ): UploadNoticeImageResponse = uploadNoticeImageUseCase.execute(image) + + /** + * 모든 공지사항의 제목 목록을 조회합니다. + * + * @return 공지사항 제목 목록이 포함된 응답 객체 리스트 + */ + @GetMapping("/title") + fun queryNoticeTitle(): List = queryNoticeTitleUseCase.execute() + + /** + * 특정 공지사항의 상세 정보를 조회합니다. + * + * @param noticeId 조회할 공지사항의 고유 식별자 + * @return 공지사항 상세 정보가 포함된 응답 객체 + */ + @GetMapping("/{notice-id}") + fun queryDetailsNotice( + @PathVariable(name = "notice-id", required = true) + noticeId: UUID, + ): QueryDetailsNoticeResponse = queryDetailsNoticeUseCase.execute(noticeId) + + /** + * 특정 유형의 공지사항 목록을 조회합니다. + * + * @param noticeType 조회할 공지사항 유형 (선택 사항) + * @return 해당 유형의 공지사항 목록이 포함된 응답 객체 + */ + @GetMapping + fun queryNoticeListByType( + @RequestParam("type") noticeType: NoticeType?, + ): QueryListNoticeResponse = queryListNoticeListByTypeUseCase.execute(noticeType) + + /** + * 특정 공지사항을 삭제합니다. + * + * @param id 삭제할 공지사항의 고유 식별자 + */ + @ResponseStatus(value = HttpStatus.NO_CONTENT) + @DeleteMapping("/{notice-id}") + fun deleteNotice( + @PathVariable(name = "notice-id")id: UUID, + ) = deleteNoticeUseCase.execute(id) +} From 331abb21b3373f92b4366dfc08cdcf841fa2ed1e Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:25:41 +0900 Subject: [PATCH 34/49] feat ( #8 ) : NoticeJpaEntity --- .../out/entity/notice/NoticeJpaEntity.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/entity/notice/NoticeJpaEntity.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/entity/notice/NoticeJpaEntity.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/entity/notice/NoticeJpaEntity.kt new file mode 100644 index 0000000..0454100 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/entity/notice/NoticeJpaEntity.kt @@ -0,0 +1,46 @@ +package hs.kr.entrydsm.feed.adapter.out.entity.notice + +import hs.kr.entrydsm.feed.adapter.out.entity.attachFile.AttachFileJpaEntity +import hs.kr.entrydsm.feed.global.entity.BaseEntity +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.JoinColumn +import jakarta.persistence.OneToMany +import java.util.* + +/** + * 공지사항 정보를 데이터베이스에 저장하기 위한 JPA 엔티티 클래스입니다. + * + * @property title 공지사항 제목 (최대 100자) + * @property content 공지사항 내용 (최대 5000자) + * @property fileName 첨부 파일명 (선택 사항) + * @property attachFile 공지사항에 첨부된 파일 목록 (Lazy 로딩) + * @property adminId 공지사항을 작성한 관리자 ID (UUID) + * @property isPinned 상단 고정 여부 + * @property type 공지사항 유형 (NoticeType ENUM) + * @param id 엔티티의 고유 식별자 (생성 시 자동 생성됨) + */ +@Entity(name = "tbl_notice") +class NoticeJpaEntity( + id: UUID? = null, + @Column(name = "title", length = 100, nullable = false) + var title: String, + @Column(name = "content", length = 5000, nullable = false) + var content: String, + @Column(name = "file_name", nullable = true) + var fileName: String? = null, + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn(name = "noticeId") + var attachFile: List? = emptyList(), + @Column(name = "admin_id", nullable = false, columnDefinition = "BINARY(16)") + var adminId: UUID, + @Column(nullable = false) + var isPinned: Boolean, + @Column(nullable = false) + @Enumerated(value = EnumType.STRING) + var type: NoticeType, +) : BaseEntity(id) From cb3de54c9c076ca8d3453ab2a54a905bafad0a98 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:25:52 +0900 Subject: [PATCH 35/49] feat ( #8 ) : NoticeMapper --- .../adapter/out/mapper/notice/NoticeMapper.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/mapper/notice/NoticeMapper.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/mapper/notice/NoticeMapper.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/mapper/notice/NoticeMapper.kt new file mode 100644 index 0000000..44a4bd2 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/mapper/notice/NoticeMapper.kt @@ -0,0 +1,28 @@ +package hs.kr.entrydsm.feed.adapter.out.mapper.notice + +import hs.kr.entrydsm.feed.adapter.out.entity.notice.NoticeJpaEntity +import hs.kr.entrydsm.feed.model.notice.Notice +import org.mapstruct.Mapper + +/** + * Notice 도메인 모델과 JPA 엔티티 간의 변환을 담당하는 매퍼 인터페이스입니다. + * MapStruct를 사용하여 구현체가 자동으로 생성됩니다. + */ +@Mapper(componentModel = "spring") +interface NoticeMapper { + /** + * Notice 도메인 모델을 JPA 엔티티로 변환합니다. + * + * @param model 변환할 Notice 도메인 모델 + * @return 변환된 NoticeJpaEntity 인스턴스 + */ + fun toEntity(model: Notice): NoticeJpaEntity + + /** + * JPA 엔티티를 Notice 도메인 모델로 변환합니다. + * + * @param entity 변환할 NoticeJpaEntity 인스턴스 + * @return 변환된 Notice 도메인 모델 + */ + fun toModel(entity: NoticeJpaEntity): Notice +} From e0d5d1083e8d5e9332821b31e771c414d15d5451 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:26:08 +0900 Subject: [PATCH 36/49] feat ( #8 ) : NoticeRepository --- .../notice/repository/NoticeRepository.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/repository/NoticeRepository.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/repository/NoticeRepository.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/repository/NoticeRepository.kt new file mode 100644 index 0000000..3d7d81c --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/repository/NoticeRepository.kt @@ -0,0 +1,20 @@ +package hs.kr.entrydsm.feed.adapter.out.persistence.notice.repository + +import hs.kr.entrydsm.feed.adapter.out.entity.notice.NoticeJpaEntity +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import org.springframework.data.jpa.repository.JpaRepository +import java.util.UUID + +/** + * 공지사항 데이터에 접근하기 위한 JPA Repository 인터페이스입니다. + * 공지사항 엔티티의 CRUD 및 커스텀 쿼리 메서드를 제공합니다. + */ +interface NoticeRepository : JpaRepository { + /** + * 주어진 유형에 해당하는 모든 공지사항 엔티티를 조회합니다. + * + * @param type 조회할 공지사항 유형 + * @return 조회된 공지사항 엔티티 목록 (없을 경우 빈 목록 반환) + */ + fun findAllByType(type: NoticeType): List +} From 9614481cabec0f18ae84f15ed0cb4c2ee4e066f2 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:26:46 +0900 Subject: [PATCH 37/49] feat ( #8 ) : NoticePersistenceAdapter --- .../notice/NoticePersistenceAdapter.kt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/NoticePersistenceAdapter.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/NoticePersistenceAdapter.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/NoticePersistenceAdapter.kt new file mode 100644 index 0000000..68ed711 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/NoticePersistenceAdapter.kt @@ -0,0 +1,45 @@ +package hs.kr.entrydsm.feed.adapter.out.persistence.notice + +import hs.kr.entrydsm.feed.adapter.out.mapper.notice.NoticeMapper +import hs.kr.entrydsm.feed.adapter.out.persistence.notice.repository.NoticeRepository +import hs.kr.entrydsm.feed.application.notice.port.out.DeleteNoticePort +import hs.kr.entrydsm.feed.application.notice.port.out.FindNoticePort +import hs.kr.entrydsm.feed.application.notice.port.out.SaveNoticePort +import hs.kr.entrydsm.feed.model.notice.Notice +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Component +import java.util.UUID + +/** + * 공지사항 도메인과 데이터베이스 간의 상호작용을 담당하는 어댑터 클래스입니다. + * + * @property noticeRepository 공지사항 엔티티를 데이터베이스에서 조작하기 위한 리포지토리 + * @property noticeMapper 공지사항 도메인 객체와 엔티티 간의 변환을 담당하는 매퍼 + */ +@Component +class NoticePersistenceAdapter( + private val noticeRepository: NoticeRepository, + private val noticeMapper: NoticeMapper, +) : SaveNoticePort, FindNoticePort, DeleteNoticePort { + override fun saveNotice(notice: Notice) { + noticeRepository.save(noticeMapper.toEntity(notice)) + } + + override fun findByIdOrNull(noticeId: UUID): Notice? = + noticeRepository.findByIdOrNull(noticeId)?.let { + noticeMapper.toModel(it) + } + + override fun deleteNotice(notice: Notice) { + noticeRepository.delete(noticeMapper.toEntity(notice)) + } + + override fun findAll(): List { + return noticeRepository.findAll().map { noticeMapper.toModel(it) } + } + + override fun findAllByType(noticeType: NoticeType): List { + return noticeRepository.findAllByType(noticeType).map { noticeMapper.toModel(it) } + } +} From 13d4581730e7f3a2fed21381c21c41f0d741c501 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:28:21 +0900 Subject: [PATCH 38/49] =?UTF-8?q?feat=20(=20#8=20)=20:=20Notice=20in=20por?= =?UTF-8?q?t=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notice/port/in/CreateNoticeUseCase.kt | 16 +++++++++++++ .../notice/port/in/DeleteNoticeUseCase.kt | 16 +++++++++++++ .../port/in/QueryDetailsNoticeUseCase.kt | 18 +++++++++++++++ .../port/in/QueryNoticeListByTypeUseCase.kt | 18 +++++++++++++++ .../notice/port/in/QueryNoticeTitleUseCase.kt | 16 +++++++++++++ .../notice/port/in/UpdateNoticeUseCase.kt | 23 +++++++++++++++++++ .../port/in/UploadNoticeImageUseCase.kt | 18 +++++++++++++++ 7 files changed, 125 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/CreateNoticeUseCase.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/DeleteNoticeUseCase.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryDetailsNoticeUseCase.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryNoticeListByTypeUseCase.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryNoticeTitleUseCase.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/UpdateNoticeUseCase.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/UploadNoticeImageUseCase.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/CreateNoticeUseCase.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/CreateNoticeUseCase.kt new file mode 100644 index 0000000..896478c --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/CreateNoticeUseCase.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.feed.application.notice.port.`in` + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.request.CreateNoticeRequest + +/** + * 공지사항 생성을 위한 유스케이스 인터페이스입니다. + * 공지사항 도메인에서 새로운 공지사항을 생성하는 역할을 담당합니다. + */ +interface CreateNoticeUseCase { + /** + * 새로운 공지사항을 생성합니다. + * + * @param request 공지사항 생성 요청 데이터 + */ + fun execute(request: CreateNoticeRequest) +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/DeleteNoticeUseCase.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/DeleteNoticeUseCase.kt new file mode 100644 index 0000000..cb0fb78 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/DeleteNoticeUseCase.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.feed.application.notice.port.`in` + +import java.util.UUID + +/** + * 공지사항 삭제를 위한 유스케이스 인터페이스입니다. + * 공지사항 도메인에서 특정 공지사항을 삭제하는 역할을 담당합니다. + */ +interface DeleteNoticeUseCase { + /** + * 특정 공지사항을 삭제합니다. + * + * @param noticeId 삭제할 공지사항의 고유 식별자 + */ + fun execute(noticeId: UUID) +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryDetailsNoticeUseCase.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryDetailsNoticeUseCase.kt new file mode 100644 index 0000000..610d870 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryDetailsNoticeUseCase.kt @@ -0,0 +1,18 @@ +package hs.kr.entrydsm.feed.application.notice.port.`in` + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.QueryDetailsNoticeResponse +import java.util.UUID + +/** + * 공지사항 상세 조회를 위한 유스케이스 인터페이스입니다. + * 공지사항 도메인에서 특정 공지사항의 상세 정보를 조회하는 역할을 담당합니다. + */ +interface QueryDetailsNoticeUseCase { + /** + * 특정 공지사항의 상세 정보를 조회합니다. + * + * @param noticeId 조회할 공지사항의 고유 식별자 + * @return 공지사항 상세 정보 응답 + */ + fun execute(noticeId: UUID): QueryDetailsNoticeResponse +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryNoticeListByTypeUseCase.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryNoticeListByTypeUseCase.kt new file mode 100644 index 0000000..dd1c98b --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryNoticeListByTypeUseCase.kt @@ -0,0 +1,18 @@ +package hs.kr.entrydsm.feed.application.notice.port.`in` + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.QueryListNoticeResponse +import hs.kr.entrydsm.feed.model.notice.type.NoticeType + +/** + * 유형별 공지사항 목록 조회를 위한 유스케이스 인터페이스입니다. + * 공지사항 도메인에서 특정 유형의 공지사항 목록을 조회하는 역할을 담당합니다. + */ +interface QueryNoticeListByTypeUseCase { + /** + * 특정 유형의 공지사항 목록을 조회합니다. + * + * @param noticeType 조회할 공지사항 유형 (null인 경우 모든 유형 포함) + * @return 공지사항 목록 응답 + */ + fun execute(noticeType: NoticeType?): QueryListNoticeResponse +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryNoticeTitleUseCase.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryNoticeTitleUseCase.kt new file mode 100644 index 0000000..fbd6a46 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/QueryNoticeTitleUseCase.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.feed.application.notice.port.`in` + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.QueryNoticeTitleResponse + +/** + * 공지사항 제목 조회를 위한 유스케이스 인터페이스입니다. + * 공지사항 도메인에서 공지사항의 제목 목록을 조회하는 역할을 담당합니다. + */ +interface QueryNoticeTitleUseCase { + /** + * 공지사항 제목 목록을 조회합니다. + * + * @return 공지사항 제목 목록 응답 + */ + fun execute(): List +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/UpdateNoticeUseCase.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/UpdateNoticeUseCase.kt new file mode 100644 index 0000000..cee04a8 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/UpdateNoticeUseCase.kt @@ -0,0 +1,23 @@ +package hs.kr.entrydsm.feed.application.notice.port.`in` + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.request.UpdateNoticeRequest +import org.springframework.http.ResponseEntity +import java.util.UUID + +/** + * 공지사항 수정을 위한 유스케이스 인터페이스입니다. + * 공지사항 도메인에서 기존 공지사항을 수정하는 역할을 담당합니다. + */ +interface UpdateNoticeUseCase { + /** + * 특정 공지사항을 수정합니다. + * + * @param noticeId 수정할 공지사항의 고유 식별자 + * @param request 공지사항 수정 요청 데이터 + * @return 수정 결과 응답 엔티티 + */ + fun execute( + noticeId: UUID, + request: UpdateNoticeRequest, + ): ResponseEntity +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/UploadNoticeImageUseCase.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/UploadNoticeImageUseCase.kt new file mode 100644 index 0000000..9f204dc --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/in/UploadNoticeImageUseCase.kt @@ -0,0 +1,18 @@ +package hs.kr.entrydsm.feed.application.notice.port.`in` + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.UploadNoticeImageResponse +import org.springframework.web.multipart.MultipartFile + +/** + * 공지사항 이미지 업로드를 위한 유스케이스 인터페이스입니다. + * 공지사항 도메인에서 공지사항에 첨부할 이미지를 업로드하는 역할을 담당합니다. + */ +interface UploadNoticeImageUseCase { + /** + * 공지사항에 첨부할 이미지를 업로드합니다. + * + * @param image 업로드할 이미지 파일 + * @return 이미지 업로드 결과 응답 + */ + fun execute(image: MultipartFile): UploadNoticeImageResponse +} From 82575d366dda5c358a092bcc548f9de266c1351e Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:28:41 +0900 Subject: [PATCH 39/49] =?UTF-8?q?feat=20(=20#8=20)=20:=20Notice=20out=20po?= =?UTF-8?q?rt=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notice/port/out/DeleteNoticePort.kt | 16 +++++++++ .../notice/port/out/FindNoticePort.kt | 34 +++++++++++++++++++ .../notice/port/out/SaveNoticePort.kt | 16 +++++++++ 3 files changed, 66 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/DeleteNoticePort.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/FindNoticePort.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/SaveNoticePort.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/DeleteNoticePort.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/DeleteNoticePort.kt new file mode 100644 index 0000000..e0bce98 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/DeleteNoticePort.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.feed.application.notice.port.out + +import hs.kr.entrydsm.feed.model.notice.Notice + +/** + * 공지사항 삭제를 위한 포트 인터페이스입니다. + * 공지사항 도메인 객체를 삭제하는 메서드를 정의합니다. + */ +interface DeleteNoticePort { + /** + * 공지사항을 삭제합니다. + * + * @param notice 삭제할 공지사항 도메인 객체 + */ + fun deleteNotice(notice: Notice) +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/FindNoticePort.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/FindNoticePort.kt new file mode 100644 index 0000000..28fecd7 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/FindNoticePort.kt @@ -0,0 +1,34 @@ +package hs.kr.entrydsm.feed.application.notice.port.out + +import hs.kr.entrydsm.feed.model.notice.Notice +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import java.util.UUID + +/** + * 공지사항 조회를 위한 포트 인터페이스입니다. + * 공지사항 도메인 객체를 다양한 방식으로 조회하는 메서드를 정의합니다. + */ +interface FindNoticePort { + /** + * 공지사항 ID로 공지사항을 조회합니다. + * + * @param noticeId 조회할 공지사항의 고유 식별자 + * @return 조회된 공지사항 객체, 없을 경우 null + */ + fun findByIdOrNull(noticeId: UUID): Notice? + + /** + * 특정 유형의 모든 공지사항을 조회합니다. + * + * @param noticeType 조회할 공지사항 유형 + * @return 해당 유형의 공지사항 목록 (없을 경우 빈 목록) + */ + fun findAllByType(noticeType: NoticeType): List + + /** + * 모든 공지사항을 조회합니다. + * + * @return 모든 공지사항 목록 (없을 경우 빈 목록) + */ + fun findAll(): List +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/SaveNoticePort.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/SaveNoticePort.kt new file mode 100644 index 0000000..3a54122 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/port/out/SaveNoticePort.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.feed.application.notice.port.out + +import hs.kr.entrydsm.feed.model.notice.Notice + +/** + * 공지사항 저장을 위한 포트 인터페이스입니다. + * 공지사항 도메인 객체를 저장하거나 업데이트하는 메서드를 정의합니다. + */ +interface SaveNoticePort { + /** + * 공지사항을 저장하거나 업데이트합니다. + * + * @param notice 저장하거나 업데이트할 공지사항 도메인 객체 + */ + fun saveNotice(notice: Notice) +} From a27239802bb864a68be237ddfea999c7b85d7ba0 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:29:09 +0900 Subject: [PATCH 40/49] =?UTF-8?q?feat=20(=20#8=20)=20:=20Notice=20Service?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notice/service/CreateNoticeService.kt | 54 ++++++++++++ .../notice/service/DeleteNoticeService.kt | 31 +++++++ .../service/QueryDetailsNoticeService.kt | 69 +++++++++++++++ .../service/QueryNoticeListByTypeService.kt | 53 ++++++++++++ .../notice/service/QueryNoticeTitleService.kt | 33 ++++++++ .../notice/service/UpdateNoticeService.kt | 83 +++++++++++++++++++ .../service/UploadNoticeImageService.kt | 31 +++++++ 7 files changed, 354 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/CreateNoticeService.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/DeleteNoticeService.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryDetailsNoticeService.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryNoticeListByTypeService.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryNoticeTitleService.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/UpdateNoticeService.kt create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/UploadNoticeImageService.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/CreateNoticeService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/CreateNoticeService.kt new file mode 100644 index 0000000..f3f7f69 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/CreateNoticeService.kt @@ -0,0 +1,54 @@ +package hs.kr.entrydsm.feed.application.notice.service + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.request.CreateNoticeRequest +import hs.kr.entrydsm.feed.application.attachFile.port.out.FindAttachFilePort +import hs.kr.entrydsm.feed.application.notice.exception.AttachFileNotFoundException +import hs.kr.entrydsm.feed.application.notice.port.`in`.CreateNoticeUseCase +import hs.kr.entrydsm.feed.application.notice.port.out.SaveNoticePort +import hs.kr.entrydsm.feed.global.utils.admin.AdminUtils +import hs.kr.entrydsm.feed.model.notice.Notice +import org.springframework.stereotype.Service + +/** + * 공지사항 생성을 처리하는 서비스 클래스입니다. + * + * @property adminUtils 관리자 인증 유틸리티 + * @property findAttachFilePort 첨부 파일 조회를 위한 포트 + * @property saveNoticePort 공지사항 저장을 위한 포트 + */ +@Service +class CreateNoticeService( + private val adminUtils: AdminUtils, + private val findAttachFilePort: FindAttachFilePort, + private val saveNoticePort: SaveNoticePort, +) : CreateNoticeUseCase { + /** + * 새로운 공지사항을 생성합니다. + * + * @param request 공지사항 생성 요청 데이터 + * @throws hs.kr.entrydsm.feed.application.notice.exception.AttachFileNotFoundException 첨부 파일을 찾을 수 없는 경우 + * @throws hs.kr.entrydsm.feed.global.exception.UnauthorizedException 관리자 인증에 실패한 경우 + */ + override fun execute(request: CreateNoticeRequest) { + val admin = adminUtils.getCurrentAdminId() + val attachFiles = + request.attachFileName?.let { fileNames -> + fileNames.flatMap { fileName -> + val files = findAttachFilePort.findByOriginalAttachFileName(fileName) + files ?: throw AttachFileNotFoundException + } + } ?: emptyList() + + saveNoticePort.saveNotice( + Notice( + title = request.title, + content = request.content, + type = request.type, + isPinned = request.isPinned, + adminId = admin, + fileName = request.fileName, + attachFile = attachFiles, + ), + ) + } +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/DeleteNoticeService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/DeleteNoticeService.kt new file mode 100644 index 0000000..9401424 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/DeleteNoticeService.kt @@ -0,0 +1,31 @@ +package hs.kr.entrydsm.feed.application.notice.service + +import hs.kr.entrydsm.feed.application.notice.exception.NoticeNotFoundException +import hs.kr.entrydsm.feed.application.notice.port.`in`.DeleteNoticeUseCase +import hs.kr.entrydsm.feed.application.notice.port.out.DeleteNoticePort +import hs.kr.entrydsm.feed.application.notice.port.out.FindNoticePort +import org.springframework.stereotype.Service +import java.util.UUID + +/** + * 공지사항 삭제를 처리하는 서비스 클래스입니다. + * + * @property findNoticePort 공지사항 조회를 위한 포트 + * @property deleteNoticePort 공지사항 삭제를 위한 포트 + */ +@Service +class DeleteNoticeService( + private val findNoticePort: FindNoticePort, + private val deleteNoticePort: DeleteNoticePort, +) : DeleteNoticeUseCase { + /** + * 지정된 ID의 공지사항을 삭제합니다. + * + * @param noticeId 삭제할 공지사항의 고유 식별자 + * @throws hs.kr.entrydsm.feed.application.notice.exception.NoticeNotFoundException 지정된 ID의 공지사항을 찾을 수 없는 경우 + */ + override fun execute(noticeId: UUID) { + val notice = findNoticePort.findByIdOrNull(noticeId) ?: throw NoticeNotFoundException + deleteNoticePort.deleteNotice(notice) + } +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryDetailsNoticeService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryDetailsNoticeService.kt new file mode 100644 index 0000000..6475d81 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryDetailsNoticeService.kt @@ -0,0 +1,69 @@ +package hs.kr.entrydsm.feed.application.notice.service + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.AttachFileElement +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.QueryDetailsNoticeResponse +import hs.kr.entrydsm.feed.application.notice.exception.NoticeNotFoundException +import hs.kr.entrydsm.feed.application.notice.port.`in`.QueryDetailsNoticeUseCase +import hs.kr.entrydsm.feed.application.notice.port.out.FindNoticePort +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import org.springframework.stereotype.Service +import java.util.UUID + +/** + * 공지사항 상세 조회를 처리하는 서비스 클래스입니다. + * + * @property findNoticePort 공지사항 조회를 위한 포트 + * @property fileUtil 파일 URL 생성을 위한 유틸리티 + */ +@Service +class QueryDetailsNoticeService( + private val findNoticePort: FindNoticePort, + private val fileUtil: FileUtil, +) : QueryDetailsNoticeUseCase { + /** + * 공지사항의 상세 정보를 조회합니다. + * + * @param noticeId 조회할 공지사항의 고유 식별자 + * @return 공지사항 상세 정보 응답 + * @throws hs.kr.entrydsm.feed.application.notice.exception.NoticeNotFoundException 지정된 ID의 공지사항을 찾을 수 없는 경우 + */ + override fun execute(noticeId: UUID): QueryDetailsNoticeResponse { + val notice = findNoticePort.findByIdOrNull(noticeId) ?: throw NoticeNotFoundException + val imageURL = notice.fileName?.let { getUrl(it, PathList.NOTICE) } + + val attachFile = + notice.attachFile?.map { + // 첨부파일 이름과 url을 묶어서 여러개 반환하기때문에 List로 묶는다 + AttachFileElement( + attachFileUrl = getUrl(it.uploadedFileName, PathList.ATTACH_FILE), + attachFileName = it.originalAttachFileName, + ) + } + + return notice.run { + QueryDetailsNoticeResponse( + title = title, + content = content, + createdAt = createdAt, + type = type, + imageURL = imageURL, + imageName = fileName, + attachFiles = attachFile!!, + isPinned = isPinned, + ) + } + } + + /** + * 파일 경로와 파일명으로 URL을 생성합니다. + * + * @param file 파일명 + * @param path 파일 경로 + * @return 생성된 파일 URL + */ + private fun getUrl( + file: String, + path: String, + ) = fileUtil.generateObjectUrl(file, path) +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryNoticeListByTypeService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryNoticeListByTypeService.kt new file mode 100644 index 0000000..39dc2bf --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryNoticeListByTypeService.kt @@ -0,0 +1,53 @@ +package hs.kr.entrydsm.feed.application.notice.service + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.NoticeResponse +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.QueryListNoticeResponse +import hs.kr.entrydsm.feed.application.notice.port.`in`.QueryNoticeListByTypeUseCase +import hs.kr.entrydsm.feed.application.notice.port.out.FindNoticePort +import hs.kr.entrydsm.feed.model.notice.Notice +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import org.springframework.stereotype.Service + +/** + * 유형별 공지사항 목록 조회를 처리하는 서비스 클래스입니다. + * + * @property findNoticePort 공지사항 조회를 위한 포트 + */ +@Service +class QueryNoticeListByTypeService( + private val findNoticePort: FindNoticePort, +) : QueryNoticeListByTypeUseCase { + /** + * 공지사항 목록을 조회합니다. 유형별로 필터링할 수 있습니다. + * + * @param noticeType 조회할 공지사항 유형 (null인 경우 모든 유형 조회) + * @return 공지사항 목록 응답 (고정 공지가 상단에 정렬됨) + */ + override fun execute(noticeType: NoticeType?): QueryListNoticeResponse { + val notices = + getNoticeList(noticeType).map { it -> + NoticeResponse( + id = it.id!!, + title = it.title, + isPinned = it.isPinned, + type = it.type, + createdAt = it.createdAt, + ) + }.sortedWith( + compareByDescending { it.isPinned } + .thenByDescending { it.createdAt }, + ) + + return QueryListNoticeResponse(notices) + } + + /** + * 공지사항 목록을 조회합니다. 유형별로 필터링할 수 있습니다. + * + * @param noticeType 조회할 공지사항 유형 (null인 경우 모든 유형 조회) + * @return 공지사항 목록 + */ + private fun getNoticeList(noticeType: NoticeType?): List { + return noticeType?.let { findNoticePort.findAllByType(it) } ?: findNoticePort.findAll() + } +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryNoticeTitleService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryNoticeTitleService.kt new file mode 100644 index 0000000..20445e5 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/QueryNoticeTitleService.kt @@ -0,0 +1,33 @@ +package hs.kr.entrydsm.feed.application.notice.service + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.QueryNoticeTitleResponse +import hs.kr.entrydsm.feed.application.notice.port.`in`.QueryNoticeTitleUseCase +import hs.kr.entrydsm.feed.application.notice.port.out.FindNoticePort +import org.springframework.stereotype.Service + +/** + * 공지사항 제목 조회를 처리하는 서비스 클래스입니다. + * + * @property findNoticePort 공지사항 조회를 위한 포트 + */ +@Service +class QueryNoticeTitleService( + private val findNoticePort: FindNoticePort, +) : QueryNoticeTitleUseCase { + /** + * 모든 공지사항의 제목과 작성일을 조회합니다. + * + * @return 공지사항 제목 목록 (최신순으로 정렬) + */ + override fun execute(): List { + return findNoticePort.findAll() + .map { + it -> + QueryNoticeTitleResponse( + id = it.id!!, + title = it.title, + it.createdAt, + ) + }.sortedByDescending { it.createdAt } + } +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/UpdateNoticeService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/UpdateNoticeService.kt new file mode 100644 index 0000000..7a21bec --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/UpdateNoticeService.kt @@ -0,0 +1,83 @@ +package hs.kr.entrydsm.feed.application.notice.service + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.request.UpdateNoticeRequest +import hs.kr.entrydsm.feed.application.attachFile.port.out.FindAttachFilePort +import hs.kr.entrydsm.feed.application.notice.exception.AttachFileNotFoundException +import hs.kr.entrydsm.feed.application.notice.exception.NoticeNotFoundException +import hs.kr.entrydsm.feed.application.notice.port.`in`.UpdateNoticeUseCase +import hs.kr.entrydsm.feed.application.notice.port.out.FindNoticePort +import hs.kr.entrydsm.feed.global.utils.admin.AdminUtils +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import hs.kr.entrydsm.feed.model.attachFile.AttachFile +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Service +import java.util.UUID + +/** + * 공지사항 수정을 처리하는 서비스 클래스입니다. + * + * @property findNoticePort 공지사항 조회를 위한 포트 + * @property fileUtil 파일 업로드 유틸리티 + * @property adminUtils 관리자 인증 유틸리티 + * @property findAttachFilePort 첨부 파일 조회를 위한 포트 + */ +@Service +class UpdateNoticeService( + private val findNoticePort: FindNoticePort, + private val fileUtil: FileUtil, + private val adminUtils: AdminUtils, + private val findAttachFilePort: FindAttachFilePort, +) : UpdateNoticeUseCase { + /** + * 공지사항을 수정합니다. + * + * @param noticeId 수정할 공지사항의 고유 식별자 + * @param request 공지사항 수정 요청 데이터 + * @return 수정된 이미지 URL이 포함된 응답 (이미지가 없는 경우 NO_CONTENT) + * @throws hs.kr.entrydsm.feed.application.notice.exception.NoticeNotFoundException 지정된 ID의 공지사항을 찾을 수 없는 경우 + * @throws hs.kr.entrydsm.feed.application.notice.exception.AttachFileNotFoundException 첨부 파일을 찾을 수 없는 경우 + * @throws hs.kr.entrydsm.feed.global.exception.UnauthorizedException 관리자 인증에 실패한 경우 + */ + override fun execute( + noticeId: UUID, + request: UpdateNoticeRequest, + ): ResponseEntity { + val adminId = adminUtils.getCurrentAdminId() + + val notice = findNoticePort.findByIdOrNull(noticeId) ?: throw NoticeNotFoundException + val fileName = request.fileName + val attachFiles = findAttachFiles(request.attachFileName) + + request.run { + notice.updateNotice( + newTitle = title, + newContent = content, + newIsPinned = isPinned, + newType = type, + newFileName = fileName, + newAdminId = adminId, + newAttachFile = attachFiles, + ) + } + + return fileName?.let { + ResponseEntity.ok(fileUtil.generateObjectUrl(it, PathList.NOTICE)) + } ?: ResponseEntity(HttpStatus.NO_CONTENT) + } + + /** + * 파일명 목록으로 첨부 파일 목록을 조회합니다. + * + * @param fileNameList 조회할 파일명 목록 (null인 경우 빈 목록 반환) + * @return 조회된 첨부 파일 목록 + * @throws hs.kr.entrydsm.feed.application.notice.exception.AttachFileNotFoundException 첨부 파일을 찾을 수 없는 경우 + */ + private fun findAttachFiles(fileNameList: List?): List { + return fileNameList?.flatMap { fileName -> + val fileList = findAttachFilePort.findByOriginalAttachFileName(fileName) + fileList ?: throw AttachFileNotFoundException + } ?: emptyList() + } +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/UploadNoticeImageService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/UploadNoticeImageService.kt new file mode 100644 index 0000000..1cc5d5b --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/notice/service/UploadNoticeImageService.kt @@ -0,0 +1,31 @@ +package hs.kr.entrydsm.feed.application.notice.service + +import hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response.UploadNoticeImageResponse +import hs.kr.entrydsm.feed.application.notice.port.`in`.UploadNoticeImageUseCase +import hs.kr.entrydsm.feed.infrastructure.s3.PathList +import hs.kr.entrydsm.feed.infrastructure.s3.util.FileUtil +import jakarta.transaction.Transactional +import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile + +/** + * 공지사항 이미지 업로드를 처리하는 서비스 클래스입니다. + * + * @property fileUtil 파일 업로드 유틸리티 + */ +@Transactional +@Service +class UploadNoticeImageService( + private val fileUtil: FileUtil, +) : UploadNoticeImageUseCase { + /** + * 공지사항에 첨부할 이미지를 업로드합니다. + * + * @param image 업로드할 이미지 파일 + * @return 업로드된 이미지의 URL과 파일명이 포함된 응답 + */ + override fun execute(image: MultipartFile): UploadNoticeImageResponse { + val fileName = fileUtil.upload(image, PathList.NOTICE) + return UploadNoticeImageResponse(fileUtil.generateObjectUrl(fileName, PathList.NOTICE), fileName) + } +} From 1215abef9d98b145d514925591d02cd0fdb5576d Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 17:41:45 +0900 Subject: [PATCH 41/49] =?UTF-8?q?refactor=20(=20#8=20)=20:=20CreateAttachF?= =?UTF-8?q?ileResponse=20data=20class=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/attachFile/dto/response/CreateAttachFileResponse.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/dto/response/CreateAttachFileResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/dto/response/CreateAttachFileResponse.kt index 6d0d165..edf81da 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/dto/response/CreateAttachFileResponse.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/dto/response/CreateAttachFileResponse.kt @@ -6,7 +6,7 @@ package hs.kr.entrydsm.feed.adapter.`in`.attachFile.dto.response * @property fileName 원본 파일 이름 * @property url 업로드된 파일에 접근할 수 있는 URL */ -class CreateAttachFileResponse( +data class CreateAttachFileResponse( val fileName: String, val url: String, ) From 731b4897c98f88246306145f9ddc252d39648b02 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 18:00:47 +0900 Subject: [PATCH 42/49] docs ( #8 ) : kdoc --- .../in/notice/dto/response/NoticeResponse.kt | 9 +++++++++ .../dto/response/QueryDetailsNoticeResponse.kt | 18 ++++++++++++++++++ .../dto/response/QueryListNoticeResponse.kt | 6 ++++++ .../dto/response/QueryNoticeTitleResponse.kt | 8 ++++++++ .../dto/response/UploadNoticeImageResponse.kt | 6 ++++++ 5 files changed, 47 insertions(+) diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/NoticeResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/NoticeResponse.kt index 811d830..a152a78 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/NoticeResponse.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/NoticeResponse.kt @@ -4,6 +4,15 @@ import hs.kr.entrydsm.feed.model.notice.type.NoticeType import java.time.LocalDateTime import java.util.* +/** + * 공지사항 목록 조회 응답을 위한 데이터 클래스입니다. + * + * @property id 공지사항 고유 식별자 + * @property title 공지사항 제목 + * @property type 공지사항 유형 + * @property isPinned 공지사항 고정 여부 + * @property createdAt 공지사항 생성 일시 + */ data class NoticeResponse( val id: UUID, val title: String, diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryDetailsNoticeResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryDetailsNoticeResponse.kt index cf0fc78..dead3d3 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryDetailsNoticeResponse.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryDetailsNoticeResponse.kt @@ -3,6 +3,18 @@ package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response import hs.kr.entrydsm.feed.model.notice.type.NoticeType import java.time.LocalDateTime +/** + * 공지사항 상세 조회 응답을 위한 데이터 클래스입니다. + * + * @property title 공지사항 제목 + * @property content 공지사항 내용 + * @property createdAt 공지사항 생성 일시 + * @property type 공지사항 유형 + * @property imageURL 공지사항에 첨부된 이미지 URL (없을 수 있음) + * @property imageName 공지사항에 첨부된 이미지 파일명 (없을 수 있음) + * @property attachFiles 공지사항에 첨부된 파일 목록 (없을 수 있음) + * @property isPinned 공지사항 고정 여부 + */ data class QueryDetailsNoticeResponse( val title: String, val content: String, @@ -14,6 +26,12 @@ data class QueryDetailsNoticeResponse( val isPinned: Boolean, ) +/** + * 공지사항에 첨부된 파일 정보를 나타내는 데이터 클래스입니다. + * + * @property attachFileUrl 첨부 파일 다운로드 URL + * @property attachFileName 첨부 파일 원본 이름 + */ data class AttachFileElement( val attachFileUrl: String, val attachFileName: String, diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryListNoticeResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryListNoticeResponse.kt index eac8d8f..91ac602 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryListNoticeResponse.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryListNoticeResponse.kt @@ -1,5 +1,11 @@ package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response +/** + * 공지사항 목록 조회 응답을 위한 데이터 클래스입니다. + * 여러 개의 공지사항 정보를 리스트 형태로 반환할 때 사용됩니다. + * + * @property notices 공지사항 정보 목록 (NoticeResponse 리스트) + */ data class QueryListNoticeResponse( val notices: List, ) diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryNoticeTitleResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryNoticeTitleResponse.kt index 03e94a8..69a1f86 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryNoticeTitleResponse.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/QueryNoticeTitleResponse.kt @@ -3,6 +3,14 @@ package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response import java.time.LocalDateTime import java.util.UUID +/** + * 공지사항 제목 목록 조회 응답을 위한 데이터 클래스입니다. + * 주로 공지사항 목록에서 제목만 보여줄 때 사용됩니다. + * + * @property id 공지사항 고유 식별자 + * @property title 공지사항 제목 + * @property createdAt 공지사항 생성 일시 + */ data class QueryNoticeTitleResponse( val id: UUID, val title: String, diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/UploadNoticeImageResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/UploadNoticeImageResponse.kt index a10321d..d5fb836 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/UploadNoticeImageResponse.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/response/UploadNoticeImageResponse.kt @@ -1,5 +1,11 @@ package hs.kr.entrydsm.feed.adapter.`in`.notice.dto.response +/** + * 공지사항 이미지 업로드 응답을 위한 데이터 클래스입니다. + * + * @property fileUrl 업로드된 이미지 파일에 접근할 수 있는 URL + * @property fileName 업로드된 이미지 파일의 원본 이름 + */ data class UploadNoticeImageResponse( val fileUrl: String, val fileName: String, From 654323847dc531dfcdada3c021de48993318e7fb Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 18:01:03 +0900 Subject: [PATCH 43/49] =?UTF-8?q?feat=20(=20#8=20)=20:=20Notice=20model=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/entrydsm/feed/model/notice/Notice.kt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/Notice.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/Notice.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/Notice.kt new file mode 100644 index 0000000..12f4f7a --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/Notice.kt @@ -0,0 +1,63 @@ +package hs.kr.entrydsm.feed.model.notice + +import hs.kr.entrydsm.feed.model.attachFile.AttachFile +import hs.kr.entrydsm.feed.model.notice.type.NoticeType +import java.time.LocalDateTime +import java.util.UUID + +/** + * 공지사항 도메인 모델 클래스입니다. + * + * @property id 공지사항의 고유 식별자 (생성 시 자동 할당) + * @property title 공지사항 제목 + * @property content 공지사항 내용 + * @property fileName 첨부 파일명 (선택 사항) + * @property attachFile 첨부 파일 목록 (선택 사항) + * @property adminId 공지사항을 작성한 관리자 ID + * @property isPinned 상단 고정 여부 + * @property type 공지사항 유형 + * @property createdAt 공지사항 생성 일시 (기본값: 현재 시간) + */ +data class Notice( + val id: UUID? = null, + val title: String, + val content: String, + val fileName: String? = null, + val attachFile: List? = emptyList(), + val adminId: UUID, + val isPinned: Boolean, + val type: NoticeType, + val createdAt: LocalDateTime = LocalDateTime.now(), +) { + /** + * 공지사항 정보를 업데이트합니다. + * + * @param newTitle 새로운 제목 + * @param newContent 새로운 내용 + * @param newFileName 새로운 파일명 (선택 사항) + * @param newAttachFile 새로운 첨부 파일 목록 (선택 사항) + * @param newAdminId 수정한 관리자 ID + * @param newIsPinned 새로운 상단 고정 여부 + * @param newType 새로운 공지사항 유형 + * @return 업데이트된 Notice 객체 + */ + fun updateNotice( + newTitle: String, + newContent: String, + newFileName: String?, + newAttachFile: List? = emptyList(), + newAdminId: UUID, + newIsPinned: Boolean, + newType: NoticeType, + ): Notice { + return copy( + title = newTitle, + content = newContent, + fileName = newFileName, + attachFile = newAttachFile, + adminId = newAdminId, + isPinned = newIsPinned, + type = newType, + ) + } +} From 545268f31d73b4d337a1112c03ef00ca381f1765 Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 18:01:13 +0900 Subject: [PATCH 44/49] =?UTF-8?q?feat=20(=20#8=20)=20:=20NoticeType=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt new file mode 100644 index 0000000..115a99b --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt @@ -0,0 +1,6 @@ +package hs.kr.entrydsm.feed.model.notice.type + +enum class NoticeType { + GUIDE, + NOTICE, +} From e70ff6ab72a2016e6ba38237e0cc53e551abbfeb Mon Sep 17 00:00:00 2001 From: coehgns Date: Tue, 29 Jul 2025 18:04:17 +0900 Subject: [PATCH 45/49] =?UTF-8?q?chore=20(=20#14=20)=20:=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entrydsm/feed/global/config/AwsConfig.kt | 29 -------- .../feed/global/config/FilterConfig.kt | 23 ------- .../feed/global/config/SecurityConfig.kt | 66 ------------------- .../feed/global/error/ErrorResponse.kt | 6 -- .../global/error/GlobalExceptionFilter.kt | 42 ------------ .../feed/global/error/exception/ErrorCode.kt | 39 ----------- .../feed/global/security/jwt/JwtFilter.kt | 44 ------------- .../feign/FeignClientErrorDecoder.kt | 24 ------- .../feign/client/user/model/User.kt | 13 ---- .../feign/configuration/FeignConfig.kt | 16 ----- 10 files changed, 302 deletions(-) delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/config/AwsConfig.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/config/FilterConfig.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/config/SecurityConfig.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/ErrorResponse.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionFilter.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/ErrorCode.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtFilter.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/FeignClientErrorDecoder.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/User.kt delete mode 100644 src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/FeignConfig.kt diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/config/AwsConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/config/AwsConfig.kt deleted file mode 100644 index d11c259..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/config/AwsConfig.kt +++ /dev/null @@ -1,29 +0,0 @@ -package hs.kr.entrydsm.feed.global.config - -import com.amazonaws.auth.AWSCredentials -import com.amazonaws.auth.AWSStaticCredentialsProvider -import com.amazonaws.auth.BasicAWSCredentials -import com.amazonaws.services.s3.AmazonS3 -import com.amazonaws.services.s3.AmazonS3ClientBuilder -import org.springframework.beans.factory.annotation.Value -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration -class AwsConfig( - @Value("\${cloud.aws.credentials.accessKey}") - private val accessKey: String, - @Value("\${cloud.aws.credentials.secretKey}") - private val secretKey: String, - @Value("\${cloud.aws.region.static}") - private val region: String -) { - @Bean - fun amazonS3(): AmazonS3 { - val awsCredentials: AWSCredentials = BasicAWSCredentials(accessKey, secretKey) - return AmazonS3ClientBuilder.standard() - .withRegion(region) - .withCredentials(AWSStaticCredentialsProvider(awsCredentials)) - .build() - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/config/FilterConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/config/FilterConfig.kt deleted file mode 100644 index 0b353e1..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/config/FilterConfig.kt +++ /dev/null @@ -1,23 +0,0 @@ -package hs.kr.entrydsm.feed.global.config - -import com.fasterxml.jackson.databind.ObjectMapper -import hs.kr.entrydsm.feed.global.error.GlobalExceptionFilter -import hs.kr.entrydsm.feed.global.security.jwt.JwtFilter -import org.springframework.security.config.annotation.SecurityConfigurerAdapter -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.web.DefaultSecurityFilterChain -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter -import org.springframework.stereotype.Component - -@Component -class FilterConfig( - private val objectMapper: ObjectMapper -) : SecurityConfigurerAdapter() { - override fun configure(builder: HttpSecurity) { - builder.addFilterBefore( - JwtFilter(), - UsernamePasswordAuthenticationFilter::class.java - ) - builder.addFilterBefore(GlobalExceptionFilter(objectMapper), JwtFilter::class.java) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/config/SecurityConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/config/SecurityConfig.kt deleted file mode 100644 index 0639cdf..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/config/SecurityConfig.kt +++ /dev/null @@ -1,66 +0,0 @@ -package hs.kr.entrydsm.feed.global.config - -import com.fasterxml.jackson.databind.ObjectMapper -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.http.HttpMethod -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.http.SessionCreationPolicy -import org.springframework.security.web.SecurityFilterChain - -@Configuration -class SecurityConfig( - private val objectMapper: ObjectMapper -) { - companion object { - private const val ADMIN_ROLE = "ADMIN" - } - - @Bean - protected fun filterChain(http: HttpSecurity): SecurityFilterChain { - http - .csrf { it.disable() } - .cors { } - .formLogin { it.disable() } - .sessionManagement { - it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) - } - .authorizeHttpRequests { auth -> - auth - .requestMatchers("/") - .permitAll() - .requestMatchers("/reply/**") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.POST, "/faq/**") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.PATCH, "/faq/**") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.DELETE, "/faq/**") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.GET, "/faq/all/title-type") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.GET, "/faq/**") - .permitAll() - .requestMatchers(HttpMethod.GET, "/reserve/**") - .permitAll() - .requestMatchers(HttpMethod.POST, "/notice") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.PATCH, "/notice/**") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.POST, "/notice/image") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.POST, "/screen") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.POST, "/attach-file") - .hasRole(ADMIN_ROLE) - .requestMatchers(HttpMethod.GET, "/notice/**") - .permitAll() - .anyRequest() - .authenticated() - } - .with(FilterConfig(objectMapper)) { } - .build() - - return http.build() - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/ErrorResponse.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/ErrorResponse.kt deleted file mode 100644 index 7e7c138..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/ErrorResponse.kt +++ /dev/null @@ -1,6 +0,0 @@ -package hs.kr.entrydsm.feed.global.error - -data class ErrorResponse( - val status: Int, - val message: String? -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionFilter.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionFilter.kt deleted file mode 100644 index 62ee587..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/GlobalExceptionFilter.kt +++ /dev/null @@ -1,42 +0,0 @@ -package hs.kr.entrydsm.feed.global.error - -import com.fasterxml.jackson.databind.ObjectMapper -import hs.kr.entrydsm.feed.global.error.exception.EquusException -import hs.kr.entrydsm.feed.global.error.exception.ErrorCode -import jakarta.servlet.FilterChain -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import org.springframework.http.MediaType -import org.springframework.web.filter.OncePerRequestFilter -import java.io.IOException -import java.nio.charset.StandardCharsets - -class GlobalExceptionFilter( - private val objectMapper: ObjectMapper -) : OncePerRequestFilter() { - - override fun doFilterInternal( - request: HttpServletRequest, - response: HttpServletResponse, - filterChain: FilterChain - ) { - try { - filterChain.doFilter(request, response) - } catch (e: EquusException) { - println(e.errorCode) - writerErrorCode(response, e.errorCode) - } catch (e: Exception) { - e.printStackTrace() - writerErrorCode(response, ErrorCode.INTERNAL_SERVER_ERROR) - } - } - - @Throws(IOException::class) - private fun writerErrorCode(response: HttpServletResponse, errorCode: ErrorCode) { - val errorResponse = ErrorResponse(errorCode.status, errorCode.message) - response.status = errorCode.status - response.characterEncoding = StandardCharsets.UTF_8.name() - response.contentType = MediaType.APPLICATION_JSON_VALUE - response.writer.write(objectMapper.writeValueAsString(errorResponse)) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/ErrorCode.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/ErrorCode.kt deleted file mode 100644 index 69d78dd..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/error/exception/ErrorCode.kt +++ /dev/null @@ -1,39 +0,0 @@ -package hs.kr.entrydsm.feed.global.error.exception - -enum class ErrorCode( - val status: Int, - val message: String -) { - // Feign - FEIGN_BAD_REQUEST(400, "Feign Bad Request"), - FEIGN_UNAUTHORIZED(401, "Feign UnAuthorized"), - FEIGN_FORBIDDEN(403, "Feign Forbidden"), - FEIGN_SERVER_ERROR(500, "Feign Server Error"), - - // Internal Server Error - INTERNAL_SERVER_ERROR(500, "Internal Server Error"), - - // UnAuthorization - INVALID_TOKEN(401, "Invalid Token"), - EXPIRED_TOKEN(401, "Expired Token"), - - // Forbidden - ACCESS_DENIED_QUESTION(403, "No Permission To Access Question"), - ACCESS_DENIED_REPLY(403, "No Permission To Comment Question"), - FEED_WRITER_MISMATCH(403, "Feed Writer Mismatch"), - - // Not Found - QUESTION_NOT_FOUND(404, "Question Not Found"), - REPLY_NOT_FOUND(404, "Reply Not Found"), - FAQ_NOT_FOUND(404, "Faq Not Found"), - NOTICE_NOT_FOUND(404, "Notice Not Found"), - SCREEN_NOT_FOUND(404, "Screen Not Found"), - ATTACH_FILE_NOT_FOUND(404, "Attach Not Found"), - - // Bad Request - FILE_IS_EMPTY(400, "File does not exist"), - BAD_FILE_EXTENSION(400, "File Extension is invalid"), - - // Conflict - REPLY_EXISTS(409, "Reply Already Exists") -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtFilter.kt b/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtFilter.kt deleted file mode 100644 index a77ccd3..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/global/security/jwt/JwtFilter.kt +++ /dev/null @@ -1,44 +0,0 @@ -package hs.kr.entrydsm.feed.global.security.jwt - -import jakarta.servlet.FilterChain -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.core.Authentication -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.core.context.SecurityContextHolder.clearContext -import org.springframework.security.core.userdetails.User -import org.springframework.security.core.userdetails.UserDetails -import org.springframework.web.filter.OncePerRequestFilter - -class JwtFilter : OncePerRequestFilter() { - override fun doFilterInternal( - request: HttpServletRequest, - response: HttpServletResponse, - filterChain: FilterChain - ) { - val userId: String? = request.getHeader("Request-User-Id") - val role: UserRole? = request.getHeader("Request-User-Role")?.let { UserRole.valueOf(it) } - - if ((userId == null) || (role == null)) { - filterChain.doFilter(request, response) - return - } - - val authorities = mutableListOf(SimpleGrantedAuthority("ROLE_${role.name}")) - val userDetails: UserDetails = User(userId, "", authorities) - val authentication: Authentication = - UsernamePasswordAuthenticationToken(userDetails, "", userDetails.authorities) - - clearContext() - SecurityContextHolder.getContext().authentication = authentication - filterChain.doFilter(request, response) - } -} - -enum class UserRole { - ROOT, - ADMIN, - USER -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/FeignClientErrorDecoder.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/FeignClientErrorDecoder.kt deleted file mode 100644 index 6e294a2..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/FeignClientErrorDecoder.kt +++ /dev/null @@ -1,24 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.feign - -import feign.FeignException -import feign.Response -import feign.codec.ErrorDecoder -import hs.kr.entrydsm.feed.infrastructure.feign.exception.FeignBadRequestException -import hs.kr.entrydsm.feed.infrastructure.feign.exception.FeignForbiddenException -import hs.kr.entrydsm.feed.infrastructure.feign.exception.FeignServerError -import hs.kr.entrydsm.feed.infrastructure.feign.exception.FeignUnAuthorizedException - -class FeignClientErrorDecoder : ErrorDecoder { - override fun decode(methodKey: String?, response: Response?): Exception { - print("feign error : ${response?.reason()}") - if (response!!.status() >= 400) { - when (response.status()) { - 400 -> throw FeignBadRequestException - 401 -> throw FeignUnAuthorizedException - 403 -> throw FeignForbiddenException - else -> throw FeignServerError - } - } - return FeignException.errorStatus(methodKey, response) - } -} diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/User.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/User.kt deleted file mode 100644 index c90e1fe..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/client/user/model/User.kt +++ /dev/null @@ -1,13 +0,0 @@ -package hs.kr.equus.feed.infrastructure.feign.client.user.model - -import hs.kr.entrydsm.feed.global.security.jwt.UserRole -import java.util.UUID - -data class User( - val id: UUID, - val phoneNumber: String, - val name: String, - val isParent: Boolean, - val receiptCode: UUID?, - val role: UserRole -) diff --git a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/FeignConfig.kt b/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/FeignConfig.kt deleted file mode 100644 index b0badc1..0000000 --- a/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/feign/configuration/FeignConfig.kt +++ /dev/null @@ -1,16 +0,0 @@ -package hs.kr.entrydsm.feed.infrastructure.feign.configuration - -import feign.codec.ErrorDecoder -import hs.kr.entrydsm.feed.infrastructure.feign.FeignClientErrorDecoder -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean -import org.springframework.cloud.openfeign.EnableFeignClients -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@EnableFeignClients(basePackages = ["hs.kr.entrydsm.feed"]) -@Configuration -class FeignConfig { - @Bean - @ConditionalOnMissingBean(value = [ErrorDecoder::class]) - fun commonFeignErrorDecoder(): FeignClientErrorDecoder = FeignClientErrorDecoder() -} From 4f9e2d3de8c76c43d00fed0d8ba91da01eef0fcc Mon Sep 17 00:00:00 2001 From: coehgns Date: Wed, 30 Jul 2025 13:44:31 +0900 Subject: [PATCH 46/49] docs ( #18 ) : kdoc --- .../attachFile/service/CreateAttachFileService.kt | 14 ++++++++++++++ .../entrydsm/feed/model/notice/type/NoticeType.kt | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt index 3dc551b..3d72272 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt @@ -29,6 +29,20 @@ class CreateAttachFileService( private val deleteAttachFilePort: DeleteAttachFilePort, private val saveAttachFilePort: SaveAttachFilePort, ) : CreateAttachFileUseCase { + /** + * 첨부 파일을 업로드하고 저장된 파일 정보를 반환합니다. + * + * @param attachFile 업로드할 파일 목록 (MultipartFile) + * @return 업로드된 파일 정보 목록 (CreateAttachFileResponse 리스트) + * + * @throws Exception 파일 업로드 중 오류가 발생한 경우 + * + * 이 메서드는 다음 작업을 수행합니다: + * 1. 이미 동일한 이름의 파일이 존재하면 삭제합니다. + * 2. 파일을 S3에 업로드합니다. + * 3. 업로드된 파일 정보를 데이터베이스에 저장합니다. + * 4. 업로드된 파일에 접근할 수 있는 URL을 생성하여 응답을 반환합니다. + */ override fun execute(attachFile: List): List { val attachFileResponses = mutableListOf() diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt index 115a99b..1795c36 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt @@ -1,5 +1,11 @@ package hs.kr.entrydsm.feed.model.notice.type +/** + * 공지사항의 유형을 나타내는 열거형 클래스입니다. + * + * @property GUIDE 안내사항을 나타내는 유형입니다. (가이드) + * @property NOTICE 공지사항을 나타내는 유형입니다. (일반 공지) + */ enum class NoticeType { GUIDE, NOTICE, From fb325501119c13d13d5ee9cb738de9d6150454da Mon Sep 17 00:00:00 2001 From: coehgns Date: Wed, 30 Jul 2025 13:44:31 +0900 Subject: [PATCH 47/49] docs ( #8 ) : kdoc --- .../attachFile/service/CreateAttachFileService.kt | 14 ++++++++++++++ .../entrydsm/feed/model/notice/type/NoticeType.kt | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt index 3dc551b..3d72272 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/application/attachFile/service/CreateAttachFileService.kt @@ -29,6 +29,20 @@ class CreateAttachFileService( private val deleteAttachFilePort: DeleteAttachFilePort, private val saveAttachFilePort: SaveAttachFilePort, ) : CreateAttachFileUseCase { + /** + * 첨부 파일을 업로드하고 저장된 파일 정보를 반환합니다. + * + * @param attachFile 업로드할 파일 목록 (MultipartFile) + * @return 업로드된 파일 정보 목록 (CreateAttachFileResponse 리스트) + * + * @throws Exception 파일 업로드 중 오류가 발생한 경우 + * + * 이 메서드는 다음 작업을 수행합니다: + * 1. 이미 동일한 이름의 파일이 존재하면 삭제합니다. + * 2. 파일을 S3에 업로드합니다. + * 3. 업로드된 파일 정보를 데이터베이스에 저장합니다. + * 4. 업로드된 파일에 접근할 수 있는 URL을 생성하여 응답을 반환합니다. + */ override fun execute(attachFile: List): List { val attachFileResponses = mutableListOf() diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt index 115a99b..1795c36 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/model/notice/type/NoticeType.kt @@ -1,5 +1,11 @@ package hs.kr.entrydsm.feed.model.notice.type +/** + * 공지사항의 유형을 나타내는 열거형 클래스입니다. + * + * @property GUIDE 안내사항을 나타내는 유형입니다. (가이드) + * @property NOTICE 공지사항을 나타내는 유형입니다. (일반 공지) + */ enum class NoticeType { GUIDE, NOTICE, From eda9e101fefe73162bd29c421257328b08ab7ff7 Mon Sep 17 00:00:00 2001 From: coehgns Date: Wed, 30 Jul 2025 13:53:09 +0900 Subject: [PATCH 48/49] docs ( #8 ) : kdoc --- .../notice/dto/request/CreateNoticeRequest.kt | 13 +++++++++ .../notice/dto/request/UpdateNoticeRequest.kt | 13 +++++++++ .../AttachFilePersistenceAdapter.kt | 24 ++++++++++++++++ .../notice/NoticePersistenceAdapter.kt | 28 +++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/CreateNoticeRequest.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/CreateNoticeRequest.kt index 0e5b4a7..1dae02b 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/CreateNoticeRequest.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/CreateNoticeRequest.kt @@ -5,6 +5,19 @@ import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Size +/** + * 공지사항 생성을 위한 요청 데이터 클래스입니다. + * + * 이 클래스는 클라이언트로부터 전달받은 공지사항 생성 요청 데이터를 담고 있으며, + * 유효성 검증을 위한 어노테이션들이 적용되어 있습니다. + * + * @property title 공지사항 제목 (필수, 최대 100자) + * @property content 공지사항 내용 (필수, 최대 5000자) + * @property isPinned 공지사항 상단 고정 여부 (필수) + * @property type 공지사항 유형 (필수, GUIDE 또는 NOTICE) + * @property fileName 공지사항에 첨부된 이미지 파일명 (선택) + * @property attachFileName 공지사항에 첨부된 파일들의 원본 파일명 목록 (선택) + */ data class CreateNoticeRequest( @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") @Size(max = 100, message = "title은 최대 100자까지 가능합니다.") diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/UpdateNoticeRequest.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/UpdateNoticeRequest.kt index d572f03..7cc8a18 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/UpdateNoticeRequest.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/notice/dto/request/UpdateNoticeRequest.kt @@ -5,6 +5,19 @@ import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Size +/** + * 공지사항 수정을 위한 요청 데이터 클래스입니다. + * + * 이 클래스는 클라이언트로부터 전달받은 공지사항 수정 요청 데이터를 담고 있으며, + * 유효성 검증을 위한 어노테이션들이 적용되어 있습니다. + * + * @property title 수정할 공지사항 제목 (필수, 최대 100자) + * @property content 수정할 공지사항 내용 (필수, 최대 5000자) + * @property isPinned 공지사항 상단 고정 여부 (필수) + * @property type 공지사항 유형 (필수, GUIDE 또는 NOTICE) + * @property fileName 수정할 공지사항의 이미지 파일명 (선택, null인 경우 기존 이미지 유지) + * @property attachFileName 수정할 공지사항의 첨부 파일명 목록 (선택, 비어있을 수 있음) + */ data class UpdateNoticeRequest( @field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.") @Size(max = 100, message = "title은 최대 100자까지 가능합니다.") diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/AttachFilePersistenceAdapter.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/AttachFilePersistenceAdapter.kt index a912daf..a3cce8a 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/AttachFilePersistenceAdapter.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/attachFile/AttachFilePersistenceAdapter.kt @@ -21,18 +21,42 @@ class AttachFilePersistenceAdapter( private val attachFileRepository: AttachFileRepository, private val attachFileMapper: AttachFileMapper, ) : ExistsAttachFilePort, DeleteAttachFilePort, SaveAttachFilePort, FindAttachFilePort { + + /** + * 주어진 원본 파일명을 가진 첨부 파일이 존재하는지 확인합니다. + * + * @param attachFileName 확인할 원본 첨부 파일명 + * @return 파일이 존재하면 true, 그렇지 않으면 false 반환 + */ override fun existsByOriginalAttachFileName(attachFileName: String): Boolean { return attachFileRepository.existsByOriginalAttachFileName(attachFileName) } + /** + * 주어진 원본 파일명을 가진 모든 첨부 파일을 삭제합니다. + * + * @param attachFileName 삭제할 첨부 파일의 원본 파일명 + */ override fun deleteByOriginalAttachFileName(attachFileName: String) { attachFileRepository.deleteByOriginalAttachFileName(attachFileName) } + /** + * 첨부 파일을 저장하거나 업데이트합니다. + * + * @param attachFile 저장할 첨부 파일 도메인 객체 + * @return 저장된 첨부 파일 도메인 객체 + */ override fun save(attachFile: AttachFile): AttachFile { return attachFileMapper.toModel(attachFileRepository.save(attachFileMapper.toEntity(attachFile))) } + /** + * 주어진 원본 파일명을 가진 모든 첨부 파일을 조회합니다. + * + * @param attachFileName 조회할 첨부 파일의 원본 파일명 + * @return 조회된 첨부 파일 도메인 객체 목록, 없을 경우 null 반환 + */ override fun findByOriginalAttachFileName(attachFileName: String): List? { return attachFileRepository.findByOriginalAttachFileName(attachFileName)?.map { attachFileMapper.toModel(it) } } diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/NoticePersistenceAdapter.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/NoticePersistenceAdapter.kt index 68ed711..5d87fa1 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/NoticePersistenceAdapter.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/out/persistence/notice/NoticePersistenceAdapter.kt @@ -22,23 +22,51 @@ class NoticePersistenceAdapter( private val noticeRepository: NoticeRepository, private val noticeMapper: NoticeMapper, ) : SaveNoticePort, FindNoticePort, DeleteNoticePort { + + /** + * 공지사항을 저장하거나 업데이트합니다. + * + * @param notice 저장 또는 업데이트할 공지사항 도메인 객체 + */ override fun saveNotice(notice: Notice) { noticeRepository.save(noticeMapper.toEntity(notice)) } + /** + * 주어진 ID에 해당하는 공지사항을 조회합니다. + * + * @param noticeId 조회할 공지사항의 고유 식별자 + * @return 조회된 공지사항 도메인 객체, 없을 경우 null 반환 + */ override fun findByIdOrNull(noticeId: UUID): Notice? = noticeRepository.findByIdOrNull(noticeId)?.let { noticeMapper.toModel(it) } + /** + * 공지사항을 삭제합니다. + * + * @param notice 삭제할 공지사항 도메인 객체 + */ override fun deleteNotice(notice: Notice) { noticeRepository.delete(noticeMapper.toEntity(notice)) } + /** + * 모든 공지사항을 조회합니다. + * + * @return 모든 공지사항 도메인 객체 목록 (없을 경우 빈 목록 반환) + */ override fun findAll(): List { return noticeRepository.findAll().map { noticeMapper.toModel(it) } } + /** + * 지정된 유형의 모든 공지사항을 조회합니다. + * + * @param noticeType 조회할 공지사항의 유형 (GUIDE 또는 NOTICE) + * @return 지정된 유형의 공지사항 도메인 객체 목록 (없을 경우 빈 목록 반환) + */ override fun findAllByType(noticeType: NoticeType): List { return noticeRepository.findAllByType(noticeType).map { noticeMapper.toModel(it) } } From 10bdfd0b17975ff453cc528a8801369e346054b3 Mon Sep 17 00:00:00 2001 From: coehgns Date: Wed, 30 Jul 2025 13:54:48 +0900 Subject: [PATCH 49/49] docs ( #8 ) : kdoc --- .../adapter/in/attachFile/AttachFileWebAdapter.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/AttachFileWebAdapter.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/AttachFileWebAdapter.kt index 14313aa..ea40f4f 100644 --- a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/AttachFileWebAdapter.kt +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/adapter/in/attachFile/AttachFileWebAdapter.kt @@ -20,6 +20,21 @@ import org.springframework.web.multipart.MultipartFile class AttachFileWebAdapter( private val createAttachFileUseCase: CreateAttachFileService, ) { + /** + * 하나 이상의 첨부 파일을 업로드하고, 업로드된 파일 정보를 반환합니다. + * + * 이 메서드는 클라이언트로부터 전송된 첨부 파일을 받아 서버에 저장하고, + * 저장된 파일에 대한 정보(파일명, 다운로드 URL 등)를 반환합니다. + * + * @param attachFile 업로드할 첨부 파일 목록 (multipart/form-data 형식의 'attach_file' 파라미터로 전달) + * @return 업로드된 첨부 파일 정보 목록 (CreateAttachFileResponse 리스트) + * + * @throws org.springframework.web.multipart.MultipartException 파일 업로드에 실패한 경우 + * @throws java.io.IOException 파일 저장 중 I/O 오류가 발생한 경우 + * @throws hs.kr.entrydsm.feed.global.error.exception.InternalServerErrorException 내부 서버 오류가 발생한 경우 + * + * @see CreateAttachFileService.execute + */ @PostMapping fun createAttachFile( @RequestPart(value = "attach_file") attachFile: List,