diff --git a/build.gradle.kts b/build.gradle.kts index d1d7631..cd82c0b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,6 +52,7 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") /* swagger */ implementation("io.springfox:springfox-swagger-ui:3.0.0") diff --git a/src/main/kotlin/com/project/goms/domain/account/entity/Authority.kt b/src/main/kotlin/com/project/goms/domain/account/entity/Authority.kt index 3abbaa6..238fad0 100644 --- a/src/main/kotlin/com/project/goms/domain/account/entity/Authority.kt +++ b/src/main/kotlin/com/project/goms/domain/account/entity/Authority.kt @@ -1,8 +1,6 @@ package com.project.goms.domain.account.entity enum class Authority{ - ROLE_STUDENT, ROLE_STUDENT_COUNCIL; - } diff --git a/src/main/kotlin/com/project/goms/domain/outing/common/exception/PublicHolidayException.kt b/src/main/kotlin/com/project/goms/domain/outing/common/exception/PublicHolidayException.kt new file mode 100644 index 0000000..118097f --- /dev/null +++ b/src/main/kotlin/com/project/goms/domain/outing/common/exception/PublicHolidayException.kt @@ -0,0 +1,6 @@ +package com.project.goms.domain.outing.common.exception + +import com.project.goms.global.error.ErrorCode +import com.project.goms.global.error.exception.GomsException + +class PublicHolidayException : GomsException(ErrorCode.PUBLIC_HOLIDAY_EXCEPTION) \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/outing/entity/Outing.kt b/src/main/kotlin/com/project/goms/domain/outing/entity/Outing.kt index 5f9a3d0..151f543 100644 --- a/src/main/kotlin/com/project/goms/domain/outing/entity/Outing.kt +++ b/src/main/kotlin/com/project/goms/domain/outing/entity/Outing.kt @@ -15,5 +15,5 @@ class Outing( @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "account_idx") - val account: Account, + val account: Account ): BaseIdxEntity(idx) \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/outing/entity/OutingStatus.kt b/src/main/kotlin/com/project/goms/domain/outing/entity/OutingStatus.kt new file mode 100644 index 0000000..8a6d888 --- /dev/null +++ b/src/main/kotlin/com/project/goms/domain/outing/entity/OutingStatus.kt @@ -0,0 +1,7 @@ +package com.project.goms.domain.outing.entity + +enum class OutingStatus { + AVAILABLE, + UNAVAILABLE, + PUBLIC_HOLIDAY +} \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/outing/scheduler/OutingScheduler.kt b/src/main/kotlin/com/project/goms/domain/outing/scheduler/OutingScheduler.kt index 47bcb39..4628cd4 100644 --- a/src/main/kotlin/com/project/goms/domain/outing/scheduler/OutingScheduler.kt +++ b/src/main/kotlin/com/project/goms/domain/outing/scheduler/OutingScheduler.kt @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component @Component class OutingScheduler( private val reminderOutingUseCase: ReminderOutingUseCase, - private val saveRateStudentUseCase: SaveLateAccountUseCase, + private val saveLateStudentUseCase: SaveLateAccountUseCase, private val deleteOutingStudentsUseCase: DeleteOutingStudentsUseCase, ) { @@ -17,7 +17,7 @@ class OutingScheduler( fun sendOutingMessage() = reminderOutingUseCase.execute() @Scheduled(cron = "0 30 7 ? * 3") // 매주 수요일 7시 30분에 지각자를 저장한다. - fun checkRateStudent() = saveRateStudentUseCase.execute() + fun checkLateStudent() = saveLateStudentUseCase.execute() @Scheduled(cron = "0 50 7 ? * 3") // 매주 수요일 7시 50분에 외출자를 삭제한다. fun deleteOutingStudents() = deleteOutingStudentsUseCase.execute() diff --git a/src/main/kotlin/com/project/goms/domain/studentCouncil/common/property/OutingStatusExpTimeProperties.kt b/src/main/kotlin/com/project/goms/domain/studentCouncil/common/property/OutingStatusExpTimeProperties.kt new file mode 100644 index 0000000..855e12e --- /dev/null +++ b/src/main/kotlin/com/project/goms/domain/studentCouncil/common/property/OutingStatusExpTimeProperties.kt @@ -0,0 +1,10 @@ +package com.project.goms.domain.studentCouncil.common.property + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.ConstructorBinding + +@ConstructorBinding +@ConfigurationProperties("outing.status") +class OutingStatusExpTimeProperties ( + val expiredAt : Int +) \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/studentCouncil/common/util/StudentCouncilConverter.kt b/src/main/kotlin/com/project/goms/domain/studentCouncil/common/util/StudentCouncilConverter.kt index 1fde942..aab0d0c 100644 --- a/src/main/kotlin/com/project/goms/domain/studentCouncil/common/util/StudentCouncilConverter.kt +++ b/src/main/kotlin/com/project/goms/domain/studentCouncil/common/util/StudentCouncilConverter.kt @@ -1,13 +1,16 @@ package com.project.goms.domain.studentCouncil.common.util import com.project.goms.domain.studentCouncil.presentation.data.request.GrantAuthorityRequest +import com.project.goms.domain.studentCouncil.presentation.data.request.OutingBanRequest import com.project.goms.domain.studentCouncil.presentation.data.response.AllAccountResponse import com.project.goms.domain.studentCouncil.usecase.dto.AllAccountDto import com.project.goms.domain.studentCouncil.usecase.dto.GrantAuthorityDto +import com.project.goms.domain.studentCouncil.usecase.dto.OutingBanDto interface StudentCouncilConverter { fun toDto(request: GrantAuthorityRequest): GrantAuthorityDto + fun toDto(request: OutingBanRequest): OutingBanDto fun toResponse(dto: List): List } \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/studentCouncil/common/util/impl/StudentCouncilConverterImpl.kt b/src/main/kotlin/com/project/goms/domain/studentCouncil/common/util/impl/StudentCouncilConverterImpl.kt index 9f2d5f1..0612bb9 100644 --- a/src/main/kotlin/com/project/goms/domain/studentCouncil/common/util/impl/StudentCouncilConverterImpl.kt +++ b/src/main/kotlin/com/project/goms/domain/studentCouncil/common/util/impl/StudentCouncilConverterImpl.kt @@ -3,9 +3,11 @@ package com.project.goms.domain.studentCouncil.common.util.impl import com.project.goms.domain.account.presentation.data.response.StudentNumResponse import com.project.goms.domain.studentCouncil.common.util.StudentCouncilConverter import com.project.goms.domain.studentCouncil.presentation.data.request.GrantAuthorityRequest +import com.project.goms.domain.studentCouncil.presentation.data.request.OutingBanRequest import com.project.goms.domain.studentCouncil.presentation.data.response.AllAccountResponse import com.project.goms.domain.studentCouncil.usecase.dto.AllAccountDto import com.project.goms.domain.studentCouncil.usecase.dto.GrantAuthorityDto +import com.project.goms.domain.studentCouncil.usecase.dto.OutingBanDto import org.springframework.stereotype.Component @Component @@ -14,6 +16,9 @@ class StudentCouncilConverterImpl: StudentCouncilConverter { override fun toDto(request: GrantAuthorityRequest): GrantAuthorityDto = GrantAuthorityDto(accountIdx = request.accountIdx, authority = request.authority) + override fun toDto(request: OutingBanRequest): OutingBanDto = + OutingBanDto(status = request.status) + override fun toResponse(dto: List): List = dto.map { AllAccountResponse( diff --git a/src/main/kotlin/com/project/goms/domain/studentCouncil/entity/OutingStatus.kt b/src/main/kotlin/com/project/goms/domain/studentCouncil/entity/OutingStatus.kt new file mode 100644 index 0000000..966efab --- /dev/null +++ b/src/main/kotlin/com/project/goms/domain/studentCouncil/entity/OutingStatus.kt @@ -0,0 +1,16 @@ +package com.project.goms.domain.studentCouncil.entity + +import org.springframework.data.annotation.Id +import org.springframework.data.redis.core.RedisHash +import org.springframework.data.redis.core.TimeToLive +import java.util.* +import java.util.concurrent.TimeUnit + +@RedisHash("outing_status") +data class OutingStatus ( + @Id + val outingStatusUUID: UUID, + + @TimeToLive(unit = TimeUnit.SECONDS) + val expiredAt: Int +) \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/studentCouncil/entity/repository/OutingStatusRepository.kt b/src/main/kotlin/com/project/goms/domain/studentCouncil/entity/repository/OutingStatusRepository.kt new file mode 100644 index 0000000..c912a5d --- /dev/null +++ b/src/main/kotlin/com/project/goms/domain/studentCouncil/entity/repository/OutingStatusRepository.kt @@ -0,0 +1,7 @@ +package com.project.goms.domain.studentCouncil.entity.repository + +import com.project.goms.domain.studentCouncil.entity.OutingStatus +import org.springframework.data.repository.CrudRepository +import java.util.UUID + +interface OutingStatusRepository: CrudRepository \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/studentCouncil/presentation/StudentCouncilController.kt b/src/main/kotlin/com/project/goms/domain/studentCouncil/presentation/StudentCouncilController.kt index 149fe56..10078ec 100644 --- a/src/main/kotlin/com/project/goms/domain/studentCouncil/presentation/StudentCouncilController.kt +++ b/src/main/kotlin/com/project/goms/domain/studentCouncil/presentation/StudentCouncilController.kt @@ -3,6 +3,7 @@ package com.project.goms.domain.studentCouncil.presentation import com.project.goms.domain.account.entity.Authority import com.project.goms.domain.studentCouncil.common.util.StudentCouncilConverter import com.project.goms.domain.studentCouncil.presentation.data.request.GrantAuthorityRequest +import com.project.goms.domain.studentCouncil.presentation.data.request.OutingBanRequest import com.project.goms.domain.studentCouncil.presentation.data.response.AllAccountResponse import com.project.goms.domain.studentCouncil.usecase.* import org.springframework.http.HttpStatus @@ -20,6 +21,7 @@ class StudentCouncilController( private val saveOutingBlackListUseCase: SaveOutingBlackListUseCase, private val deleteOutingBlackListUseCase: DeleteOutingBlackListUseCase, private val searchAccountUseCase: SearchAccountUseCase, + private val outingBanUseCase: OutingBanUseCase ) { @PostMapping("outing") @@ -61,4 +63,10 @@ class StudentCouncilController( .let { studentCouncilConverter.toResponse(it) } .let { ResponseEntity.ok(it) } + @PostMapping("/outing/ban") + fun outingBan(@RequestBody outingBanRequest: OutingBanRequest): ResponseEntity = + studentCouncilConverter.toDto(outingBanRequest) + .let { outingBanUseCase.execute(it) } + .let { ResponseEntity.status(HttpStatus.RESET_CONTENT).build() } + } \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/studentCouncil/presentation/data/request/OutingBanRequest.kt b/src/main/kotlin/com/project/goms/domain/studentCouncil/presentation/data/request/OutingBanRequest.kt new file mode 100644 index 0000000..f2faef0 --- /dev/null +++ b/src/main/kotlin/com/project/goms/domain/studentCouncil/presentation/data/request/OutingBanRequest.kt @@ -0,0 +1,8 @@ +package com.project.goms.domain.studentCouncil.presentation.data.request + +import org.jetbrains.annotations.NotNull + +data class OutingBanRequest( + @field:NotNull + val status: Boolean +) \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/studentCouncil/usecase/OutingBanUseCase.kt b/src/main/kotlin/com/project/goms/domain/studentCouncil/usecase/OutingBanUseCase.kt new file mode 100644 index 0000000..3ba803e --- /dev/null +++ b/src/main/kotlin/com/project/goms/domain/studentCouncil/usecase/OutingBanUseCase.kt @@ -0,0 +1,37 @@ +package com.project.goms.domain.studentCouncil.usecase + +import com.project.goms.domain.studentCouncil.common.property.OutingStatusExpTimeProperties +import com.project.goms.domain.studentCouncil.entity.OutingStatus +import com.project.goms.domain.studentCouncil.entity.repository.OutingStatusRepository +import com.project.goms.domain.studentCouncil.usecase.dto.OutingBanDto +import com.project.goms.global.annotation.UseCaseWithTransaction +import mu.KotlinLogging +import java.util.* + +private val log = KotlinLogging.logger {} + +@UseCaseWithTransaction +class OutingBanUseCase( + private val outingStatusRepository: OutingStatusRepository, + private val outingStatusExpTimeProperties: OutingStatusExpTimeProperties +) { + + fun execute(outingBanDto: OutingBanDto) { + when (outingBanDto.status) { + true -> { + val outingStatus = OutingStatus( + outingStatusUUID = UUID.randomUUID(), + expiredAt = outingStatusExpTimeProperties.expiredAt + ) + log.info("이번주는 외출 불가능 상태 입니다.") + outingStatusRepository.save(outingStatus) + } + + false -> { + log.info("이번주는 외출 가능한 상태입니다.") + outingStatusRepository.deleteAll() + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/domain/studentCouncil/usecase/dto/OutingBanDto.kt b/src/main/kotlin/com/project/goms/domain/studentCouncil/usecase/dto/OutingBanDto.kt new file mode 100644 index 0000000..b47aa07 --- /dev/null +++ b/src/main/kotlin/com/project/goms/domain/studentCouncil/usecase/dto/OutingBanDto.kt @@ -0,0 +1,5 @@ +package com.project.goms.domain.studentCouncil.usecase.dto + +data class OutingBanDto( + val status : Boolean +) \ No newline at end of file diff --git a/src/main/kotlin/com/project/goms/global/error/ErrorCode.kt b/src/main/kotlin/com/project/goms/global/error/ErrorCode.kt index 6a78766..b5b5323 100644 --- a/src/main/kotlin/com/project/goms/global/error/ErrorCode.kt +++ b/src/main/kotlin/com/project/goms/global/error/ErrorCode.kt @@ -20,6 +20,7 @@ enum class ErrorCode( // OUTING BLACKLIST_NOT_ALLOW_OUTING("블랙리스트인 학생은 외출을 할 수 없습니다.", HttpStatus.BAD_REQUEST), OUTING_UUID_UNVERIFIED("검증되지 않은 외출 식별자 입니다.", HttpStatus.BAD_REQUEST), + PUBLIC_HOLIDAY_EXCEPTION("오늘은 공휴일 외출하지 않는 날 입니다.", HttpStatus.BAD_REQUEST), // FEIGN FEIGN_BAD_REQUEST("FEIGN Bad Request", HttpStatus.BAD_REQUEST), diff --git a/src/main/kotlin/com/project/goms/global/security/SecurityConfig.kt b/src/main/kotlin/com/project/goms/global/security/SecurityConfig.kt index 2ca7b33..332cede 100644 --- a/src/main/kotlin/com/project/goms/global/security/SecurityConfig.kt +++ b/src/main/kotlin/com/project/goms/global/security/SecurityConfig.kt @@ -49,6 +49,7 @@ class SecurityConfig( .mvcMatchers(HttpMethod.POST, "/api/v1/student-council/black-list/{accountIdx}").hasAnyAuthority(Authority.ROLE_STUDENT_COUNCIL.name) .mvcMatchers(HttpMethod.DELETE, "/api/v1/student-council/black-list/{accountIdx}").hasAnyAuthority(Authority.ROLE_STUDENT_COUNCIL.name) .mvcMatchers(HttpMethod.GET, "/api/v1/student-council/search").hasAnyAuthority(Authority.ROLE_STUDENT_COUNCIL.name) + .mvcMatchers(HttpMethod.POST, "/api/v1/student-council/ban").hasAnyAuthority(Authority.ROLE_STUDENT_COUNCIL.name) // /health .mvcMatchers(HttpMethod.GET, "/").permitAll() diff --git a/src/main/kotlin/com/project/goms/infrastructure/discord/usecase/SendDiscordUseCase.kt b/src/main/kotlin/com/project/goms/infrastructure/discord/usecase/SendDiscordUseCase.kt index a2d7419..8ccb6ff 100644 --- a/src/main/kotlin/com/project/goms/infrastructure/discord/usecase/SendDiscordUseCase.kt +++ b/src/main/kotlin/com/project/goms/infrastructure/discord/usecase/SendDiscordUseCase.kt @@ -1,7 +1,10 @@ package com.project.goms.infrastructure.discord.usecase import com.project.goms.domain.late.entity.repository.LateRepository +import com.project.goms.domain.outing.common.exception.PublicHolidayException +import com.project.goms.domain.outing.entity.OutingStatus import com.project.goms.domain.outing.usecase.SendMessageUseCase +import com.project.goms.domain.studentCouncil.entity.repository.OutingStatusRepository import com.project.goms.infrastructure.discord.usecase.dto.DiscordDto import com.project.goms.infrastructure.feign.client.DiscordFeignClient import mu.KotlinLogging @@ -13,17 +16,23 @@ private val log = KotlinLogging.logger {} @Component class SendDiscordUseCase( private val discordFeignClient: DiscordFeignClient, - private val lateRepository: LateRepository -): SendMessageUseCase { + private val lateRepository: LateRepository, + private val outingStatusRepository: OutingStatusRepository +) : SendMessageUseCase { override fun sendMessage() { var content = "" + var outingStatus: OutingStatus = OutingStatus.AVAILABLE val lateOneWeekAgoCount = lateRepository.lateCountOntWeekAgo(LocalDate.now().minusWeeks(1)) log.info { "one week ago late count is $lateOneWeekAgoCount" } - when (lateOneWeekAgoCount < 3) { - true -> { + if (lateOneWeekAgoCount > 2) outingStatus = OutingStatus.UNAVAILABLE + if (outingStatusRepository.count().toInt() != 0) outingStatus = OutingStatus.UNAVAILABLE + + log.info { outingStatus } + when (outingStatus) { + OutingStatus.AVAILABLE -> { content = "@everyone \n" content += "\uD83D\uDCE2 오늘은 수요일 입니다. \uD83D\uDCE2 \n" content += "> 금일 수요외출제를 통해 외출을 할 학생들은 반드시 저녁을 먹고 7시부터 자유롭게 외출 해주시고, \n" @@ -32,12 +41,16 @@ class SendDiscordUseCase( content += "> 외출 시 꼭 운동화 착용 부탁드립니다!\n" } - false -> { + OutingStatus.UNAVAILABLE -> { content = "@everyone \n" content += "\uD83D\uDCE2 오늘은 수요일 입니다. \uD83D\uDCE2 \n" content += "> 저번주 외출제 지각생이 ${lateOneWeekAgoCount}명이여서 외출제는 진행하지 않습니다. \n" content += "> 오늘 외출하다 걸릴시 무단 외출입니다. \n" } + + OutingStatus.PUBLIC_HOLIDAY -> { + throw PublicHolidayException() + } } discordFeignClient.sendDiscord(DiscordDto(content = content)) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f6e1adc..4e081df 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -37,6 +37,9 @@ gauth: outing: expiredAt: ${OUTING_EXP} + status: + expiredAt: ${OUTING_STATUS_EXP} + discord: webhookUrl: ${DISCORD_WEBHOOK_URL}