Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
branches: [ "main", "develop" ]
pull_request:

permissions:
contents: read
pull-requests: read
Comment thread
inpink marked this conversation as resolved.

jobs:
detect-changes:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ subprojects {


dependencies {
add("testRuntimeOnly", "org.junit.platform:junit-platform-launcher:1.13.3")
add("testRuntimeOnly", "org.junit.platform:junit-platform-launcher")
add("testImplementation", "io.mockk:mockk:1.13.13")
}
Comment thread
inpink marked this conversation as resolved.
}
Expand Down
31 changes: 31 additions & 0 deletions payment/services/api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
plugins {
id("io.spring.dependency-management")
id("org.springframework.boot")
kotlin("jvm")
kotlin("plugin.spring")
kotlin("plugin.jpa")
}

dependencies {
implementation(project(":voucher-adapter"))


implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("com.h2database:h2")
testImplementation("io.rest-assured:rest-assured")

// 테스트에서 도메인 클래스 접근을 위해 필요
testImplementation(project(":voucher-domain"))
testImplementation(project(":voucher-application"))


runtimeOnly("com.mysql:mysql-connector-j")

}

tasks.withType<Test> {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package app.payment

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication(scanBasePackages = ["app.payment"])
class PaymentApiApplication

fun main(args: Array<String>) {
runApplication<PaymentApiApplication>(*args)
}
25 changes: 25 additions & 0 deletions payment/services/api/src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
spring:
datasource:
url: jdbc:h2:mem:testdb;MODE=MySQL
driver-class-name: org.h2.Driver
username: sa
password:

jpa:
hibernate:
ddl-auto: create-drop
properties:
hibernate:
format_sql: true
show_sql: true
dialect: org.hibernate.dialect.H2Dialect
Comment thread
inpink marked this conversation as resolved.

h2:
console:
enabled: true

logging:
level:
app.payment: DEBUG
org.springframework.web: DEBUG
org.hibernate.SQL: DEBUG
12 changes: 0 additions & 12 deletions payment/src/main/kotlin/app/payment/domain/voucher/Voucher.kt

This file was deleted.

This file was deleted.

29 changes: 29 additions & 0 deletions payment/voucher/adapter/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
kotlin("jvm")
kotlin("plugin.spring")
kotlin("plugin.jpa")
id("io.spring.dependency-management")
}

dependencies {
implementation(project(":voucher-application"))
implementation(project(":voucher-domain"))


implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")


// --- test ---
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.mockito")
}
testImplementation("com.ninja-squad:springmockk:4.0.2")

Comment thread
inpink marked this conversation as resolved.
}

