Skip to content

Commit

Permalink
Merge pull request #5 from gloddy-dev/feat/#4
Browse files Browse the repository at this point in the history
[Feat]: Category, Article 도메인 및 JPA 모델 설계 및 초기 UseCase, API 구현
jihwan2da authored Jan 8, 2024
2 parents f09e362 + b020bf7 commit 6abd505
Showing 76 changed files with 1,382 additions and 23 deletions.
5 changes: 4 additions & 1 deletion bootstrap/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
server:
port: {PORT}

spring:
config:
import:
- application-jpa.yml
- application-persistence.yml
File renamed without changes.
13 changes: 0 additions & 13 deletions bootstrap/src/test/kotlin/gloddy/CommunityApplicationTests.kt

This file was deleted.

8 changes: 8 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -30,6 +30,14 @@ subprojects {
dependencies{
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.slf4j:slf4j-api:2.0.10")
implementation("ch.qos.logback:logback-core:1.4.14")
testCompileOnly("org.junit.jupiter:junit-jupiter-params:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude("junit")
}
}

group = "gloddy"
9 changes: 1 addition & 8 deletions community-application/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -13,12 +13,5 @@ dependencies {
implementation("org.springframework:spring-context")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.15.2")
implementation("jakarta.validation:jakarta.validation-api")

testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.3")

testImplementation("org.assertj:assertj-core:3.24.2")
testImplementation("org.mockito:mockito-junit-jupiter:5.3.1")
testImplementation("org.mockito:mockito-core:5.3.1")
testImplementation(testFixtures(project(":community-domain")))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gloddy.article.dto.command

data class ArticleCreateCommand(
val categoryId: Long,
val title: String,
val content: String,
val images: List<String>?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package gloddy.article.dto.read

data class ArticleIdReadData(
val articleId: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gloddy.article.port.`in`

import gloddy.article.dto.command.ArticleCreateCommand
import gloddy.article.dto.read.ArticleIdReadData

interface ArticleCommandUseCase {
fun create(userId: Long, command: ArticleCreateCommand): ArticleIdReadData
fun delete(userId: Long, articleId: Long)
fun like(userId: Long, articleId: Long)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gloddy.article.port.out

import gloddy.article.Article

interface ArticleCommandPersistencePort {
fun save(article: Article) : Article
fun delete(id: Long)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gloddy.article.port.out

import gloddy.article.ArticleLike

interface ArticleLikeCommandPersistencePort {
fun save(articleLike: ArticleLike): ArticleLike
fun delete(articleLike: ArticleLike)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gloddy.article.port.out

import gloddy.article.Article
import gloddy.article.ArticleLike

interface ArticleLikeQueryPersistencePort {
fun findByUserIdAndArticleOrNull(userId: Long, article: Article): ArticleLike?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gloddy.article.port.out

import gloddy.article.Article

interface ArticleQueryPersistencePort {
fun findById(id: Long): Article
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package gloddy.article.service

import gloddy.article.Article
import gloddy.article.ArticleLike
import gloddy.article.dto.command.ArticleCreateCommand
import gloddy.article.dto.read.ArticleIdReadData
import gloddy.article.port.`in`.ArticleCommandUseCase
import gloddy.article.port.out.ArticleCommandPersistencePort
import gloddy.article.port.out.ArticleLikeCommandPersistencePort
import gloddy.article.port.out.ArticleLikeQueryPersistencePort
import gloddy.article.port.out.ArticleQueryPersistencePort
import gloddy.category.port.out.CategoryQueryPersistencePort
import gloddy.core.CategoryId
import gloddy.core.UserId
import org.springframework.stereotype.Service

@Service
class ArticleCommandService(
private val categoryQueryPersistencePort: CategoryQueryPersistencePort,
private val articleQueryPersistencePort: ArticleQueryPersistencePort,
private val articleCommandPersistencePort: ArticleCommandPersistencePort,
private val articleLikeCommandPersistencePort: ArticleLikeCommandPersistencePort,
private val articleLikeQueryPersistencePort: ArticleLikeQueryPersistencePort
) : ArticleCommandUseCase {

override fun create(userId: Long, command: ArticleCreateCommand) : ArticleIdReadData {

val category = categoryQueryPersistencePort.findById(CategoryId(command.categoryId))

val article = Article(
userId = UserId(userId),
category = category,
title = command.title,
content = command.content,
images = command.images,
).let { articleCommandPersistencePort.save(it) }
return ArticleIdReadData(articleId = article.id!!.value)
}

override fun delete(userId: Long, articleId: Long) {
val article = articleQueryPersistencePort.findById(articleId)
article.validateAuthorization(userId)
articleCommandPersistencePort.delete(article.id!!.value)
}

override fun like(userId: Long, articleId: Long) {

val article = articleQueryPersistencePort.findById(articleId)

articleLikeQueryPersistencePort.findByUserIdAndArticleOrNull(userId, article)
?.run { articleLikeCommandPersistencePort.delete(this) }
?: articleLikeCommandPersistencePort.save(
ArticleLike(
userId = UserId(userId),
article = article
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gloddy.category.port.dto

data class CategoryReadData(
val id: Long,
val name: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gloddy.category.port.`in`

import gloddy.category.port.dto.CategoryReadData

interface CategoryQueryUseCase {
fun getAll(): List<CategoryReadData>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gloddy.category.port.out

import gloddy.category.Category
import gloddy.core.CategoryId

interface CategoryQueryPersistencePort {
fun findById(id: CategoryId): Category
fun findAll(): List<Category>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gloddy.category.port.service

import gloddy.category.port.dto.CategoryReadData
import gloddy.category.port.`in`.CategoryQueryUseCase
import gloddy.category.port.out.CategoryQueryPersistencePort
import org.springframework.stereotype.Service

@Service
class CategoryQueryService(
private val categoryQueryPersistencePort: CategoryQueryPersistencePort,
) : CategoryQueryUseCase {

override fun getAll(): List<CategoryReadData> {
val categories = categoryQueryPersistencePort.findAll()
return categories.map {
CategoryReadData(
id = it.id!!.value,
name = it.name
)
}
}
}
Empty file.
8 changes: 8 additions & 0 deletions community-application/src/test/kotlin/gloddy/ServiceTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gloddy

import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.junit.jupiter.MockitoExtension

@ExtendWith(MockitoExtension::class)
abstract class ServiceTest {
}
3 changes: 3 additions & 0 deletions community-domain/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -6,6 +6,9 @@ val bootJar: BootJar by tasks
bootJar.enabled = false
jar.enabled = true

plugins {
id("java-test-fixtures")
}
dependencies {

}
Empty file.
40 changes: 40 additions & 0 deletions community-domain/src/main/kotlin/gloddy/article/Article.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package gloddy.article

import gloddy.article.exception.ArticleNoAuthorizationException
import gloddy.article.vo.ArticleImage
import gloddy.category.Category
import gloddy.core.ArticleId
import gloddy.core.UserId

data class Article(
val userId: UserId,
var category: Category,
var title: String,
var content: String,
var image: ArticleImage,
var commentCount: Int = 0,
var likeCount: Int = 0,
val id: ArticleId? = null,
) {
constructor(
userId: UserId,
category: Category,
title: String,
content: String,
images: List<String>?,
id: ArticleId? = null
) : this(
userId = userId,
category = category,
title = title,
content = content,
image = ArticleImage(images),
id = id
)

fun validateAuthorization(userId: Long) {
if (this.userId.value != userId) {
throw ArticleNoAuthorizationException()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package gloddy.article

import gloddy.core.ErrorCode

enum class ArticleErrorCode(
override val statusCode: Int,
override val errorCode: String,
override val message: String
) : ErrorCode {
NOT_FOUND(400, "ARTICLE_001", "해당 게시글을 찾을 수 없습니다."),
IMAGE_SIZE_OVER(400, "ARTICLE_002", "게시글에 이미지는 최대 3개 입니다."),
NO_AUTHORIZATION(401, "ARTICLE_003", "해당 게시글에 권한이 없습니다.")
}
19 changes: 19 additions & 0 deletions community-domain/src/main/kotlin/gloddy/article/ArticleLike.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package gloddy.article

import gloddy.core.UserId

data class ArticleLike(
val userId: UserId,
val article: Article,
val id: Long? = null,
) {
init {
verifyArticle(article)
}

private fun verifyArticle(article: Article) {
if (article.id == null) {
throw RuntimeException("ArticleLike 생성 오류 : 영속화 되지 않은 Article을 입력으로 받았습니다.")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gloddy.article.exception

import gloddy.article.ArticleErrorCode
import gloddy.core.GloddyCommunityException

class ArticleImageSizeOverException : GloddyCommunityException(ArticleErrorCode.IMAGE_SIZE_OVER)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gloddy.article.exception

import gloddy.article.ArticleErrorCode
import gloddy.core.GloddyCommunityException

class ArticleNoAuthorizationException : GloddyCommunityException(ArticleErrorCode.NO_AUTHORIZATION)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gloddy.article.exception

import gloddy.article.ArticleErrorCode
import gloddy.core.GloddyCommunityException

class ArticleNotFoundException : GloddyCommunityException(ArticleErrorCode.NOT_FOUND)
18 changes: 18 additions & 0 deletions community-domain/src/main/kotlin/gloddy/article/vo/ArticleImage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gloddy.article.vo

import gloddy.article.exception.ArticleImageSizeOverException

data class ArticleImage(
val images: List<String>?
) {

init {
verifySize(images)
}

private fun verifySize(images: List<String>?) {
if (!images.isNullOrEmpty() && images.size > 3) {
throw ArticleImageSizeOverException()
}
}
}
8 changes: 8 additions & 0 deletions community-domain/src/main/kotlin/gloddy/category/Category.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gloddy.category

import gloddy.core.CategoryId

data class Category(
var name: String,
val id: CategoryId? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gloddy.category

import gloddy.core.ErrorCode

enum class CategoryErrorCode(
override val statusCode: Int,
override val errorCode: String,
override val message: String
): ErrorCode {
NOT_FOUND(400, "CATEGORY_001", "해당 카테고리를 찾을 수 없습니다.")
}
Loading

0 comments on commit 6abd505

Please sign in to comment.