diff --git a/bootstrap/src/main/resources/application.yml b/bootstrap/src/main/resources/application.yml index 214c13c..4446fa1 100644 --- a/bootstrap/src/main/resources/application.yml +++ b/bootstrap/src/main/resources/application.yml @@ -6,3 +6,4 @@ spring: import: - application-persistence.yml - application-internal.yml + - application-message.yml diff --git a/community-application/src/main/kotlin/gloddy/user/port/in/dto/UserSaveRequest.kt b/community-application/src/main/kotlin/gloddy/user/port/in/dto/UserSaveRequest.kt new file mode 100644 index 0000000..5ac8a86 --- /dev/null +++ b/community-application/src/main/kotlin/gloddy/user/port/in/dto/UserSaveRequest.kt @@ -0,0 +1,24 @@ +package gloddy.user.port.`in`.dto + +import gloddy.user.User + +data class UserSaveRequest( + val id: Long, + val isCertifiedStudent: Boolean, + val profileImage: String, + val nickName: String, + val countryName: String?, + val countryImage: String?, + val reliabilityLevel: String +) + +fun UserSaveRequest.toDomain(): User = + User( + id = this.id, + isCertifiedStudent = this.isCertifiedStudent, + profileImage = this.profileImage, + nickname = this.nickName, + countryName = this.countryName, + countryImage = this.countryImage, + reliabilityLevel = this.reliabilityLevel + ) diff --git a/community-application/src/main/kotlin/gloddy/user/port/out/UserCommandPort.kt b/community-application/src/main/kotlin/gloddy/user/port/out/UserCommandPort.kt new file mode 100644 index 0000000..464c22e --- /dev/null +++ b/community-application/src/main/kotlin/gloddy/user/port/out/UserCommandPort.kt @@ -0,0 +1,7 @@ +package gloddy.user.port.out + +import gloddy.user.User + +interface UserCommandPort { + fun save(user: User): User +} \ No newline at end of file diff --git a/community-application/src/main/kotlin/gloddy/user/service/UserCommandService.kt b/community-application/src/main/kotlin/gloddy/user/service/UserCommandService.kt new file mode 100644 index 0000000..44903ed --- /dev/null +++ b/community-application/src/main/kotlin/gloddy/user/service/UserCommandService.kt @@ -0,0 +1,16 @@ +package gloddy.user.service + +import gloddy.user.port.`in`.dto.UserSaveRequest +import gloddy.user.port.`in`.dto.toDomain +import gloddy.user.port.out.UserCommandPort +import org.springframework.stereotype.Service + +@Service +class UserCommandService( + private val userCommandPort: UserCommandPort +) { + + fun save(request: UserSaveRequest) { + userCommandPort.save(request.toDomain()) + } +} \ No newline at end of file diff --git a/community-domain/src/main/kotlin/gloddy/core/ErrorCode.kt b/community-domain/src/main/kotlin/gloddy/core/ErrorCode.kt index fb30fe4..5cbf74c 100644 --- a/community-domain/src/main/kotlin/gloddy/core/ErrorCode.kt +++ b/community-domain/src/main/kotlin/gloddy/core/ErrorCode.kt @@ -17,5 +17,8 @@ enum class ErrorCode( ARTICLE_NO_AUTHORIZATION(401, "ARTICLE_003", "해당 게시글에 권한이 없습니다."), // Category - CATEGORY_NOT_FOUND(404, "CATEGORY_001", "해당 카테고리를 찾을 수 없습니다.") + CATEGORY_NOT_FOUND(404, "CATEGORY_001", "해당 카테고리를 찾을 수 없습니다."), + + // User + USER_NOT_FOUND(404, "COMMUNITY_USER_001", "해당 유저를 찾을 수 없습니다.") } \ No newline at end of file diff --git a/community-domain/src/main/kotlin/gloddy/user/User.kt b/community-domain/src/main/kotlin/gloddy/user/User.kt new file mode 100644 index 0000000..e30b665 --- /dev/null +++ b/community-domain/src/main/kotlin/gloddy/user/User.kt @@ -0,0 +1,11 @@ +package gloddy.user + +data class User( + val id: Long, + val isCertifiedStudent: Boolean, + val profileImage: String, + val nickname: String, + val countryName: String?, + val countryImage: String?, + val reliabilityLevel: String +) \ No newline at end of file diff --git a/community-domain/src/main/kotlin/gloddy/user/UserException.kt b/community-domain/src/main/kotlin/gloddy/user/UserException.kt new file mode 100644 index 0000000..bf3fbc4 --- /dev/null +++ b/community-domain/src/main/kotlin/gloddy/user/UserException.kt @@ -0,0 +1,10 @@ +package gloddy.user + +import gloddy.core.ErrorCode +import gloddy.core.GloddyCommunityException + +class UserNotFoundException : GloddyCommunityException( + statusCode = ErrorCode.USER_NOT_FOUND.statusCode, + errorCode = ErrorCode.USER_NOT_FOUND.errorCode, + message = ErrorCode.USER_NOT_FOUND.message +) \ No newline at end of file diff --git a/community-infrastructure/build.gradle.kts b/community-infrastructure/build.gradle.kts index 23724a8..9241888 100644 --- a/community-infrastructure/build.gradle.kts +++ b/community-infrastructure/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation(project(":community-application")) implementation(project(":community-domain")) + //jpa, db implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta") kapt("com.querydsl:querydsl-apt:5.0.0:jakarta") @@ -26,7 +27,13 @@ dependencies { kapt("org.springframework.boot:spring-boot-configuration-processor") runtimeOnly("com.h2database:h2") runtimeOnly("com.mysql:mysql-connector-j") + //openfeing implementation("org.springframework.cloud:spring-cloud-starter-openfeign") + //sqs + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.5") + implementation(platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.3")) + implementation("io.awspring.cloud:spring-cloud-aws-starter-sqs") + //test testImplementation(testFixtures(project(":community-domain"))) testImplementation("org.springframework.boot:spring-boot-starter-test") } diff --git a/community-infrastructure/src/main/kotlin/gloddy/inMessage/payload/UserMessagePayload.kt b/community-infrastructure/src/main/kotlin/gloddy/inMessage/payload/UserMessagePayload.kt new file mode 100644 index 0000000..410443d --- /dev/null +++ b/community-infrastructure/src/main/kotlin/gloddy/inMessage/payload/UserMessagePayload.kt @@ -0,0 +1,9 @@ +package gloddy.inMessage.payload + +import java.time.LocalDateTime + +data class UserMessagePayload( + val userId: Long, + val eventType: UserMessagePayloadType, + val eventDateTime: LocalDateTime +) \ No newline at end of file diff --git a/community-infrastructure/src/main/kotlin/gloddy/inMessage/payload/UserMessagePayloadType.kt b/community-infrastructure/src/main/kotlin/gloddy/inMessage/payload/UserMessagePayloadType.kt new file mode 100644 index 0000000..d762b3c --- /dev/null +++ b/community-infrastructure/src/main/kotlin/gloddy/inMessage/payload/UserMessagePayloadType.kt @@ -0,0 +1,7 @@ +package gloddy.inMessage.payload + +enum class UserMessagePayloadType { + JOIN, + UPDATE_PROFILE, + UPGRADE_RELIABILITY +} \ No newline at end of file diff --git a/community-infrastructure/src/main/kotlin/gloddy/inMessage/payload/handler/UserMessagePayloadHandler.kt b/community-infrastructure/src/main/kotlin/gloddy/inMessage/payload/handler/UserMessagePayloadHandler.kt new file mode 100644 index 0000000..9afcb4d --- /dev/null +++ b/community-infrastructure/src/main/kotlin/gloddy/inMessage/payload/handler/UserMessagePayloadHandler.kt @@ -0,0 +1,29 @@ +package gloddy.inMessage.payload.handler + +import gloddy.inMessage.payload.UserMessagePayload +import gloddy.internal.client.UserQueryClient +import gloddy.user.port.`in`.dto.UserSaveRequest +import gloddy.user.service.UserCommandService +import org.springframework.stereotype.Component + +@Component +class UserMessagePayloadHandler( + private val userQueryClient: UserQueryClient, + private val userCommandService: UserCommandService +) { + + fun handle(payload: UserMessagePayload) { + val userPreviewPayload = userQueryClient.getUserPreview(payload.userId) + userCommandService.save( + UserSaveRequest( + id = userPreviewPayload.id, + isCertifiedStudent = userPreviewPayload.isCertifiedStudent, + profileImage = userPreviewPayload.profileImage, + nickName = userPreviewPayload.nickName, + countryName = userPreviewPayload.countryName, + countryImage = userPreviewPayload.countryImage, + reliabilityLevel = userPreviewPayload.reliabilityLevel + ) + ) + } +} \ No newline at end of file diff --git a/community-infrastructure/src/main/kotlin/gloddy/inMessage/sqs/SqsSubscriber.kt b/community-infrastructure/src/main/kotlin/gloddy/inMessage/sqs/SqsSubscriber.kt new file mode 100644 index 0000000..1f31eaf --- /dev/null +++ b/community-infrastructure/src/main/kotlin/gloddy/inMessage/sqs/SqsSubscriber.kt @@ -0,0 +1,19 @@ +package gloddy.inMessage.sqs + +import gloddy.inMessage.payload.handler.UserMessagePayloadHandler +import gloddy.inMessage.sqs.util.MessageParser +import gloddy.user.service.UserCommandService +import io.awspring.cloud.sqs.annotation.SqsListener +import org.springframework.stereotype.Component + +@Component +class SqsSubscriber( + private val userMessagePayloadHandler: UserMessagePayloadHandler +) { + + @SqsListener(value = ["\${sqs.queue.user}"]) + fun handleApplyEvent(message: String) { + val payload = MessageParser.parseUserMessage(message) + userMessagePayloadHandler.handle(payload) + } +} \ No newline at end of file diff --git a/community-infrastructure/src/main/kotlin/gloddy/inMessage/sqs/util/MessageParser.kt b/community-infrastructure/src/main/kotlin/gloddy/inMessage/sqs/util/MessageParser.kt new file mode 100644 index 0000000..75f6499 --- /dev/null +++ b/community-infrastructure/src/main/kotlin/gloddy/inMessage/sqs/util/MessageParser.kt @@ -0,0 +1,22 @@ +package gloddy.inMessage.sqs.util + +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import gloddy.inMessage.payload.UserMessagePayload + +class MessageParser { + + companion object{ + private val objectMapper = jacksonObjectMapper().registerModule(JavaTimeModule()) + + fun parseUserMessage(message: String): UserMessagePayload { + val payload = parsePayloadFromMessage(message) + return objectMapper.readValue(payload, UserMessagePayload::class.java) + } + + private fun parsePayloadFromMessage(message: String): String { + val outerMessage: Map = objectMapper.readValue(message, Map::class.java) as Map + return outerMessage["Message"] as String + } + } +} \ No newline at end of file diff --git a/community-infrastructure/src/main/kotlin/gloddy/persistence/user/UserJpaEntity.kt b/community-infrastructure/src/main/kotlin/gloddy/persistence/user/UserJpaEntity.kt new file mode 100644 index 0000000..eccbbd7 --- /dev/null +++ b/community-infrastructure/src/main/kotlin/gloddy/persistence/user/UserJpaEntity.kt @@ -0,0 +1,20 @@ +package gloddy.persistence.user + +import jakarta.persistence.* + +@Entity +@Table(name = "users") +class UserJpaEntity( + @Id + val id: Long, + @Column(nullable = false) + val isCertifiedStudent: Boolean, + @Column(nullable = false) + val profileImage: String, + @Column(nullable = false) + val nickname: String, + val countryName: String?, + val countryImage: String?, + @Column(nullable = false) + val reliabilityLevel: String, +) \ No newline at end of file diff --git a/community-infrastructure/src/main/kotlin/gloddy/persistence/user/adapter/UserCommandAdapter.kt b/community-infrastructure/src/main/kotlin/gloddy/persistence/user/adapter/UserCommandAdapter.kt new file mode 100644 index 0000000..856654f --- /dev/null +++ b/community-infrastructure/src/main/kotlin/gloddy/persistence/user/adapter/UserCommandAdapter.kt @@ -0,0 +1,18 @@ +package gloddy.persistence.user.adapter + +import gloddy.persistence.user.repository.UserJpaRepository +import gloddy.persistence.util.mapper.toDomain +import gloddy.persistence.util.mapper.toEntity +import gloddy.user.User +import gloddy.user.port.out.UserCommandPort +import org.springframework.stereotype.Component + +@Component +class UserCommandAdapter( + private val userJpaRepository: UserJpaRepository, +) : UserCommandPort { + + override fun save(user: User): User { + return userJpaRepository.save(user.toEntity()).toDomain() + } +} \ No newline at end of file diff --git a/community-infrastructure/src/main/kotlin/gloddy/persistence/user/repository/UserJpaRepository.kt b/community-infrastructure/src/main/kotlin/gloddy/persistence/user/repository/UserJpaRepository.kt new file mode 100644 index 0000000..04f5931 --- /dev/null +++ b/community-infrastructure/src/main/kotlin/gloddy/persistence/user/repository/UserJpaRepository.kt @@ -0,0 +1,7 @@ +package gloddy.persistence.user.repository + +import gloddy.persistence.user.UserJpaEntity +import org.springframework.data.jpa.repository.JpaRepository + +interface UserJpaRepository : JpaRepository { +} \ No newline at end of file diff --git a/community-infrastructure/src/main/kotlin/gloddy/persistence/util/mapper/UserMapper.kt b/community-infrastructure/src/main/kotlin/gloddy/persistence/util/mapper/UserMapper.kt new file mode 100644 index 0000000..faaef07 --- /dev/null +++ b/community-infrastructure/src/main/kotlin/gloddy/persistence/util/mapper/UserMapper.kt @@ -0,0 +1,26 @@ +package gloddy.persistence.util.mapper + +import gloddy.persistence.user.UserJpaEntity +import gloddy.user.User + +fun User.toEntity(): UserJpaEntity = + UserJpaEntity( + id = this.id, + isCertifiedStudent = this.isCertifiedStudent, + profileImage = this.profileImage, + nickname = this.nickname, + countryName = this.countryName, + countryImage = this.countryImage, + reliabilityLevel = this.reliabilityLevel + ) + +fun UserJpaEntity.toDomain(): User = + User( + id = this.id, + isCertifiedStudent = this.isCertifiedStudent, + profileImage = this.profileImage, + nickname = this.nickname, + countryName = this.countryName, + countryImage = this.countryImage, + reliabilityLevel = this.reliabilityLevel + ) \ No newline at end of file diff --git a/community-infrastructure/src/main/resources/application-message.yml b/community-infrastructure/src/main/resources/application-message.yml new file mode 100644 index 0000000..0910006 --- /dev/null +++ b/community-infrastructure/src/main/resources/application-message.yml @@ -0,0 +1,15 @@ +spring: + cloud: + aws: + region: + static: ap-northeast-2 + credentials: + access-key: ${AWS_ACCESS_KEY} + secret-key: ${AWS_SECRET_KEY} + sqs: + listener: + poll-timeout: 20000 + +sqs: + queue: + user: ${USER_SQS_QUEUE_NAME} \ No newline at end of file