diff --git a/module-domain/src/main/kotlin/universe/sparkle/domain/GameType.kt b/module-domain/src/main/kotlin/universe/sparkle/domain/GameType.kt index eb97c1b..2fca31b 100644 --- a/module-domain/src/main/kotlin/universe/sparkle/domain/GameType.kt +++ b/module-domain/src/main/kotlin/universe/sparkle/domain/GameType.kt @@ -1,5 +1,10 @@ package universe.sparkle.domain enum class GameType { - SHORT, LONG + SHORT, LONG; + + companion object { + const val CONTRACT_SHORT = "SHORT" + const val CONTRACT_LONG = "LONG" + } } diff --git a/module-domain/src/main/kotlin/universe/sparkle/domain/UserMissionState.kt b/module-domain/src/main/kotlin/universe/sparkle/domain/UserMissionState.kt new file mode 100644 index 0000000..2831b53 --- /dev/null +++ b/module-domain/src/main/kotlin/universe/sparkle/domain/UserMissionState.kt @@ -0,0 +1,5 @@ +package universe.sparkle.domain + +enum class UserMissionState { + SUCCESS, FAILED, UNDECIDED +} diff --git a/module-domain/src/main/kotlin/universe/sparkle/domain/model/AuthenticationToken.kt b/module-domain/src/main/kotlin/universe/sparkle/domain/model/AuthenticationToken.kt index 3cbf430..9cbe4d0 100644 --- a/module-domain/src/main/kotlin/universe/sparkle/domain/model/AuthenticationToken.kt +++ b/module-domain/src/main/kotlin/universe/sparkle/domain/model/AuthenticationToken.kt @@ -4,4 +4,5 @@ data class AuthenticationToken( val id: Long, val nickname: String?, val image: String?, + val couple: Couple? ) diff --git a/module-domain/src/main/kotlin/universe/sparkle/domain/model/Couple.kt b/module-domain/src/main/kotlin/universe/sparkle/domain/model/Couple.kt new file mode 100644 index 0000000..13be95d --- /dev/null +++ b/module-domain/src/main/kotlin/universe/sparkle/domain/model/Couple.kt @@ -0,0 +1,10 @@ +package universe.sparkle.domain.model + +import java.time.LocalDate + +data class Couple( + val id: Long? = null, + val startDate: LocalDate, + val heartToken: Int = 5, + val isDelete: Boolean = false, +) diff --git a/module-domain/src/main/kotlin/universe/sparkle/domain/model/User.kt b/module-domain/src/main/kotlin/universe/sparkle/domain/model/User.kt index 2c2998d..9f5c95c 100644 --- a/module-domain/src/main/kotlin/universe/sparkle/domain/model/User.kt +++ b/module-domain/src/main/kotlin/universe/sparkle/domain/model/User.kt @@ -8,7 +8,7 @@ data class User( val snsAuthCode: String, val nickname: String? = null, val image: String? = null, - val fcmToken: String? = null, + val couple: Couple? = null, ) { init { @@ -23,7 +23,7 @@ data class User( snsAuthCode = this.snsAuthCode, nickname = updateNickname, image = updateImage, - fcmToken = this.fcmToken, + couple = this.couple, ) } @@ -37,5 +37,6 @@ fun User.toAuthenticationToken() = this.id?.let { userId -> id = userId, nickname = this.nickname, image = this.image, + couple = this.couple, ) } diff --git a/module-infrastructure/build.gradle b/module-infrastructure/build.gradle index dd7b48a..88c9fb0 100644 --- a/module-infrastructure/build.gradle +++ b/module-infrastructure/build.gradle @@ -50,7 +50,10 @@ dependencies { developmentOnly libs.spring.boot.devtools testRuntimeOnly libs.db.h2 - testImplementation libs.test.spring.boot.starter + testImplementation(libs.test.spring.boot.starter) { + exclude module: 'mockito-core' + } + testImplementation libs.test.mockk testImplementation libs.test.spring.security } diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/config/QueryDslConfig.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/config/QueryDslConfig.kt index c984cba..9079950 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/config/QueryDslConfig.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/config/QueryDslConfig.kt @@ -1,6 +1,6 @@ package universe.sparkle.infrastructure.config -import com.querydsl.jpa.impl.JPAQueryFactory +/*import com.querydsl.jpa.impl.JPAQueryFactory import jakarta.persistence.EntityManager import jakarta.persistence.PersistenceContext import org.springframework.beans.factory.annotation.Autowired @@ -13,4 +13,4 @@ class QueryDslConfig @Autowired constructor( ) { @Bean fun jpaQueryFactory(): JPAQueryFactory = JPAQueryFactory(entityManager) -} +}*/ diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/NotInitializedIdException.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/NotInitializedIdException.kt new file mode 100644 index 0000000..c900525 --- /dev/null +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/NotInitializedIdException.kt @@ -0,0 +1,5 @@ +package universe.sparkle.infrastructure.persistence + +class NotInitializedIdException( + override val message: String = "ID has not been initialized yet" +) : IllegalArgumentException(message) diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/CoupleEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/CoupleEntity.kt index d0ae5c2..ef603b3 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/CoupleEntity.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/CoupleEntity.kt @@ -15,10 +15,11 @@ class CoupleEntity( id: Long? = null, startDate: LocalDate, heartToken: Int = 5, + isDelete: Boolean = false, ) { @Id @GeneratedValue(strategy = GenerationType.AUTO) - @Column(name = "couple_id") + @Column(name = "id") var id: Long? = id protected set @@ -30,4 +31,8 @@ class CoupleEntity( @ColumnDefault("5") var heartToken: Int = heartToken protected set + + @Column(name = "is_delete", nullable = false) + @ColumnDefault("false") + var isDelete: Boolean = isDelete } diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/GameEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/GameEntity.kt index 2f4e03a..5ca1945 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/GameEntity.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/GameEntity.kt @@ -1,6 +1,7 @@ package universe.sparkle.infrastructure.persistence.entity import jakarta.persistence.Column +import jakarta.persistence.Convert import jakarta.persistence.DiscriminatorColumn import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue @@ -9,33 +10,47 @@ import jakarta.persistence.Id import jakarta.persistence.Inheritance import jakarta.persistence.InheritanceType import jakarta.persistence.Table -import java.time.LocalDateTime +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import universe.sparkle.domain.GameResult +import universe.sparkle.infrastructure.persistence.entity.converter.GameResultAttributeConverter +import java.time.Instant @Entity @Table(name = "game") @Inheritance(strategy = InheritanceType.JOINED) -@DiscriminatorColumn -open class GameEntity( +@DiscriminatorColumn(name = "GAME_TYPE") +class GameEntity( id: Long? = null, + finishedAt: Instant? = null, + result: GameResult = GameResult.UNDECIDED, coupleId: Long, - enable: Boolean = true, - finishedAt: LocalDateTime? = null, + onDelete: Boolean = false, ) { + @Id - @Column(name = "game_id") - @GeneratedValue(strategy = GenerationType.AUTO) - var id: Long? = null + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + var id: Long? = id protected set - @Column(name = "couple_id", nullable = false) - var coupleId = coupleId + @Column(name = "finished_at") + var finishedAt: Instant? = finishedAt protected set - @Column(name = "enable", nullable = false) - var enable: Boolean = enable + @Size(max = 10) + @Convert(converter = GameResultAttributeConverter::class) + @Column(name = "result", length = 10) + var result: GameResult = result protected set - @Column(name = "finished_at") - var finishedAt: LocalDateTime? = finishedAt + @NotNull + @Column(name = "couple_id", nullable = false) + var couple: Long = coupleId + protected set + + @NotNull + @Column(name = "on_delete", nullable = false) + var onDelete: Boolean = onDelete protected set } diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/GameRoundEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/GameRoundEntity.kt new file mode 100644 index 0000000..fffe001 --- /dev/null +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/GameRoundEntity.kt @@ -0,0 +1,43 @@ +package universe.sparkle.infrastructure.persistence.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import jakarta.validation.constraints.NotNull +import java.time.Instant + +@Entity +@Table(name = "game_round") +class GameRoundEntity( + id: Long? = null, + gameId: Long, + missionCategoryId: Long, + winnerUserId: Long, + updatedAt: Instant, +) { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + var id: Long? = id + protected set + + @NotNull + @Column(name = "game_id", nullable = false) + var gameId: Long = gameId + protected set + + @Column(name = "mission_category_id") + var missionCategoryId: Long = missionCategoryId + protected set + + @Column(name = "winner_user_id") + var winnerUserId: Long = winnerUserId + protected set + + @Column(name = "updated_at") + var updatedAt: Instant? = updatedAt + protected set +} diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/MissionCategoryEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/MissionCategoryEntity.kt index 9520ecb..a90003a 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/MissionCategoryEntity.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/MissionCategoryEntity.kt @@ -7,6 +7,8 @@ import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id import jakarta.persistence.Table +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size import universe.sparkle.domain.MissionTool import universe.sparkle.domain.MissionType import universe.sparkle.infrastructure.persistence.entity.converter.MissionToolAttributeConverter @@ -23,55 +25,72 @@ class MissionCategoryEntity( example: String, image: String, level: Int, - expectedTime: Int, + expectedTime: Int? = null, missionType: MissionType, missionTool: MissionTool, ) { @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(name = "mission_category_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) var id: Long? = id protected set - @Column(name = "title", nullable = false) + @Size(max = 50) + @NotNull + @Column(name = "title", nullable = false, length = 50) var title: String = title protected set + @Size(max = 255) + @NotNull @Column(name = "description", nullable = false) var description: String = description protected set + @Size(max = 255) + @NotNull @Column(name = "rule", nullable = false) var rule: String = rule protected set + @Size(max = 255) + @NotNull @Column(name = "tip", nullable = false) var tip: String = tip protected set + @Size(max = 255) + @NotNull @Column(name = "example", nullable = false) var example: String = example protected set + @Size(max = 255) + @NotNull @Column(name = "image", nullable = false) var image: String = image protected set + @NotNull @Column(name = "level", nullable = false) var level: Int = level protected set @Column(name = "expected_time", nullable = false) - var expectedTime: Int = expectedTime + var expectedTime: Int? = expectedTime protected set - @Column(name = "mission_type", nullable = false) + @Size(max = 30) + @NotNull @Convert(converter = MissionTypeAttributeConverter::class) + @Column(name = "mission_type", nullable = false, length = 30) var missionType: MissionType = missionType protected set - @Column(name = "mission_tool", nullable = false) + @Size(max = 30) + @NotNull @Convert(converter = MissionToolAttributeConverter::class) + @Column(name = "mission_tool", nullable = false, length = 30) var missionTool: MissionTool = missionTool protected set } diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/MissionContentEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/MissionContentEntity.kt index bb74f9e..97ec43c 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/MissionContentEntity.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/MissionContentEntity.kt @@ -2,34 +2,46 @@ package universe.sparkle.infrastructure.persistence.entity import jakarta.persistence.Column import jakarta.persistence.Entity +import jakarta.persistence.FetchType import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne import jakarta.persistence.Table +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import org.hibernate.annotations.OnDelete +import org.hibernate.annotations.OnDeleteAction @Entity @Table(name = "mission_content") class MissionContentEntity( id: Long? = null, - missionCategoryId: Long, + missionCategoryEntity: MissionCategoryEntity, content: String, - recommendTime: String, + recommendTime: Int? = null, ) { @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(name = "mission_content_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) var id: Long? = id protected set - @Column(name = "mission_category_id", nullable = false) - var missionCategoryId: Long = missionCategoryId + @NotNull + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @OnDelete(action = OnDeleteAction.CASCADE) + @JoinColumn(name = "mission_category_id", nullable = false) + var missionCategory: MissionCategoryEntity = missionCategoryEntity protected set + @Size(max = 255) + @NotNull @Column(name = "content", nullable = false) var content: String = content protected set - @Column(name = "recommend_time", nullable = false) - var recommendTime: String = recommendTime + @Column(name = "recommend_time") + var recommendTime: Int? = recommendTime protected set } diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/NotificationInformationEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/NotificationInformationEntity.kt new file mode 100644 index 0000000..dc0b933 --- /dev/null +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/NotificationInformationEntity.kt @@ -0,0 +1,21 @@ +package universe.sparkle.infrastructure.persistence.entity + +import jakarta.persistence.Column +import jakarta.persistence.EmbeddedId +import jakarta.persistence.Entity +import jakarta.persistence.Table + +@Entity +@Table(name = "notification_information") +class NotificationInformationEntity( + id: NotificationInformationEntityId, + enableAllNotification: Boolean = true, +) { + @EmbeddedId + var id: NotificationInformationEntityId = id + protected set + + @Column(name = "enable_all_notification") + var enableAllNotification: Boolean = enableAllNotification + protected set +} diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/NotificationInformationEntityId.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/NotificationInformationEntityId.kt new file mode 100644 index 0000000..dc3dca3 --- /dev/null +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/NotificationInformationEntityId.kt @@ -0,0 +1,41 @@ +package universe.sparkle.infrastructure.persistence.entity + +import jakarta.persistence.Column +import jakarta.persistence.Embeddable +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import org.hibernate.Hibernate +import java.io.Serializable +import java.util.Objects + +@Embeddable +class NotificationInformationEntityId( + userId: Long, + fcmToken: String, +) : Serializable { + @NotNull + @Column(name = "user_id", nullable = false) + var userId: Long = userId + protected set + + @Size(max = 255) + @NotNull + @Column(name = "fcm_token", nullable = false) + var fcmToken: String = fcmToken + protected set + + override fun hashCode(): Int = Objects.hash(userId, fcmToken) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false + + other as NotificationInformationEntityId + + return userId == other.userId && + fcmToken == other.fcmToken + } + + companion object { + private const val serialVersionUID = -7994741440230625710L + } +} diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/ShortGameEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/ShortGameEntity.kt index 0c722a6..a58821e 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/ShortGameEntity.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/ShortGameEntity.kt @@ -2,13 +2,12 @@ package universe.sparkle.infrastructure.persistence.entity import jakarta.persistence.DiscriminatorValue import jakarta.persistence.Entity -import jakarta.persistence.PrimaryKeyJoinColumn +import jakarta.persistence.Table +import universe.sparkle.domain.GameType @Entity -@DiscriminatorValue("ShortGame") -@PrimaryKeyJoinColumn(name = "game_id") +@Table(name = "short_game") +@DiscriminatorValue(value = GameType.CONTRACT_SHORT) class ShortGameEntity( coupleId: Long, -) : GameEntity( - coupleId = coupleId, -) +) : GameEntity(coupleId = coupleId) diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/UserEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/UserEntity.kt index 8b48e6a..496b499 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/UserEntity.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/UserEntity.kt @@ -3,26 +3,30 @@ package universe.sparkle.infrastructure.persistence.entity import jakarta.persistence.Column import jakarta.persistence.Convert import jakarta.persistence.Entity +import jakarta.persistence.FetchType import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne import jakarta.persistence.Table import universe.sparkle.domain.SnsType +import universe.sparkle.domain.model.User import universe.sparkle.infrastructure.persistence.entity.converter.SnsTypeAttributeConverter @Entity -@Table(name = "user") +@Table(name = "users") class UserEntity( id: Long? = null, snsType: SnsType, snsAuthCode: String, nickname: String? = null, image: String? = null, - fcmToken: String? = null, + couple: CoupleEntity? = null, ) { @Id @GeneratedValue(strategy = GenerationType.AUTO) - @Column(name = "user_id") + @Column(name = "id") var id: Long? = id protected set @@ -43,7 +47,14 @@ class UserEntity( var image: String? = image protected set - @Column(name = "fcm_token") - var fcmToken: String? = fcmToken + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "couple_id") + var couple: CoupleEntity? = couple protected set + + fun update(user: User) { + if (user.id != this.id) throw IllegalStateException("서로 다른 유저 정보로 업데이트가 불가합니다.") + nickname = user.nickname + image = user.image + } } diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/UserMissionEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/UserMissionEntity.kt new file mode 100644 index 0000000..2b0bd56 --- /dev/null +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/UserMissionEntity.kt @@ -0,0 +1,64 @@ +package universe.sparkle.infrastructure.persistence.entity + +import jakarta.persistence.Column +import jakarta.persistence.Convert +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import org.hibernate.annotations.OnDelete +import org.hibernate.annotations.OnDeleteAction +import universe.sparkle.domain.UserMissionState +import universe.sparkle.infrastructure.persistence.entity.converter.UserMissionStateAttributeConverter +import java.time.Instant + +@Entity +@Table(name = "user_mission") +class UserMissionEntity( + id: Long? = null, + roundEntity: GameRoundEntity, + missionContentEntity: MissionContentEntity, + userId: Long, + result: UserMissionState = UserMissionState.UNDECIDED, + updatedAt: Instant? = null, +) { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + var id: Long? = id + protected set + + @NotNull + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @OnDelete(action = OnDeleteAction.CASCADE) + @JoinColumn(name = "round_id", nullable = false) + var roundEntity: GameRoundEntity = roundEntity + protected set + + @ManyToOne(fetch = FetchType.LAZY) + @OnDelete(action = OnDeleteAction.SET_NULL) + @JoinColumn(name = "mission_content_id") + var missionContentEntity: MissionContentEntity = missionContentEntity + protected set + + @NotNull + @Column(name = "user_id", nullable = false) + var userId: Long = userId + protected set + + @Size(max = 10) + @Convert(converter = UserMissionStateAttributeConverter::class) + @Column(name = "result", length = 10) + var result: UserMissionState = result + protected set + + @Column(name = "updated_at") + var updatedAt: Instant? = updatedAt + protected set +} diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/WishCouponEntity.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/WishCouponEntity.kt new file mode 100644 index 0000000..3e48b4e --- /dev/null +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/WishCouponEntity.kt @@ -0,0 +1,77 @@ +package universe.sparkle.infrastructure.persistence.entity + +import jakarta.persistence.Column +import jakarta.persistence.Convert +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import org.hibernate.annotations.OnDelete +import org.hibernate.annotations.OnDeleteAction +import universe.sparkle.domain.GameType +import universe.sparkle.infrastructure.persistence.entity.converter.GameTypeAttributeConverter +import java.time.Instant + +@Entity +@Table(name = "wish_coupon") +class WishCouponEntity( + id: Long? = null, + content: String? = null, + visible: Boolean = false, + usable: Boolean = false, + usedAt: Instant? = null, + ownerUserEntity: UserEntity, + gameEntity: GameEntity? = null, + gameType: GameType, +) { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + var id: Long? = id + protected set + + @Size(max = 180) + @Column(name = "content", length = 180) + var content: String? = content + protected set + + @NotNull + @Column(name = "visible", nullable = false) + var visible: Boolean = visible + protected set + + @NotNull + @Column(name = "usable", nullable = false) + var usable: Boolean = usable + protected set + + @Column(name = "used_at") + var usedAt: Instant? = usedAt + protected set + + @NotNull + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @OnDelete(action = OnDeleteAction.CASCADE) + @JoinColumn(name = "owner_user_id", nullable = false) + var ownerUserEntity: UserEntity = ownerUserEntity + protected set + + @ManyToOne(fetch = FetchType.LAZY) + @OnDelete(action = OnDeleteAction.SET_NULL) + @JoinColumn(name = "game_id") + var gameEntity: GameEntity? = gameEntity + protected set + + @Size(max = 20) + @NotNull + @Convert(converter = GameTypeAttributeConverter::class) + @Column(name = "game_type", nullable = false, length = 20) + var gameType: GameType? = gameType + protected set +} diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/converter/UserMissionStateAttributeConverter.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/converter/UserMissionStateAttributeConverter.kt new file mode 100644 index 0000000..f1868aa --- /dev/null +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/entity/converter/UserMissionStateAttributeConverter.kt @@ -0,0 +1,13 @@ +package universe.sparkle.infrastructure.persistence.entity.converter + +import universe.sparkle.domain.UserMissionState + +class UserMissionStateAttributeConverter : EnumAttributeConvertor() { + override fun convertToDataBase(attribute: UserMissionState): String { + return attribute.name + } + + override fun convertToEntity(dbData: String): UserMissionState { + return UserMissionState.valueOf(dbData) + } +} diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/mapper/CoupleMapper.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/mapper/CoupleMapper.kt new file mode 100644 index 0000000..a05e1ca --- /dev/null +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/mapper/CoupleMapper.kt @@ -0,0 +1,20 @@ +package universe.sparkle.infrastructure.persistence.mapper + +import universe.sparkle.domain.model.Couple +import universe.sparkle.infrastructure.persistence.NotInitializedIdException +import universe.sparkle.infrastructure.persistence.entity.CoupleEntity + +fun CoupleEntity.toDomain() = Couple( + id = this.id ?: throw NotInitializedIdException(), + startDate = this.startDate, + heartToken = this.heartToken, + isDelete = this.isDelete, +) + +fun Couple.toEntity() = CoupleEntity( + id = this.id, + startDate = this.startDate, + heartToken = this.heartToken, + isDelete = this.isDelete, + +) diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/mapper/UserMapper.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/mapper/UserMapper.kt index b61ceec..d9304e2 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/mapper/UserMapper.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/mapper/UserMapper.kt @@ -10,7 +10,7 @@ fun UserEntity.toDomain() = User( snsAuthCode = this.snsAuthCode, nickname = this.nickname, image = this.image, - fcmToken = this.fcmToken, + couple = this.couple?.toDomain(), ) fun User.toEntity() = UserEntity( @@ -19,5 +19,5 @@ fun User.toEntity() = UserEntity( snsAuthCode = this.snsAuthCode, nickname = this.nickname, image = this.image, - fcmToken = this.fcmToken, + couple = this.couple?.toEntity(), ) diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserDslRepository.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserDslRepository.kt deleted file mode 100644 index 053ffe5..0000000 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserDslRepository.kt +++ /dev/null @@ -1,3 +0,0 @@ -package universe.sparkle.infrastructure.persistence.repository - -interface UserDslRepository diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserRepository.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserRepository.kt index a0a6d91..eaf96fd 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserRepository.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserRepository.kt @@ -3,7 +3,7 @@ package universe.sparkle.infrastructure.persistence.repository import org.springframework.data.jpa.repository.JpaRepository import universe.sparkle.infrastructure.persistence.entity.UserEntity -interface UserRepository : JpaRepository, UserDslRepository { +interface UserRepository : JpaRepository { fun findBySnsAuthCode(snsAuthCode: String): UserEntity? } diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserSupportRepository.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserSupportRepository.kt deleted file mode 100644 index d999df3..0000000 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/persistence/repository/UserSupportRepository.kt +++ /dev/null @@ -1,18 +0,0 @@ -package universe.sparkle.infrastructure.persistence.repository - -import com.querydsl.jpa.impl.JPAQueryFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Repository -import universe.sparkle.infrastructure.persistence.entity.QUserEntity -import universe.sparkle.infrastructure.persistence.entity.UserEntity - -@Repository -class UserSupportRepository @Autowired constructor( - private val queryFactory: JPAQueryFactory, -) : UserDslRepository { - fun findUserByIdJoinCouple(userId: Long): UserEntity? { - return queryFactory.selectFrom(QUserEntity.userEntity) - .where(QUserEntity.userEntity.id.eq(userId)) - .fetchOne() - } -} diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/security/UserDetail.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/security/UserDetail.kt index 3d46714..299fbb1 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/security/UserDetail.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/security/UserDetail.kt @@ -4,12 +4,14 @@ import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.userdetails.UserDetails import universe.sparkle.domain.model.AuthenticationToken +import universe.sparkle.domain.model.Couple import java.util.stream.Collectors class UserDetail constructor( val id: Long, private val nickname: String?, val image: String?, + val couple: Couple?, private val authorities: List = listOf(SimpleGrantedAuthority("User")), ) : UserDetails { @@ -37,6 +39,7 @@ class UserDetail constructor( id = user.id, nickname = user.nickname, image = user.image, + couple = user.couple ) } } diff --git a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/security/UserDetailMapper.kt b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/security/UserDetailMapper.kt index 18893b5..43a287b 100644 --- a/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/security/UserDetailMapper.kt +++ b/module-infrastructure/src/main/kotlin/universe/sparkle/infrastructure/security/UserDetailMapper.kt @@ -6,4 +6,5 @@ fun UserDetail.toAuthenticationToken() = AuthenticationToken( id = this.id, nickname = this.username, image = this.image, + couple = this.couple ) diff --git a/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/filter/JwtAuthenticationFilterTest.kt b/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/filter/JwtAuthenticationFilterTest.kt new file mode 100644 index 0000000..f1c187c --- /dev/null +++ b/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/filter/JwtAuthenticationFilterTest.kt @@ -0,0 +1,101 @@ +package universe.sparkle.infrastructure.filter + +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.InjectMockKs +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import io.mockk.verify +import jakarta.servlet.FilterChain +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.mock.web.MockHttpServletRequest +import org.springframework.mock.web.MockHttpServletResponse +import universe.sparkle.domain.exception.Unauthorized +import universe.sparkle.domain.model.AuthenticationToken +import universe.sparkle.domain.usecase.ExtractTokenInputBoundary + +@ExtendWith(MockKExtension::class) +class JwtAuthenticationFilterTest { + + @MockK + private lateinit var extractTokenUseCase: ExtractTokenInputBoundary + + @MockK + private lateinit var filterChain: FilterChain + + @InjectMockKs + private lateinit var jwtAuthenticationFilter: JwtAuthenticationFilter + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this.jwtAuthenticationFilter, relaxed = true, relaxUnitFun = true) + } + + @Test + fun `JWT 인증이 필요없는 경로에서는 검증을 하지않고 다음 필터를 실행한다`() { + // given + val request = MockHttpServletRequest().apply { + this.requestURI = "/status/uni" + } + val response = MockHttpServletResponse() + every { filterChain.doFilter(request, response) } answers {} + // when + jwtAuthenticationFilter.doFilter( + request, + response, + filterChain, + ) + // then + assertAll( + { verify(exactly = 0) { extractTokenUseCase.invoke("") } }, + { verify(exactly = 1) { filterChain.doFilter(request, response) } }, + ) + } + + @Test + fun `JWT 인증이 필요한 경우 올바른 인증 Header가 없다면 예외가 발생한다`() { + // given + val request = MockHttpServletRequest().apply { + this.requestURI = "/api/testPath" + } + val response = MockHttpServletResponse() + every { filterChain.doFilter(request, response) } answers {} + every { extractTokenUseCase.invoke("testToken") } answers { `테스트 유저 인증 정보`() } + // when & then + assertThatThrownBy { + jwtAuthenticationFilter.doFilter(request, response, filterChain) + }.isInstanceOf(Unauthorized.TokenNotExistent::class.java) + .hasMessage("토큰 값이 존재하지 않습니다.") + } + + @Test + fun `JWT 인증이 필요한 경우 인증 후에 다음 필터로 연결한다`() { + // given + val testToken = "Test-Token" + val request = MockHttpServletRequest().apply { + this.requestURI = "/api/testPath" + this.addHeader("Authorization", "Bearer $testToken") + } + val response = MockHttpServletResponse() + every { filterChain.doFilter(request, response) } answers {} + every { extractTokenUseCase.invoke(testToken) } answers { `테스트 유저 인증 정보`() } + // when + jwtAuthenticationFilter.doFilter(request, response, filterChain) + // then + assertAll( + { verify(exactly = 1) { extractTokenUseCase.invoke(testToken) } }, + { verify(exactly = 1) { filterChain.doFilter(request, response) } }, + ) + } + + private fun `테스트 유저 인증 정보`() = AuthenticationToken( + id = 1L, + nickname = "testUser", + image = "testImage", + couple = null, + ) +} diff --git a/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/persistence/gateway/UserAdapterTest.kt b/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/persistence/gateway/UserAdapterTest.kt index 62ec978..fd90bf1 100644 --- a/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/persistence/gateway/UserAdapterTest.kt +++ b/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/persistence/gateway/UserAdapterTest.kt @@ -12,7 +12,7 @@ import universe.sparkle.domain.gateway.UserGateway import universe.sparkle.domain.model.User @DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@AutoConfigureTestDatabase class UserAdapterTest @Autowired constructor( private val userAdapter: UserGateway, ) { diff --git a/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/persistence/repository/UserRepositoryTest.kt b/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/persistence/repository/UserRepositoryTest.kt index e64f71e..063f14f 100644 --- a/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/persistence/repository/UserRepositoryTest.kt +++ b/module-infrastructure/src/test/kotlin/universe/sparkle/infrastructure/persistence/repository/UserRepositoryTest.kt @@ -11,7 +11,7 @@ import universe.sparkle.domain.SnsType import universe.sparkle.infrastructure.persistence.entity.UserEntity @DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@AutoConfigureTestDatabase class UserRepositoryTest @Autowired constructor( private val userRepository: UserRepository, ) { diff --git a/module-usecase/src/main/kotlin/universe/sparkle/usecase/user/BeIssuedAuthTokenUseCase.kt b/module-usecase/src/main/kotlin/universe/sparkle/usecase/user/BeIssuedAuthTokenUseCase.kt index ee79378..f9671ef 100644 --- a/module-usecase/src/main/kotlin/universe/sparkle/usecase/user/BeIssuedAuthTokenUseCase.kt +++ b/module-usecase/src/main/kotlin/universe/sparkle/usecase/user/BeIssuedAuthTokenUseCase.kt @@ -18,7 +18,7 @@ import javax.crypto.spec.SecretKeySpec @Service internal class BeIssuedAuthTokenUseCase @Autowired constructor( private val jwtConfig: JwtConfigContract, - private val userGateway: UserGateway + private val userGateway: UserGateway, ) : BeIssuedAuthTokenInputBoundary { private val signatureAlgorithm = SignatureAlgorithm.HS256 diff --git a/module-usecase/src/test/kotlin/universe/sparkle/usecase/AuthTokenUseCaseTest.kt b/module-usecase/src/test/kotlin/universe/sparkle/usecase/AuthTokenUseCaseTest.kt index 600d70f..e178316 100644 --- a/module-usecase/src/test/kotlin/universe/sparkle/usecase/AuthTokenUseCaseTest.kt +++ b/module-usecase/src/test/kotlin/universe/sparkle/usecase/AuthTokenUseCaseTest.kt @@ -80,7 +80,7 @@ class AuthTokenUseCaseTest { every { jwtConfig.getRefreshExpiryPeriodDay() } answers { 2L } every { userGateway.findUserBySnsAuthCode(user.snsAuthCode) } answers { user } every { userDetailGateway.loadUserById("1") } answers { - AuthenticationToken(id = 1L, nickname = null, image = null) + AuthenticationToken(id = 1L, nickname = null, image = null, couple = null) } // when val authToken = beIssuedAuthTokenUseCase(user) @@ -110,7 +110,7 @@ class AuthTokenUseCaseTest { every { jwtConfig.getRefreshExpiryPeriodDay() } answers { 2L } every { userGateway.findUserBySnsAuthCode(user.snsAuthCode) } answers { user } every { userDetailGateway.loadUserById("1") } answers { - AuthenticationToken(id = 1L, nickname = null, image = null) + AuthenticationToken(id = 1L, nickname = null, image = null, couple = null) } // when val authToken = beIssuedAuthTokenUseCase(user) @@ -135,7 +135,7 @@ class AuthTokenUseCaseTest { every { jwtConfig.getRefreshExpiryPeriodDay() } answers { 2L } every { userGateway.findUserBySnsAuthCode(user.snsAuthCode) } answers { user } every { userDetailGateway.loadUserById("1") } answers { - AuthenticationToken(id = 1L, nickname = null, image = null) + AuthenticationToken(id = 1L, nickname = null, image = null, couple = null) } // when val authToken = beIssuedAuthTokenUseCase(user)