tasks.withType<Test> {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package app.payment.voucher.adapter.inbound

data class GetVoucherResponse(
val id: Long,
val consumptionType: String,
val title: String,
val description: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package app.payment.voucher.adapter.inbound

import app.payment.voucher.application.port.inbound.CurrentPublishedVouchers

data class GetVouchersResponse(
val vouchers: List<GetVoucherResponse>,
) {
companion object {
fun from(items: List<CurrentPublishedVouchers>): GetVouchersResponse =
GetVouchersResponse(
vouchers =
items.map {
GetVoucherResponse(
id = it.id,
consumptionType = it.consumptionType.name,
title = it.title,
description = it.description,
)
},
)
Comment thread
inpink marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package app.payment.voucher.adapter.inbound

import app.payment.voucher.application.port.inbound.VoucherUseCase
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/v1/vouchers")
class VoucherController(
private val voucherUseCase: VoucherUseCase,
) {
@GetMapping
fun getVouchers(): ResponseEntity<GetVouchersResponse> {
val vouchers = voucherUseCase.getPublishedVouchers()
return ResponseEntity.ok()
.body(GetVouchersResponse.from(vouchers))
}
}
Comment thread
inpink marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package app.payment.voucher.adapter.outbound

import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaAuditing

@Configuration
@EnableJpaAuditing
class JpaConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package app.payment.voucher.adapter.outbound

import app.payment.voucher.domain.VoucherContent
import jakarta.persistence.Entity
import jakarta.persistence.EntityListeners
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.Table
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.OffsetDateTime

@Entity
@Table(name = "voucher_contents")
@EntityListeners(AuditingEntityListener::class)
class VoucherContentJpaEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val voucherId: Long,
val version: Int,
val title: String,
val description: String,
val activeFrom: OffsetDateTime,
val activeUntil: OffsetDateTime,
@CreatedDate
var createdAt: OffsetDateTime? = null
) {
fun toDomain(): VoucherContent {
val id = requireNotNull(id) { "VoucherContent.id must not be null" }
val createdAt = requireNotNull(createdAt) { "VoucherContent.createdAt must not be null" }

return VoucherContent(
id = id,
voucherId = voucherId,
version = version,
title = title,
description = description,
activeFrom = activeFrom,
activeUntil = activeUntil,
createdAt = createdAt
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package app.payment.voucher.adapter.outbound

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import java.time.OffsetDateTime

interface VoucherContentJpaRepository : JpaRepository<VoucherContentJpaEntity, Long> {
@Query("""
SELECT vc FROM VoucherContentJpaEntity vc
WHERE vc.voucherId IN :voucherIds
AND vc.activeFrom <= :now
AND vc.activeUntil >= :now
""")
fun findActiveContentsByVoucherIds(
@Param("voucherIds") voucherIds: List<Long>,
@Param("now") now: OffsetDateTime
): List<VoucherContentJpaEntity>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package app.payment.voucher.adapter.outbound

import app.payment.voucher.application.port.outbound.VoucherContentReader
import app.payment.voucher.domain.VoucherContent
import org.springframework.stereotype.Repository
import java.time.OffsetDateTime

@Repository
class VoucherContentReaderImpl(
private val voucherContentRepository: VoucherContentJpaRepository,
) : VoucherContentReader {
override fun readCurrentContents(publishedVoucherIds: List<Long>, now: OffsetDateTime): List<VoucherContent> {
if (publishedVoucherIds.isEmpty()) {
return emptyList()
}

return voucherContentRepository.findActiveContentsByVoucherIds(publishedVoucherIds, now)
.map { it.toDomain() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package app.payment.voucher.adapter.outbound

import app.payment.voucher.domain.ConsumptionType
import app.payment.voucher.domain.Voucher
import app.payment.voucher.domain.VoucherStatus
import app.payment.voucher.domain.VoucherType
import jakarta.persistence.Entity
import jakarta.persistence.EntityListeners
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.Table
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.OffsetDateTime

@Entity
@Table(name = "vouchers")
@EntityListeners(AuditingEntityListener::class)
class VoucherJpaEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Enumerated(EnumType.STRING)
val type: VoucherType,
@Enumerated(EnumType.STRING)
val consumptionType: ConsumptionType,
@Enumerated(EnumType.STRING)
var status: VoucherStatus,
@CreatedDate
var createdAt: OffsetDateTime? = null,
@LastModifiedDate
var updatedAt: OffsetDateTime? = null
) {
fun toDomain(): Voucher {
val id = requireNotNull(id) { "Voucher.id must not be null" }
val createdAt = requireNotNull(createdAt) { "Voucher.createdAt must not be null" }
val updatedAt = requireNotNull(updatedAt) { "Voucher.updatedAt must not be null" }

return Voucher(
id = id,
type = type,
consumptionType = consumptionType,
status = status,
createdAt = createdAt,
updatedAt = updatedAt
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package app.payment.voucher.adapter.outbound

import app.payment.voucher.domain.VoucherStatus
import org.springframework.data.jpa.repository.JpaRepository

interface VoucherJpaRepository : JpaRepository<VoucherJpaEntity, Long> {
fun findByStatus(status: VoucherStatus): List<VoucherJpaEntity>
Comment thread
inpink marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package app.payment.voucher.adapter.outbound

import app.payment.voucher.application.port.outbound.VoucherReader
import app.payment.voucher.domain.Voucher
import app.payment.voucher.domain.VoucherStatus
import org.springframework.stereotype.Repository

@Repository
class VoucherReaderImpl(
private val voucherRepository: VoucherJpaRepository
): VoucherReader {
override fun readByStatus(status: VoucherStatus): List<Voucher> {
return voucherRepository.findByStatus(status)
.map { it.toDomain() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package app.payment.voucher.adapter

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.data.jpa.repository.config.EnableJpaRepositories

@SpringBootApplication
@EntityScan("app.payment.voucher.adapter.outbound")
@EnableJpaRepositories("app.payment.voucher.adapter.outbound")
class TestConfiguration
Loading