From da88d66505698fbedf36d58637148b521e46b26b Mon Sep 17 00:00:00 2001 From: pricelees Date: Mon, 15 Sep 2025 11:55:27 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=ED=85=8C=EB=A7=88=20API=EB=A5=BC?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=EB=B3=84=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/theme/business/ThemeService.kt | 58 +++++++++++++------ .../kotlin/roomescape/theme/docs/ThemeApi.kt | 19 ++++-- ...eController.kt => AdminThemeController.kt} | 37 ++++++------ .../roomescape/theme/web/AdminThemeDto.kt | 18 +++++- .../theme/web/PublicThemeController.kt | 37 ++++++++++++ .../roomescape/theme/web/PublicThemeDto.kt | 44 ++++++++++++++ 6 files changed, 167 insertions(+), 46 deletions(-) rename src/main/kotlin/roomescape/theme/web/{ThemeController.kt => AdminThemeController.kt} (76%) create mode 100644 src/main/kotlin/roomescape/theme/web/PublicThemeController.kt create mode 100644 src/main/kotlin/roomescape/theme/web/PublicThemeDto.kt diff --git a/src/main/kotlin/roomescape/theme/business/ThemeService.kt b/src/main/kotlin/roomescape/theme/business/ThemeService.kt index 17f38aac..e641a533 100644 --- a/src/main/kotlin/roomescape/theme/business/ThemeService.kt +++ b/src/main/kotlin/roomescape/theme/business/ThemeService.kt @@ -16,6 +16,13 @@ import roomescape.theme.web.* private val log: KLogger = KotlinLogging.logger {} +/** + * Structure: + * - Public: 모두가 접근 가능한 메서드 + * - Store Admin: 매장 관리자가 사용하는 메서드 + * - HQ Admin: 본사 관리자가 사용하는 메서드 + * - Common: 공통 메서드 + */ @Service class ThemeService( private val themeRepository: ThemeRepository, @@ -23,6 +30,17 @@ class ThemeService( private val tsidFactory: TsidFactory, private val adminService: AdminService ) { + // ======================================== + // Public (인증 불필요) + // ======================================== + @Transactional(readOnly = true) + fun findInfoById(id: Long): ThemeInfoResponse { + log.info { "[ThemeService.findById] 테마 조회 시작: id=$id" } + + return findOrThrow(id).toInfoResponse() + .also { log.info { "[ThemeService.findById] 테마 조회 완료: id=$id" } } + } + @Transactional(readOnly = true) fun findThemesByIds(request: ThemeIdListResponse): ThemeInfoListResponse { log.info { "[ThemeService.findThemesByIds] 예약 페이지에서의 테마 목록 조회 시작: themeIds=${request.themeIds}" } @@ -37,20 +55,14 @@ class ThemeService( result.add(theme) } - return result.toListResponse().also { + return result.toInfoListResponse().also { log.info { "[ThemeService.findThemesByIds] ${it.themes.size} / ${request.themeIds.size} 개 테마 조회 완료" } } } - @Transactional(readOnly = true) - fun findThemesForReservation(): ThemeInfoListResponse { - log.info { "[ThemeService.findThemesForReservation] 예약 페이지에서의 테마 목록 조회 시작" } - - return themeRepository.findOpenedThemes() - .toListResponse() - .also { log.info { "[ThemeService.findThemesForReservation] ${it.themes.size}개 테마 조회 완료" } } - } - + // ======================================== + // HQ Admin (본사) + // ======================================== @Transactional(readOnly = true) fun findAdminThemes(): AdminThemeSummaryListResponse { log.info { "[ThemeService.findAdminThemes] 관리자 페이지에서의 테마 목록 조회 시작" } @@ -73,14 +85,6 @@ class ThemeService( .also { log.info { "[ThemeService.findAdminThemeDetail] 테마 상세 조회 완료: id=$id, name=${theme.name}" } } } - @Transactional(readOnly = true) - fun findSummaryById(id: Long): ThemeInfoResponse { - log.info { "[ThemeService.findById] 테마 조회 시작: id=$id" } - - return findOrThrow(id).toSummaryResponse() - .also { log.info { "[ThemeService.findById] 테마 조회 완료: id=$id" } } - } - @Transactional fun createTheme(request: ThemeCreateRequest): ThemeCreateResponseV2 { log.info { "[ThemeService.createTheme] 테마 생성 시작: name=${request.name}" } @@ -137,6 +141,24 @@ class ThemeService( } } + // ======================================== + // Store Admin (매장) + // ======================================== + @Transactional(readOnly = true) + fun findActiveThemes(): SimpleActiveThemeListResponse { + log.info { "[ThemeService.findActiveThemes] open 상태인 모든 테마 조회 시작" } + + return themeRepository.findActiveThemes() + .toSimpleActiveThemeResponse() + .also { + log.info { "[ThemeService.findActiveThemes] ${it.themes.size}개 테마 조회 완료" } + } + } + + + // ======================================== + // Common (공통 메서드) + // ======================================== private fun findOrThrow(id: Long): ThemeEntity { log.info { "[ThemeService.findOrThrow] 테마 조회 시작: id=$id" } diff --git a/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt b/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt index a367e9bd..94d68e63 100644 --- a/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt +++ b/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt @@ -16,7 +16,7 @@ import roomescape.common.dto.response.CommonApiResponse import roomescape.theme.web.* @Tag(name = "5. 관리자 테마 API", description = "관리자 페이지에서 테마를 조회 / 추가 / 삭제할 때 사용합니다.") -interface ThemeAPI { +interface HQAdminThemeAPI { @AdminOnly(type = AdminType.HQ, privilege = Privilege.READ_SUMMARY) @Operation(summary = "모든 테마 조회", description = "관리자 페이지에서 요약된 테마 목록을 조회합니다.", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) @@ -44,14 +44,23 @@ interface ThemeAPI { @PathVariable id: Long, @Valid @RequestBody themeUpdateRequest: ThemeUpdateRequest ): ResponseEntity> +} - @Public - @Operation(summary = "예약 페이지에서 모든 테마 조회", description = "모든 테마를 조회합니다.") +interface StoreAdminThemeAPI { + @AdminOnly(type = AdminType.STORE, privilege = Privilege.READ_SUMMARY) + @Operation(summary = "테마 조회", description = "현재 open 상태인 모든 테마 ID + 이름 조회", tags = ["관리자 로그인이 필요한 API"]) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) - fun findUserThemes(): ResponseEntity> + fun findActiveThemes(): ResponseEntity> +} +interface PublicThemeAPI { @Public - @Operation(summary = "예약 페이지에서 입력한 날짜에 가능한 테마 조회", description = "입력한 날짜에 가능한 테마를 조회합니다.") + @Operation(summary = "입력된 모든 ID에 대한 테마 조회") @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) fun findThemesByIds(request: ThemeIdListResponse): ResponseEntity> + + @Public + @Operation(summary = "입력된 테마 ID에 대한 정보 조회") + @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) + fun findThemeInfoById(@PathVariable id: Long): ResponseEntity> } diff --git a/src/main/kotlin/roomescape/theme/web/ThemeController.kt b/src/main/kotlin/roomescape/theme/web/AdminThemeController.kt similarity index 76% rename from src/main/kotlin/roomescape/theme/web/ThemeController.kt rename to src/main/kotlin/roomescape/theme/web/AdminThemeController.kt index dbd66da5..5554ac16 100644 --- a/src/main/kotlin/roomescape/theme/web/ThemeController.kt +++ b/src/main/kotlin/roomescape/theme/web/AdminThemeController.kt @@ -4,30 +4,14 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import roomescape.common.dto.response.CommonApiResponse import roomescape.theme.business.ThemeService -import roomescape.theme.docs.ThemeAPI +import roomescape.theme.docs.HQAdminThemeAPI +import roomescape.theme.docs.StoreAdminThemeAPI import java.net.URI @RestController -class ThemeController( +class HQAdminThemeController( private val themeService: ThemeService, -) : ThemeAPI { - - @PostMapping("/themes/retrieve") - override fun findThemesByIds( - @RequestBody request: ThemeIdListResponse - ): ResponseEntity> { - val response = themeService.findThemesByIds(request) - - return ResponseEntity.ok(CommonApiResponse(response)) - } - - @GetMapping("/themes") - override fun findUserThemes(): ResponseEntity> { - val response = themeService.findThemesForReservation() - - return ResponseEntity.ok(CommonApiResponse(response)) - } - +) : HQAdminThemeAPI { @GetMapping("/admin/themes") override fun findAdminThemes(): ResponseEntity> { val response = themeService.findAdminThemes() @@ -67,3 +51,16 @@ class ThemeController( return ResponseEntity.ok().build() } } + +@RestController +class StoreAdminController( + private val themeService: ThemeService +) : StoreAdminThemeAPI { + + @GetMapping("/admin/themes/active") + override fun findActiveThemes(): ResponseEntity> { + val response = themeService.findActiveThemes() + + return ResponseEntity.ok(CommonApiResponse(response)) + } +} diff --git a/src/main/kotlin/roomescape/theme/web/AdminThemeDto.kt b/src/main/kotlin/roomescape/theme/web/AdminThemeDto.kt index e37070ee..3306d22e 100644 --- a/src/main/kotlin/roomescape/theme/web/AdminThemeDto.kt +++ b/src/main/kotlin/roomescape/theme/web/AdminThemeDto.kt @@ -5,6 +5,18 @@ import roomescape.theme.infrastructure.persistence.Difficulty import roomescape.theme.infrastructure.persistence.ThemeEntity import java.time.LocalDateTime +/** + * Theme API DTO + * + * Structure: + * - HQ Admin DTO: 본사 관리자가 사용하는 테마 관련 DTO들 + * - Store Admin DTO: 매장 관리자가 사용하는 테마 관련 DTO들 + */ + + +// ======================================== +// HQ Admin DTO (본사) +// ======================================== data class ThemeCreateRequest( val name: String, val description: String, @@ -129,9 +141,9 @@ fun ThemeEntity.toAdminThemeDetailResponse(createdBy: OperatorInfo, updatedBy: O updatedBy = updatedBy ) -data class ThemeIdListResponse( - val themeIds: List -) +// ======================================== +// Store Admin DTO +// ======================================== data class ThemeInfoResponse( val id: Long, diff --git a/src/main/kotlin/roomescape/theme/web/PublicThemeController.kt b/src/main/kotlin/roomescape/theme/web/PublicThemeController.kt new file mode 100644 index 00000000..45eea3d0 --- /dev/null +++ b/src/main/kotlin/roomescape/theme/web/PublicThemeController.kt @@ -0,0 +1,37 @@ +package roomescape.theme.web + +import org.springframework.http.ResponseEntity +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.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import roomescape.common.dto.response.CommonApiResponse +import roomescape.theme.business.ThemeService +import roomescape.theme.docs.PublicThemeAPI + +@RestController +@RequestMapping("/themes") +class PublicThemeController( + private val themeService: ThemeService, +): PublicThemeAPI { + + @PostMapping("/batch") + override fun findThemesByIds( + @RequestBody request: ThemeIdListRequest + ): ResponseEntity> { + val response = themeService.findThemesByIds(request) + + return ResponseEntity.ok(CommonApiResponse(response)) + } + + @GetMapping("/{id}") + override fun findThemeInfoById( + @PathVariable id: Long + ): ResponseEntity> { + val response = themeService.findInfoById(id) + + return ResponseEntity.ok(CommonApiResponse(response)) + } +} diff --git a/src/main/kotlin/roomescape/theme/web/PublicThemeDto.kt b/src/main/kotlin/roomescape/theme/web/PublicThemeDto.kt new file mode 100644 index 00000000..e79601a2 --- /dev/null +++ b/src/main/kotlin/roomescape/theme/web/PublicThemeDto.kt @@ -0,0 +1,44 @@ +package roomescape.theme.web + +import roomescape.theme.infrastructure.persistence.Difficulty +import roomescape.theme.infrastructure.persistence.ThemeEntity + +data class ThemeIdListRequest( + val themeIds: List +) + +data class ThemeInfoResponse( + val id: Long, + val name: String, + val thumbnailUrl: String, + val description: String, + val difficulty: Difficulty, + val price: Int, + val minParticipants: Short, + val maxParticipants: Short, + val availableMinutes: Short, + val expectedMinutesFrom: Short, + val expectedMinutesTo: Short +) + +fun ThemeEntity.toInfoResponse() = ThemeInfoResponse( + id = this.id, + name = this.name, + thumbnailUrl = this.thumbnailUrl, + description = this.description, + difficulty = this.difficulty, + price = this.price, + minParticipants = this.minParticipants, + maxParticipants = this.maxParticipants, + availableMinutes = this.availableMinutes, + expectedMinutesFrom = this.expectedMinutesFrom, + expectedMinutesTo = this.expectedMinutesTo +) + +data class ThemeInfoListResponse( + val themes: List +) + +fun List.toInfoListResponse() = ThemeInfoListResponse( + themes = this.map { it.toInfoResponse() } +)