diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/in/web/AdminWebAdapter.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/in/web/AdminWebAdapter.kt new file mode 100644 index 0000000..58eb56c --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/in/web/AdminWebAdapter.kt @@ -0,0 +1,63 @@ +package hs.kr.entrydsm.user.domain.admin.adapter.`in`.web + +import hs.kr.entrydsm.user.domain.admin.adapter.`in`.web.dto.request.AdminLoginRequest +import hs.kr.entrydsm.user.domain.admin.application.port.`in`.AdminLoginUseCase +import hs.kr.entrydsm.user.domain.admin.application.port.`in`.AdminTokenRefreshUseCase +import hs.kr.entrydsm.user.domain.admin.application.port.`in`.DeleteAllTableUseCase +import hs.kr.entrydsm.user.domain.admin.application.port.`in`.QueryAdminByUUIDUseCase +import hs.kr.entrydsm.user.global.document.admin.AdminApiDocument +import hs.kr.entrydsm.user.global.utils.token.dto.TokenResponse +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestHeader +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import java.util.UUID + +/** + * 관리자 관련 HTTP 요청을 처리하는 REST 컨트롤러 클래스입니다. + */ +@RestController +@RequestMapping("/admin") +class AdminWebAdapter( + private val adminLoginUseCase: AdminLoginUseCase, + private val adminTokenRefreshUseCase: AdminTokenRefreshUseCase, + private val deleteAllTableUseCase: DeleteAllTableUseCase, + private val queryAdminByUUIDUseCase: QueryAdminByUUIDUseCase, +) : AdminApiDocument { + /** + * 관리자 로그인을 처리합니다. + */ + @PostMapping("/auth") + override fun login( + @RequestBody @Valid + adminLoginRequest: AdminLoginRequest, + ): TokenResponse = adminLoginUseCase.login(adminLoginRequest) + + /** + * 관리자 토큰을 갱신합니다. + */ + @PutMapping("/auth") + override fun tokenRefresh( + @RequestHeader("X-Refresh-Token") refreshToken: String, + ): TokenResponse = adminTokenRefreshUseCase.refresh(refreshToken) + + /** + * 모든 테이블을 삭제합니다. + */ + @DeleteMapping("/auth") + override fun deleteAllTable() = deleteAllTableUseCase.deleteAllTables() + + /** + * UUID로 관리자 정보를 조회합니다. + */ + @GetMapping("/{adminId}") + override fun findAdminById( + @PathVariable adminId: UUID, + ) = queryAdminByUUIDUseCase.queryByUUID(adminId) +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/in/web/dto/response/InternalAdminResponse.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/in/web/dto/response/InternalAdminResponse.kt new file mode 100644 index 0000000..38d00e4 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/in/web/dto/response/InternalAdminResponse.kt @@ -0,0 +1,10 @@ +package hs.kr.entrydsm.user.domain.admin.adapter.`in`.web.dto.response + +import java.util.UUID + +/** + * 내부 시스템 간 관리자 정보 응답 데이터를 담는 DTO 클래스입니다. + */ +data class InternalAdminResponse( + val id: UUID, +) diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/AdminJpaEntity.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/AdminJpaEntity.kt new file mode 100644 index 0000000..e685bd7 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/AdminJpaEntity.kt @@ -0,0 +1,22 @@ +package hs.kr.entrydsm.user.domain.admin.adapter.out + +import hs.kr.entrydsm.user.global.base.BaseUUIDEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import java.util.UUID + +/** + * 관리자 정보를 데이터베이스에 저장하기 위한 JPA 엔티티 클래스입니다. + * 데이터베이스의 tbl_admin 테이블과 매핑됩니다. + * + * @property adminId 관리자 로그인 ID + * @property password 해시화된 비밀번호 + */ +@Entity(name = "tbl_admin") +class AdminJpaEntity( + id: UUID?, + @Column(name = "admin_id", length = 15, nullable = false) + val adminId: String, + @Column(name = "password", length = 60, nullable = false) + val password: String, +) : BaseUUIDEntity(id) diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/mapper/AdminMapper.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/mapper/AdminMapper.kt new file mode 100644 index 0000000..eff1ff8 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/mapper/AdminMapper.kt @@ -0,0 +1,29 @@ +package hs.kr.entrydsm.user.domain.admin.adapter.out.mapper + +import hs.kr.entrydsm.user.domain.admin.adapter.out.AdminJpaEntity +import hs.kr.entrydsm.user.domain.admin.model.Admin +import hs.kr.entrydsm.user.global.mapper.GenericMapper +import org.mapstruct.Mapper + +/** + * [Admin] 도메인 모델과 [AdminJpaEntity] JPA 엔티티 간의 변환을 담당하는 MapStruct 매퍼 클래스입니다. + */ +@Mapper(componentModel = "spring") +abstract class AdminMapper : GenericMapper { + + /** + * 도메인 모델 [Admin]을 JPA 엔티티 [AdminJpaEntity]로 변환합니다. + * + * @param model 변환할 도메인 모델 + * @return 변환된 JPA 엔티티 + */ + abstract override fun toEntity(model: Admin): AdminJpaEntity + + /** + * JPA 엔티티 [AdminJpaEntity]를 도메인 모델 [Admin]로 변환합니다. + * + * @param entity 변환할 JPA 엔티티 (nullable) + * @return 변환된 도메인 모델 (nullable) + */ + abstract override fun toModel(entity: AdminJpaEntity?): Admin? +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/persistence/AdminPersistenceAdapter.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/persistence/AdminPersistenceAdapter.kt new file mode 100644 index 0000000..d27a912 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/persistence/AdminPersistenceAdapter.kt @@ -0,0 +1,47 @@ +package hs.kr.entrydsm.user.domain.admin.adapter.out.persistence + +import hs.kr.entrydsm.user.domain.admin.adapter.out.mapper.AdminMapper +import hs.kr.entrydsm.user.domain.admin.adapter.out.persistence.repository.AdminRepository +import hs.kr.entrydsm.user.domain.admin.application.port.out.AdminPort +import hs.kr.entrydsm.user.domain.admin.model.Admin +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Component +import java.util.UUID + +/** + * 관리자 데이터의 영속성 처리를 담당하는 어댑터 클래스입니다. + */ +@Component +class AdminPersistenceAdapter( + private val adminRepository: AdminRepository, + private val adminMapper: AdminMapper, +) : AdminPort { + /** + * UUID를 이용하여 관리자 정보를 조회합니다. + * + * @param id 조회할 관리자의 UUID + * @return 조회된 관리자 정보, 존재하지 않을 경우 null + */ + override fun findById(id: UUID): Admin? { + return adminRepository.findByIdOrNull(id)?.let { adminMapper.toModel(it) } + } + + /** + * 관리자 ID를 이용하여 관리자 정보를 조회합니다. + * + * @param adminId 조회할 관리자의 ID + * @return 조회된 관리자 정보, 존재하지 않을 경우 null + */ + override fun findByAdminId(adminId: String): Admin? { + return adminRepository.findByAdminId(adminId)?.let { adminMapper.toModel(it) } + } + + /** + * 관리자 정보를 저장합니다. + * + * @param admin 저장할 관리자 정보 + */ + override fun save(admin: Admin) { + adminRepository.save(adminMapper.toEntity(admin)) + } +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/persistence/repository/AdminRepository.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/persistence/repository/AdminRepository.kt new file mode 100644 index 0000000..6d3bdc5 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/adapter/out/persistence/repository/AdminRepository.kt @@ -0,0 +1,15 @@ +package hs.kr.entrydsm.user.domain.admin.adapter.out.persistence.repository + +import hs.kr.entrydsm.user.domain.admin.adapter.out.AdminJpaEntity +import org.springframework.data.jpa.repository.JpaRepository +import java.util.UUID + +/** + * 관리자 JPA 엔티티에 대한 데이터 액세스를 담당하는 리포지토리 인터페이스입니다. + */ +interface AdminRepository : JpaRepository { + /** + * 관리자 ID로 관리자를 조회합니다. + */ + fun findByAdminId(adminId: String): AdminJpaEntity? +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/AdminFacadeUseCase.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/AdminFacadeUseCase.kt new file mode 100644 index 0000000..1b373d3 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/AdminFacadeUseCase.kt @@ -0,0 +1,18 @@ +package hs.kr.entrydsm.user.domain.admin.application.port.`in` + +import hs.kr.entrydsm.user.domain.admin.model.Admin + +/** + * 관리자 Facade 기능을 정의하는 UseCase 인터페이스입니다. + */ +interface AdminFacadeUseCase { + /** + * 현재 인증된 관리자의 사용자 정보를 조회합니다. + */ + fun getCurrentUser(): Admin + + /** + * 관리자 ID로 사용자 정보를 조회합니다. + */ + fun getUserById(adminId: String): Admin +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/AdminLoginUseCase.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/AdminLoginUseCase.kt new file mode 100644 index 0000000..14a9681 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/AdminLoginUseCase.kt @@ -0,0 +1,18 @@ +package hs.kr.entrydsm.user.domain.admin.application.port.`in` + +import hs.kr.entrydsm.user.domain.admin.adapter.`in`.web.dto.request.AdminLoginRequest +import hs.kr.entrydsm.user.global.utils.token.dto.TokenResponse + +/** + * 관리자 로그인 유스케이스 인터페이스입니다. + * 관리자 인증 및 토큰 발급 처리를 정의합니다. + */ +interface AdminLoginUseCase { + /** + * 관리자 로그인을 처리합니다. + * + * @param request 관리자 로그인 요청 정보 + * @return 생성된 인증 토큰 응답 + */ + fun login(request: AdminLoginRequest): TokenResponse +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/AdminTokenRefreshUseCase.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/AdminTokenRefreshUseCase.kt new file mode 100644 index 0000000..044adc3 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/AdminTokenRefreshUseCase.kt @@ -0,0 +1,13 @@ +package hs.kr.entrydsm.user.domain.admin.application.port.`in` + +import hs.kr.entrydsm.user.global.utils.token.dto.TokenResponse + +/** + * 관리자 토큰 갱신 기능을 정의하는 UseCase 인터페이스입니다. + */ +interface AdminTokenRefreshUseCase { + /** + * 관리자 토큰을 갱신합니다. + */ + fun refresh(token: String): TokenResponse +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/DeleteAllTableUseCase.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/DeleteAllTableUseCase.kt new file mode 100644 index 0000000..2f5cd5b --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/DeleteAllTableUseCase.kt @@ -0,0 +1,11 @@ +package hs.kr.entrydsm.user.domain.admin.application.port.`in` + +/** + * 모든 테이블 삭제 기능을 정의하는 UseCase 인터페이스입니다. + */ +interface DeleteAllTableUseCase { + /** + * 모든 테이블을 삭제합니다. + */ + fun deleteAllTables() +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/QueryAdminByUUIDUseCase.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/QueryAdminByUUIDUseCase.kt new file mode 100644 index 0000000..5d34082 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/in/QueryAdminByUUIDUseCase.kt @@ -0,0 +1,14 @@ +package hs.kr.entrydsm.user.domain.admin.application.port.`in` + +import hs.kr.entrydsm.user.domain.admin.adapter.`in`.web.dto.response.InternalAdminResponse +import java.util.UUID + +/** + * UUID로 관리자 조회 기능을 정의하는 UseCase 인터페이스입니다. + */ +interface QueryAdminByUUIDUseCase { + /** + * UUID로 관리자 정보를 조회합니다. + */ + fun queryByUUID(adminId: UUID): InternalAdminResponse +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/out/AdminPort.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/out/AdminPort.kt new file mode 100644 index 0000000..5eb2dcd --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/out/AdminPort.kt @@ -0,0 +1,7 @@ +package hs.kr.entrydsm.user.domain.admin.application.port.out + +/** + * 관리자 관련 모든 포트 인터페이스를 통합한 포트입니다. + * 관리자 데이터의 CRUD 작업을 위한 모든 인터페이스를 상속받습니다. + */ +interface AdminPort : SaveAdminPort, QueryAdminPort diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/out/QueryAdminPort.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/out/QueryAdminPort.kt new file mode 100644 index 0000000..de87863 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/out/QueryAdminPort.kt @@ -0,0 +1,26 @@ +package hs.kr.entrydsm.user.domain.admin.application.port.out + +import hs.kr.entrydsm.user.domain.admin.model.Admin +import java.util.UUID + +/** + * 관리자 조회 작업을 위한 포트 인터페이스입니다. + * 헥사고날 아키텍처에서 도메인 계층이 인프라스트럭처 계층과 통신하기 위한 인터페이스입니다. + */ +interface QueryAdminPort { + /** + * 관리자 ID로 관리자를 조회합니다. + * + * @param adminId 조회할 관리자 ID + * @return 조회된 관리자 도메인 모델 (없으면 null) + */ + fun findByAdminId(adminId: String): Admin? + + /** + * UUID로 관리자를 조회합니다. + * + * @param id 조회할 관리자 UUID + * @return 조회된 관리자 도메인 모델 (없으면 null) + */ + fun findById(id: UUID): Admin? +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/out/SaveAdminPort.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/out/SaveAdminPort.kt new file mode 100644 index 0000000..c309f27 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/port/out/SaveAdminPort.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.user.domain.admin.application.port.out + +import hs.kr.entrydsm.user.domain.admin.model.Admin + +/** + * 관리자 저장 작업을 위한 포트 인터페이스입니다. + * 헥사고날 아키텍처에서 도메인 계층이 인프라스트럭처 계층과 통신하기 위한 인터페이스입니다. + */ +interface SaveAdminPort { + /** + * 관리자 정보를 저장합니다. + * + * @param admin 저장할 관리자 도메인 모델 + */ + fun save(admin: Admin) +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/service/AdminTokenRefreshService.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/service/AdminTokenRefreshService.kt new file mode 100644 index 0000000..2e58d2a --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/service/AdminTokenRefreshService.kt @@ -0,0 +1,21 @@ +package hs.kr.entrydsm.user.domain.admin.application.service + +import hs.kr.entrydsm.user.domain.admin.application.port.`in`.AdminTokenRefreshUseCase +import hs.kr.entrydsm.user.global.security.jwt.JwtTokenProvider +import hs.kr.entrydsm.user.global.utils.token.dto.TokenResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +/** + * 관리자 토큰 갱신 비즈니스 로직을 처리하는 서비스 클래스입니다. + */ +@Service +class AdminTokenRefreshService( + private val jwtTokenProvider: JwtTokenProvider, +) : AdminTokenRefreshUseCase { + /** + * 관리자의 리프레시 토큰을 이용하여 새로운 액세스 토큰을 발급합니다. + */ + @Transactional + override fun refresh(refreshToken: String): TokenResponse = jwtTokenProvider.reIssue(refreshToken) +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/service/DeleteAllTableService.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/service/DeleteAllTableService.kt new file mode 100644 index 0000000..807b85a --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/service/DeleteAllTableService.kt @@ -0,0 +1,18 @@ +package hs.kr.entrydsm.user.domain.admin.application.service + +import hs.kr.entrydsm.user.domain.admin.application.port.`in`.DeleteAllTableUseCase +import hs.kr.entrydsm.user.infrastructure.kafka.producer.DeleteAllTableProducer +import org.springframework.stereotype.Service + +/** + * 모든 테이블 삭제 비즈니스 로직을 처리하는 서비스 클래스입니다. + */ +@Service +class DeleteAllTableService( + private val deleteAllTableProducer: DeleteAllTableProducer, +) : DeleteAllTableUseCase { + /** + * 모든 테이블을 삭제합니다. + */ + override fun deleteAllTables() = deleteAllTableProducer.send() +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/service/QueryAdminByUUIDService.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/service/QueryAdminByUUIDService.kt new file mode 100644 index 0000000..1ddf192 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/application/service/QueryAdminByUUIDService.kt @@ -0,0 +1,28 @@ +package hs.kr.entrydsm.user.domain.admin.application.service + +import hs.kr.entrydsm.user.domain.admin.adapter.`in`.web.dto.response.InternalAdminResponse +import hs.kr.entrydsm.user.domain.admin.application.port.`in`.QueryAdminByUUIDUseCase +import hs.kr.entrydsm.user.domain.admin.application.port.out.QueryAdminPort +import hs.kr.entrydsm.user.domain.admin.exception.AdminNotFoundException +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +/** + * UUID로 관리자 조회 비즈니스 로직을 처리하는 서비스 클래스입니다. + */ +@Service +class QueryAdminByUUIDService( + private val queryAdminPort: QueryAdminPort, +) : QueryAdminByUUIDUseCase { + /** + * UUID를 이용하여 관리자 정보를 조회합니다. + */ + @Transactional(readOnly = true) + override fun queryByUUID(adminId: UUID): InternalAdminResponse { + val admin = queryAdminPort.findById(adminId) ?: throw AdminNotFoundException + return InternalAdminResponse( + id = admin.id!!, + ) + } +} diff --git a/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/model/Admin.kt b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/model/Admin.kt new file mode 100644 index 0000000..9020f16 --- /dev/null +++ b/casper-user/src/main/kotlin/hs/kr/entrydsm/user/domain/admin/model/Admin.kt @@ -0,0 +1,17 @@ +package hs.kr.entrydsm.user.domain.admin.model + +import java.util.UUID + +/** + * 관리자 도메인 모델을 나타내는 데이터 클래스입니다. + * 시스템 관리자의 인증 정보를 관리합니다. + * + * @property id 관리자 고유 식별자 + * @property adminId 관리자 로그인 ID + * @property password 암호화된 비밀번호 + */ +data class Admin( + val id: UUID?, + val adminId: String, + val password: String, +)