diff --git a/build.gradle.kts b/build.gradle.kts index 6d01618..4a031eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -68,6 +68,9 @@ dependencies { // swagger implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2") + // security + implementation("org.springframework.boot:spring-boot-starter-security") + // testcontainers testImplementation("org.testcontainers:junit-jupiter") testImplementation("org.springframework.boot:spring-boot-testcontainers") @@ -94,6 +97,7 @@ dependencies { implementation("org.springframework.retry:spring-retry:2.0.5") // test + testImplementation("org.springframework.security:spring-security-test") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc") testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1") diff --git a/src/main/kotlin/io/ticketaka/api/common/domain/map/TokenWaitingMap.kt b/src/main/kotlin/io/ticketaka/api/common/domain/map/TokenWaitingMap.kt index cb3a5dd..1358d88 100644 --- a/src/main/kotlin/io/ticketaka/api/common/domain/map/TokenWaitingMap.kt +++ b/src/main/kotlin/io/ticketaka/api/common/domain/map/TokenWaitingMap.kt @@ -1,22 +1,22 @@ package io.ticketaka.api.common.domain.map -import io.ticketaka.api.user.domain.Token +import io.ticketaka.api.user.domain.token.QueueToken interface TokenWaitingMap { fun put( key: Long, - token: Token, + queueToken: QueueToken, ): Boolean - fun putAll(tokens: List): Boolean + fun putAll(queueTokens: List): Boolean - fun get(key: Long): Token? + fun get(key: Long): QueueToken? - fun remove(key: Long): Token? + fun remove(key: Long): QueueToken? fun size(): Long - fun findAll(): List + fun findAll(): List fun clear() } diff --git a/src/main/kotlin/io/ticketaka/api/common/domain/queue/TokenWaitingQueue.kt b/src/main/kotlin/io/ticketaka/api/common/domain/queue/TokenWaitingQueue.kt index f81a286..2c95538 100644 --- a/src/main/kotlin/io/ticketaka/api/common/domain/queue/TokenWaitingQueue.kt +++ b/src/main/kotlin/io/ticketaka/api/common/domain/queue/TokenWaitingQueue.kt @@ -1,13 +1,13 @@ package io.ticketaka.api.common.domain.queue -import io.ticketaka.api.user.domain.Token +import io.ticketaka.api.user.domain.token.QueueToken interface TokenWaitingQueue { - fun offer(element: Token): Boolean + fun offer(element: QueueToken): Boolean - fun poll(): Token? + fun poll(): QueueToken? - fun peek(): Token? + fun peek(): QueueToken? fun size(): Long } diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/InMemoryWaitingMap.kt b/src/main/kotlin/io/ticketaka/api/common/infrastructure/InMemoryWaitingMap.kt deleted file mode 100644 index 5353ea1..0000000 --- a/src/main/kotlin/io/ticketaka/api/common/infrastructure/InMemoryWaitingMap.kt +++ /dev/null @@ -1,46 +0,0 @@ -package io.ticketaka.api.common.infrastructure - -import io.ticketaka.api.common.domain.map.TokenWaitingMap -import io.ticketaka.api.user.domain.Token -import org.springframework.stereotype.Component -import java.util.concurrent.ConcurrentHashMap - -@Component -class InMemoryWaitingMap : TokenWaitingMap { - private val tokenCache: MutableMap = ConcurrentHashMap() - - override fun put( - key: Long, - token: Token, - ): Boolean { - tokenCache[key] = token - return true - } - - override fun putAll(tokens: List): Boolean { - tokenCache.putAll(tokens.map { it.id to it }) - return true - } - - override fun get(key: Long): Token? { - return tokenCache[key] - } - - override fun findAll(): List { - return tokenCache.values.toList() - } - - override fun clear() { - tokenCache.clear() - } - - override fun remove(key: Long): Token? { - return tokenCache.remove(key) - } - - override fun size(): Long { - synchronized(this) { - return tokenCache.size.toLong() - } - } -} diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/aop/RequestMapAspect.kt b/src/main/kotlin/io/ticketaka/api/common/infrastructure/aop/RequestQueueTokenAspect.kt similarity index 85% rename from src/main/kotlin/io/ticketaka/api/common/infrastructure/aop/RequestMapAspect.kt rename to src/main/kotlin/io/ticketaka/api/common/infrastructure/aop/RequestQueueTokenAspect.kt index 191e819..711d623 100644 --- a/src/main/kotlin/io/ticketaka/api/common/infrastructure/aop/RequestMapAspect.kt +++ b/src/main/kotlin/io/ticketaka/api/common/infrastructure/aop/RequestQueueTokenAspect.kt @@ -1,7 +1,7 @@ package io.ticketaka.api.common.infrastructure.aop import io.ticketaka.api.common.domain.map.TokenWaitingMap -import io.ticketaka.api.user.domain.Token +import io.ticketaka.api.user.domain.token.QueueToken import org.aspectj.lang.ProceedingJoinPoint import org.aspectj.lang.annotation.Around import org.aspectj.lang.annotation.Aspect @@ -14,7 +14,7 @@ import java.time.LocalDateTime @Aspect @Component -class RequestMapAspect( +class RequestQueueTokenAspect( private val tokenWaitingMap: TokenWaitingMap, ) { private val logger = LoggerFactory.getLogger(this::class.java) @@ -50,16 +50,16 @@ class RequestMapAspect( } } - private fun validateTokenActive(firstToken: Token) { - if (firstToken.status != Token.Status.ACTIVE) { + private fun validateTokenActive(firstQueueToken: QueueToken) { + if (firstQueueToken.status != QueueToken.Status.ACTIVE) { val errorMessage = "토큰이 활성화되지 않았습니다." logger.debug(errorMessage) throw IllegalArgumentException(errorMessage) } } - private fun validateTokenExpiration(firstToken: Token) { - val expiredTime = firstToken.issuedTime.plusMinutes(30) + private fun validateTokenExpiration(firstQueueToken: QueueToken) { + val expiredTime = firstQueueToken.issuedTime.plusMinutes(30) val now = LocalDateTime.now() if (expiredTime.isBefore(now)) { val errorMessage = "토큰이 만료되었습니다." @@ -69,10 +69,10 @@ class RequestMapAspect( } private fun validateTokenIdentifier( - firstToken: Token, + firstQueueToken: QueueToken, authorizationHeader: Long, ) { - if (firstToken.id != authorizationHeader) { + if (firstQueueToken.id != authorizationHeader) { val errorMessage = "현재 대기 중인 토큰과 요청한 토큰이 일치하지 않습니다." logger.debug(errorMessage) throw IllegalArgumentException(errorMessage) diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtFilter.kt b/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtFilter.kt deleted file mode 100644 index ecc93c2..0000000 --- a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtFilter.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.ticketaka.api.common.infrastructure.jwt - -import jakarta.servlet.FilterChain -import jakarta.servlet.ServletException -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import org.slf4j.LoggerFactory -import org.springframework.web.filter.OncePerRequestFilter -import java.io.IOException - -// @Component -// @Order(1) -class JwtFilter( - private val jwtTokenParser: JwtTokenParser, -) : OncePerRequestFilter() { - private val log = LoggerFactory.getLogger(this::class.java) - -// private val authorizationHeader = "Authorization" - - @Throws(ServletException::class, IOException::class) - override fun doFilterInternal( - request: HttpServletRequest, - response: HttpServletResponse, - filterChain: FilterChain, - ) { - log.info("JwtFilter") - if (request.requestURI.contains("/api/token") || - request.requestURI.contains("/h2-console") || - request.requestURI.contains("/swagger") || - request.requestURI.contains("/api-docs") - ) { - filterChain.doFilter(request, response) - return - } - -// val header = request.getHeader(authorizationHeader) -// val tokenMap = jwtTokenParser.parse(header) - - // TODO 사용자 검증 - filterChain.doFilter(request, response) - } -} diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtTokenParser.kt b/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtTokenParser.kt deleted file mode 100644 index cf0cdab..0000000 --- a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtTokenParser.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.ticketaka.api.common.infrastructure.jwt - -import io.jsonwebtoken.ExpiredJwtException -import io.jsonwebtoken.Jwts -import io.jsonwebtoken.MalformedJwtException -import io.jsonwebtoken.UnsupportedJwtException -import io.jsonwebtoken.io.Decoders -import io.jsonwebtoken.security.Keys -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Component -import javax.crypto.SecretKey - -@Component -class JwtTokenParser( - private val jwtProperties: JwtProperties, -) { - private val log = LoggerFactory.getLogger(this::class.java) - - private val headerPrefix = "Bearer " - private val base64TokenSignKey = jwtProperties.base64TokenSigningKey - - fun parse(token: String): RawToken { - try { - val bytes = Decoders.BASE64.decode(base64TokenSignKey) - val key: SecretKey = Keys.hmacShaKeyFor(bytes) - - val jwt = - Jwts.parser() - .verifyWith(key) - .build() - .parseSignedClaims(extract(token)) - return RawToken(jwt.payload["tsid"] as String) - } catch (ex: UnsupportedJwtException) { - log.error("Invalid JWT Token", ex) - throw IllegalArgumentException(ex.message) - } catch (ex: MalformedJwtException) { - log.error("Invalid JWT Token", ex) - throw IllegalArgumentException() - } catch (ex: IllegalArgumentException) { - log.error("Invalid JWT Token", ex) - throw IllegalArgumentException(ex.message) - } catch (ex: ExpiredJwtException) { - log.info("JWT Token is expired", ex) - throw IllegalArgumentException(ex.message) - } - } - - private fun extract(payload: String?): String { - return if (payload.isNullOrEmpty()) { - "" - } else { - payload.substring(headerPrefix.length) - } - } -} diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtTokens.kt b/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtTokens.kt deleted file mode 100644 index 7074286..0000000 --- a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtTokens.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.ticketaka.api.common.infrastructure.jwt - -data class JwtTokens( - val accessToken: String, - val refreshToken: String, -) diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/RawToken.kt b/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/RawToken.kt deleted file mode 100644 index 359df22..0000000 --- a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/RawToken.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.ticketaka.api.common.infrastructure.jwt - -data class RawToken( - val subject: String, -) diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/queue/DBTokenWaitingQueue.kt b/src/main/kotlin/io/ticketaka/api/common/infrastructure/queue/DBTokenWaitingQueue.kt deleted file mode 100644 index 01f86d8..0000000 --- a/src/main/kotlin/io/ticketaka/api/common/infrastructure/queue/DBTokenWaitingQueue.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.ticketaka.api.common.infrastructure.queue - -import io.ticketaka.api.common.domain.queue.TokenWaitingQueue -import io.ticketaka.api.user.domain.Token -import io.ticketaka.api.user.domain.TokenRepository - -// @Component -class DBTokenWaitingQueue( - private val tokenRepository: TokenRepository, -) : TokenWaitingQueue { - override fun offer(element: Token): Boolean { - tokenRepository.save(element) - return true - } - - override fun poll(): Token? { - return tokenRepository.findFirstTokenOrderByIssuedTimeAscLimit1()?.also { - tokenRepository.delete(it) - } - } - - override fun peek(): Token? { - return tokenRepository.findFirstTokenOrderByIssuedTimeAscLimit1() - } - - override fun size(): Long { - return tokenRepository.count() - } -} diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/queue/InMemoryWaitingMap.kt b/src/main/kotlin/io/ticketaka/api/common/infrastructure/queue/InMemoryWaitingMap.kt new file mode 100644 index 0000000..9c6eeb6 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/common/infrastructure/queue/InMemoryWaitingMap.kt @@ -0,0 +1,46 @@ +package io.ticketaka.api.common.infrastructure.queue + +import io.ticketaka.api.common.domain.map.TokenWaitingMap +import io.ticketaka.api.user.domain.token.QueueToken +import org.springframework.stereotype.Component +import java.util.concurrent.ConcurrentHashMap + +@Component +class InMemoryWaitingMap : TokenWaitingMap { + private val queueTokenCache: MutableMap = ConcurrentHashMap() + + override fun put( + key: Long, + queueToken: QueueToken, + ): Boolean { + queueTokenCache[key] = queueToken + return true + } + + override fun putAll(queueTokens: List): Boolean { + queueTokenCache.putAll(queueTokens.map { it.id to it }) + return true + } + + override fun get(key: Long): QueueToken? { + return queueTokenCache[key] + } + + override fun findAll(): List { + return queueTokenCache.values.toList() + } + + override fun clear() { + queueTokenCache.clear() + } + + override fun remove(key: Long): QueueToken? { + return queueTokenCache.remove(key) + } + + override fun size(): Long { + synchronized(this) { + return queueTokenCache.size.toLong() + } + } +} diff --git a/src/main/kotlin/io/ticketaka/api/concert/domain/Concert.kt b/src/main/kotlin/io/ticketaka/api/concert/domain/Concert.kt index 57f8e27..7b78517 100644 --- a/src/main/kotlin/io/ticketaka/api/concert/domain/Concert.kt +++ b/src/main/kotlin/io/ticketaka/api/concert/domain/Concert.kt @@ -1,14 +1,17 @@ package io.ticketaka.api.concert.domain import io.ticketaka.api.common.infrastructure.tsid.TsIdKeyGenerator +import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.Id import jakarta.persistence.PostLoad import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate import jakarta.persistence.Table import jakarta.persistence.Transient import org.springframework.data.domain.Persistable import java.time.LocalDate +import java.time.LocalDateTime @Entity @Table(name = "concerts") @@ -17,6 +20,19 @@ class Concert( val id: Long, val date: LocalDate, ) : Persistable { + @Column(nullable = false, updatable = false) + var createdAt: LocalDateTime? = LocalDateTime.now() + private set + + @Column(nullable = false) + var updatedAt: LocalDateTime? = null + private set + + @PreUpdate + fun onPreUpdate() { + updatedAt = LocalDateTime.now() + } + @Transient private var isNew = true diff --git a/src/main/kotlin/io/ticketaka/api/concert/domain/Seat.kt b/src/main/kotlin/io/ticketaka/api/concert/domain/Seat.kt index 78346e8..77cbcf0 100644 --- a/src/main/kotlin/io/ticketaka/api/concert/domain/Seat.kt +++ b/src/main/kotlin/io/ticketaka/api/concert/domain/Seat.kt @@ -2,17 +2,20 @@ package io.ticketaka.api.concert.domain import io.ticketaka.api.common.exception.ReservationStateException import io.ticketaka.api.common.infrastructure.tsid.TsIdKeyGenerator +import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated import jakarta.persistence.Id import jakarta.persistence.PostLoad import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate import jakarta.persistence.Table import jakarta.persistence.Transient import org.springframework.data.domain.Persistable import java.math.BigDecimal import java.time.LocalDate +import java.time.LocalDateTime @Entity @Table(name = "seats") @@ -26,6 +29,19 @@ class Seat( val concertId: Long, val concertDate: LocalDate, ) : Persistable { + @Column(nullable = false, updatable = false) + var createdAt: LocalDateTime? = LocalDateTime.now() + private set + + @Column(nullable = false) + var updatedAt: LocalDateTime? = null + private set + + @PreUpdate + fun onPreUpdate() { + updatedAt = LocalDateTime.now() + } + @Transient private var isNew = true diff --git a/src/main/kotlin/io/ticketaka/api/point/application/PointService.kt b/src/main/kotlin/io/ticketaka/api/point/application/PointService.kt index 213400b..d406220 100644 --- a/src/main/kotlin/io/ticketaka/api/point/application/PointService.kt +++ b/src/main/kotlin/io/ticketaka/api/point/application/PointService.kt @@ -5,25 +5,25 @@ import io.ticketaka.api.point.application.dto.BalanceQueryModel import io.ticketaka.api.point.application.dto.RechargeCommand import io.ticketaka.api.point.domain.CachePointRecharger import io.ticketaka.api.point.domain.PointRechargeEvent -import io.ticketaka.api.user.application.TokenUserCacheAsideQueryService +import io.ticketaka.api.user.application.QueueTokenUserCacheAsideQueryService import org.springframework.stereotype.Service @Service class PointService( - private val tokenUserCacheAsideQueryService: TokenUserCacheAsideQueryService, + private val queueTokenUserCacheAsideQueryService: QueueTokenUserCacheAsideQueryService, private val pointCacheAsideQueryService: PointCacheAsideQueryService, private val cachePointRecharger: CachePointRecharger, private val eventBroker: EventBroker, ) { fun recharge(rechargeCommand: RechargeCommand) { - val user = tokenUserCacheAsideQueryService.getUser(rechargeCommand.userId) + val user = queueTokenUserCacheAsideQueryService.getUser(rechargeCommand.userId) val point = pointCacheAsideQueryService.getPoint(user.pointId) cachePointRecharger.recharge(point.id, rechargeCommand.amount) eventBroker.produce(PointRechargeEvent(user.id, point.id, rechargeCommand.amount)) } fun getBalance(userId: Long): BalanceQueryModel { - val user = tokenUserCacheAsideQueryService.getUser(userId) + val user = queueTokenUserCacheAsideQueryService.getUser(userId) val point = pointCacheAsideQueryService.getPoint(user.pointId) return BalanceQueryModel(user.id, point.balance) } diff --git a/src/main/kotlin/io/ticketaka/api/point/domain/Idempotent.kt b/src/main/kotlin/io/ticketaka/api/point/domain/Idempotent.kt index 0658624..6af61c3 100644 --- a/src/main/kotlin/io/ticketaka/api/point/domain/Idempotent.kt +++ b/src/main/kotlin/io/ticketaka/api/point/domain/Idempotent.kt @@ -2,14 +2,17 @@ package io.ticketaka.api.point.domain import io.ticketaka.api.common.infrastructure.IdempotentKeyGenerator import io.ticketaka.api.common.infrastructure.tsid.TsIdKeyGenerator +import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.Id import jakarta.persistence.PostLoad import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate import jakarta.persistence.Table import jakarta.persistence.Transient import org.springframework.data.domain.Persistable import java.math.BigDecimal +import java.time.LocalDateTime @Entity @Table(name = "idempotent") @@ -18,6 +21,19 @@ class Idempotent( var id: Long, val key: String, ) : Persistable { + @Column(nullable = false, updatable = false) + var createdAt: LocalDateTime? = LocalDateTime.now() + private set + + @Column(nullable = false) + var updatedAt: LocalDateTime? = null + private set + + @PreUpdate + fun onPreUpdate() { + updatedAt = LocalDateTime.now() + } + @Transient private var isNew = true diff --git a/src/main/kotlin/io/ticketaka/api/point/domain/Point.kt b/src/main/kotlin/io/ticketaka/api/point/domain/Point.kt index f2cef97..bec698d 100644 --- a/src/main/kotlin/io/ticketaka/api/point/domain/Point.kt +++ b/src/main/kotlin/io/ticketaka/api/point/domain/Point.kt @@ -20,8 +20,8 @@ class Point protected constructor( @Id val id: Long, var balance: BigDecimal, - val createTime: LocalDateTime?, - var updateTime: LocalDateTime, + val createdAt: LocalDateTime?, + var updatedAt: LocalDateTime, ) : Persistable { @Transient private var isNew = true @@ -43,7 +43,7 @@ class Point protected constructor( fun recharge(amount: BigDecimal) { if (amount < BigDecimal.ZERO) throw BadClientRequestException("충전 금액은 0보다 커야 합니다.") this.balance = this.balance.plus(amount) - this.updateTime = LocalDateTime.now() + this.updatedAt = LocalDateTime.now() } fun charge(price: BigDecimal) { @@ -65,8 +65,8 @@ class Point protected constructor( return Point( id = id, balance = balance, - createTime = null, - updateTime = updateTime, + createdAt = null, + updatedAt = updateTime, ) } @@ -75,8 +75,8 @@ class Point protected constructor( return Point( id = TsIdKeyGenerator.nextLong(), balance = balance, - createTime = now, - updateTime = now, + createdAt = now, + updatedAt = now, ) } @@ -84,8 +84,8 @@ class Point protected constructor( return Point( id = id, balance = BigDecimal.ZERO, - createTime = LocalDateTime.now(), - updateTime = LocalDateTime.now(), + createdAt = LocalDateTime.now(), + updatedAt = LocalDateTime.now(), ) } } diff --git a/src/main/kotlin/io/ticketaka/api/point/domain/PointHistory.kt b/src/main/kotlin/io/ticketaka/api/point/domain/PointHistory.kt index 96ce3be..a5534be 100644 --- a/src/main/kotlin/io/ticketaka/api/point/domain/PointHistory.kt +++ b/src/main/kotlin/io/ticketaka/api/point/domain/PointHistory.kt @@ -1,12 +1,14 @@ package io.ticketaka.api.point.domain import io.ticketaka.api.common.infrastructure.tsid.TsIdKeyGenerator +import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated import jakarta.persistence.Id import jakarta.persistence.PostLoad import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate import jakarta.persistence.Table import jakarta.persistence.Transient import org.springframework.data.domain.Persistable @@ -23,8 +25,20 @@ class PointHistory( val userId: Long, val pointId: Long, val amount: BigDecimal, - val createTime: LocalDateTime = LocalDateTime.now(), ) : Persistable { + @Column(nullable = false, updatable = false) + var createdAt: LocalDateTime? = LocalDateTime.now() + private set + + @Column(nullable = false) + var updatedAt: LocalDateTime? = null + private set + + @PreUpdate + fun onPreUpdate() { + updatedAt = LocalDateTime.now() + } + @Transient private var isNew = true diff --git a/src/main/kotlin/io/ticketaka/api/point/domain/payment/Payment.kt b/src/main/kotlin/io/ticketaka/api/point/domain/payment/Payment.kt index e392060..ea92512 100644 --- a/src/main/kotlin/io/ticketaka/api/point/domain/payment/Payment.kt +++ b/src/main/kotlin/io/ticketaka/api/point/domain/payment/Payment.kt @@ -2,10 +2,12 @@ package io.ticketaka.api.point.domain.payment import io.ticketaka.api.common.domain.AbstractAggregateRoot import io.ticketaka.api.common.infrastructure.tsid.TsIdKeyGenerator +import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.Id import jakarta.persistence.PostLoad import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate import jakarta.persistence.Table import jakarta.persistence.Transient import org.springframework.data.domain.Persistable @@ -22,6 +24,19 @@ class Payment( val userId: Long, val pointId: Long, ) : AbstractAggregateRoot(), Persistable { + @Column(nullable = false, updatable = false) + var createdAt: LocalDateTime? = LocalDateTime.now() + private set + + @Column(nullable = false) + var updatedAt: LocalDateTime? = null + private set + + @PreUpdate + fun onPreUpdate() { + updatedAt = LocalDateTime.now() + } + @Transient private var isNew = true diff --git a/src/main/kotlin/io/ticketaka/api/reservation/application/ReservationService.kt b/src/main/kotlin/io/ticketaka/api/reservation/application/ReservationService.kt index 4522d79..41698ad 100644 --- a/src/main/kotlin/io/ticketaka/api/reservation/application/ReservationService.kt +++ b/src/main/kotlin/io/ticketaka/api/reservation/application/ReservationService.kt @@ -7,21 +7,21 @@ import io.ticketaka.api.concert.domain.ConcertSeatUpdater import io.ticketaka.api.reservation.application.dto.CreateReservationCommand import io.ticketaka.api.reservation.domain.reservation.ReservationCreateEvent import io.ticketaka.api.reservation.domain.reservation.ReservationRepository -import io.ticketaka.api.user.application.TokenUserCacheAsideQueryService +import io.ticketaka.api.user.application.QueueTokenUserCacheAsideQueryService import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service class ReservationService( - private val tokenUserCacheAsideQueryService: TokenUserCacheAsideQueryService, + private val queueTokenUserCacheAsideQueryService: QueueTokenUserCacheAsideQueryService, private val concertCacheAsideQueryService: ConcertCacheAsideQueryService, private val reservationRepository: ReservationRepository, private val concertSeatUpdater: ConcertSeatUpdater, private val eventBroker: EventBroker, ) { fun createReservation(command: CreateReservationCommand) { - val user = tokenUserCacheAsideQueryService.getUser(command.userId) + val user = queueTokenUserCacheAsideQueryService.getUser(command.userId) val concert = concertCacheAsideQueryService.getConcert(command.date) val seats = concertSeatUpdater.reserve(concert.id, command.date, command.seatNumbers) eventBroker.produce(ReservationCreateEvent(user.id, concert.id, seats.map { it.id })) @@ -33,7 +33,7 @@ class ReservationService( userId: Long, reservationId: Long, ) { - val user = tokenUserCacheAsideQueryService.getUser(userId) + val user = queueTokenUserCacheAsideQueryService.getUser(userId) val reservation = reservationRepository.findById(reservationId) ?: throw NotFoundException("예약을 찾을 수 없습니다.") diff --git a/src/main/kotlin/io/ticketaka/api/reservation/domain/reservation/Reservation.kt b/src/main/kotlin/io/ticketaka/api/reservation/domain/reservation/Reservation.kt index 0ee4b56..e3975e9 100644 --- a/src/main/kotlin/io/ticketaka/api/reservation/domain/reservation/Reservation.kt +++ b/src/main/kotlin/io/ticketaka/api/reservation/domain/reservation/Reservation.kt @@ -3,12 +3,14 @@ package io.ticketaka.api.reservation.domain.reservation import io.ticketaka.api.common.infrastructure.tsid.TsIdKeyGenerator import io.ticketaka.api.concert.domain.Seat import io.ticketaka.api.user.domain.User +import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated import jakarta.persistence.Id import jakarta.persistence.PostLoad import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate import jakarta.persistence.Table import jakarta.persistence.Transient import org.springframework.data.domain.Persistable @@ -26,6 +28,19 @@ class Reservation( val userId: Long, val concertId: Long, ) : Persistable { + @Column(nullable = false, updatable = false) + var createdAt: LocalDateTime? = LocalDateTime.now() + private set + + @Column(nullable = false) + var updatedAt: LocalDateTime? = null + private set + + @PreUpdate + fun onPreUpdate() { + updatedAt = LocalDateTime.now() + } + @Transient private var isNew = true diff --git a/src/main/kotlin/io/ticketaka/api/user/application/TokenUserCacheAsideQueryService.kt b/src/main/kotlin/io/ticketaka/api/user/application/QueueTokenUserCacheAsideQueryService.kt similarity index 92% rename from src/main/kotlin/io/ticketaka/api/user/application/TokenUserCacheAsideQueryService.kt rename to src/main/kotlin/io/ticketaka/api/user/application/QueueTokenUserCacheAsideQueryService.kt index d5866ab..ff8e126 100644 --- a/src/main/kotlin/io/ticketaka/api/user/application/TokenUserCacheAsideQueryService.kt +++ b/src/main/kotlin/io/ticketaka/api/user/application/QueueTokenUserCacheAsideQueryService.kt @@ -7,7 +7,7 @@ import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Service @Service -class TokenUserCacheAsideQueryService( +class QueueTokenUserCacheAsideQueryService( private val userRepository: UserRepository, ) { @Cacheable(value = ["user"], key = "#id") diff --git a/src/main/kotlin/io/ticketaka/api/user/application/TokenUserService.kt b/src/main/kotlin/io/ticketaka/api/user/application/QueueTokenUserService.kt similarity index 64% rename from src/main/kotlin/io/ticketaka/api/user/application/TokenUserService.kt rename to src/main/kotlin/io/ticketaka/api/user/application/QueueTokenUserService.kt index 40191fb..b5c73ae 100644 --- a/src/main/kotlin/io/ticketaka/api/user/application/TokenUserService.kt +++ b/src/main/kotlin/io/ticketaka/api/user/application/QueueTokenUserService.kt @@ -1,22 +1,22 @@ package io.ticketaka.api.user.application import io.ticketaka.api.common.domain.map.TokenWaitingMap -import io.ticketaka.api.user.domain.Token +import io.ticketaka.api.user.domain.token.QueueToken import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service @Service -class TokenUserService( +class QueueTokenUserService( private val tokenWaitingMap: TokenWaitingMap, - private val tokenUserCacheAsideQueryService: TokenUserCacheAsideQueryService, + private val queueTokenUserCacheAsideQueryService: QueueTokenUserCacheAsideQueryService, private val applicationEventPublisher: ApplicationEventPublisher, ) { fun createToken(userId: Long): Long { - val user = tokenUserCacheAsideQueryService.getUser(userId) + val user = queueTokenUserCacheAsideQueryService.getUser(userId) - val token = Token.newInstance(user.id) - token.pollAllEvents().forEach { applicationEventPublisher.publishEvent(it) } - return token.id + val queueToken = QueueToken.newInstance(user.id) + queueToken.pollAllEvents().forEach { applicationEventPublisher.publishEvent(it) } + return queueToken.id } fun peekToken(tokenId: Long): Boolean { @@ -32,6 +32,6 @@ class TokenUserService( if (queueSize > 1000) { return false } - return tokenFromMap.status == Token.Status.ACTIVE + return tokenFromMap.status == QueueToken.Status.ACTIVE } } diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/Role.kt b/src/main/kotlin/io/ticketaka/api/user/domain/Role.kt new file mode 100644 index 0000000..1c95930 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/Role.kt @@ -0,0 +1,17 @@ +package io.ticketaka.api.user.domain + +import java.util.Arrays + +enum class Role { + USER, + ; + + companion object { + fun findByRole(role: String): Role { + return Arrays.stream(entries.toTypedArray()) + .filter { r -> r.name == role } + .findFirst() + .orElseThrow { IllegalArgumentException() } + } + } +} diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/TokenRepository.kt b/src/main/kotlin/io/ticketaka/api/user/domain/TokenRepository.kt deleted file mode 100644 index a9e6888..0000000 --- a/src/main/kotlin/io/ticketaka/api/user/domain/TokenRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.ticketaka.api.user.domain - -interface TokenRepository { - fun save(token: Token): Token - - fun delete(token: Token) - - fun findFirstTokenOrderByIssuedTimeAscLimit1(): Token? - - fun count(): Long -} diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/User.kt b/src/main/kotlin/io/ticketaka/api/user/domain/User.kt index 26667de..61261c9 100644 --- a/src/main/kotlin/io/ticketaka/api/user/domain/User.kt +++ b/src/main/kotlin/io/ticketaka/api/user/domain/User.kt @@ -1,13 +1,16 @@ package io.ticketaka.api.user.domain import io.ticketaka.api.common.infrastructure.tsid.TsIdKeyGenerator +import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.Id import jakarta.persistence.PostLoad import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate import jakarta.persistence.Table import jakarta.persistence.Transient import org.springframework.data.domain.Persistable +import java.time.LocalDateTime @Entity @Table(name = "users") @@ -33,6 +36,19 @@ class User protected constructor( isNew = false } + @Column(nullable = false, updatable = false) + var createdAt: LocalDateTime? = LocalDateTime.now() + private set + + @Column(nullable = false) + var updatedAt: LocalDateTime? = null + private set + + @PreUpdate + fun onPreUpdate() { + updatedAt = LocalDateTime.now() + } + companion object { fun newInstance(pointId: Long): User { return User( diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/exception/AlreadyTokenExpiredException.kt b/src/main/kotlin/io/ticketaka/api/user/domain/exception/AlreadyTokenExpiredException.kt new file mode 100644 index 0000000..97d7d16 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/exception/AlreadyTokenExpiredException.kt @@ -0,0 +1,3 @@ +package io.ticketaka.api.user.domain.exception + +class AlreadyTokenExpiredException(message: String = "토큰이 만료되었습니다.") : RuntimeException(message) diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/exception/InvalidTokenException.kt b/src/main/kotlin/io/ticketaka/api/user/domain/exception/InvalidTokenException.kt new file mode 100644 index 0000000..79a64f9 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/exception/InvalidTokenException.kt @@ -0,0 +1,3 @@ +package io.ticketaka.api.user.domain.exception + +class InvalidTokenException(message: String = "유효하지 않은 토큰입니다.") : RuntimeException(message) diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/exception/RefreshTokenNotFoundException.kt b/src/main/kotlin/io/ticketaka/api/user/domain/exception/RefreshTokenNotFoundException.kt new file mode 100644 index 0000000..b20146c --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/exception/RefreshTokenNotFoundException.kt @@ -0,0 +1,3 @@ +package io.ticketaka.api.user.domain.exception + +class RefreshTokenNotFoundException(message: String = "리프레시 토큰을 찾을 수 없습니다.") : RuntimeException(message) diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/exception/UserNotFoundException.kt b/src/main/kotlin/io/ticketaka/api/user/domain/exception/UserNotFoundException.kt new file mode 100644 index 0000000..ca51b4e --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/exception/UserNotFoundException.kt @@ -0,0 +1,3 @@ +package io.ticketaka.api.user.domain.exception + +class UserNotFoundException(message: String = "사용자를 찾을 수 없습니다.") : RuntimeException(message) diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/Token.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/QueueToken.kt similarity index 86% rename from src/main/kotlin/io/ticketaka/api/user/domain/Token.kt rename to src/main/kotlin/io/ticketaka/api/user/domain/token/QueueToken.kt index 29ea942..89d9198 100644 --- a/src/main/kotlin/io/ticketaka/api/user/domain/Token.kt +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/QueueToken.kt @@ -1,21 +1,17 @@ -package io.ticketaka.api.user.domain +package io.ticketaka.api.user.domain.token import io.ticketaka.api.common.domain.AbstractAggregateRoot import io.ticketaka.api.common.infrastructure.tsid.TsIdKeyGenerator -import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated import jakarta.persistence.Id import jakarta.persistence.PostLoad import jakarta.persistence.PrePersist -import jakarta.persistence.Table import jakarta.persistence.Transient import org.springframework.data.domain.Persistable import java.time.LocalDateTime -@Entity -@Table(name = "tokens") -class Token protected constructor( +class QueueToken protected constructor( @Id val id: Long, val issuedTime: LocalDateTime, @@ -80,8 +76,8 @@ class Token protected constructor( } companion object { - fun newInstance(userId: Long): Token { - return Token( + fun newInstance(userId: Long): QueueToken { + return QueueToken( id = TsIdKeyGenerator.nextLong(), issuedTime = LocalDateTime.now(), status = Status.ACTIVE, @@ -94,8 +90,8 @@ class Token protected constructor( issuedTime: LocalDateTime, status: Status, userId: Long, - ): Token { - return Token(id, issuedTime, status, userId) + ): QueueToken { + return QueueToken(id, issuedTime, status, userId) } } } diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/token/RawToken.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/RawToken.kt new file mode 100644 index 0000000..552bc8d --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/RawToken.kt @@ -0,0 +1,15 @@ +package io.ticketaka.api.user.domain.token + +import io.ticketaka.api.user.domain.exception.InvalidTokenException + +data class RawToken( + val subject: String, + val jti: String? = null, + val authorities: List, +) { + fun verify(authority: String) { + if (!authorities.stream().anyMatch { it.equals(authority) }) { + throw InvalidTokenException() + } + } +} diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/token/RefreshTokenInfo.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/RefreshTokenInfo.kt new file mode 100644 index 0000000..34b1674 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/RefreshTokenInfo.kt @@ -0,0 +1,46 @@ +package io.ticketaka.api.user.domain.token + +import io.ticketaka.api.user.domain.exception.AlreadyTokenExpiredException +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.PrePersist +import jakarta.persistence.PreUpdate +import jakarta.persistence.Table +import java.time.LocalDateTime + +@Entity +@Table(name = "refresh_token_info") +class RefreshTokenInfo protected constructor( + @Id + val id: Long, + var refreshTokenJti: String? = null, + var expired: Boolean = false, +) { + @Column(nullable = false, updatable = false) + var createdAt: LocalDateTime? = null + private set + + @Column(nullable = false) + var updatedAt: LocalDateTime? = null + private set + + @PrePersist + fun onPrePersist() { + val now = LocalDateTime.now() + createdAt = now + updatedAt = now + } + + @PreUpdate + fun onPreUpdate() { + updatedAt = LocalDateTime.now() + } + + fun expire() { + if (this.expired) { + throw AlreadyTokenExpiredException() + } + this.expired = true + } +} diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/token/RefreshTokenInfoRepository.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/RefreshTokenInfoRepository.kt new file mode 100644 index 0000000..2ceee9e --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/RefreshTokenInfoRepository.kt @@ -0,0 +1,9 @@ +package io.ticketaka.api.user.domain.token + +interface RefreshTokenInfoRepository { + fun save(entity: RefreshTokenInfo): RefreshTokenInfo + + fun existsByRefreshTokenJti(jti: String): Boolean + + fun findByRefreshTokenJti(jti: String): RefreshTokenInfo? +} diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/TokenCreatedEvent.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenCreatedEvent.kt similarity index 59% rename from src/main/kotlin/io/ticketaka/api/user/domain/TokenCreatedEvent.kt rename to src/main/kotlin/io/ticketaka/api/user/domain/token/TokenCreatedEvent.kt index 8a75d6f..9dd74cb 100644 --- a/src/main/kotlin/io/ticketaka/api/user/domain/TokenCreatedEvent.kt +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenCreatedEvent.kt @@ -1,7 +1,7 @@ -package io.ticketaka.api.user.domain +package io.ticketaka.api.user.domain.token import io.ticketaka.api.common.domain.DomainEvent -import io.ticketaka.api.user.domain.Token.Status +import io.ticketaka.api.user.domain.token.QueueToken.Status import java.time.LocalDateTime data class TokenCreatedEvent( @@ -11,11 +11,11 @@ data class TokenCreatedEvent( val userId: Long, val occurredOn: LocalDateTime = LocalDateTime.now(), ) : DomainEvent { - constructor(token: Token) : this( - token.id, - token.issuedTime, - token.status, - token.userId, + constructor(queueToken: QueueToken) : this( + queueToken.id, + queueToken.issuedTime, + queueToken.status, + queueToken.userId, ) override fun occurredOn(): LocalDateTime { diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenExtractor.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenExtractor.kt new file mode 100644 index 0000000..1226fef --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenExtractor.kt @@ -0,0 +1,5 @@ +package io.ticketaka.api.user.domain.token + +interface TokenExtractor { + fun extract(payload: String): String +} diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenGenerator.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenGenerator.kt new file mode 100644 index 0000000..45ae970 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenGenerator.kt @@ -0,0 +1,10 @@ +package io.ticketaka.api.user.domain.token + +import io.ticketaka.api.user.domain.Role + +interface TokenGenerator { + fun generate( + subject: String, + roles: Set, + ): Tokens +} diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenParser.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenParser.kt new file mode 100644 index 0000000..f308c5c --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenParser.kt @@ -0,0 +1,5 @@ +package io.ticketaka.api.user.domain.token + +interface TokenParser { + fun parse(token: String): RawToken +} diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenReIssuer.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenReIssuer.kt new file mode 100644 index 0000000..8cc3e70 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/TokenReIssuer.kt @@ -0,0 +1,10 @@ +package io.ticketaka.api.user.domain.token + +import io.ticketaka.api.user.domain.Role + +interface TokenReIssuer { + fun reIssue( + refreshToken: String, + roles: Set, + ): Tokens +} diff --git a/src/main/kotlin/io/ticketaka/api/user/domain/token/Tokens.kt b/src/main/kotlin/io/ticketaka/api/user/domain/token/Tokens.kt new file mode 100644 index 0000000..7cd9547 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/domain/token/Tokens.kt @@ -0,0 +1,6 @@ +package io.ticketaka.api.user.domain.token + +data class Tokens( + val accessToken: String, + val refreshToken: String, +) diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/event/TokenEventHandler.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/event/TokenEventHandler.kt index 155d1c6..6f94811 100644 --- a/src/main/kotlin/io/ticketaka/api/user/infrastructure/event/TokenEventHandler.kt +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/event/TokenEventHandler.kt @@ -2,8 +2,8 @@ package io.ticketaka.api.user.infrastructure.event import io.ticketaka.api.common.domain.map.TokenWaitingMap import io.ticketaka.api.common.exception.TooManyRequestException -import io.ticketaka.api.user.domain.Token -import io.ticketaka.api.user.domain.TokenCreatedEvent +import io.ticketaka.api.user.domain.token.QueueToken +import io.ticketaka.api.user.domain.token.TokenCreatedEvent import org.springframework.context.event.EventListener import org.springframework.stereotype.Component @@ -16,7 +16,7 @@ class TokenEventHandler( if (tokenWaitingQueue.size() >= 500) { throw TooManyRequestException("토큰 용량이 초과되었습니다.") } - val token = Token.newInstance(event.id, event.issuedTime, event.status, event.userId) - tokenWaitingQueue.put(event.id, token) + val queueToken = QueueToken.newInstance(event.id, event.issuedTime, event.status, event.userId) + tokenWaitingQueue.put(event.id, queueToken) } } diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/jpa/JpaRefreshTokenInfoRepository.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jpa/JpaRefreshTokenInfoRepository.kt new file mode 100644 index 0000000..6e49173 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jpa/JpaRefreshTokenInfoRepository.kt @@ -0,0 +1,8 @@ +package io.ticketaka.api.user.infrastructure.jpa + +import io.ticketaka.api.user.domain.token.RefreshTokenInfo +import org.springframework.data.jpa.repository.JpaRepository + +interface JpaRefreshTokenInfoRepository : JpaRepository { + fun findByRefreshTokenJti(jti: String): RefreshTokenInfo? +} diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/jpa/JpaTokenRepository.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jpa/JpaTokenRepository.kt deleted file mode 100644 index 23bbfaa..0000000 --- a/src/main/kotlin/io/ticketaka/api/user/infrastructure/jpa/JpaTokenRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.ticketaka.api.user.infrastructure.jpa - -import io.ticketaka.api.user.domain.Token -import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.data.jpa.repository.Query - -interface JpaTokenRepository : JpaRepository { - @Query("SELECT t FROM Token t ORDER BY t.issuedTime ASC LIMIT 1") - fun findFirstTokenOrderByIssuedTimeAscLimit1(): Token? -} diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtProperties.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtProperties.kt similarity index 89% rename from src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtProperties.kt rename to src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtProperties.kt index cb48b59..9e5f551 100644 --- a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtProperties.kt +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtProperties.kt @@ -1,4 +1,4 @@ -package io.ticketaka.api.common.infrastructure.jwt +package io.ticketaka.api.user.infrastructure.jwt import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenExtractor.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenExtractor.kt new file mode 100644 index 0000000..f4fa56d --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenExtractor.kt @@ -0,0 +1,17 @@ +package io.ticketaka.api.user.infrastructure.jwt + +import io.ticketaka.api.user.domain.token.TokenExtractor +import org.springframework.stereotype.Component + +@Component +class JwtTokenExtractor : TokenExtractor { + private val headerPrefix = "Bearer " + + override fun extract(payload: String): String { + return if (payload.isEmpty()) { + "" + } else { + payload.substring(headerPrefix.length) + } + } +} diff --git a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtProvider.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenGenerator.kt similarity index 51% rename from src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtProvider.kt rename to src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenGenerator.kt index bb75a64..c6ded4e 100644 --- a/src/main/kotlin/io/ticketaka/api/common/infrastructure/jwt/JwtProvider.kt +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenGenerator.kt @@ -1,28 +1,40 @@ -package io.ticketaka.api.common.infrastructure.jwt +package io.ticketaka.api.user.infrastructure.jwt import io.jsonwebtoken.Jwts +import io.jsonwebtoken.io.Decoders +import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.Keys +import io.ticketaka.api.user.domain.Role +import io.ticketaka.api.user.domain.token.TokenGenerator +import io.ticketaka.api.user.domain.token.Tokens import org.springframework.stereotype.Component -import java.nio.charset.StandardCharsets import java.time.LocalDateTime import java.time.ZoneId import java.util.Date import java.util.UUID -import kotlin.collections.HashMap @Component -class JwtProvider( +class JwtTokenGenerator( private val jwtProperties: JwtProperties, -) { - fun generate(tsid: String): JwtTokens { - return JwtTokens(generateAccessToken(tsid), generateRefreshToken(tsid)) +) : TokenGenerator { + private val encodedSigningKey = Encoders.BASE64.encode(jwtProperties.base64TokenSigningKey.toByteArray()) + + override fun generate( + subject: String, + roles: Set, + ): Tokens { + return Tokens(generateAccessToken(subject, roles), generateRefreshToken(subject)) } - private fun generateAccessToken(tsid: String): String { + private fun generateAccessToken( + subject: String, + roles: Set, + ): String { val claims = HashMap() - claims["tsid"] = tsid + claims["subject"] = subject + claims["scopes"] = getAuthorities(roles) val currentTime = LocalDateTime.now() - val key = Keys.hmacShaKeyFor(jwtProperties.base64TokenSigningKey.toByteArray(StandardCharsets.UTF_8)) + val key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(encodedSigningKey)) return Jwts.builder() .claims(claims) @@ -30,7 +42,7 @@ class JwtProvider( .issuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())) .expiration( Date.from( - currentTime.plusSeconds(jwtProperties.refreshExpirationSec) + currentTime.plusSeconds(jwtProperties.tokenExpirationSec) .atZone(ZoneId.systemDefault()).toInstant(), ), ) @@ -38,13 +50,13 @@ class JwtProvider( .compact() } - private fun generateRefreshToken(tsid: String): String { + private fun generateRefreshToken(subject: String): String { val claims = HashMap() - claims["tsid"] = tsid - claims["scopes"] = listOf("REFRESH_TOKEN") + claims["subject"] = subject + claims["scopes"] = listOf(Scopes.REFRESH_TOKEN.authority()) val randomJti = UUID.randomUUID().toString() val currentTime = LocalDateTime.now() - val key = Keys.hmacShaKeyFor(jwtProperties.base64TokenSigningKey.toByteArray(StandardCharsets.UTF_8)) + val key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(encodedSigningKey)) return Jwts.builder() .claims(claims) @@ -52,7 +64,7 @@ class JwtProvider( .issuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())) .expiration( Date.from( - currentTime.plusSeconds(jwtProperties.refreshExpirationSec.toLong()) + currentTime.plusSeconds(jwtProperties.refreshExpirationSec) .atZone(ZoneId.systemDefault()).toInstant(), ), ) @@ -60,4 +72,8 @@ class JwtProvider( .signWith(key) .compact() } + + private fun getAuthorities(roles: Set): List { + return roles.map { "ROLE_${it.name}" } + } } diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenParser.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenParser.kt new file mode 100644 index 0000000..500110d --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenParser.kt @@ -0,0 +1,62 @@ +package io.ticketaka.api.user.infrastructure.jwt + +import io.jsonwebtoken.Claims +import io.jsonwebtoken.ExpiredJwtException +import io.jsonwebtoken.Jws +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.MalformedJwtException +import io.jsonwebtoken.UnsupportedJwtException +import io.jsonwebtoken.io.Decoders +import io.jsonwebtoken.io.Encoders +import io.jsonwebtoken.security.Keys +import io.ticketaka.api.user.domain.exception.AlreadyTokenExpiredException +import io.ticketaka.api.user.domain.exception.InvalidTokenException +import io.ticketaka.api.user.domain.token.RawToken +import io.ticketaka.api.user.domain.token.TokenParser +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +@Component +class JwtTokenParser( + private val jwtProperties: JwtProperties, +) : TokenParser { + private val log = LoggerFactory.getLogger(this::class.java) + + private val encodedSigningKey = Encoders.BASE64.encode(jwtProperties.base64TokenSigningKey.toByteArray()) + + override fun parse(token: String): RawToken { + try { + val key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(encodedSigningKey)) + val jwt: Jws = + Jwts + .parser() + .verifyWith(key) + .build() + .parseSignedClaims(token) + val payload = jwt.payload + val jti = + payload["jti"] as String? + ?: return RawToken( + subject = payload["subject"] as String, + authorities = payload["scopes"] as List, + ) + return RawToken( + subject = payload["subject"] as String, + jti = jti, + authorities = payload["scopes"] as List, + ) + } catch (ex: UnsupportedJwtException) { + log.error("Invalid JWT Token", ex) + throw InvalidTokenException() + } catch (ex: MalformedJwtException) { + log.error("Invalid JWT Token", ex) + throw InvalidTokenException() + } catch (ex: IllegalArgumentException) { + log.error("Invalid JWT Token", ex) + throw InvalidTokenException() + } catch (expiredEx: ExpiredJwtException) { + log.info("JWT Token is expired", expiredEx) + throw AlreadyTokenExpiredException() + } + } +} diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenReIssuer.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenReIssuer.kt new file mode 100644 index 0000000..9c8c534 --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/JwtTokenReIssuer.kt @@ -0,0 +1,41 @@ +package io.ticketaka.api.user.infrastructure.jwt + +import io.ticketaka.api.user.domain.Role +import io.ticketaka.api.user.domain.UserRepository +import io.ticketaka.api.user.domain.exception.RefreshTokenNotFoundException +import io.ticketaka.api.user.domain.exception.UserNotFoundException +import io.ticketaka.api.user.domain.token.RefreshTokenInfoRepository +import io.ticketaka.api.user.domain.token.TokenExtractor +import io.ticketaka.api.user.domain.token.TokenGenerator +import io.ticketaka.api.user.domain.token.TokenParser +import io.ticketaka.api.user.domain.token.TokenReIssuer +import io.ticketaka.api.user.domain.token.Tokens +import org.springframework.stereotype.Component + +@Component +class JwtTokenReIssuer( + private val tokenGenerator: TokenGenerator, + private val tokenExtractor: TokenExtractor, + private val tokenParser: TokenParser, + private val refreshTokenInfoRepository: RefreshTokenInfoRepository, + private val userRepository: UserRepository, +) : TokenReIssuer { + override fun reIssue( + refreshToken: String, + roles: Set, + ): Tokens { + val rawToken = tokenExtractor.extract(refreshToken) + val token = tokenParser.parse(rawToken) + + token.verify(Scopes.REFRESH_TOKEN.authority()) + + if (!refreshTokenInfoRepository.existsByRefreshTokenJti(token.jti!!)) { + throw RefreshTokenNotFoundException() + } + + val user = userRepository.findById(token.subject.toLong()) ?: throw UserNotFoundException() + val refreshTokenJti = refreshTokenInfoRepository.findByRefreshTokenJti(token.jti) ?: throw RefreshTokenNotFoundException() + refreshTokenJti.expire() + return tokenGenerator.generate(user.getId().toString(), roles) + } +} diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/Scope.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/Scope.kt new file mode 100644 index 0000000..c85b40c --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/jwt/Scope.kt @@ -0,0 +1,10 @@ +package io.ticketaka.api.user.infrastructure.jwt + +enum class Scopes { + REFRESH_TOKEN, + ; + + fun authority(): String { + return "ROLE_${this.name}" + } +} diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/RefreshTokenInfoRepositoryImpl.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/RefreshTokenInfoRepositoryImpl.kt new file mode 100644 index 0000000..33d860a --- /dev/null +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/RefreshTokenInfoRepositoryImpl.kt @@ -0,0 +1,23 @@ +package io.ticketaka.api.user.infrastructure.persistence + +import io.ticketaka.api.user.domain.token.RefreshTokenInfo +import io.ticketaka.api.user.domain.token.RefreshTokenInfoRepository +import io.ticketaka.api.user.infrastructure.jpa.JpaRefreshTokenInfoRepository +import org.springframework.stereotype.Repository + +@Repository +class RefreshTokenInfoRepositoryImpl( + private val jpaRefreshTokenInfoRepository: JpaRefreshTokenInfoRepository, +) : RefreshTokenInfoRepository { + override fun save(entity: RefreshTokenInfo): RefreshTokenInfo { + return jpaRefreshTokenInfoRepository.save(entity) + } + + override fun existsByRefreshTokenJti(jti: String): Boolean { + return jpaRefreshTokenInfoRepository.findByRefreshTokenJti(jti) != null + } + + override fun findByRefreshTokenJti(jti: String): RefreshTokenInfo? { + return jpaRefreshTokenInfoRepository.findByRefreshTokenJti(jti) + } +} diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/TokenRepositoryComposition.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/TokenRepositoryComposition.kt deleted file mode 100644 index 2455fd7..0000000 --- a/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/TokenRepositoryComposition.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.ticketaka.api.user.infrastructure.persistence - -import io.ticketaka.api.user.domain.Token -import io.ticketaka.api.user.domain.TokenRepository -import io.ticketaka.api.user.infrastructure.jpa.JpaTokenRepository -import org.springframework.stereotype.Component - -@Component -class TokenRepositoryComposition( - private val jpaTokenRepository: JpaTokenRepository, -) : TokenRepository { - override fun save(token: Token): Token { - return jpaTokenRepository.save(token) - } - - override fun delete(token: Token) { - jpaTokenRepository.delete(token) - } - - override fun findFirstTokenOrderByIssuedTimeAscLimit1(): Token? { - return jpaTokenRepository.findFirstTokenOrderByIssuedTimeAscLimit1() - } - - override fun count(): Long { - return jpaTokenRepository.count() - } -} diff --git a/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/UserRepositoryComposition.kt b/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/UserRepositoryImpl.kt similarity index 79% rename from src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/UserRepositoryComposition.kt rename to src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/UserRepositoryImpl.kt index fb4e5dd..c6b7716 100644 --- a/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/UserRepositoryComposition.kt +++ b/src/main/kotlin/io/ticketaka/api/user/infrastructure/persistence/UserRepositoryImpl.kt @@ -6,7 +6,7 @@ import io.ticketaka.api.user.infrastructure.jpa.JpaUserRepository import org.springframework.stereotype.Repository @Repository -class UserRepositoryComposition(private val jpaUserRepository: JpaUserRepository) : UserRepository { +class UserRepositoryImpl(private val jpaUserRepository: JpaUserRepository) : UserRepository { override fun findById(id: Long): User? { return jpaUserRepository.findById(id).orElse(null) } diff --git a/src/main/kotlin/io/ticketaka/api/user/presentation/TokenApi.kt b/src/main/kotlin/io/ticketaka/api/user/presentation/QueueTokenApi.kt similarity index 77% rename from src/main/kotlin/io/ticketaka/api/user/presentation/TokenApi.kt rename to src/main/kotlin/io/ticketaka/api/user/presentation/QueueTokenApi.kt index 10c0c2a..c61f974 100644 --- a/src/main/kotlin/io/ticketaka/api/user/presentation/TokenApi.kt +++ b/src/main/kotlin/io/ticketaka/api/user/presentation/QueueTokenApi.kt @@ -1,6 +1,6 @@ package io.ticketaka.api.user.presentation -import io.ticketaka.api.user.application.TokenUserService +import io.ticketaka.api.user.application.QueueTokenUserService import io.ticketaka.api.user.presentation.dto.CreateTokenRequest import io.ticketaka.api.user.presentation.dto.CreateTokenResponse import io.ticketaka.api.user.presentation.dto.PeekTokenRequest @@ -14,14 +14,14 @@ import java.time.LocalDateTime @RestController @RequestMapping("/api/token") -class TokenApi( - private val tokenUserService: TokenUserService, -) : TokenApiSpecification { +class QueueTokenApi( + private val queueTokenUserService: QueueTokenUserService, +) : QueueTokenApiSpecification { @PostMapping override fun createToken( @RequestBody request: CreateTokenRequest, ): ResponseEntity { - val createdTokenTsid = tokenUserService.createToken(request.userId) + val createdTokenTsid = queueTokenUserService.createToken(request.userId) return ResponseEntity .ok(CreateTokenResponse(createdTokenTsid)) } @@ -30,7 +30,7 @@ class TokenApi( override fun peekToken( @RequestBody peekTokenRequest: PeekTokenRequest, ): ResponseEntity { - val result = tokenUserService.peekToken(peekTokenRequest.tokenId) + val result = queueTokenUserService.peekToken(peekTokenRequest.tokenId) return ResponseEntity .ok(PeekTokenResponse(result, LocalDateTime.now())) } diff --git a/src/main/kotlin/io/ticketaka/api/user/presentation/TokenApiSpecification.kt b/src/main/kotlin/io/ticketaka/api/user/presentation/QueueTokenApiSpecification.kt similarity index 96% rename from src/main/kotlin/io/ticketaka/api/user/presentation/TokenApiSpecification.kt rename to src/main/kotlin/io/ticketaka/api/user/presentation/QueueTokenApiSpecification.kt index 846f38a..4a97e69 100644 --- a/src/main/kotlin/io/ticketaka/api/user/presentation/TokenApiSpecification.kt +++ b/src/main/kotlin/io/ticketaka/api/user/presentation/QueueTokenApiSpecification.kt @@ -9,7 +9,7 @@ import io.ticketaka.api.user.presentation.dto.PeekTokenResponse import org.springframework.http.ResponseEntity @Tag(name = "Token", description = "토큰 도메인 API") -interface TokenApiSpecification { +interface QueueTokenApiSpecification { @Operation( summary = "토큰 대기열 조회 API", description = "토큰이 현재 사용자의 차례인지 확인하는 코드입니다.", diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e70d544..64b3908 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -45,7 +45,7 @@ jwt: tokenExpirationSec: 1800 refreshExpirationSec: 604800 tokenIssuer: tiketatka - base64TokenSigningKey: l8rSsOP9c5VhhhT3Rg2WAOhXnN4reW9NMaYqUFj11rmtqOSUE9AS + base64TokenSigningKey: -hlsZR9K42niHhGXI4qhpeetTZ-MFC121DXl_MzFBq0 token: size: 200 capacity: 200 \ No newline at end of file diff --git a/src/main/resources/sql/data.sql b/src/main/resources/sql/data.sql index e0ce4a8..dda9c13 100644 --- a/src/main/resources/sql/data.sql +++ b/src/main/resources/sql/data.sql @@ -1,116 +1,116 @@ -insert into `points` (`id`, `balance`, `create_time`, `update_time`) values (1, 5000, '2024-01-01 00:00:00', '2024-01-01 00:00:00'); -insert into `points` (`id`, `balance`, `create_time`, `update_time`) values (2, 2000, '2024-01-02 00:00:00', '2024-01-02 00:00:00'); -insert into `points` (`id`, `balance`, `create_time`, `update_time`) values (3, 1000, '2024-01-03 00:00:00', '2024-01-03 00:00:00'); -insert into `points` (`id`, `balance`, `create_time`, `update_time`) values (4, 90000, '2024-01-04 00:00:00', '2024-01-04 00:00:00'); -insert into `points` (`id`, `balance`, `create_time`, `update_time`) values (5, 3000, '2024-01-05 00:00:00', '2024-01-05 00:00:00'); +insert into `points` (`id`, `balance`, `created_at`, `updated_at`) values (1, 5000, '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `points` (`id`, `balance`, `created_at`, `updated_at`) values (2, 2000, '2024-01-02 00:00:00', '2024-01-02 00:00:00'); +insert into `points` (`id`, `balance`, `created_at`, `updated_at`) values (3, 1000, '2024-01-03 00:00:00', '2024-01-03 00:00:00'); +insert into `points` (`id`, `balance`, `created_at`, `updated_at`) values (4, 90000, '2024-01-04 00:00:00', '2024-01-04 00:00:00'); +insert into `points` (`id`, `balance`, `created_at`, `updated_at`) values (5, 3000, '2024-01-05 00:00:00', '2024-01-05 00:00:00'); -insert into `users` (`id`, `point_id`) values (1, 1); -insert into `users` (`id`, `point_id`) values (2, 2); -insert into `users` (`id`, `point_id`) values (3, 3); -insert into `users` (`id`, `point_id`) values (4, 4); -insert into `users` (`id`, `point_id`) values (5, 5); +insert into `users` (`id`, `point_id`, `created_at`, `updated_at`) values (1, 1, '2024-01-05 00:00:00', '2024-01-05 00:00:00'); +insert into `users` (`id`, `point_id`, `created_at`, `updated_at`) values (2, 2, '2024-01-05 00:00:00', '2024-01-05 00:00:00'); +insert into `users` (`id`, `point_id`, `created_at`, `updated_at`) values (3, 3, '2024-01-05 00:00:00', '2024-01-05 00:00:00'); +insert into `users` (`id`, `point_id`, `created_at`, `updated_at`) values (4, 4, '2024-01-05 00:00:00', '2024-01-05 00:00:00'); +insert into `users` (`id`, `point_id`, `created_at`, `updated_at`) values (5, 5, '2024-01-05 00:00:00', '2024-01-05 00:00:00'); -insert into `concerts` (`id`, `date`) values (1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (1, 'A1', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (2, 'A2', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (3, 'A3', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (4, 'A4', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (5, 'A5', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (6, 'A6', 100, 'RESERVED', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (7, 'A7', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (8, 'A8', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (9, 'A9', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (10, 'A10' , 100, 'AVAILABLE', 1, '2024-05-05'); +insert into `concerts` (`id`, `date`, `created_at`, `updated_at`) values (1, '2024-05-05', '2024-01-05 00:00:00', '2024-01-05 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (1, 'A1', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (2, 'A2', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (3, 'A3', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (4, 'A4', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (5, 'A5', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (6, 'A6', 100, 'RESERVED', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (7, 'A7', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (8, 'A8', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (9, 'A9', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (10, 'A10' , 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (11, 'B1', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (12, 'B2', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (13, 'B3', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (14, 'B4', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (15, 'B5', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (16, 'B6', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (17, 'B7', 100, 'RESERVED', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (18, 'B8', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (19, 'B9', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (20, 'B10', 100, 'AVAILABLE', 1, '2024-05-05'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (11, 'B1', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (12, 'B2', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (13, 'B3', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (14, 'B4', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (15, 'B5', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (16, 'B6', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (17, 'B7', 100, 'RESERVED', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (18, 'B8', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (19, 'B9', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (20, 'B10', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (21, 'C1', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (22, 'C2', 100, 'RESERVED', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (23, 'C3', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (24, 'C4', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (25, 'C5', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (26, 'C6', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (27, 'C7', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (28, 'C8', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (29, 'C9', 100, 'AVAILABLE', 1, '2024-05-05'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (30, 'C10', 100, 'AVAILABLE', 1, '2024-05-05'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (21, 'C1', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (22, 'C2', 100, 'RESERVED', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (23, 'C3', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (24, 'C4', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (25, 'C5', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (26, 'C6', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (27, 'C7', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (28, 'C8', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (29, 'C9', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (30, 'C10', 100, 'AVAILABLE', 1, '2024-05-05', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); -insert into `concerts` (`id`, `date`) values (2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (31, 'A1', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (32, 'A2', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (33, 'A3', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (34, 'A4', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (35, 'A5', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (36, 'A6', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (37, 'A7', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (38, 'A8', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (39, 'A9', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (40, 'A10', 100, 'AVAILABLE', 2, '2024-05-04'); +insert into `concerts` (`id`, `date`, `created_at`, `updated_at`) values (2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (31, 'A1', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (32, 'A2', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (33, 'A3', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (34, 'A4', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (35, 'A5', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (36, 'A6', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (37, 'A7', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (38, 'A8', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (39, 'A9', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (40, 'A10', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (41, 'B1', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (42, 'B2', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (43, 'B3', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (44, 'B4', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (45, 'B5', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (46, 'B6', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (47, 'B7', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (48, 'B8', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (49, 'B9', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (50, 'B10', 100, 'AVAILABLE', 2, '2024-05-04'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (41, 'B1', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (42, 'B2', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (43, 'B3', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (44, 'B4', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (45, 'B5', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (46, 'B6', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (47, 'B7', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (48, 'B8', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (49, 'B9', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (50, 'B10', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (51, 'C1', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (52, 'C2', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (53, 'C3', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (54, 'C4', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (55, 'C5', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (56, 'C6', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (57, 'C7', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (58, 'C8', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (59, 'C9', 100, 'AVAILABLE', 2, '2024-05-04'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (60, 'C10', 100, 'AVAILABLE', 2, '2024-05-04'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (51, 'C1', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (52, 'C2', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (53, 'C3', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (54, 'C4', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (55, 'C5', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (56, 'C6', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (57, 'C7', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (58, 'C8', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (59, 'C9', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (60, 'C10', 100, 'AVAILABLE', 2, '2024-05-04', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); -insert into `concerts` (`id`, `date`) values (3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (61, 'A1', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (62, 'A2', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (63, 'A3', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (64, 'A4', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (65, 'A5', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (66, 'A6', 100, 'RESERVED', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (67, 'A7', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (68, 'A8', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (69, 'A9', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (70, 'A10', 100, 'AVAILABLE', 3, '2024-05-06'); +insert into `concerts` (`id`, `date`, `created_at`, `updated_at`) values (3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (61, 'A1', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (62, 'A2', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (63, 'A3', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (64, 'A4', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (65, 'A5', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (66, 'A6', 100, 'RESERVED', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (67, 'A7', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (68, 'A8', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (69, 'A9', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (70, 'A10', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (71, 'B1', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (72, 'B2', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (73, 'B3', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (74, 'B4', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (75, 'B5', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (76, 'B6', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (77, 'B7', 100, 'RESERVED', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (78, 'B8', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (79, 'B9', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (80, 'B10', 100, 'AVAILABLE', 3, '2024-05-06'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (71, 'B1', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (72, 'B2', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (73, 'B3', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (74, 'B4', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (75, 'B5', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (76, 'B6', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (77, 'B7', 100, 'RESERVED', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (78, 'B8', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (79, 'B9', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (80, 'B10', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (81, 'C1', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (82, 'C2', 100, 'RESERVED', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (83, 'C3', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (84, 'C4', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (85, 'C5', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (86, 'C6', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (87, 'C7', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (88, 'C8', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (89, 'C9', 100, 'AVAILABLE', 3, '2024-05-06'); -insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`) values (90, 'C10', 100, 'AVAILABLE', 3, '2024-05-06'); \ No newline at end of file +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (81, 'C1', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (82, 'C2', 100, 'RESERVED', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (83, 'C3', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (84, 'C4', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (85, 'C5', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (86, 'C6', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (87, 'C7', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (88, 'C8', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (89, 'C9', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); +insert into `seats` (`id`, `number`, `price`, `status`, `concert_id`, `concert_date`, `created_at`, `updated_at`) values (90, 'C10', 100, 'AVAILABLE', 3, '2024-05-06', '2024-01-01 00:00:00', '2024-01-01 00:00:00'); \ No newline at end of file diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql index c82a4a7..1bfd0a4 100644 --- a/src/main/resources/sql/schema.sql +++ b/src/main/resources/sql/schema.sql @@ -1,4 +1,5 @@ drop table if exists `users`; +drop table if exists `refresh_token_info`; drop table if exists `points`; drop table if exists `tokens`; drop table if exists `concerts`; @@ -10,16 +11,27 @@ drop table if exists `reservations_seats`; drop table if exists `idempotent`; CREATE TABLE `users` ( - `id` bigint primary key, + `id` bigint primary key, `point_id` bigint not null, + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null, INDEX user_idx (point_id) ); +CREATE TABLE refresh_token_info ( + `id` bigint primary key, + `refresh_token_jti` varchar(255) not null unique, + `expired` boolean not null default false, + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null +); + + CREATE TABLE `points` ( `id` bigint primary key, `balance` decimal(19, 4) null, - `create_time` datetime not null , - `update_time` datetime null + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null ); CREATE TABLE point_histories ( @@ -28,21 +40,16 @@ CREATE TABLE point_histories ( `user_id` bigint not null, `point_id` bigint not null, `amount` DECIMAL(19, 4) null, - `create_time` datetime not null, + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null, INDEX point_history_idx (user_id, point_id) ); -CREATE TABLE `tokens` ( - `id` bigint primary key, - `issued_time` datetime null, - `status` enum('ACTIVE', 'EXPIRED') null, - `user_id` bigint not null, - INDEX token_idx (user_id, issued_time) -); - CREATE TABLE `concerts` ( `id` bigint primary key, `date` date not null, + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null, INDEX concert_idx (date) ); @@ -53,6 +60,8 @@ CREATE TABLE `seats` ( `status` enum('AVAILABLE', 'RESERVED', 'OCCUPIED') null , `concert_id` bigint not null, `concert_date` date not null, + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null, INDEX seat_idx (concert_id) ); @@ -63,6 +72,8 @@ CREATE TABLE `reservations` ( `expiration_time` datetime null, `user_id` bigint not null, `concert_id` bigint not null, + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null, INDEX reservation_idx (user_id, concert_id) ); @@ -72,14 +83,20 @@ CREATE TABLE `payments` ( `payment_time` datetime null, `user_id` bigint not null, `point_id` bigint not null, + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null, INDEX payment_idx (user_id, point_id) ); CREATE TABLE `reservations_seats` ( `id` bigint auto_increment primary key, `reservation_id` bigint not null, - `seat_id` bigint not null + `seat_id` bigint not null, + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null ); CREATE TABLE `idempotent` ( `id` bigint auto_increment primary key, - `key` varchar(255) not null unique + `key` varchar(255) not null unique, + `created_at` datetime(6) not null, + `updated_at` datetime(6) not null ); diff --git a/src/test/kotlin/io/ticketaka/api/common/domain/queue/TokenWaitingQueueTest.kt b/src/test/kotlin/io/ticketaka/api/common/domain/queue/QueueTokenWaitingQueueTest.kt similarity index 82% rename from src/test/kotlin/io/ticketaka/api/common/domain/queue/TokenWaitingQueueTest.kt rename to src/test/kotlin/io/ticketaka/api/common/domain/queue/QueueTokenWaitingQueueTest.kt index 6091099..e39c369 100644 --- a/src/test/kotlin/io/ticketaka/api/common/domain/queue/TokenWaitingQueueTest.kt +++ b/src/test/kotlin/io/ticketaka/api/common/domain/queue/QueueTokenWaitingQueueTest.kt @@ -1,8 +1,8 @@ package io.ticketaka.api.common.domain.queue import io.ticketaka.api.point.domain.Point -import io.ticketaka.api.user.domain.Token import io.ticketaka.api.user.domain.User +import io.ticketaka.api.user.domain.token.QueueToken import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -12,7 +12,7 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @ExtendWith(MockitoExtension::class) -class TokenWaitingQueueTest { +class QueueTokenWaitingQueueTest { @Test fun `test offer`() { // given @@ -22,10 +22,10 @@ class TokenWaitingQueueTest { mock { on { offer(any()) } doReturn true } - val token = Token.newInstance(user.id) + val queueToken = QueueToken.newInstance(user.id) // when - val result = mockTokenWaitingQueue.offer(token) + val result = mockTokenWaitingQueue.offer(queueToken) // then assertTrue(result) @@ -36,13 +36,13 @@ class TokenWaitingQueueTest { // given val point = Point.newInstance() val user = User.newInstance(point.id) - val mockTokenWaitingQueue = + val mockQueueTokenWaitingQueue = mock { - on { poll() } doReturn Token.newInstance(user.id) + on { poll() } doReturn QueueToken.newInstance(user.id) } // when - val result = mockTokenWaitingQueue.poll() + val result = mockQueueTokenWaitingQueue.poll() // then assertTrue(result != null) diff --git a/src/test/kotlin/io/ticketaka/api/point/application/PointServiceTest.kt b/src/test/kotlin/io/ticketaka/api/point/application/PointServiceTest.kt index e5ab055..96c1dc4 100644 --- a/src/test/kotlin/io/ticketaka/api/point/application/PointServiceTest.kt +++ b/src/test/kotlin/io/ticketaka/api/point/application/PointServiceTest.kt @@ -4,7 +4,7 @@ import io.ticketaka.api.common.exception.BadClientRequestException import io.ticketaka.api.point.application.dto.RechargeCommand import io.ticketaka.api.point.domain.CachePointRecharger import io.ticketaka.api.point.domain.Point -import io.ticketaka.api.user.application.TokenUserCacheAsideQueryService +import io.ticketaka.api.user.application.QueueTokenUserCacheAsideQueryService import io.ticketaka.api.user.domain.User import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -31,8 +31,8 @@ class PointServiceTest { 20000.toBigDecimal(), ) - val tokenUserCacheAsideQueryService = - mock { + val queueTokenUserCacheAsideQueryService = + mock { on { getUser(any()) } doReturn user } @@ -49,7 +49,7 @@ class PointServiceTest { } val pointService = - PointService(tokenUserCacheAsideQueryService, pointCacheAsideQueryService, cachePointRecharger, mock()) + PointService(queueTokenUserCacheAsideQueryService, pointCacheAsideQueryService, cachePointRecharger, mock()) // when pointService.recharge(rechargeCommand) @@ -70,8 +70,8 @@ class PointServiceTest { (-20000).toBigDecimal(), ) - val tokenUserCacheAsideQueryService = - mock { + val queueTokenUserCacheAsideQueryService = + mock { on { getUser(any()) } doReturn user } @@ -87,7 +87,7 @@ class PointServiceTest { } val pointService = - PointService(tokenUserCacheAsideQueryService, pointCacheAsideQueryService, cachePointRecharger, mock()) + PointService(queueTokenUserCacheAsideQueryService, pointCacheAsideQueryService, cachePointRecharger, mock()) // when val exception = @@ -104,8 +104,8 @@ class PointServiceTest { // given val point = Point.newInstance() val user = User.newInstance(point.id) - val tokenUserCacheAsideQueryService = - mock { + val queueTokenUserCacheAsideQueryService = + mock { on { getUser(any()) } doReturn user } val pointCacheAsideQueryService = @@ -114,7 +114,7 @@ class PointServiceTest { } val cachePointRecharger = mock() val pointService = - PointService(tokenUserCacheAsideQueryService, pointCacheAsideQueryService, cachePointRecharger, mock()) + PointService(queueTokenUserCacheAsideQueryService, pointCacheAsideQueryService, cachePointRecharger, mock()) // when val balanceQueryModel = pointService.getBalance(user.id) diff --git a/src/test/kotlin/io/ticketaka/api/reservation/application/ReservationServiceTest.kt b/src/test/kotlin/io/ticketaka/api/reservation/application/ReservationServiceTest.kt index 3842397..8d0fac9 100644 --- a/src/test/kotlin/io/ticketaka/api/reservation/application/ReservationServiceTest.kt +++ b/src/test/kotlin/io/ticketaka/api/reservation/application/ReservationServiceTest.kt @@ -10,7 +10,7 @@ import io.ticketaka.api.point.domain.Point import io.ticketaka.api.reservation.application.dto.CreateReservationCommand import io.ticketaka.api.reservation.domain.reservation.Reservation import io.ticketaka.api.reservation.domain.reservation.ReservationRepository -import io.ticketaka.api.user.application.TokenUserCacheAsideQueryService +import io.ticketaka.api.user.application.QueueTokenUserCacheAsideQueryService import io.ticketaka.api.user.domain.User import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -36,8 +36,8 @@ class ReservationServiceTest { val seats = setOf(Seat.newInstance(seatNumber, 1000.toBigDecimal(), concert.id)) val reservation = Reservation.createPendingReservation(user.id, concert.id) - val mockTokenUserCacheAsideQueryService = - mock { + val mockQueueTokenUserCacheAsideQueryService = + mock { on { getUser(any()) } doReturn user } @@ -53,7 +53,7 @@ class ReservationServiceTest { val reservationService = ReservationService( - mockTokenUserCacheAsideQueryService, + mockQueueTokenUserCacheAsideQueryService, mockConcertSeatCacheAsideQueryService, mock(), mockConcertUpdater, @@ -82,8 +82,8 @@ class ReservationServiceTest { val point = Point.newInstance(10000.toBigDecimal()) val user = User.newInstance(point.id) - val mockTokenUserCacheAsideQueryService = - mock { + val mockQueueTokenUserCacheAsideQueryService = + mock { on { getUser(any()) } doReturn user } val mockConcertSeatCacheAsideQueryService = @@ -98,7 +98,7 @@ class ReservationServiceTest { val reservationService = ReservationService( - mockTokenUserCacheAsideQueryService, + mockQueueTokenUserCacheAsideQueryService, mockConcertSeatCacheAsideQueryService, mock(), mockConcertUpdater, @@ -130,7 +130,7 @@ class ReservationServiceTest { val mockReservationRepository = mock() - val mockTokenUserCacheAsideQueryService = mock() + val mockQueueTokenUserCacheAsideQueryService = mock() val mockConcertSeatCacheAsideQueryService = mock { on { getConcert(date) } doThrow NotFoundException(notFoundConcertErrorMessage) @@ -138,7 +138,7 @@ class ReservationServiceTest { val reservationService = ReservationService( - mockTokenUserCacheAsideQueryService, + mockQueueTokenUserCacheAsideQueryService, mockConcertSeatCacheAsideQueryService, mockReservationRepository, mock(), @@ -173,14 +173,14 @@ class ReservationServiceTest { on { findById(reservation.id) } doReturn reservation } - val mockTokenUserCacheAsideQueryService = - mock { + val mockQueueTokenUserCacheAsideQueryService = + mock { on { getUser(user.id) } doReturn user } val reservationService = ReservationService( - mockTokenUserCacheAsideQueryService, + mockQueueTokenUserCacheAsideQueryService, mock(), mockReservationRepository, mock(), @@ -203,14 +203,14 @@ class ReservationServiceTest { mock { on { findById(any()) } doThrow NotFoundException(notFoundReservationErrorMessage) } - val mockTokenUserCacheAsideQueryService = - mock { + val mockQueueTokenUserCacheAsideQueryService = + mock { on { getUser(user.id) } doReturn user } val reservationService = ReservationService( - mockTokenUserCacheAsideQueryService, + mockQueueTokenUserCacheAsideQueryService, mock(), mockReservationRepository, mock(), @@ -244,14 +244,14 @@ class ReservationServiceTest { on { findById(reservation.id) } doReturn reservation } - val mockTokenUserCacheAsideQueryService = - mock { + val mockQueueTokenUserCacheAsideQueryService = + mock { on { getUser(user.id) } doReturn user } val reservationService = ReservationService( - mockTokenUserCacheAsideQueryService, + mockQueueTokenUserCacheAsideQueryService, mock(), mockReservationRepository, mock(), diff --git a/src/test/kotlin/io/ticketaka/api/token/application/TokenUserServiceTest.kt b/src/test/kotlin/io/ticketaka/api/token/application/QueueQueueTokenUserServiceTest.kt similarity index 55% rename from src/test/kotlin/io/ticketaka/api/token/application/TokenUserServiceTest.kt rename to src/test/kotlin/io/ticketaka/api/token/application/QueueQueueTokenUserServiceTest.kt index 24312b3..89cecea 100644 --- a/src/test/kotlin/io/ticketaka/api/token/application/TokenUserServiceTest.kt +++ b/src/test/kotlin/io/ticketaka/api/token/application/QueueQueueTokenUserServiceTest.kt @@ -3,10 +3,10 @@ package io.ticketaka.api.token.application import io.ticketaka.api.common.domain.map.TokenWaitingMap import io.ticketaka.api.common.exception.NotFoundException import io.ticketaka.api.point.domain.Point -import io.ticketaka.api.user.application.TokenUserCacheAsideQueryService -import io.ticketaka.api.user.application.TokenUserService -import io.ticketaka.api.user.domain.Token +import io.ticketaka.api.user.application.QueueTokenUserCacheAsideQueryService +import io.ticketaka.api.user.application.QueueTokenUserService import io.ticketaka.api.user.domain.User +import io.ticketaka.api.user.domain.token.QueueToken import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -19,7 +19,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify @ExtendWith(MockitoExtension::class) -class TokenUserServiceTest { +class QueueQueueTokenUserServiceTest { @Test fun createTokenTest() { // given @@ -27,14 +27,14 @@ class TokenUserServiceTest { val user = User.newInstance(point.id) - val mockTokenUserCacheAsideQueryService = - mock { + val mockQueueTokenUserCacheAsideQueryService = + mock { on { getUser(any()) } doReturn user } - val tokenUserService = TokenUserService(mock(), mockTokenUserCacheAsideQueryService, mock()) + val queueTokenUserService = QueueTokenUserService(mock(), mockQueueTokenUserCacheAsideQueryService, mock()) // when - val createToken = tokenUserService.createToken(user.id) + val createToken = queueTokenUserService.createToken(user.id) // then assertThat(createToken).isNotNull @@ -44,35 +44,35 @@ class TokenUserServiceTest { fun `when user not found then throw exception`() { // given val mockTokenWaitingQueue = mock() - val mockTokenUserCacheAsideQueryService = - mock { + val mockQueueTokenUserCacheAsideQueryService = + mock { on { getUser(any()) } doThrow NotFoundException("사용자를 찾을 수 없습니다.") } - val tokenUserService = TokenUserService(mockTokenWaitingQueue, mockTokenUserCacheAsideQueryService, mock()) + val queueTokenUserService = QueueTokenUserService(mockTokenWaitingQueue, mockQueueTokenUserCacheAsideQueryService, mock()) // when assertThrows { - tokenUserService.createToken(any()) + queueTokenUserService.createToken(any()) } // then - verify(mockTokenUserCacheAsideQueryService).getUser(any()) + verify(mockQueueTokenUserCacheAsideQueryService).getUser(any()) } @Test fun `peekToken returns true when the order of the token queue matches`() { // given val userId = 1L - val tokenPosition0 = Token.newInstance(userId) + val queueTokenPosition0 = QueueToken.newInstance(userId) val mockTokenWaitingMap = mock { - on { get(tokenPosition0.id) } doReturn tokenPosition0 + on { get(queueTokenPosition0.id) } doReturn queueTokenPosition0 } - val tokenUserService = TokenUserService(mockTokenWaitingMap, mock(), mock()) + val queueTokenUserService = QueueTokenUserService(mockTokenWaitingMap, mock(), mock()) // when - tokenUserService.peekToken(tokenPosition0.id) + queueTokenUserService.peekToken(queueTokenPosition0.id) // then - verify(mockTokenWaitingMap).get(tokenPosition0.id) + verify(mockTokenWaitingMap).get(queueTokenPosition0.id) } }