diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 5ce5db777..69e11bac9 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -97,4 +97,6 @@ jobs: SWAGGER_UI_PATH=${{ secrets.SWAGGER_UI_PATH }} FRONT_DOMAIN=${{ secrets.FRONT_DOMAIN }} BACK_DOMAIN=${{ secrets.BACK_DOMAIN_PROD }} - + CSRF_HEADER=${{ secrets.CSRF_HEADER }} + CSRF_COOKIE=${{ secrets.CSRF_COOKIE }} + CSRF_PARAMETER=${{ secrets.CSRF_PARAMETER }} \ No newline at end of file diff --git a/.github/workflows/deploy-stag.yml b/.github/workflows/deploy-stag.yml index 806b01b6d..c4ef3a7c6 100644 --- a/.github/workflows/deploy-stag.yml +++ b/.github/workflows/deploy-stag.yml @@ -96,3 +96,6 @@ jobs: SWAGGER_UI_PATH=${{ secrets.SWAGGER_UI_PATH }} FRONT_LOCAL=${{ secrets.FRONT_LOCAL }} BACK_DOMAIN=${{ secrets.BACK_DOMAIN_STAG }} + CSRF_HEADER=${{ secrets.CSRF_HEADER }} + CSRF_COOKIE=${{ secrets.CSRF_COOKIE }} + CSRF_PARAMETER=${{ secrets.CSRF_PARAMETER }} diff --git a/Dockerfile b/Dockerfile index 9f25df36a..e99ba6ea4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -71,6 +71,15 @@ ENV BACK_DOMAIN ${BACK_DOMAIN} ENV SPRING_PROFILES_ACTIVE ${PROFILE} +ARG CSRF_HEADER +ENV CSRF_HEADER ${CSRF_HEADER} + +ARG CSRF_COOKIE +ENV CSRF_COOKIE ${CSRF_COOKIE} + +ARG CSRF_PARAMETER +ENV CSRF_PARAMETER ${CSRF_PARAMETER} + EXPOSE 8080 ENTRYPOINT ["java", "-jar", "application.jar"] diff --git a/maeumgagym-application/build.gradle.kts b/maeumgagym-application/build.gradle.kts index 6dbcb1a0a..ea6e4d968 100644 --- a/maeumgagym-application/build.gradle.kts +++ b/maeumgagym-application/build.gradle.kts @@ -22,6 +22,9 @@ dependencies { implementation("com.querydsl:querydsl-jpa:${PluginVersions.QUERY_DSL}") kapt("com.querydsl:querydsl-apt:${PluginVersions.QUERY_DSL}:jpa") kapt("org.springframework.boot:spring-boot-configuration-processor") + + // redis + implementation(Dependencies.REDIS) } allOpen { diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/TableNames.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/TableNames.kt index 382f290ea..811095c79 100644 --- a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/TableNames.kt +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/TableNames.kt @@ -32,4 +32,6 @@ object TableNames { const val PICKLE_TAG_INDEX = "${INDEX_PREFIX}pickle_tag" const val PICKLE_LIKE_INDEX = "${INDEX_PREFIX}pickle_like" + + const val REDIS_STEP_TABLE = "${TABLE_PREFIX}step" } diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/pickle/mapper/PickleMapper.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/pickle/mapper/PickleMapper.kt index 94ea2255d..cc96d92db 100644 --- a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/pickle/mapper/PickleMapper.kt +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/pickle/mapper/PickleMapper.kt @@ -19,7 +19,7 @@ class PickleMapper( description = description, title = title, uploader = userMapper.toEntity(uploader), - likes = em.find(PickleJpaEntity::class.java, videoId).likes, + likes = em.find(PickleJpaEntity::class.java, videoId)?.likes ?: mutableSetOf(), likeCount = likeCount, tags = tags, createdAt = createdAt diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/RoutinePersistenceAdapter.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/RoutinePersistenceAdapter.kt index 3cefe386b..e773d6934 100644 --- a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/RoutinePersistenceAdapter.kt +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/RoutinePersistenceAdapter.kt @@ -2,21 +2,23 @@ package com.info.maeumgagym.domain.routine import com.info.common.PersistenceAdapter import com.info.maeumgagym.domain.routine.mapper.RoutineMapper +import com.info.maeumgagym.domain.routine.repository.RoutineNativeRepository import com.info.maeumgagym.domain.routine.repository.RoutineRepository import com.info.maeumgagym.routine.model.Routine import com.info.maeumgagym.routine.port.out.DeleteRoutinePort import com.info.maeumgagym.routine.port.out.ReadRoutinePort -import com.info.maeumgagym.routine.port.out.ReadRoutineByIdPort import com.info.maeumgagym.routine.port.out.SaveRoutinePort import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional +import java.time.DayOfWeek import java.util.* @PersistenceAdapter internal class RoutinePersistenceAdapter( private val routineMapper: RoutineMapper, - private val routineRepository: RoutineRepository -) : SaveRoutinePort, ReadRoutinePort, DeleteRoutinePort, ReadRoutineByIdPort { + private val routineRepository: RoutineRepository, + private val routineNativeRepository: RoutineNativeRepository +) : SaveRoutinePort, ReadRoutinePort, DeleteRoutinePort { @Transactional(propagation = Propagation.MANDATORY) override fun save(routine: Routine): Routine { @@ -24,15 +26,20 @@ internal class RoutinePersistenceAdapter( return routineMapper.toDomain(routineJpaEntity) } + override fun readById(routineId: Long): Routine? = + routineRepository.findById(routineId)?.let { routineMapper.toDomain(it) } + override fun readAllByUserId(userId: UUID): List { val routineEntityList = routineRepository.findAllByUserId(userId) return routineEntityList.map { routineMapper.toDomain(it) } } + override fun readByUserIdAndDayOfWeekAndIsArchivedFalse(userId: UUID, dayOfWeek: DayOfWeek): Routine? = + routineNativeRepository.findByUserIdAndDayOfWeekAndIsArchivedFalse(userId, dayOfWeek.name)?.run { + routineMapper.toDomain(this) + } + @Transactional(propagation = Propagation.MANDATORY) override fun delete(routine: Routine) = routineRepository.delete(routineMapper.toEntity(routine)) - - override fun readRoutineById(routineId: Long): Routine? = - routineRepository.findById(routineId)?.let { routineMapper.toDomain(it) } } diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/entity/RoutineJpaEntity.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/entity/RoutineJpaEntity.kt index 98dbec25d..79c013bc8 100644 --- a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/entity/RoutineJpaEntity.kt +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/entity/RoutineJpaEntity.kt @@ -21,7 +21,7 @@ class RoutineJpaEntity( var routineName: String = routineName protected set - @ElementCollection + @ElementCollection(fetch = FetchType.EAGER) var exerciseInfoList: MutableList = exerciseInfoList protected set diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/mapper/RoutineMapper.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/mapper/RoutineMapper.kt index 12e1973ab..82cf7a365 100644 --- a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/mapper/RoutineMapper.kt +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/mapper/RoutineMapper.kt @@ -7,9 +7,12 @@ import com.info.maeumgagym.routine.model.ExerciseInfoModel import com.info.maeumgagym.routine.model.Routine import com.info.maeumgagym.routine.model.RoutineStatusModel import org.springframework.stereotype.Component +import javax.persistence.EntityManager @Component -class RoutineMapper { +class RoutineMapper( + private val em: EntityManager +) { fun toEntity(routine: Routine): RoutineJpaEntity = routine.run { RoutineJpaEntity( id = id, diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/repository/RoutineNativeRepository.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/repository/RoutineNativeRepository.kt new file mode 100644 index 000000000..cd492494d --- /dev/null +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/repository/RoutineNativeRepository.kt @@ -0,0 +1,24 @@ +package com.info.maeumgagym.domain.routine.repository + +import com.info.maeumgagym.TableNames +import com.info.maeumgagym.domain.routine.entity.RoutineJpaEntity +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.Repository +import org.springframework.data.repository.query.Param +import java.util.* + +@org.springframework.stereotype.Repository +interface RoutineNativeRepository : Repository { + + @Query( + value = "SELECT * FROM ${TableNames.ROUTINE_TABLE} r " + + "INNER JOIN ${TableNames.ROUTINE_TABLE}_day_of_weeks d " + + "ON r.id = d.${TableNames.ROUTINE_TABLE}_id " + + "WHERE r.is_archived = false AND r.user_id = :userId AND d.day_of_weeks LIKE :dayOfWeek", + nativeQuery = true + ) + fun findByUserIdAndDayOfWeekAndIsArchivedFalse( + @Param("userId") userId: UUID, + @Param("dayOfWeek") dayOfWeek: String + ): RoutineJpaEntity? +} diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/repository/RoutineRepository.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/repository/RoutineRepository.kt index 19fbc55d6..8c4b3a866 100644 --- a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/repository/RoutineRepository.kt +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/routine/repository/RoutineRepository.kt @@ -2,16 +2,15 @@ package com.info.maeumgagym.domain.routine.repository import com.info.maeumgagym.domain.routine.entity.RoutineJpaEntity import org.springframework.data.repository.Repository -import java.util.UUID +import java.util.* @org.springframework.stereotype.Repository interface RoutineRepository : Repository { + fun save(entity: RoutineJpaEntity): RoutineJpaEntity - fun findAllByUserId(userId: UUID): List + fun findById(id: Long): RoutineJpaEntity? - fun save(entity: RoutineJpaEntity): RoutineJpaEntity + fun findAllByUserId(userId: UUID): List fun delete(entity: RoutineJpaEntity) - - fun findById(id: Long): RoutineJpaEntity? } diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/StepPersistenceAdapter.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/StepPersistenceAdapter.kt new file mode 100644 index 000000000..72d9d481b --- /dev/null +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/StepPersistenceAdapter.kt @@ -0,0 +1,22 @@ +package com.info.maeumgagym.domain.step + +import com.info.common.PersistenceAdapter +import com.info.maeumgagym.domain.step.mapper.StepMapper +import com.info.maeumgagym.domain.step.repository.StepRepository +import com.info.maeumgagym.step.model.Step +import com.info.maeumgagym.step.port.out.ReadStepPort +import com.info.maeumgagym.step.port.out.SaveStepPort +import org.springframework.transaction.annotation.Propagation +import org.springframework.transaction.annotation.Transactional + +@PersistenceAdapter +internal class StepPersistenceAdapter( + private val stepRepository: StepRepository, + private val stepMapper: StepMapper +) : SaveStepPort, ReadStepPort { + override fun readByUserOauthId(oauthId: String): Step? = + stepRepository.findById(oauthId)?.let { stepMapper.toDomain(it) } + + @Transactional(propagation = Propagation.MANDATORY) + override fun save(step: Step): Step = stepMapper.toDomain(stepRepository.save(stepMapper.toEntity(step))) +} diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/entity/StepRedisEntity.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/entity/StepRedisEntity.kt new file mode 100644 index 000000000..e48418643 --- /dev/null +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/entity/StepRedisEntity.kt @@ -0,0 +1,24 @@ +package com.info.maeumgagym.domain.step.entity + +import com.info.maeumgagym.TableNames +import org.springframework.data.annotation.Id +import org.springframework.data.redis.core.RedisHash +import org.springframework.data.redis.core.TimeToLive + +@RedisHash(value = TableNames.REDIS_STEP_TABLE) +class StepRedisEntity( + id: String, + numberOfSteps: Int, + ttl: Long +) { + @Id + var id: String = id + protected set + + var numberOfSteps: Int = numberOfSteps + protected set + + @TimeToLive + var ttl: Long = ttl + protected set +} diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/mapper/StepMapper.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/mapper/StepMapper.kt new file mode 100644 index 000000000..6d049c68b --- /dev/null +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/mapper/StepMapper.kt @@ -0,0 +1,24 @@ +package com.info.maeumgagym.domain.step.mapper + +import com.info.maeumgagym.domain.step.entity.StepRedisEntity +import com.info.maeumgagym.step.model.Step +import org.springframework.stereotype.Component + +@Component +class StepMapper { + fun toEntity(domain: Step): StepRedisEntity = domain.run { + StepRedisEntity( + id = id, + numberOfSteps = numberOfSteps, + ttl = ttl + ) + } + + fun toDomain(entity: StepRedisEntity): Step = entity.run { + Step( + id = id, + numberOfSteps = numberOfSteps, + ttl = ttl + ) + } +} diff --git a/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/repository/StepRepository.kt b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/repository/StepRepository.kt new file mode 100644 index 000000000..af48a816a --- /dev/null +++ b/maeumgagym-application/src/main/kotlin/com/info/maeumgagym/domain/step/repository/StepRepository.kt @@ -0,0 +1,10 @@ +package com.info.maeumgagym.domain.step.repository + +import com.info.maeumgagym.domain.step.entity.StepRedisEntity +import org.springframework.data.repository.Repository +@org.springframework.stereotype.Repository +interface StepRepository : Repository { + fun save(entity: StepRedisEntity): StepRedisEntity + + fun findById(id: String): StepRedisEntity? +} diff --git a/maeumgagym-common/src/main/kotlin/com/info/common/ReadOnlyUseCase.kt b/maeumgagym-common/src/main/kotlin/com/info/common/ReadOnlyUseCase.kt new file mode 100644 index 000000000..b80cdb0be --- /dev/null +++ b/maeumgagym-common/src/main/kotlin/com/info/common/ReadOnlyUseCase.kt @@ -0,0 +1,11 @@ +package com.info.common + +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS) +@MustBeDocumented +@Transactional(readOnly = true) +@Service +annotation class ReadOnlyUseCase() diff --git a/maeumgagym-common/src/main/kotlin/com/info/common/UseCase.kt b/maeumgagym-common/src/main/kotlin/com/info/common/UseCase.kt index cd1f02f0c..4ac4a1d8c 100644 --- a/maeumgagym-common/src/main/kotlin/com/info/common/UseCase.kt +++ b/maeumgagym-common/src/main/kotlin/com/info/common/UseCase.kt @@ -1,9 +1,12 @@ package com.info.common import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Isolation +import org.springframework.transaction.annotation.Transactional @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) @MustBeDocumented +@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) @Service annotation class UseCase() diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/AppleOAuthService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/AppleOAuthService.kt index e080de761..8a0c256d1 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/AppleOAuthService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/AppleOAuthService.kt @@ -14,11 +14,8 @@ import com.info.maeumgagym.user.model.User import com.info.maeumgagym.user.port.out.ExistUserPort import com.info.maeumgagym.user.port.out.RecoveryUserPort import com.info.maeumgagym.user.port.out.SaveUserPort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class AppleOAuthService( private val saveUserPort: SaveUserPort, private val generateJwtPort: GenerateJwtPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/DuplicatedCheckService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/DuplicatedCheckService.kt index 97f31feda..dd66ca510 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/DuplicatedCheckService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/DuplicatedCheckService.kt @@ -1,10 +1,10 @@ package com.info.maeumgagym.auth.service -import com.info.common.UseCase +import com.info.common.ReadOnlyUseCase import com.info.maeumgagym.auth.port.`in`.DuplicatedNicknameCheckUseCase import com.info.maeumgagym.user.port.out.ExistUserPort -@UseCase +@ReadOnlyUseCase internal class DuplicatedCheckService( private val existUserPort: ExistUserPort ) : DuplicatedNicknameCheckUseCase { diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/GoogleOAuthService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/GoogleOAuthService.kt index 0aad44bdd..65e6550ba 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/GoogleOAuthService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/GoogleOAuthService.kt @@ -2,22 +2,21 @@ package com.info.maeumgagym.auth.service import com.info.common.UseCase import com.info.maeumgagym.auth.exception.AlreadyExistUserException -import com.info.maeumgagym.user.exception.DuplicatedNicknameException -import com.info.maeumgagym.user.exception.UserNotFoundException import com.info.maeumgagym.auth.port.`in`.GoogleLoginUseCase import com.info.maeumgagym.auth.port.`in`.GoogleRecoveryUseCase import com.info.maeumgagym.auth.port.`in`.GoogleSignupUseCase import com.info.maeumgagym.auth.port.out.GenerateJwtPort import com.info.maeumgagym.auth.port.out.GetGoogleInfoPort import com.info.maeumgagym.auth.port.out.RevokeGoogleTokenPort +import com.info.maeumgagym.user.exception.DuplicatedNicknameException +import com.info.maeumgagym.user.exception.UserNotFoundException import com.info.maeumgagym.user.model.Role import com.info.maeumgagym.user.model.User -import com.info.maeumgagym.user.port.out.* -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional +import com.info.maeumgagym.user.port.out.ExistUserPort +import com.info.maeumgagym.user.port.out.RecoveryUserPort +import com.info.maeumgagym.user.port.out.SaveUserPort @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class GoogleOAuthService( private val getGoogleInfoPort: GetGoogleInfoPort, private val saveUserPort: SaveUserPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/KakaoOAuthService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/KakaoOAuthService.kt index d904bbb7c..2bd93d825 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/KakaoOAuthService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/KakaoOAuthService.kt @@ -2,21 +2,20 @@ package com.info.maeumgagym.auth.service import com.info.common.UseCase import com.info.maeumgagym.auth.exception.AlreadyExistUserException -import com.info.maeumgagym.auth.port.`in`.KakaoRecoveryUseCase -import com.info.maeumgagym.user.exception.UserNotFoundException import com.info.maeumgagym.auth.port.`in`.KakaoLoginUseCase +import com.info.maeumgagym.auth.port.`in`.KakaoRecoveryUseCase import com.info.maeumgagym.auth.port.`in`.KakaoSignupUseCase import com.info.maeumgagym.auth.port.out.GenerateJwtPort import com.info.maeumgagym.auth.port.out.GetKakaoInfoPort import com.info.maeumgagym.user.exception.DuplicatedNicknameException +import com.info.maeumgagym.user.exception.UserNotFoundException import com.info.maeumgagym.user.model.Role import com.info.maeumgagym.user.model.User -import com.info.maeumgagym.user.port.out.* -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional +import com.info.maeumgagym.user.port.out.ExistUserPort +import com.info.maeumgagym.user.port.out.RecoveryUserPort +import com.info.maeumgagym.user.port.out.SaveUserPort @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class KakaoOAuthService( private val getKakaoInfoPort: GetKakaoInfoPort, private val generateJwtPort: GenerateJwtPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/ReissueService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/ReissueService.kt index 3b7e05852..314a99a89 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/ReissueService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/ReissueService.kt @@ -3,11 +3,8 @@ package com.info.maeumgagym.auth.service import com.info.common.UseCase import com.info.maeumgagym.auth.port.`in`.ReissueUseCase import com.info.maeumgagym.auth.port.out.ReissuePort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class ReissueService( private val reissuePort: ReissuePort ) : ReissueUseCase { diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/WithdrawalUserService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/WithdrawalUserService.kt index 58945def0..ac380c789 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/WithdrawalUserService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/WithdrawalUserService.kt @@ -5,11 +5,8 @@ import com.info.maeumgagym.auth.port.`in`.WithdrawalUserUseCase import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort import com.info.maeumgagym.auth.port.out.RevokeTokensPort import com.info.maeumgagym.user.port.out.DeleteUserPort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class WithdrawalUserService( private val deleteUserPort: DeleteUserPort, private val readCurrentUserPort: ReadCurrentUserPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/common/exception/ErrorCode.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/common/exception/ErrorCode.kt index 896bd7e4b..968228a73 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/common/exception/ErrorCode.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/common/exception/ErrorCode.kt @@ -33,6 +33,7 @@ enum class ErrorCode( PICKLE_NOT_FOUND(404, "Pickle Not Found"), PICKLE_COMMENT_NOT_FOUND(404, "Pickle Comment Not Found"), PICKLE_REPLY_NOT_FOUND(404, "Pickle Reply Not Found"), + STEP_NOT_FOUND(404, "Step Not Found"), // Bad Request FILE_TYPE_MISMATCHED(400, "File Type Mismatched"), @@ -47,5 +48,7 @@ enum class ErrorCode( DUPLICATED_NICKNAME(409, "Duplicated Nickname"), ALREADY_EXIST_USER(409, "Already Exists User"), ALREADY_EXIST_PICKLE(409, "Already Exists Pickle"), - ALREADY_STARTED_WAKA(409, "Already Started Wakatime") + ALREADY_EXIST_STEP(409, "Alreay Exists Step"), + ALREADY_STARTED_WAKA(409, "Already Started Wakatime"), + OTHER_ROUTINE_ALREADY_USING_AT_DAY_OF_WEEK(409, "Other Routine Already Using At Day of week") } diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleCommentService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleCommentService.kt index 14bda3f07..e260fd94d 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleCommentService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleCommentService.kt @@ -8,11 +8,8 @@ import com.info.maeumgagym.pickle.model.PickleComment import com.info.maeumgagym.pickle.port.`in`.CreatePickleCommentUseCase import com.info.maeumgagym.pickle.port.out.ExistsPicklePort import com.info.maeumgagym.pickle.port.out.SavePickleCommentPort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class CreatePickleCommentService( private val savePickleCommentPort: SavePickleCommentPort, private val readCurrentUserPort: ReadCurrentUserPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleReplyCommentService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleReplyCommentService.kt index ba861cf46..d4d4a9a02 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleReplyCommentService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleReplyCommentService.kt @@ -11,11 +11,8 @@ import com.info.maeumgagym.pickle.port.`in`.CreatePickleReplyCommentUseCase import com.info.maeumgagym.pickle.port.out.ExistsPicklePort import com.info.maeumgagym.pickle.port.out.ReadPickleCommentPort import com.info.maeumgagym.pickle.port.out.SavePickleReplyPort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class CreatePickleReplyCommentService( private val savePickleReplyPort: SavePickleReplyPort, private val readCurrentUserPort: ReadCurrentUserPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleService.kt index a880479ba..3e328bb86 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/CreatePickleService.kt @@ -3,17 +3,17 @@ package com.info.maeumgagym.pickle.service import com.info.common.UseCase import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort import com.info.maeumgagym.pickle.dto.request.CreatePickleRequest -import com.info.maeumgagym.pickle.exception.* +import com.info.maeumgagym.pickle.exception.AlreadyExistPickleException +import com.info.maeumgagym.pickle.exception.NotUploadedToVideoServerException +import com.info.maeumgagym.pickle.exception.TagTooLongException +import com.info.maeumgagym.pickle.exception.VideoAndUploaderMismatchedException import com.info.maeumgagym.pickle.model.Pickle import com.info.maeumgagym.pickle.port.`in`.CreatePickleUseCase import com.info.maeumgagym.pickle.port.out.ExistsPicklePort import com.info.maeumgagym.pickle.port.out.ReadVideoIdAndUploaderIdPort import com.info.maeumgagym.pickle.port.out.SavePicklePort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class CreatePickleService( private val savePicklePort: SavePicklePort, private val existsPicklePort: ExistsPicklePort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleCommentService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleCommentService.kt index cbc889da3..5d104dcf2 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleCommentService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleCommentService.kt @@ -7,11 +7,8 @@ import com.info.maeumgagym.pickle.exception.PickleCommentNotFoundException import com.info.maeumgagym.pickle.port.`in`.DeletePickleCommentUseCase import com.info.maeumgagym.pickle.port.out.DeletePickleCommentPort import com.info.maeumgagym.pickle.port.out.ReadPickleCommentPort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class DeletePickleCommentService( private val deletePickleCommentPort: DeletePickleCommentPort, private val currentUserPort: ReadCurrentUserPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleReplyService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleReplyService.kt index 2c04ca44b..ec461f9b6 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleReplyService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleReplyService.kt @@ -7,11 +7,8 @@ import com.info.maeumgagym.pickle.exception.PickleReplyNotFoundException import com.info.maeumgagym.pickle.port.`in`.DeletePickleReplyUseCase import com.info.maeumgagym.pickle.port.out.DeletePickleReplyPort import com.info.maeumgagym.pickle.port.out.ReadPickleReplyPort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class DeletePickleReplyService( private val readCurrentUserPort: ReadCurrentUserPort, private val readPickleReplyPort: ReadPickleReplyPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleService.kt index f3d677ea6..28b2a484e 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/DeletePickleService.kt @@ -5,14 +5,11 @@ import com.info.maeumgagym.auth.exception.PermissionDeniedException import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort import com.info.maeumgagym.pickle.exception.PickleNotFoundException import com.info.maeumgagym.pickle.port.`in`.DeletePickleUseCase -import com.info.maeumgagym.pickle.port.out.DeletePicklePort import com.info.maeumgagym.pickle.port.out.DeleteOriginalVideoPort +import com.info.maeumgagym.pickle.port.out.DeletePicklePort import com.info.maeumgagym.pickle.port.out.ReadPicklePort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class DeletePickleService( private val deletePicklePort: DeletePicklePort, private val deleteOriginalVideoPort: DeleteOriginalVideoPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/GetPreSignedURLService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/GetPreSignedURLService.kt index 8a2294abb..db66e88e6 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/GetPreSignedURLService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/GetPreSignedURLService.kt @@ -1,6 +1,6 @@ package com.info.maeumgagym.pickle.service -import com.info.common.UseCase +import com.info.common.ReadOnlyUseCase import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort import com.info.maeumgagym.pickle.dto.response.PreSignedUploadURLResponse import com.info.maeumgagym.pickle.exception.FileTypeMismatchedException @@ -9,7 +9,7 @@ import com.info.maeumgagym.pickle.port.`in`.GetPreSignedUploadURLUseCase import com.info.maeumgagym.pickle.port.out.GetPreSignedVideoUploadURLPort import com.info.maeumgagym.pickle.port.out.SaveVideoIdAndUploaderIdPort -@UseCase +@ReadOnlyUseCase internal class GetPreSignedURLService( private val getPreSignedVideoUploadURLPort: GetPreSignedVideoUploadURLPort, private val saveVideoIdAndUploaderIdPort: SaveVideoIdAndUploaderIdPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/LikePickleService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/LikePickleService.kt index b1749a4d0..5a2e43746 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/LikePickleService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/LikePickleService.kt @@ -7,11 +7,8 @@ import com.info.maeumgagym.pickle.model.Pickle import com.info.maeumgagym.pickle.model.PickleLike import com.info.maeumgagym.pickle.port.`in`.LikePickleUseCase import com.info.maeumgagym.pickle.port.out.* -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class LikePickleService( private val savePickleLikePort: SavePickleLikePort, private val readPickleLikePort: ReadPickleLikePort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/LoadPickleService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/LoadPickleService.kt index b07441767..abe36a237 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/LoadPickleService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/LoadPickleService.kt @@ -1,6 +1,6 @@ package com.info.maeumgagym.pickle.service -import com.info.common.UseCase +import com.info.common.ReadOnlyUseCase import com.info.maeumgagym.pickle.dto.response.PickleListResponse import com.info.maeumgagym.pickle.dto.response.PickleResponse import com.info.maeumgagym.pickle.exception.PickleNotFoundException @@ -15,7 +15,7 @@ import com.info.maeumgagym.pickle.port.out.ReadPicklePort import com.info.maeumgagym.pose.exception.PoseNotFoundException import com.info.maeumgagym.pose.port.out.ReadPosePort -@UseCase +@ReadOnlyUseCase internal class LoadPickleService( private val readPicklePort: ReadPicklePort, private val generateM3u8URLPort: GenerateM3u8URLPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/ReadAllPagedPickleCommentService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/ReadAllPagedPickleCommentService.kt index 07d892ea7..83088255b 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/ReadAllPagedPickleCommentService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/ReadAllPagedPickleCommentService.kt @@ -1,13 +1,13 @@ package com.info.maeumgagym.pickle.service -import com.info.common.UseCase +import com.info.common.ReadOnlyUseCase import com.info.maeumgagym.pickle.dto.response.PickleCommentListResponse import com.info.maeumgagym.pickle.exception.PickleNotFoundException import com.info.maeumgagym.pickle.port.`in`.ReadAllPagedPickleCommentUseCase import com.info.maeumgagym.pickle.port.out.ExistsPicklePort import com.info.maeumgagym.pickle.port.out.ReadPickleCommentsPort -@UseCase +@ReadOnlyUseCase internal class ReadAllPagedPickleCommentService( private val existsPicklePort: ExistsPicklePort, private val readPickleCommentsPort: ReadPickleCommentsPort diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/ReadAllPickleReplyService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/ReadAllPickleReplyService.kt index 1b879a1ff..86b74fc15 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/ReadAllPickleReplyService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/ReadAllPickleReplyService.kt @@ -1,13 +1,13 @@ package com.info.maeumgagym.pickle.service -import com.info.common.UseCase +import com.info.common.ReadOnlyUseCase import com.info.maeumgagym.pickle.dto.response.PickleReplyListResponse import com.info.maeumgagym.pickle.exception.PickleCommentNotFoundException import com.info.maeumgagym.pickle.port.`in`.LoadAllPickleReplyUseCase -import com.info.maeumgagym.pickle.port.out.ReadPickleReplyPort import com.info.maeumgagym.pickle.port.out.ReadPickleCommentPort +import com.info.maeumgagym.pickle.port.out.ReadPickleReplyPort -@UseCase +@ReadOnlyUseCase internal class ReadAllPickleReplyService( private val readPickleReplyPort: ReadPickleReplyPort, private val readPickleCommentPort: ReadPickleCommentPort diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/UpdatePickleService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/UpdatePickleService.kt index ea18883e2..330b185bd 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/UpdatePickleService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pickle/service/UpdatePickleService.kt @@ -10,11 +10,8 @@ import com.info.maeumgagym.pickle.model.Pickle import com.info.maeumgagym.pickle.port.`in`.UpdatePickleUseCase import com.info.maeumgagym.pickle.port.out.ReadPicklePort import com.info.maeumgagym.pickle.port.out.SavePicklePort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class UpdatePickleService( private val savePicklePort: SavePicklePort, private val readCurrentUserPort: ReadCurrentUserPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pose/service/ReadPoseService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pose/service/ReadPoseService.kt index 8ccddeb70..f8062b951 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pose/service/ReadPoseService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/pose/service/ReadPoseService.kt @@ -1,12 +1,12 @@ package com.info.maeumgagym.pose.service -import com.info.common.UseCase +import com.info.common.ReadOnlyUseCase import com.info.maeumgagym.pose.dto.response.PoseDetailResponse import com.info.maeumgagym.pose.exception.PoseNotFoundException import com.info.maeumgagym.pose.port.`in`.ReadByIdUseCase import com.info.maeumgagym.pose.port.out.ReadPosePort -@UseCase +@ReadOnlyUseCase internal class ReadPoseService( private val readPosePort: ReadPosePort ) : ReadByIdUseCase { diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/quote/service/ReadRandomQuoteService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/quote/service/ReadRandomQuoteService.kt index 71c62d384..70de9a0fc 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/quote/service/ReadRandomQuoteService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/quote/service/ReadRandomQuoteService.kt @@ -1,11 +1,11 @@ package com.info.maeumgagym.quote.service -import com.info.common.UseCase +import com.info.common.ReadOnlyUseCase import com.info.maeumgagym.quote.dto.response.QuoteResponse import com.info.maeumgagym.quote.port.`in`.ReadRandomQuoteUseCase import com.info.maeumgagym.quote.vo.Quotes -@UseCase +@ReadOnlyUseCase internal class ReadRandomQuoteService : ReadRandomQuoteUseCase { override fun readRandomQuote(): QuoteResponse = diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/report/service/ReportService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/report/service/ReportService.kt index 1bd2f8c12..a2fc58041 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/report/service/ReportService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/report/service/ReportService.kt @@ -19,11 +19,8 @@ import com.info.maeumgagym.report.port.`in`.ReportUserUseCase import com.info.maeumgagym.report.port.out.SaveReportPort import com.info.maeumgagym.user.exception.UserNotFoundException import com.info.maeumgagym.user.port.out.ReadUserPort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class ReportService( private val readCurrentUserPort: ReadCurrentUserPort, private val saveReportPort: SaveReportPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/exception/OtherRoutineAlreadyUsingAtDayOfWeekException.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/exception/OtherRoutineAlreadyUsingAtDayOfWeekException.kt new file mode 100644 index 000000000..a1e4d384c --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/exception/OtherRoutineAlreadyUsingAtDayOfWeekException.kt @@ -0,0 +1,7 @@ +package com.info.maeumgagym.routine.exception + +import com.info.maeumgagym.common.exception.ErrorCode +import com.info.maeumgagym.common.exception.MaeumGaGymException + +object OtherRoutineAlreadyUsingAtDayOfWeekException : + MaeumGaGymException(ErrorCode.OTHER_ROUTINE_ALREADY_USING_AT_DAY_OF_WEEK) diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/model/Routine.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/model/Routine.kt index feb1a20b7..40e5c704c 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/model/Routine.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/model/Routine.kt @@ -1,7 +1,11 @@ package com.info.maeumgagym.routine.model +import com.info.maeumgagym.routine.dto.ExerciseInfoDto +import com.info.maeumgagym.routine.dto.RoutineStatusDto +import com.info.maeumgagym.routine.dto.response.RoutineResponse import java.time.DayOfWeek -import java.util.UUID +import java.time.format.TextStyle +import java.util.* data class Routine( val id: Long? = null, @@ -10,4 +14,29 @@ data class Routine( val dayOfWeeks: MutableSet?, val routineStatusModel: RoutineStatusModel, val userId: UUID -) +) { + fun toResponse(): RoutineResponse = + RoutineResponse( + id = id!!, + routineName = routineName, + exerciseInfoList = exerciseInfoModelList.map { + ExerciseInfoDto( + exerciseName = it.exerciseName, + repetitions = it.repetitions, + sets = it.sets + ) + }, + dayOfWeeks = dayOfWeeks?.map { + it.getDisplayName( + TextStyle.FULL, + Locale.KOREA + ) + }, + routineStatus = routineStatusModel.run { + RoutineStatusDto( + isArchived = isArchived, + isShared = isShared + ) + } + ) +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/port/in/ReadTodayRoutineUseCase.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/port/in/ReadTodayRoutineUseCase.kt new file mode 100644 index 000000000..a1c38f146 --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/port/in/ReadTodayRoutineUseCase.kt @@ -0,0 +1,8 @@ +package com.info.maeumgagym.routine.port.`in` + +import com.info.maeumgagym.routine.dto.response.RoutineResponse + +interface ReadTodayRoutineUseCase { + + fun readTodayRoutine(): RoutineResponse? +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/port/out/ReadRoutinePort.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/port/out/ReadRoutinePort.kt index fb49e0db4..a300c20d7 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/port/out/ReadRoutinePort.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/port/out/ReadRoutinePort.kt @@ -1,9 +1,14 @@ package com.info.maeumgagym.routine.port.out import com.info.maeumgagym.routine.model.Routine -import java.util.UUID +import java.time.DayOfWeek +import java.util.* interface ReadRoutinePort { + fun readById(routineId: Long): Routine? + fun readAllByUserId(userId: UUID): List + + fun readByUserIdAndDayOfWeekAndIsArchivedFalse(userId: UUID, dayOfWeek: DayOfWeek): Routine? } diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/CreateRoutineService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/CreateRoutineService.kt index 8d5b72102..922f80adf 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/CreateRoutineService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/CreateRoutineService.kt @@ -3,21 +3,29 @@ package com.info.maeumgagym.routine.service import com.info.common.UseCase import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort import com.info.maeumgagym.routine.dto.request.CreateRoutineRequest +import com.info.maeumgagym.routine.exception.OtherRoutineAlreadyUsingAtDayOfWeekException import com.info.maeumgagym.routine.model.Routine import com.info.maeumgagym.routine.model.RoutineStatusModel import com.info.maeumgagym.routine.port.`in`.CreateRoutineUseCase +import com.info.maeumgagym.routine.port.out.ReadRoutinePort import com.info.maeumgagym.routine.port.out.SaveRoutinePort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class CreateRoutineService( private val saveRoutinePort: SaveRoutinePort, + private val readRoutinePort: ReadRoutinePort, private val readCurrentUserPort: ReadCurrentUserPort ) : CreateRoutineUseCase { override fun createRoutine(req: CreateRoutineRequest) { + val user = readCurrentUserPort.readCurrentUser() + + req.dayOfWeeks?.forEach { + if (readRoutinePort.readByUserIdAndDayOfWeekAndIsArchivedFalse(user.id!!, it) != null) { + throw OtherRoutineAlreadyUsingAtDayOfWeekException + } + } + req.run { // 루틴 저장 saveRoutinePort.save( @@ -29,7 +37,7 @@ internal class CreateRoutineService( isArchived = isArchived, isShared = isShared ), - userId = readCurrentUserPort.readCurrentUser().id!! + userId = user.id!! ) ) } diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/DeleteRoutineService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/DeleteRoutineService.kt index fcb7c2d2a..1dcf69839 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/DeleteRoutineService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/DeleteRoutineService.kt @@ -6,20 +6,17 @@ import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort import com.info.maeumgagym.routine.exception.RoutineNotFoundException import com.info.maeumgagym.routine.port.`in`.DeleteRoutineUseCase import com.info.maeumgagym.routine.port.out.DeleteRoutinePort -import com.info.maeumgagym.routine.port.out.ReadRoutineByIdPort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional +import com.info.maeumgagym.routine.port.out.ReadRoutinePort @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class DeleteRoutineService( private val deleteRoutinePort: DeleteRoutinePort, - private val readRoutineByIdPort: ReadRoutineByIdPort, + private val readRoutinePort: ReadRoutinePort, private val readCurrentUserPort: ReadCurrentUserPort ) : DeleteRoutineUseCase { override fun deleteRoutine(id: Long) { // (routine.id = id)인 루틴 찾기, 없다면 -> 예외 처리 - val routine = readRoutineByIdPort.readRoutineById(id) ?: throw RoutineNotFoundException + val routine = readRoutinePort.readById(id) ?: throw RoutineNotFoundException // 토큰으로 유저 찾기 val user = readCurrentUserPort.readCurrentUser() diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/ReadMyAllRoutineService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/ReadMyAllRoutineService.kt index 42151885c..e7ad1e6c4 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/ReadMyAllRoutineService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/ReadMyAllRoutineService.kt @@ -1,18 +1,13 @@ package com.info.maeumgagym.routine.service -import com.info.common.UseCase +import com.info.common.ReadOnlyUseCase import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort -import com.info.maeumgagym.routine.dto.ExerciseInfoDto -import com.info.maeumgagym.routine.dto.RoutineStatusDto import com.info.maeumgagym.routine.dto.response.RoutineListResponse -import com.info.maeumgagym.routine.dto.response.RoutineResponse import com.info.maeumgagym.routine.port.`in`.ReadAllMyRoutineUseCase import com.info.maeumgagym.routine.port.out.ReadRoutinePort import com.info.maeumgagym.user.dto.response.UserResponse -import java.time.format.TextStyle -import java.util.* -@UseCase +@ReadOnlyUseCase internal class ReadMyAllRoutineService( private val readRoutinePort: ReadRoutinePort, private val readCurrentUserPort: ReadCurrentUserPort @@ -27,31 +22,7 @@ internal class ReadMyAllRoutineService( // 반환 return RoutineListResponse( // 루틴 리스트 돌면서 List로 매핑 - routineList = routineList.map { - RoutineResponse( - id = it.id!!, - routineName = it.routineName, - exerciseInfoList = it.exerciseInfoModelList.map { exerciseInfoModel -> - ExerciseInfoDto( - exerciseName = exerciseInfoModel.exerciseName, - repetitions = exerciseInfoModel.repetitions, - sets = exerciseInfoModel.sets - ) - }, - dayOfWeeks = it.dayOfWeeks?.map { dayOfWeek -> - dayOfWeek.getDisplayName( - TextStyle.FULL, - Locale.KOREA - ) - }, - routineStatus = it.routineStatusModel.run { - RoutineStatusDto( - isArchived = isArchived, - isShared = isShared - ) - } - ) - }, + routineList = routineList.map { it.toResponse() }, // 유저 정보 userInfo = UserResponse(nickname = user.nickname, profileImage = user.profileImage) ) diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/ReadTodayRoutineService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/ReadTodayRoutineService.kt new file mode 100644 index 000000000..7bb64072c --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/ReadTodayRoutineService.kt @@ -0,0 +1,21 @@ +package com.info.maeumgagym.routine.service + +import com.info.common.ReadOnlyUseCase +import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort +import com.info.maeumgagym.routine.dto.response.RoutineResponse +import com.info.maeumgagym.routine.port.`in`.ReadTodayRoutineUseCase +import com.info.maeumgagym.routine.port.out.ReadRoutinePort +import java.time.LocalDate + +@ReadOnlyUseCase +class ReadTodayRoutineService( + private val readRoutinePort: ReadRoutinePort, + private val readCurrentUserPort: ReadCurrentUserPort +) : ReadTodayRoutineUseCase { + + override fun readTodayRoutine(): RoutineResponse? = + readRoutinePort.readByUserIdAndDayOfWeekAndIsArchivedFalse( + readCurrentUserPort.readCurrentUser().id!!, + LocalDate.now().dayOfWeek + )?.run { toResponse() } +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/UpdateRoutineService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/UpdateRoutineService.kt index 43721254c..6d8f2d971 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/UpdateRoutineService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/routine/service/UpdateRoutineService.kt @@ -4,19 +4,17 @@ import com.info.common.UseCase import com.info.maeumgagym.auth.exception.PermissionDeniedException import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort import com.info.maeumgagym.routine.dto.request.UpdateRoutineRequest +import com.info.maeumgagym.routine.exception.OtherRoutineAlreadyUsingAtDayOfWeekException import com.info.maeumgagym.routine.exception.RoutineNotFoundException import com.info.maeumgagym.routine.model.Routine import com.info.maeumgagym.routine.model.RoutineStatusModel import com.info.maeumgagym.routine.port.`in`.UpdateRoutineUseCase -import com.info.maeumgagym.routine.port.out.ReadRoutineByIdPort +import com.info.maeumgagym.routine.port.out.ReadRoutinePort import com.info.maeumgagym.routine.port.out.SaveRoutinePort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class UpdateRoutineService( - private val readRoutineByIdPort: ReadRoutineByIdPort, + private val readRoutinePort: ReadRoutinePort, private val readCurrentUserPort: ReadCurrentUserPort, private val saveRoutinePort: SaveRoutinePort ) : UpdateRoutineUseCase { @@ -25,11 +23,17 @@ internal class UpdateRoutineService( val user = readCurrentUserPort.readCurrentUser() // (routine.id = routineId)인 루틴 찾기, 없다면 -> 예외처리 - val routine = readRoutineByIdPort.readRoutineById(routineId) ?: throw RoutineNotFoundException + val routine = readRoutinePort.readById(routineId) ?: throw RoutineNotFoundException // 루틴을 만든 이가 토큰의 유저가 맞는지 검증, 아닐시 -> 예외처리 if (user.id != routine.userId) throw PermissionDeniedException + req.dayOfWeeks?.forEach { + if (readRoutinePort.readByUserIdAndDayOfWeekAndIsArchivedFalse(user.id!!, it) != null) { + throw OtherRoutineAlreadyUsingAtDayOfWeekException + } + } + routine.run { // 루틴 업데이트 saveRoutinePort.save( diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/exception/AlreadyExistStepException.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/exception/AlreadyExistStepException.kt new file mode 100644 index 000000000..b13fa7b9a --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/exception/AlreadyExistStepException.kt @@ -0,0 +1,6 @@ +package com.info.maeumgagym.step.exception + +import com.info.maeumgagym.common.exception.ErrorCode +import com.info.maeumgagym.common.exception.MaeumGaGymException + +object AlreadyExistStepException : MaeumGaGymException(ErrorCode.ALREADY_EXIST_STEP) diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/exception/StepNotFoundException.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/exception/StepNotFoundException.kt new file mode 100644 index 000000000..34b737c1a --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/exception/StepNotFoundException.kt @@ -0,0 +1,6 @@ +package com.info.maeumgagym.step.exception + +import com.info.maeumgagym.common.exception.ErrorCode +import com.info.maeumgagym.common.exception.MaeumGaGymException + +object StepNotFoundException : MaeumGaGymException(ErrorCode.STEP_NOT_FOUND) diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/model/Step.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/model/Step.kt new file mode 100644 index 000000000..8481a1f5b --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/model/Step.kt @@ -0,0 +1,7 @@ +package com.info.maeumgagym.step.model + +data class Step( + val id: String, + val numberOfSteps: Int, + val ttl: Long +) diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/in/CreateStepUseCase.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/in/CreateStepUseCase.kt new file mode 100644 index 000000000..f5fde917b --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/in/CreateStepUseCase.kt @@ -0,0 +1,5 @@ +package com.info.maeumgagym.step.port.`in` + +interface CreateStepUseCase { + fun createStep() +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/in/UpdateStepUseCase.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/in/UpdateStepUseCase.kt new file mode 100644 index 000000000..127fc45ed --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/in/UpdateStepUseCase.kt @@ -0,0 +1,5 @@ +package com.info.maeumgagym.step.port.`in` + +interface UpdateStepUseCase { + fun updateStep(numberOfSteps: Int) +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/out/ReadStepPort.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/out/ReadStepPort.kt new file mode 100644 index 000000000..ccaa05f7a --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/out/ReadStepPort.kt @@ -0,0 +1,7 @@ +package com.info.maeumgagym.step.port.out + +import com.info.maeumgagym.step.model.Step + +interface ReadStepPort { + fun readByUserOauthId(oauthId: String): Step? +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/out/SaveStepPort.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/out/SaveStepPort.kt new file mode 100644 index 000000000..5fa2df33b --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/port/out/SaveStepPort.kt @@ -0,0 +1,7 @@ +package com.info.maeumgagym.step.port.out + +import com.info.maeumgagym.step.model.Step + +interface SaveStepPort { + fun save(step: Step): Step +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/service/CreateStepService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/service/CreateStepService.kt new file mode 100644 index 000000000..5115e2a6c --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/service/CreateStepService.kt @@ -0,0 +1,39 @@ +package com.info.maeumgagym.step.service + +import com.info.common.UseCase +import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort +import com.info.maeumgagym.step.exception.AlreadyExistStepException +import com.info.maeumgagym.step.model.Step +import com.info.maeumgagym.step.port.`in`.CreateStepUseCase +import com.info.maeumgagym.step.port.out.ReadStepPort +import com.info.maeumgagym.step.port.out.SaveStepPort +import java.time.Duration +import java.time.LocalDateTime + +@UseCase +internal class CreateStepService( + private val saveStepPort: SaveStepPort, + private val readStepPort: ReadStepPort, + private val readCurrentUserPort: ReadCurrentUserPort +) : CreateStepUseCase { + override fun createStep() { + val user = readCurrentUserPort.readCurrentUser() + if (readStepPort.readByUserOauthId(user.oauthId) != null) { + throw AlreadyExistStepException + } + + saveStepPort.save( + Step( + id = user.oauthId, + numberOfSteps = 0, + ttl = secondsUntilTomorrow() + ) + ) + } + + private fun secondsUntilTomorrow(): Long { + val now = LocalDateTime.now() + val tomorrowStart = now.toLocalDate().plusDays(1).atStartOfDay() + return Duration.between(now, tomorrowStart).toSeconds() + } +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/service/UpdateStepService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/service/UpdateStepService.kt new file mode 100644 index 000000000..b18badc6a --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/step/service/UpdateStepService.kt @@ -0,0 +1,22 @@ +package com.info.maeumgagym.step.service + +import com.info.common.UseCase +import com.info.maeumgagym.auth.port.out.ReadCurrentUserPort +import com.info.maeumgagym.step.exception.StepNotFoundException +import com.info.maeumgagym.step.port.`in`.UpdateStepUseCase +import com.info.maeumgagym.step.port.out.ReadStepPort +import com.info.maeumgagym.step.port.out.SaveStepPort + +@UseCase +internal class UpdateStepService( + private val saveStepPort: SaveStepPort, + private val readStepPort: ReadStepPort, + private val readCurrentUserPort: ReadCurrentUserPort +) : UpdateStepUseCase { + override fun updateStep(numberOfSteps: Int) { + val user = readCurrentUserPort.readCurrentUser() + val step = readStepPort.readByUserOauthId(user.oauthId) ?: throw StepNotFoundException + + saveStepPort.save(step.copy(numberOfSteps = numberOfSteps)) + } +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/wakatime/service/EndWakatimeService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/wakatime/service/EndWakatimeService.kt index 6d10f0aca..80a9d2302 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/wakatime/service/EndWakatimeService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/wakatime/service/EndWakatimeService.kt @@ -9,13 +9,10 @@ import com.info.maeumgagym.wakatime.model.WakaTime import com.info.maeumgagym.wakatime.port.`in`.EndWakatimeUseCase import com.info.maeumgagym.wakatime.port.out.ReadWakaTimePort import com.info.maeumgagym.wakatime.port.out.SaveWakaTimePort -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional import java.time.Duration import java.time.LocalDateTime @UseCase -@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = [Exception::class]) internal class EndWakatimeService( private val readCurrentUserPort: ReadCurrentUserPort, private val saveUserPort: SaveUserPort, diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/wakatime/service/StartWakatimeService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/wakatime/service/StartWakatimeService.kt index 1286f7df7..e83ed209e 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/wakatime/service/StartWakatimeService.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/wakatime/service/StartWakatimeService.kt @@ -6,12 +6,9 @@ import com.info.maeumgagym.user.model.User import com.info.maeumgagym.user.port.out.SaveUserPort import com.info.maeumgagym.wakatime.exception.AlreadyWakaStartedException import com.info.maeumgagym.wakatime.port.`in`.StartWakatimeUseCase -import org.springframework.transaction.annotation.Isolation -import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime @UseCase -@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = [Exception::class]) internal class StartWakatimeService( private val readCurrentUserPort: ReadCurrentUserPort, private val saveUserPort: SaveUserPort diff --git a/maeumgagym-infrastructure/build.gradle.kts b/maeumgagym-infrastructure/build.gradle.kts index 9d7f13c0b..2a8ac06c2 100644 --- a/maeumgagym-infrastructure/build.gradle.kts +++ b/maeumgagym-infrastructure/build.gradle.kts @@ -56,6 +56,11 @@ dependencyManagement { } } +tasks.test { + useJUnitPlatform() + systemProperty("spring.profiles.active", "local") +} + allOpen { annotations("org.springframework.data.redis.core.RedisHash") } diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/filter/FilterConfig.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/filter/FilterConfig.kt index f1f5d6ff3..9399426a9 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/filter/FilterConfig.kt +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/filter/FilterConfig.kt @@ -1,7 +1,6 @@ package com.info.maeumgagym.global.config.filter import com.fasterxml.jackson.databind.ObjectMapper -import com.info.maeumgagym.global.error.GlobalExceptionFilter import com.info.maeumgagym.global.jwt.JwtAdapter import com.info.maeumgagym.global.jwt.JwtFilter import com.info.maeumgagym.global.jwt.JwtResolver diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/GlobalExceptionFilter.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/filter/GlobalExceptionFilter.kt similarity index 93% rename from maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/GlobalExceptionFilter.kt rename to maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/filter/GlobalExceptionFilter.kt index 7a8a73639..50914a36d 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/GlobalExceptionFilter.kt +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/filter/GlobalExceptionFilter.kt @@ -1,8 +1,9 @@ -package com.info.maeumgagym.global.error +package com.info.maeumgagym.global.config.filter import com.fasterxml.jackson.databind.ObjectMapper import com.info.maeumgagym.common.exception.MaeumGaGymException import com.info.maeumgagym.common.exception.ErrorCode +import com.info.maeumgagym.global.error.ErrorResponse import org.springframework.http.MediaType import org.springframework.web.filter.OncePerRequestFilter import java.io.IOException diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/security/SecurityConfig.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/security/SecurityConfig.kt index 96d0312d2..7c8fa140b 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/security/SecurityConfig.kt +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/config/security/SecurityConfig.kt @@ -2,6 +2,7 @@ package com.info.maeumgagym.global.config.security import com.fasterxml.jackson.databind.ObjectMapper import com.info.maeumgagym.global.config.filter.FilterConfig +import com.info.maeumgagym.global.env.security.CSRFProperties import com.info.maeumgagym.global.env.security.SecurityProperties import com.info.maeumgagym.global.error.CustomAccessDeniedHandler import com.info.maeumgagym.global.error.CustomAuthenticationEntryPoint @@ -25,11 +26,20 @@ class SecurityConfig( private val objectMapper: ObjectMapper, private val jwtResolver: JwtResolver, private val jwtAdapter: JwtAdapter, - private val property: SecurityProperties + private val securityProperty: SecurityProperties, + private val csrfProperties: CSRFProperties ) { @Bean protected fun filterChain(http: HttpSecurity): SecurityFilterChain = - http.csrf().csrfTokenRepository(CookieCsrfTokenRepository()).and() + http.csrf().csrfTokenRepository( + CookieCsrfTokenRepository().apply { + setSecure(true) + setCookieName(csrfProperties.cookie) + setHeaderName(csrfProperties.header) + setCookieHttpOnly(true) + setParameterName(csrfProperties.parameter) + } + ).and() .formLogin().disable() .requiresChannel().anyRequest().requiresSecure().and() // XSS 공격 방지(HTTPS 요청 요구) local test시 주석 처리할 것 .sessionManagement() @@ -47,8 +57,8 @@ class SecurityConfig( .anyRequest().authenticated() .and() .cors().and() - .exceptionHandling().accessDeniedHandler(CustomAccessDeniedHandler()) - .authenticationEntryPoint(CustomAuthenticationEntryPoint()).and() + .exceptionHandling().accessDeniedHandler(CustomAccessDeniedHandler(objectMapper, csrfProperties.header)) + .authenticationEntryPoint(CustomAuthenticationEntryPoint(objectMapper)).and() .headers().frameOptions().sameOrigin().and() .apply(FilterConfig(objectMapper, jwtResolver, jwtAdapter)) .and().build() @@ -56,7 +66,7 @@ class SecurityConfig( @Bean fun corsConfigurationSource(): CorsConfigurationSource { val configuration = CorsConfiguration().apply { - allowedOrigins = listOf(property.frontDomain, property.backDomain) + allowedOrigins = listOf(securityProperty.frontDomain, securityProperty.backDomain) allowedMethods = listOf("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD") allowCredentials = true addAllowedHeader("*") diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/env/security/CSRFProperties.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/env/security/CSRFProperties.kt new file mode 100644 index 000000000..c8dd5673a --- /dev/null +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/env/security/CSRFProperties.kt @@ -0,0 +1,12 @@ +package com.info.maeumgagym.global.env.security + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.ConstructorBinding + +@ConstructorBinding +@ConfigurationProperties("csrf") +data class CSRFProperties( + val header: String, + val cookie: String, + val parameter: String +) diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/CustomAccessDeniedHandler.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/CustomAccessDeniedHandler.kt index 7e8f4962c..820cd9ca7 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/CustomAccessDeniedHandler.kt +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/CustomAccessDeniedHandler.kt @@ -10,7 +10,10 @@ import org.springframework.security.web.access.AccessDeniedHandler import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse -class CustomAccessDeniedHandler : AccessDeniedHandler { +class CustomAccessDeniedHandler( + private val objectMapper: ObjectMapper, + private val headerName: String +) : AccessDeniedHandler { private var errorPage: String? = null @@ -28,8 +31,14 @@ class CustomAccessDeniedHandler : AccessDeniedHandler { } else if (this.errorPage == null) { logger.debug { "Responding with 403 status code" } //response.sendError(HttpStatus.FORBIDDEN.value(), "csrf token missing") - val responseBody = ObjectMapper().writeValueAsString( - ErrorResponse( + + val responseBody = objectMapper.writeValueAsString( + request.getHeader(headerName)?.run { + ErrorResponse( + 403, + "Invalid CSRF Token" + ) + } ?: ErrorResponse( 403, "CSRF Token Not in Possession" ) diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/CustomAuthenticationEntryPoint.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/CustomAuthenticationEntryPoint.kt index c1a585056..033501959 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/CustomAuthenticationEntryPoint.kt +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/error/CustomAuthenticationEntryPoint.kt @@ -9,7 +9,9 @@ import org.springframework.security.web.AuthenticationEntryPoint import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse -class CustomAuthenticationEntryPoint : AuthenticationEntryPoint { +class CustomAuthenticationEntryPoint( + private val objectMapper: ObjectMapper +) : AuthenticationEntryPoint { private companion object { val logger: KLogger = KotlinLogging.logger { } @@ -23,7 +25,7 @@ class CustomAuthenticationEntryPoint : AuthenticationEntryPoint { logger.debug { "Pre-authenticated entry point called. Rejecting access" } //response!!.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied") - val responseBody = ObjectMapper().writeValueAsString( + val responseBody = objectMapper.writeValueAsString( ErrorResponse( 401, "Access Token Not in Possession" diff --git a/maeumgagym-infrastructure/src/main/resources/application-local.yml b/maeumgagym-infrastructure/src/main/resources/application-local.yml index 4091c3e97..0604bd301 100644 --- a/maeumgagym-infrastructure/src/main/resources/application-local.yml +++ b/maeumgagym-infrastructure/src/main/resources/application-local.yml @@ -1,11 +1,11 @@ spring: config: activate: - on-profile: local-config + on-profile: local jpa: hibernate: - ddl-auto: create + ddl-auto: update properties: hibernate: format_sql: true @@ -39,3 +39,8 @@ redis: logging: level: root: info + +csrf: + header: ${CSRF_HEADER:X-XSRF-TOKEN} + cookie: ${CSRF_COOKIE:XSRF-TOKEN} + parameter: ${CSRF_PARAMETER:_csrf} diff --git a/maeumgagym-infrastructure/src/main/resources/application-prod.yml b/maeumgagym-infrastructure/src/main/resources/application-prod.yml index 9e312de4f..e710b1d90 100644 --- a/maeumgagym-infrastructure/src/main/resources/application-prod.yml +++ b/maeumgagym-infrastructure/src/main/resources/application-prod.yml @@ -1,7 +1,7 @@ spring: config: activate: - on-profile: prod-config + on-profile: prod management: endpoint: @@ -14,3 +14,8 @@ server: back-domain: ${BACK_DOMAIN} servlet: context-path: /maeumgagym + +csrf: + header: ${CSRF_HEADER:X-XSRF-TOKEN} + cookie: ${CSRF_COOKIE:XSRF-TOKEN} + parameter: ${CSRF_PARAMETER:_csrf} diff --git a/maeumgagym-infrastructure/src/main/resources/application-stag.yml b/maeumgagym-infrastructure/src/main/resources/application-stag.yml index 7e67d42e7..33b7fb116 100644 --- a/maeumgagym-infrastructure/src/main/resources/application-stag.yml +++ b/maeumgagym-infrastructure/src/main/resources/application-stag.yml @@ -1,7 +1,7 @@ spring: config: activate: - on-profile: stag-config + on-profile: stag management: endpoint: @@ -14,3 +14,8 @@ server: back-domain: ${BACK_DOMAIN} servlet: context-path: /maeumgagym + +csrf: + header: ${CSRF_HEADER:X-XSRF-TOKEN} + cookie: ${CSRF_COOKIE:XSRF-TOKEN} + parameter: ${CSRF_PARAMETER:_csrf} diff --git a/maeumgagym-infrastructure/src/main/resources/application-swagger.yml b/maeumgagym-infrastructure/src/main/resources/application-swagger.yml index 19a3fd79c..bf8cc654a 100644 --- a/maeumgagym-infrastructure/src/main/resources/application-swagger.yml +++ b/maeumgagym-infrastructure/src/main/resources/application-swagger.yml @@ -9,5 +9,8 @@ springdoc: swagger-ui: path: ${SWAGGER_UI_PATH:/swagger-ui} display-request-duration: true + csrf: + header-name: ${CSRF_HEADER:X-XSRF-TOKEN} + cookie-name: ${CSRF_COOKIE:XSRF-TOKEN} cache: disabled: true diff --git a/maeumgagym-infrastructure/src/main/resources/application.yml b/maeumgagym-infrastructure/src/main/resources/application.yml index f5db327cb..6c2671f90 100644 --- a/maeumgagym-infrastructure/src/main/resources/application.yml +++ b/maeumgagym-infrastructure/src/main/resources/application.yml @@ -1,9 +1,9 @@ spring: profiles: group: - local: "local-config, jwt, file, swagger" - prod: "prod-config, prod-datasource, jwt, log, redis, file, swagger" - stag: "stag-config, stag-datasource, jwt, log, redis, file, swagger" + "local": "local, jwt, file, swagger" + "prod": "prod, prod-datasource, jwt, log, redis, file, swagger" + "stag": "stag, stag-datasource, jwt, log, redis, file, swagger" # format jackson: @@ -18,3 +18,9 @@ server: max-connections: 8192 accept-count: 100 connection-timeout: 20000 + +management: + endpoints: + web: + exposure: + include: "health, heapdump, threaddump" diff --git a/maeumgagym-infrastructure/src/main/resources/banner.txt b/maeumgagym-infrastructure/src/main/resources/banner.txt new file mode 100644 index 000000000..b950c8fd6 --- /dev/null +++ b/maeumgagym-infrastructure/src/main/resources/banner.txt @@ -0,0 +1,16 @@ + +███╗ ███╗ █████╗ ███████╗███╗ ██╗██╗ ██╗███╗ ███╗ ██████╗ █████╗ ██████╗██╗ ██╗███╗ ███╗ +████╗ ████║██╔══██╗██╔════╝████╗ ██║██║ ██║████╗ ████║██╔════╝ ██╔══██╗██╔════╝╚██╗ ██╔╝████╗ ████║ +██╔████╔██║███████║█████╗ ██╔██╗ ██║██║ ██║██╔████╔██║██║ ███╗███████║██║ ███╗╚████╔╝ ██╔████╔██║ +██║╚██╔╝██║██╔══██║██╔══╝ ██║╚██╗██║██║ ██║██║╚██╔╝██║██║ ██║██╔══██║██║ ██║ ╚██╔╝ ██║╚██╔╝██║ +██║ ╚═╝ ██║██║ ██║███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║╚██████╔╝██║ ██║╚██████╔╝ ██║ ██║ ╚═╝ ██║ +╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ + +██████╗ █████╗ ██████╗██╗ ██╗███████╗███╗ ██╗██████╗ ██╗ ██╗███╗ ██╗██╗████████╗ +██╔══██╗██╔══██╗██╔════╝██║ ██╔╝██╔════╝████╗ ██║██╔══██╗ ██║ ██║████╗ ██║██║╚══██╔══╝ +██████╔╝███████║██║ █████╔╝ █████╗ ██╔██╗ ██║██║ ██║ ██║ ██║██╔██╗ ██║██║ ██║ +██╔══██╗██╔══██║██║ ██╔═██╗ ██╔══╝ ██║╚██╗██║██║ ██║ ██║ ██║██║╚██╗██║██║ ██║ +██████╔╝██║ ██║╚██████╗██║ ██╗███████╗██║ ╚████║██████╔╝ ╚██████╔╝██║ ╚████║██║ ██║ +╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ + +${AnsiColor.BRIGHT_GREEN}::: Spring Boot :::${AnsiColor.DEFAULT}${spring-boot.formatted-version} diff --git a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/CreatePickleServiceTests.kt b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/CreatePickleServiceTests.kt new file mode 100644 index 000000000..9d90607cb --- /dev/null +++ b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/CreatePickleServiceTests.kt @@ -0,0 +1,64 @@ +package com.info.maeumgagym.domain.pickle + +import com.info.maeumgagym.domain.auth.AuthTestModule.saveInContext +import com.info.maeumgagym.domain.pickle.PickleTestModule.saveInRepository +import com.info.maeumgagym.domain.pickle.entity.PickleJpaEntity +import com.info.maeumgagym.domain.pickle.repository.PickleRepository +import com.info.maeumgagym.domain.user.UserTestModule +import com.info.maeumgagym.domain.user.UserTestModule.saveInRepository +import com.info.maeumgagym.domain.user.entity.UserJpaEntity +import com.info.maeumgagym.domain.user.mapper.UserMapper +import com.info.maeumgagym.domain.user.repository.UserRepository +import com.info.maeumgagym.global.file.VideoIdAndUploaderIdRepository +import com.info.maeumgagym.global.file.entity.VideoIdAndUploaderIdRedisEntity +import com.info.maeumgagym.pickle.exception.AlreadyExistPickleException +import com.info.maeumgagym.pickle.port.`in`.CreatePickleUseCase +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.transaction.annotation.Transactional + +@Transactional +@SpringBootTest +class CreatePickleServiceTests @Autowired constructor( + private val pickleRepository: PickleRepository, + private val videoIdAndUploaderIdRepository: VideoIdAndUploaderIdRepository, + private val createPickleUseCase: CreatePickleUseCase, + private val userRepository: UserRepository, + private val userMapper: UserMapper +) { + + private lateinit var pickle: PickleJpaEntity + private lateinit var user: UserJpaEntity + + @BeforeEach + fun initialize() { + user = UserTestModule.createTestUser().saveInRepository(userRepository).saveInContext(userMapper) + pickle = PickleTestModule.createPickle(user) + } + + @Test + fun uploadPickle() { + videoIdAndUploaderIdRepository.save(VideoIdAndUploaderIdRedisEntity(pickle.videoId, user.id!!, 1000)) + Assertions.assertDoesNotThrow { + createPickleUseCase.createPickle( + PickleTestModule.getUploadPickleRequest(pickle.videoId) + ) + } + Assertions.assertNotNull( + pickleRepository.findById(pickle.videoId) + ) + } + + @Test + fun uploadPickleWithExistsVideoId() { + pickle = pickle.saveInRepository(pickleRepository) + Assertions.assertThrows(AlreadyExistPickleException::class.java) { + createPickleUseCase.createPickle( + PickleTestModule.getUploadPickleRequest(pickle.videoId) + ) + } + } +} diff --git a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/LoadPickleServiceTests.kt b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/LoadPickleServiceTests.kt new file mode 100644 index 000000000..e111929ae --- /dev/null +++ b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/LoadPickleServiceTests.kt @@ -0,0 +1,93 @@ +package com.info.maeumgagym.domain.pickle + +import com.info.maeumgagym.domain.auth.AuthTestModule.saveInContext +import com.info.maeumgagym.domain.pickle.PickleTestModule.saveInRepository +import com.info.maeumgagym.domain.pickle.entity.PickleJpaEntity +import com.info.maeumgagym.domain.pickle.mapper.PickleMapper +import com.info.maeumgagym.domain.pickle.repository.PickleRepository +import com.info.maeumgagym.domain.user.UserTestModule +import com.info.maeumgagym.domain.user.UserTestModule.saveInRepository +import com.info.maeumgagym.domain.user.entity.UserJpaEntity +import com.info.maeumgagym.domain.user.mapper.UserMapper +import com.info.maeumgagym.domain.user.repository.UserRepository +import com.info.maeumgagym.pickle.dto.response.PickleResponse +import com.info.maeumgagym.pickle.exception.PickleNotFoundException +import com.info.maeumgagym.pickle.exception.ThereNoPicklesException +import com.info.maeumgagym.pickle.model.Pickle.Companion.toResponse +import com.info.maeumgagym.pickle.port.`in`.LoadPickleFromIdUseCase +import com.info.maeumgagym.pickle.port.`in`.LoadPickleFromPoseUseCase +import com.info.maeumgagym.pickle.port.`in`.LoadRecommendationPicklesUseCase +import com.info.maeumgagym.pickle.port.out.GenerateM3u8URLPort +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.RepeatedTest +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.transaction.annotation.Transactional + +@Transactional +@SpringBootTest +class LoadPickleServiceTests @Autowired constructor( + private val loadRecommendationPicklesUseCase: LoadRecommendationPicklesUseCase, + private val loadPickleFromIdUseCase: LoadPickleFromIdUseCase, + private val loadPickleFromPoseUseCase: LoadPickleFromPoseUseCase, + private val pickleRepository: PickleRepository, + private val pickleMapper: PickleMapper, + private val userRepository: UserRepository, + private val userMapper: UserMapper, + private val generateM3u8URLPort: GenerateM3u8URLPort +) { + + private lateinit var user: UserJpaEntity + private lateinit var pickleList: List + + @BeforeEach + fun initialize() { + user = UserTestModule.createTestUser().saveInRepository(userRepository).saveInContext(userMapper) + val pickleMutableList = mutableListOf() + for (i in 1..10) { + pickleMutableList.add( + PickleTestModule.createPickle(user).saveInRepository(pickleRepository) + ) + } + pickleList = pickleMutableList + } + + @RepeatedTest(10) + fun loadRecommendationPickle() { + Assertions.assertTrue( + getPickleResponseListFromPickleList().containsAll( + loadRecommendationPicklesUseCase.loadRecommendationPickles(0).pickleList + ) + ) + } + + @Test + fun loadRecommendationPickleWithTooBigIndex() { + Assertions.assertThrows(ThereNoPicklesException::class.java) { + loadRecommendationPicklesUseCase.loadRecommendationPickles(Int.MAX_VALUE) + } + } + + @RepeatedTest(10) + fun loadPickleFromId() { + Assertions.assertTrue( + getPickleResponseListFromPickleList().contains( + loadPickleFromIdUseCase.loadPickleFromId(pickleList.random().videoId) + ) + ) + } + + @Test + fun loadPickleFromNonExistsId() { + Assertions.assertThrows(PickleNotFoundException::class.java) { + loadPickleFromIdUseCase.loadPickleFromId("a") + } + } + + private fun getPickleResponseListFromPickleList(): List = + pickleList.map { + pickleMapper.toDomain(it).toResponse(generateM3u8URLPort.generateURL(it.videoId)) + } +} diff --git a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/PickleTestModule.kt b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/PickleTestModule.kt index b7efa55e5..71f93327e 100644 --- a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/PickleTestModule.kt +++ b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/PickleTestModule.kt @@ -4,6 +4,7 @@ import com.info.maeumgagym.domain.pickle.entity.PickleJpaEntity import com.info.maeumgagym.domain.pickle.repository.PickleRepository import com.info.maeumgagym.domain.pose.PoseTestModule import com.info.maeumgagym.domain.user.entity.UserJpaEntity +import com.info.maeumgagym.pickle.dto.request.CreatePickleRequest import com.info.maeumgagym.pickle.dto.request.UpdatePickleRequest import java.util.* @@ -31,14 +32,14 @@ object PickleTestModule { fun PickleJpaEntity.saveInRepository(pickleRepository: PickleRepository): PickleJpaEntity = pickleRepository.save(this) - /* - fun getUploadPickleRequest(videoId: String): PickleUploadRequest = - PickleUploadRequest( - videoId = videoId, - title = PICKLE_TITLE, - description = PICKLE_DESCRIPTION, - tags = PICKLE_TAGS.toMutableSet() - )*/ + + fun getUploadPickleRequest(videoId: String): CreatePickleRequest = + CreatePickleRequest( + videoId = videoId, + title = PICKLE_TITLE, + description = PICKLE_DESCRIPTION, + tags = PICKLE_TAGS.toMutableSet() + ) fun getUpdatePickleRequest(): UpdatePickleRequest = UpdatePickleRequest( diff --git a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/UpdatePickleServiceTests.kt b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/UpdatePickleServiceTests.kt new file mode 100644 index 000000000..b15fa349f --- /dev/null +++ b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/pickle/UpdatePickleServiceTests.kt @@ -0,0 +1,82 @@ +package com.info.maeumgagym.domain.pickle + +import com.info.maeumgagym.auth.exception.PermissionDeniedException +import com.info.maeumgagym.domain.auth.AuthTestModule.saveInContext +import com.info.maeumgagym.domain.pickle.PickleTestModule.saveInRepository +import com.info.maeumgagym.domain.pickle.entity.PickleJpaEntity +import com.info.maeumgagym.domain.pickle.repository.PickleRepository +import com.info.maeumgagym.domain.user.UserTestModule +import com.info.maeumgagym.domain.user.UserTestModule.saveInRepository +import com.info.maeumgagym.domain.user.entity.UserJpaEntity +import com.info.maeumgagym.domain.user.mapper.UserMapper +import com.info.maeumgagym.domain.user.repository.UserRepository +import com.info.maeumgagym.pickle.exception.PickleNotFoundException +import com.info.maeumgagym.pickle.port.`in`.UpdatePickleUseCase +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.transaction.annotation.Transactional + +@Transactional +@SpringBootTest +class UpdatePickleServiceTests @Autowired constructor( + private val pickleRepository: PickleRepository, + private val updatePickleUseCase: UpdatePickleUseCase, + private val userRepository: UserRepository, + private val userMapper: UserMapper +) { + + private lateinit var pickle: PickleJpaEntity + private lateinit var user: UserJpaEntity + + @BeforeEach + fun initialize() { + user = UserTestModule.createTestUser().saveInRepository(userRepository).saveInContext(userMapper) + pickle = PickleTestModule.createPickle(user) + } + + @Test + fun updatePickle() { + pickle = pickle.saveInRepository(pickleRepository) + Assertions.assertDoesNotThrow { + updatePickleUseCase.updatePickle( + pickle.videoId, + PickleTestModule.getUpdatePickleRequest() + ) + } + Assertions.assertTrue( + PickleTestModule.verifyUpdatedPickle(pickle, user) + ) + } + + @Test + fun updateNonExistsPickle() { + Assertions.assertThrows(PickleNotFoundException::class.java) { + updatePickleUseCase.updatePickle( + pickle.videoId, + PickleTestModule.getUpdatePickleRequest() + ) + } + Assertions.assertNull( + pickleRepository.findById(pickle.videoId) + ) + } + + @Test + fun updateOthersPickle() { + val otherUser = UserTestModule.createOtherUser().saveInRepository(userRepository) + pickle = PickleTestModule.createPickle(otherUser).saveInRepository(pickleRepository) + + Assertions.assertThrows(PermissionDeniedException::class.java) { + updatePickleUseCase.updatePickle( + pickle.videoId, + PickleTestModule.getUpdatePickleRequest() + ) + } + Assertions.assertFalse( + PickleTestModule.verifyUpdatedPickle(pickle, otherUser) + ) + } +} diff --git a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/CreateRoutineServiceTests.kt b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/CreateRoutineServiceTests.kt index 7dfcdef70..2280144f6 100644 --- a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/CreateRoutineServiceTests.kt +++ b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/CreateRoutineServiceTests.kt @@ -1,12 +1,16 @@ package com.info.maeumgagym.domain.routine import com.info.maeumgagym.domain.auth.AuthTestModule.saveInContext +import com.info.maeumgagym.domain.routine.RoutineTestModule.saveInRepository +import com.info.maeumgagym.domain.routine.RoutineTestModule.setArchived +import com.info.maeumgagym.domain.routine.repository.RoutineNativeRepository import com.info.maeumgagym.domain.routine.repository.RoutineRepository -import com.info.maeumgagym.domain.user.entity.UserJpaEntity -import com.info.maeumgagym.domain.user.mapper.UserMapper import com.info.maeumgagym.domain.user.UserTestModule import com.info.maeumgagym.domain.user.UserTestModule.saveInRepository +import com.info.maeumgagym.domain.user.entity.UserJpaEntity +import com.info.maeumgagym.domain.user.mapper.UserMapper import com.info.maeumgagym.domain.user.repository.UserRepository +import com.info.maeumgagym.routine.exception.OtherRoutineAlreadyUsingAtDayOfWeekException import com.info.maeumgagym.routine.port.`in`.CreateRoutineUseCase import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach @@ -53,4 +57,40 @@ internal class CreateRoutineServiceTests @Autowired constructor( // ) // } // } + + /** + * @see CreateRoutineUseCase.createRoutine + * @when 실패 상황 : 이미 다른 루틴이 할당된 요일로 새로운 루틴을 생성하려 함 + * @fail 정상적으로 루틴이 저장되는지 확인 + * @fail 아래의 함수가 정상 작동하는지 확인 + * @see RoutineNativeRepository.findByUserIdAndDayOfWeekAndIsArchivedFalse + */ + @Test + fun createRoutineWithDayOfWeeksAlreadyOtherRoutineUsingAt() { + RoutineTestModule.createTestRoutine(user.id!!).saveInRepository(routineRepository) + + Assertions.assertThrows(OtherRoutineAlreadyUsingAtDayOfWeekException::class.java) { + createRoutineUseCase.createRoutine( + RoutineTestModule.getCreateRoutineRequest() + ) + } + } + + /** + * @see CreateRoutineUseCase.createRoutine + * @when 실패 상황 : 이미 다른 루틴이 할당된 요일로 새로운 루틴을 생성하려 함 + * @fail 정상적으로 루틴이 저장되는지 확인 + * @fail 아래의 함수가 정상 작동하는지 확인 + * @see RoutineNativeRepository.findByUserIdAndDayOfWeekAndIsArchivedFalse + */ + @Test + fun createRoutineWithDayOfWeeksAlreadyOtherRoutineUsingAtButArchived() { + RoutineTestModule.createTestRoutine(user.id!!).setArchived(true).saveInRepository(routineRepository) + + Assertions.assertDoesNotThrow { + createRoutineUseCase.createRoutine( + RoutineTestModule.getCreateRoutineRequest() + ) + } + } } diff --git a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/ReadRoutineServiceTests.kt b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/ReadRoutineServiceTests.kt index f9ef3f379..701ba6f99 100644 --- a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/ReadRoutineServiceTests.kt +++ b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/ReadRoutineServiceTests.kt @@ -1,26 +1,31 @@ package com.info.maeumgagym.domain.routine import com.info.maeumgagym.domain.auth.AuthTestModule.saveInContext +import com.info.maeumgagym.domain.routine.RoutineTestModule.appendDayOfWeek +import com.info.maeumgagym.domain.routine.RoutineTestModule.deleteDayOfWeek import com.info.maeumgagym.domain.routine.RoutineTestModule.saveInRepository import com.info.maeumgagym.domain.routine.repository.RoutineRepository -import com.info.maeumgagym.domain.user.entity.UserJpaEntity -import com.info.maeumgagym.domain.user.mapper.UserMapper import com.info.maeumgagym.domain.user.UserTestModule import com.info.maeumgagym.domain.user.UserTestModule.saveInRepository +import com.info.maeumgagym.domain.user.entity.UserJpaEntity +import com.info.maeumgagym.domain.user.mapper.UserMapper import com.info.maeumgagym.domain.user.repository.UserRepository import com.info.maeumgagym.routine.port.`in`.ReadAllMyRoutineUseCase +import com.info.maeumgagym.routine.port.`in`.ReadTodayRoutineUseCase import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate import kotlin.random.Random @Transactional @SpringBootTest internal class ReadRoutineServiceTests @Autowired constructor( private val readMyAllRoutineUseCase: ReadAllMyRoutineUseCase, + private val readTodayRoutineUseCase: ReadTodayRoutineUseCase, private val routineRepository: RoutineRepository, private val userRepository: UserRepository, private val userMapper: UserMapper @@ -60,4 +65,24 @@ internal class ReadRoutineServiceTests @Autowired constructor( fun readNotSharedRoutine() { TODO("루틴 단일 조회 기능 구현 후 생성") } + + @Test + fun readTodayRoutine() { + RoutineTestModule.createTestRoutine(user.id!!).appendDayOfWeek(LocalDate.now().dayOfWeek) + .saveInRepository(routineRepository) + + Assertions.assertNotNull( + readTodayRoutineUseCase.readTodayRoutine() + ) + } + + @Test + fun readTodayRoutineButNotExists() { + RoutineTestModule.createTestRoutine(user.id!!).deleteDayOfWeek(LocalDate.now().dayOfWeek) + .saveInRepository(routineRepository) + + Assertions.assertNull( + readTodayRoutineUseCase.readTodayRoutine() + ) + } } diff --git a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/RoutineTestModule.kt b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/RoutineTestModule.kt index 2368c7d08..09dacfb71 100644 --- a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/RoutineTestModule.kt +++ b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/RoutineTestModule.kt @@ -51,8 +51,8 @@ internal object RoutineTestModule { fun getUpdateRoutineRequest(originRoutineEntity: RoutineJpaEntity): UpdateRoutineRequest = UpdateRoutineRequest( routineName = originRoutineEntity.routineName, - isArchived = !originRoutineEntity.routineStatus.isArchived, - isShared = !originRoutineEntity.routineStatus.isShared, + isArchived = originRoutineEntity.routineStatus.isArchived, + isShared = originRoutineEntity.routineStatus.isShared, exerciseInfoModelList = originRoutineEntity.exerciseInfoList.map { ExerciseInfoModel( it.exerciseName, @@ -60,12 +60,23 @@ internal object RoutineTestModule { it.sets ) }.toMutableList(), - dayOfWeeks = originRoutineEntity.dayOfWeeks + dayOfWeeks = mutableSetOf(DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, DayOfWeek.FRIDAY, DayOfWeek.SATURDAY) ) fun RoutineJpaEntity.saveInRepository(routineRepository: RoutineRepository): RoutineJpaEntity = routineRepository.save(this) + fun RoutineJpaEntity.setArchived(boolean: Boolean): RoutineJpaEntity = + RoutineJpaEntity( + routineName = routineName, + exerciseInfoList = exerciseInfoList, + dayOfWeeks = dayOfWeeks, + routineStatus = RoutineStatus(boolean, routineStatus.isShared), + createdAt = createdAt, + id = id, + userId = userId + ) + fun RoutineJpaEntity.setShared(boolean: Boolean): RoutineJpaEntity = RoutineJpaEntity( routineName = routineName, @@ -76,4 +87,26 @@ internal object RoutineTestModule { id = id, userId = userId ) + + fun RoutineJpaEntity.appendDayOfWeek(dayOfWeek: DayOfWeek): RoutineJpaEntity = + RoutineJpaEntity( + routineName = routineName, + exerciseInfoList = exerciseInfoList, + dayOfWeeks = dayOfWeeks?.apply { add(dayOfWeek) } ?: mutableSetOf(dayOfWeek), + routineStatus = routineStatus, + createdAt = createdAt, + id = id, + userId = userId + ) + + fun RoutineJpaEntity.deleteDayOfWeek(dayOfWeek: DayOfWeek): RoutineJpaEntity = + RoutineJpaEntity( + routineName = routineName, + exerciseInfoList = exerciseInfoList, + dayOfWeeks = dayOfWeeks?.apply { remove(dayOfWeek) }, + routineStatus = routineStatus, + createdAt = createdAt, + id = id, + userId = userId + ) } diff --git a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/UpdateRoutineServiceTests.kt b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/UpdateRoutineServiceTests.kt index 5e72f4d6b..e0c9b3da3 100644 --- a/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/UpdateRoutineServiceTests.kt +++ b/maeumgagym-infrastructure/src/test/kotlin/com/info/maeumgagym/domain/routine/UpdateRoutineServiceTests.kt @@ -2,14 +2,17 @@ package com.info.maeumgagym.domain.routine import com.info.maeumgagym.auth.exception.PermissionDeniedException import com.info.maeumgagym.domain.auth.AuthTestModule.saveInContext -import com.info.maeumgagym.domain.routine.entity.RoutineJpaEntity import com.info.maeumgagym.domain.routine.RoutineTestModule.saveInRepository +import com.info.maeumgagym.domain.routine.RoutineTestModule.setArchived +import com.info.maeumgagym.domain.routine.entity.RoutineJpaEntity +import com.info.maeumgagym.domain.routine.repository.RoutineNativeRepository import com.info.maeumgagym.domain.routine.repository.RoutineRepository -import com.info.maeumgagym.domain.user.entity.UserJpaEntity -import com.info.maeumgagym.domain.user.mapper.UserMapper import com.info.maeumgagym.domain.user.UserTestModule import com.info.maeumgagym.domain.user.UserTestModule.saveInRepository +import com.info.maeumgagym.domain.user.entity.UserJpaEntity +import com.info.maeumgagym.domain.user.mapper.UserMapper import com.info.maeumgagym.domain.user.repository.UserRepository +import com.info.maeumgagym.routine.exception.OtherRoutineAlreadyUsingAtDayOfWeekException import com.info.maeumgagym.routine.exception.RoutineNotFoundException import com.info.maeumgagym.routine.port.`in`.UpdateRoutineUseCase import org.junit.jupiter.api.Assertions @@ -68,4 +71,60 @@ internal class UpdateRoutineServiceTests @Autowired constructor( ) } } + + /** + * @see UpdateRoutineUseCase.updateRoutine + * @when 실패 상황 : 이미 다른 루틴이 할당된 요일로 루틴의 정보를 수정하려함 + * @fail 정상적으로 수정한 루틴이 저장되는지 확인 + * @fail 정상적으로 루틴이 저장되는지 확인 + * @fail 아래의 함수가 정상 작동하는지 확인 + * @see RoutineNativeRepository.findByUserIdAndDayOfWeekAndIsArchivedFalse + */ + @Test + fun updateRoutineWithDayOfWeeksAlreadyOtherRoutineUsingAt() { + // 기본 루틴을 수요일, 금요일, 토요일에 할당 + updateRoutineUseCase.updateRoutine( + RoutineTestModule.getUpdateRoutineRequest(routine), + routine.id!! + ) + // 새로운 루틴 생성 (기본 루틴과 요일이 겹치지 않음) + val otherRoutine = RoutineTestModule.createTestRoutine(user.id!!).saveInRepository(routineRepository) + + // 새로운 루틴을 기본 루틴의 요일과 똑같이 변경 -> 예외 발생해야 함 + Assertions.assertThrows(OtherRoutineAlreadyUsingAtDayOfWeekException::class.java) { + updateRoutineUseCase.updateRoutine( + RoutineTestModule.getUpdateRoutineRequest(otherRoutine), + otherRoutine.id!! + ) + } + } + + /** + * @see UpdateRoutineUseCase.updateRoutine + * @when 성공 상황 : 이미 다른 루틴이 할당된 요일로 루틴의 정보를 수정하려 했으나, 그 루틴은 보관 상태 + * @fail 정상적으로 수정한 루틴이 저장되는지 확인 + * @fail 정상적으로 루틴이 저장되는지 확인 + * @fail 아래의 함수가 정상 작동하는지 확인 + * @see RoutineNativeRepository.findByUserIdAndDayOfWeekAndIsArchivedFalse + */ + @Test + fun updateRoutineWithDayOfWeeksAlreadyOtherRoutineUsingAtButArchived() { + // 기본 루틴을 수요일, 금요일, 토요일에 할당 + updateRoutineUseCase.updateRoutine( + RoutineTestModule.getUpdateRoutineRequest(routine), + routine.id!! + ) + // 루틴 보관 + routine.setArchived(true).saveInRepository(routineRepository) + // 새로운 루틴 생성 (기본 루틴과 요일이 겹치지 않음) + val otherRoutine = RoutineTestModule.createTestRoutine(user.id!!).saveInRepository(routineRepository) + + // 새로운 루틴을 기본 루틴의 요일과 똑같이 변경 -> 예외 발생해야 함 + Assertions.assertDoesNotThrow { + updateRoutineUseCase.updateRoutine( + RoutineTestModule.getUpdateRoutineRequest(otherRoutine), + otherRoutine.id!! + ) + } + } } diff --git a/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/public/PublicController.kt b/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/public/PublicController.kt index ef8472adf..913fc617c 100644 --- a/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/public/PublicController.kt +++ b/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/public/PublicController.kt @@ -2,6 +2,9 @@ package com.info.maeumgagym.controller.public import com.info.common.WebAdapter import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.headers.Header +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.HttpStatus import org.springframework.validation.annotation.Validated @@ -16,6 +19,16 @@ import org.springframework.web.bind.annotation.ResponseStatus class PublicController { @Operation(summary = "CSRF Token 발급 받기") + @ApiResponse( + responseCode = "200", + headers = [ + Header( + name = "Set-Cookie", + description = "CSRF Token", + schema = Schema(type = "string", example = "XSRF-TOKEN=...; Secure; HttpOnly; SameSite=Strict") + ) + ] + ) @GetMapping("/csrf") @ResponseStatus(HttpStatus.OK) fun getCSRFToken() { diff --git a/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/routine/RoutineController.kt b/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/routine/RoutineController.kt index 2271ed5bc..28cc4b549 100644 --- a/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/routine/RoutineController.kt +++ b/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/routine/RoutineController.kt @@ -4,15 +4,14 @@ import com.info.common.WebAdapter import com.info.maeumgagym.controller.routine.dto.CreateRoutineWebRequest import com.info.maeumgagym.controller.routine.dto.UpdateRoutineWebRequest import com.info.maeumgagym.routine.dto.response.RoutineListResponse -import com.info.maeumgagym.routine.port.`in`.CreateRoutineUseCase -import com.info.maeumgagym.routine.port.`in`.DeleteRoutineUseCase -import com.info.maeumgagym.routine.port.`in`.ReadAllMyRoutineUseCase -import com.info.maeumgagym.routine.port.`in`.UpdateRoutineUseCase +import com.info.maeumgagym.routine.dto.response.RoutineResponse +import com.info.maeumgagym.routine.port.`in`.* import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.HttpStatus import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.* +import javax.servlet.http.HttpServletResponse import javax.validation.Valid import javax.validation.constraints.Positive @@ -21,8 +20,9 @@ import javax.validation.constraints.Positive @WebAdapter @Validated class RoutineController( - private val createRoutineUseCase: CreateRoutineUseCase, + private val readTodayRoutineUseCase: ReadTodayRoutineUseCase, private val readAllMyRoutineUseCase: ReadAllMyRoutineUseCase, + private val createRoutineUseCase: CreateRoutineUseCase, private val deleteRoutineUseCase: DeleteRoutineUseCase, private val updateRoutineUseCase: UpdateRoutineUseCase ) { @@ -36,6 +36,13 @@ class RoutineController( createRoutineUseCase.createRoutine(req.toRequest()) } + @Operation(summary = "오늘의 루틴 조회 API") + @GetMapping("/today") + fun readTodayRoutine(httpServletResponse: HttpServletResponse): RoutineResponse? = + readTodayRoutineUseCase.readTodayRoutine().apply { + if (this == null) httpServletResponse.status = 204 + } + @Operation(summary = "내 루틴 전체 조회 API") @GetMapping("/me/all") fun readAllMyRoutine(): RoutineListResponse = readAllMyRoutineUseCase.readAllMyRoutine() @@ -47,7 +54,9 @@ class RoutineController( @Valid @Positive(message = "0보다 커야 합니다.") id: Long - ) { deleteRoutineUseCase.deleteRoutine(id) } + ) { + deleteRoutineUseCase.deleteRoutine(id) + } @Operation(summary = "루틴 수정 API") @PutMapping("/{id}") @@ -58,5 +67,7 @@ class RoutineController( id: Long, @RequestBody @Valid req: UpdateRoutineWebRequest - ) { updateRoutineUseCase.updateRoutine(req.toRequest(), id) } + ) { + updateRoutineUseCase.updateRoutine(req.toRequest(), id) + } } diff --git a/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/step/StepController.kt b/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/step/StepController.kt new file mode 100644 index 000000000..c768d4183 --- /dev/null +++ b/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/controller/step/StepController.kt @@ -0,0 +1,33 @@ +package com.info.maeumgagym.controller.step + +import com.info.common.WebAdapter +import com.info.maeumgagym.step.port.`in`.CreateStepUseCase +import com.info.maeumgagym.step.port.`in`.UpdateStepUseCase +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam + +@Tag(name = "Step API") +@Validated +@WebAdapter +@RequestMapping("/step") +class StepController( + private val createStepUseCase: CreateStepUseCase, + private val updateStepUseCase: UpdateStepUseCase +) { + + @Operation(summary = "걸음수 생성 API") + @PostMapping + fun createStep() = createStepUseCase.createStep() + + @Operation(summary = "걸음수 수정 API") + @PutMapping + fun updateStep( + @RequestParam(name = "number_of_steps") + numberOfSteps: Int + ) = updateStepUseCase.updateStep(numberOfSteps) +}