Skip to content

Commit

Permalink
chore: 테스트용 토큰 발급 API 구현 (#46)
Browse files Browse the repository at this point in the history
* feat: 백신 접종내역 생성

* feat: unclassified Vaccination Discord Webhook

* chore: 테스트용 토큰 발급 API 구현 (#45)

---------

Co-authored-by: Haebin <[email protected]>
  • Loading branch information
whereami2048 and h-beeen authored Jan 6, 2025
1 parent 56a27af commit 96749e9
Show file tree
Hide file tree
Showing 26 changed files with 321 additions and 19 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")

annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")

// discord
implementation("dev.kord:kord-core:0.15.0")
}

kotlin {
Expand Down
Binary file removed dump.rdb
Binary file not shown.
43 changes: 43 additions & 0 deletions src/main/kotlin/kr/co/vacgom/api/TestController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package kr.co.vacgom.api

import kr.co.vacgom.api.auth.oauth.enums.SocialLoginProvider
import kr.co.vacgom.api.global.presentation.GlobalPath.BASE_V3
import kr.co.vacgom.api.user.application.UserTokenService
import kr.co.vacgom.api.user.repository.UserRepository
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import kotlin.random.Random

@RestController
@RequestMapping(BASE_V3 + "/TEST")
class TestController(
private val userRepository: UserRepository,
private val userTokenService: UserTokenService
) {

@PostMapping
fun test(@RequestParam tokenType: String): ResponseEntity<String> {
val user = userRepository.findAll()[0]

when (tokenType) {
"ACCESS" -> {
val accessToken = userTokenService.createAccessToken(user.id, user.role)
return ResponseEntity.ok(accessToken)
}
"REFRESH" -> {
val refreshToken = userTokenService.createRefreshToken(user.id)
return ResponseEntity.ok(refreshToken)
}
"REGISTER" -> {
val socialId = "testSocialId${Random(System.currentTimeMillis()).nextInt()}"
val registerToken = userTokenService.createRegisterToken(socialId, SocialLoginProvider.KAKAO.name)
return ResponseEntity.ok(registerToken)
}
}

return ResponseEntity.internalServerError().build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ interface BabyRepository {
fun saveAll(babies: List<Baby>): List<Baby>
fun findBabiesById(ids: List<UUID>): List<Baby>
fun findById(id: UUID): Baby
fun findAll(): List<Baby>
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository
import java.util.*

@Repository
class BabyRepositoryAdapter(private val babyJpaRepository: BabyJpaRepository): BabyRepository {
class BabyRepositoryAdapter(private val babyJpaRepository: BabyJpaRepository) : BabyRepository {
override fun save(newBaby: Baby): Baby {
return babyJpaRepository.save(newBaby)
}
Expand All @@ -20,8 +20,12 @@ class BabyRepositoryAdapter(private val babyJpaRepository: BabyJpaRepository): B
override fun findBabiesById(ids: List<UUID>): List<Baby> {
return babyJpaRepository.findAllById(ids)
}

override fun findById(id: UUID): Baby {
return babyJpaRepository.findByIdOrNull(id) ?: throw BusinessException(BabyError.BABY_NOT_FOUND)
}

override fun findAll(): List<Baby> {
return babyJpaRepository.findAll()
}
}
65 changes: 65 additions & 0 deletions src/main/kotlin/kr/co/vacgom/api/global/discord/DiscordSender.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package kr.co.vacgom.api.global.discord

import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.vacgom.api.baby.domain.Baby
import kr.co.vacgom.api.vaccine.domain.UnclassifiedVaccination
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.client.RestTemplate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@Component
class DiscordSender(
@Value("\${discord.deployWebhookURL}")
private val webhookURL: String,

@Value("\${discord.welcomeWebhookURL}")
private val welcomeURL: String,

private val objectMapper: ObjectMapper,

private val restTemplate: RestTemplate
) {
fun sendVaccinationError(
baby: Baby,
unclassifiedVaccination: UnclassifiedVaccination
) {
val headers = HttpHeaders()
headers.contentType = MediaType.APPLICATION_JSON

val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH시 mm분 ss초")
val formattedNow = now.format(formatter)

val embeds = mapOf(
"title" to "**[ \uD83D\uDEA8 긴급 \uD83D\uDEA8 ] ERR-001** 미분류 백신 추가 필요",
"description" to "**미분류 백신** : ${unclassifiedVaccination.name}\n\n" +
"**요청시간** : " + formattedNow + "\n\n" +
"**LOG**\n\n" +
"```json\n{\n" +
"\t\"id\" : ${unclassifiedVaccination.id},\n" +
"\t\"name\" : ${unclassifiedVaccination.name},\n" +
"\t\"doseRound\" : ${unclassifiedVaccination.doseRound},\n" +
"\t\"doseRoundDescription\" : ${unclassifiedVaccination.doseRoundDescription},\n" +
"\t\"vaccinatedAt\" : ${unclassifiedVaccination.vaccinatedAt},\n" +
"\t\"facility\" : ${unclassifiedVaccination.facility},\n" +
"\t\"manufacturer\" : ${unclassifiedVaccination.manufacturer},\n" +
"\t\"productName\" : ${unclassifiedVaccination.productName},\n" +
"\t\"lotNumber\" : ${unclassifiedVaccination.lotNumber},\n" +
"\t\"babyId\" : ${unclassifiedVaccination.baby.id}\n}```",
"color" to "15548997"
)
val payload = mapOf(
"content" to null,
"embeds" to listOf(embeds)
)

val jsonPayload = objectMapper.writeValueAsString(payload)
val entity = HttpEntity(jsonPayload, headers)
restTemplate.postForLocation(webhookURL, entity)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kr.co.vacgom.api.global.discord

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.client.RestTemplate

@Configuration
class RestTemplateConfig {

@Bean
fun restTemplate(): RestTemplate {
return RestTemplate()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import kr.co.vacgom.api.global.exception.error.BusinessException
import kr.co.vacgom.api.global.exception.error.ErrorCode
import kr.co.vacgom.api.global.exception.error.ErrorResponse
import kr.co.vacgom.api.global.exception.error.GlobalError
import lombok.extern.slf4j.Slf4j
import org.slf4j.Logger
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.MethodArgumentNotValidException
Expand All @@ -13,30 +15,37 @@ import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.servlet.resource.NoResourceFoundException

@Slf4j
@RestControllerAdvice
class ApiExceptionHandler {
class ApiExceptionHandler(
private val log: Logger
) {

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleMethodArgumentNotValidException(ex: MethodArgumentNotValidException): ErrorResponse {
return ErrorResponse(ex.fieldErrors)
fun handleMethodArgumentNotValidException(exception: MethodArgumentNotValidException): ErrorResponse {
log.warn(exception.message)
return ErrorResponse(exception.fieldErrors)
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException::class)
fun missingServletRequestParameterException(): ErrorResponse {
fun missingServletRequestParameterException(exception: MissingServletRequestParameterException): ErrorResponse {
log.warn(exception.message)
return ErrorResponse(GlobalError.INVALID_REQUEST_PARAM)
}

@ExceptionHandler(NoResourceFoundException::class)
fun handleUnexpectedException(exception: NoResourceFoundException): ErrorResponse {
log.warn(exception.message)
return ErrorResponse(GlobalError.GLOBAL_NOT_FOUND);
}


@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BusinessException::class)
fun handleBusinessException(exception: BusinessException): ResponseEntity<ErrorResponse?> {
log.warn(exception.message)
return convert(exception.errorCode)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ class ApiExceptionHandlingFilter(
}

responseWrapper.copyBodyToResponse()

} catch (exception: BusinessException) {
log.warn(exception.message)
setErrorResponse(response, exception)
} catch (exception: Exception) {
log.error(exception.message)
setErrorResponse(response, BusinessException(GlobalError.INTERNAL_SERVER_ERROR))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ interface UserRepository {
fun findBySocialId(socialId: String): User?
fun findById(userId: UUID): User?
fun deleteById(userId: UUID)
fun findAll(): List<User>
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import java.util.*
@Repository
class UserRepositoryAdapter(
private val userJpaRepository: UserJpaRepository
): UserRepository {
) : UserRepository {
override fun save(user: User): User {
return userJpaRepository.save(user)
}
Expand All @@ -24,4 +24,8 @@ class UserRepositoryAdapter(
override fun deleteById(userId: UUID) {
userJpaRepository.deleteById(userId)
}

override fun findAll(): List<User> {
return userJpaRepository.findAll()
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,63 @@
package kr.co.vacgom.api.vaccine.application

import kr.co.vacgom.api.baby.repository.BabyRepository
import kr.co.vacgom.api.global.discord.DiscordSender
import kr.co.vacgom.api.global.exception.error.BusinessException
import kr.co.vacgom.api.vaccine.domain.UnclassifiedVaccination
import kr.co.vacgom.api.vaccine.domain.Vaccination
import kr.co.vacgom.api.vaccine.presentation.dto.VaccinationDto
import kr.co.vacgom.api.vaccine.repository.UnclassifiedVaccinationRepository
import kr.co.vacgom.api.vaccine.repository.VaccinationRepository
import kr.co.vacgom.api.vaccine.repository.vaccine.VaccineRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional
class VaccinationService {
class VaccinationService(
private val unclassifiedVaccinationRepository: UnclassifiedVaccinationRepository,
private val vaccinationRepository: VaccinationRepository,
private val vaccineRepository: VaccineRepository,
private val babyRepository: BabyRepository,
private val discordSender: DiscordSender
) {
fun createVaccinations(request: VaccinationDto.Request.Create) {

val baby = babyRepository.findById(request.babyId)

request.vaccinationRequests.forEach {
try {
val vaccine = vaccineRepository.findByName(it.name)

val classifiedVaccination = Vaccination(
doseRound = it.doseRound,
doseRoundDescription = it.doseRoundDescription,
vaccinatedAt = it.vaccinatedAt,
facility = it.facility,
manufacturer = it.manufacturer,
productName = it.productName,
lotNumber = it.lotNumber,
vaccine = vaccine,
baby = baby
)

vaccinationRepository.save(classifiedVaccination)
} catch (businessException: BusinessException) {
val unclassifiedVaccination = UnclassifiedVaccination(
name = it.name,
doseRound = it.doseRound,
doseRoundDescription = it.doseRoundDescription,
vaccinatedAt = it.vaccinatedAt,
facility = it.facility,
manufacturer = it.manufacturer,
productName = it.productName,
lotNumber = it.lotNumber,
baby = baby
)

val savedUnclassifiedVaccination = unclassifiedVaccinationRepository.save(unclassifiedVaccination)
discordSender.sendVaccinationError(baby, savedUnclassifiedVaccination)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING
import jakarta.persistence.*
import jakarta.persistence.FetchType.LAZY
import kr.co.vacgom.api.baby.domain.Baby
import kr.co.vacgom.api.global.common.domain.BaseTimeEntity
import kr.co.vacgom.api.global.util.UuidCreator
import org.hibernate.annotations.Comment
import java.time.LocalDate
import java.util.*

@Entity
@Table(name = "TB_VACCINATION_UNCLASSIFIED")
class UnclassifiedVaccination private constructor(
class UnclassifiedVaccination(
@Column(nullable = false)
@Comment("[Not Null] 미분류 백신 이름")
val name: String,
Expand Down Expand Up @@ -46,7 +47,7 @@ class UnclassifiedVaccination private constructor(
@JoinColumn(name = "BABY_ID")
@Comment("[NotNull] 아기 Id")
val baby: Baby
) {
) : BaseTimeEntity() {

@Id
@Column(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package kr.co.vacgom.api.vaccine.domain.constants

enum class VaccineType {
NATIONAL,
NATION,
GENERAL,
EVENT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.co.vacgom.api.vaccine.exception

import kr.co.vacgom.api.global.exception.error.ErrorCode
import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatus.NOT_FOUND

enum class VaccinationError(
override val message: String,
override val status: HttpStatus,
override val code: String,
) : ErrorCode {
VACCINE_NOT_FOUND("해당 백신이 존재하지 않습니다.", NOT_FOUND, "V_001")
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface VaccinationApi {
),
]
)
fun createVaccinations(request: List<VaccinationDto.Request.Create>)
fun createVaccinations(request: VaccinationDto.Request.Create)

companion object {
const val VACCINATIONS = "/vaccinations"
Expand Down
Loading

0 comments on commit 96749e9

Please sign in to comment.