generated from pricelees/issue-pr-template
[#44] 매장 기능 도입 #45
@ -16,6 +16,13 @@ import roomescape.theme.web.*
|
|||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure:
|
||||||
|
* - Public: 모두가 접근 가능한 메서드
|
||||||
|
* - Store Admin: 매장 관리자가 사용하는 메서드
|
||||||
|
* - HQ Admin: 본사 관리자가 사용하는 메서드
|
||||||
|
* - Common: 공통 메서드
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
class ThemeService(
|
class ThemeService(
|
||||||
private val themeRepository: ThemeRepository,
|
private val themeRepository: ThemeRepository,
|
||||||
@ -23,6 +30,17 @@ class ThemeService(
|
|||||||
private val tsidFactory: TsidFactory,
|
private val tsidFactory: TsidFactory,
|
||||||
private val adminService: AdminService
|
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)
|
@Transactional(readOnly = true)
|
||||||
fun findThemesByIds(request: ThemeIdListResponse): ThemeInfoListResponse {
|
fun findThemesByIds(request: ThemeIdListResponse): ThemeInfoListResponse {
|
||||||
log.info { "[ThemeService.findThemesByIds] 예약 페이지에서의 테마 목록 조회 시작: themeIds=${request.themeIds}" }
|
log.info { "[ThemeService.findThemesByIds] 예약 페이지에서의 테마 목록 조회 시작: themeIds=${request.themeIds}" }
|
||||||
@ -37,20 +55,14 @@ class ThemeService(
|
|||||||
result.add(theme)
|
result.add(theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.toListResponse().also {
|
return result.toInfoListResponse().also {
|
||||||
log.info { "[ThemeService.findThemesByIds] ${it.themes.size} / ${request.themeIds.size} 개 테마 조회 완료" }
|
log.info { "[ThemeService.findThemesByIds] ${it.themes.size} / ${request.themeIds.size} 개 테마 조회 완료" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
// ========================================
|
||||||
fun findThemesForReservation(): ThemeInfoListResponse {
|
// HQ Admin (본사)
|
||||||
log.info { "[ThemeService.findThemesForReservation] 예약 페이지에서의 테마 목록 조회 시작" }
|
// ========================================
|
||||||
|
|
||||||
return themeRepository.findOpenedThemes()
|
|
||||||
.toListResponse()
|
|
||||||
.also { log.info { "[ThemeService.findThemesForReservation] ${it.themes.size}개 테마 조회 완료" } }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findAdminThemes(): AdminThemeSummaryListResponse {
|
fun findAdminThemes(): AdminThemeSummaryListResponse {
|
||||||
log.info { "[ThemeService.findAdminThemes] 관리자 페이지에서의 테마 목록 조회 시작" }
|
log.info { "[ThemeService.findAdminThemes] 관리자 페이지에서의 테마 목록 조회 시작" }
|
||||||
@ -73,14 +85,6 @@ class ThemeService(
|
|||||||
.also { log.info { "[ThemeService.findAdminThemeDetail] 테마 상세 조회 완료: id=$id, name=${theme.name}" } }
|
.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
|
@Transactional
|
||||||
fun createTheme(request: ThemeCreateRequest): ThemeCreateResponseV2 {
|
fun createTheme(request: ThemeCreateRequest): ThemeCreateResponseV2 {
|
||||||
log.info { "[ThemeService.createTheme] 테마 생성 시작: name=${request.name}" }
|
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 {
|
private fun findOrThrow(id: Long): ThemeEntity {
|
||||||
log.info { "[ThemeService.findOrThrow] 테마 조회 시작: id=$id" }
|
log.info { "[ThemeService.findOrThrow] 테마 조회 시작: id=$id" }
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import roomescape.common.dto.response.CommonApiResponse
|
|||||||
import roomescape.theme.web.*
|
import roomescape.theme.web.*
|
||||||
|
|
||||||
@Tag(name = "5. 관리자 테마 API", description = "관리자 페이지에서 테마를 조회 / 추가 / 삭제할 때 사용합니다.")
|
@Tag(name = "5. 관리자 테마 API", description = "관리자 페이지에서 테마를 조회 / 추가 / 삭제할 때 사용합니다.")
|
||||||
interface ThemeAPI {
|
interface HQAdminThemeAPI {
|
||||||
@AdminOnly(type = AdminType.HQ, privilege = Privilege.READ_SUMMARY)
|
@AdminOnly(type = AdminType.HQ, privilege = Privilege.READ_SUMMARY)
|
||||||
@Operation(summary = "모든 테마 조회", description = "관리자 페이지에서 요약된 테마 목록을 조회합니다.", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "모든 테마 조회", description = "관리자 페이지에서 요약된 테마 목록을 조회합니다.", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
@ -44,14 +44,23 @@ interface ThemeAPI {
|
|||||||
@PathVariable id: Long,
|
@PathVariable id: Long,
|
||||||
@Valid @RequestBody themeUpdateRequest: ThemeUpdateRequest
|
@Valid @RequestBody themeUpdateRequest: ThemeUpdateRequest
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
|
}
|
||||||
|
|
||||||
@Public
|
interface StoreAdminThemeAPI {
|
||||||
@Operation(summary = "예약 페이지에서 모든 테마 조회", description = "모든 테마를 조회합니다.")
|
@AdminOnly(type = AdminType.STORE, privilege = Privilege.READ_SUMMARY)
|
||||||
|
@Operation(summary = "테마 조회", description = "현재 open 상태인 모든 테마 ID + 이름 조회", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun findUserThemes(): ResponseEntity<CommonApiResponse<ThemeInfoListResponse>>
|
fun findActiveThemes(): ResponseEntity<CommonApiResponse<SimpleActiveThemeListResponse>>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PublicThemeAPI {
|
||||||
@Public
|
@Public
|
||||||
@Operation(summary = "예약 페이지에서 입력한 날짜에 가능한 테마 조회", description = "입력한 날짜에 가능한 테마를 조회합니다.")
|
@Operation(summary = "입력된 모든 ID에 대한 테마 조회")
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun findThemesByIds(request: ThemeIdListResponse): ResponseEntity<CommonApiResponse<ThemeInfoListResponse>>
|
fun findThemesByIds(request: ThemeIdListResponse): ResponseEntity<CommonApiResponse<ThemeInfoListResponse>>
|
||||||
|
|
||||||
|
@Public
|
||||||
|
@Operation(summary = "입력된 테마 ID에 대한 정보 조회")
|
||||||
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
|
fun findThemeInfoById(@PathVariable id: Long): ResponseEntity<CommonApiResponse<ThemeInfoResponse>>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,30 +4,14 @@ import org.springframework.http.ResponseEntity
|
|||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
import roomescape.common.dto.response.CommonApiResponse
|
import roomescape.common.dto.response.CommonApiResponse
|
||||||
import roomescape.theme.business.ThemeService
|
import roomescape.theme.business.ThemeService
|
||||||
import roomescape.theme.docs.ThemeAPI
|
import roomescape.theme.docs.HQAdminThemeAPI
|
||||||
|
import roomescape.theme.docs.StoreAdminThemeAPI
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
class ThemeController(
|
class HQAdminThemeController(
|
||||||
private val themeService: ThemeService,
|
private val themeService: ThemeService,
|
||||||
) : ThemeAPI {
|
) : HQAdminThemeAPI {
|
||||||
|
|
||||||
@PostMapping("/themes/retrieve")
|
|
||||||
override fun findThemesByIds(
|
|
||||||
@RequestBody request: ThemeIdListResponse
|
|
||||||
): ResponseEntity<CommonApiResponse<ThemeInfoListResponse>> {
|
|
||||||
val response = themeService.findThemesByIds(request)
|
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/themes")
|
|
||||||
override fun findUserThemes(): ResponseEntity<CommonApiResponse<ThemeInfoListResponse>> {
|
|
||||||
val response = themeService.findThemesForReservation()
|
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/admin/themes")
|
@GetMapping("/admin/themes")
|
||||||
override fun findAdminThemes(): ResponseEntity<CommonApiResponse<AdminThemeSummaryListResponse>> {
|
override fun findAdminThemes(): ResponseEntity<CommonApiResponse<AdminThemeSummaryListResponse>> {
|
||||||
val response = themeService.findAdminThemes()
|
val response = themeService.findAdminThemes()
|
||||||
@ -67,3 +51,16 @@ class ThemeController(
|
|||||||
return ResponseEntity.ok().build()
|
return ResponseEntity.ok().build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
class StoreAdminController(
|
||||||
|
private val themeService: ThemeService
|
||||||
|
) : StoreAdminThemeAPI {
|
||||||
|
|
||||||
|
@GetMapping("/admin/themes/active")
|
||||||
|
override fun findActiveThemes(): ResponseEntity<CommonApiResponse<SimpleActiveThemeListResponse>> {
|
||||||
|
val response = themeService.findActiveThemes()
|
||||||
|
|
||||||
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,18 @@ import roomescape.theme.infrastructure.persistence.Difficulty
|
|||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme API DTO
|
||||||
|
*
|
||||||
|
* Structure:
|
||||||
|
* - HQ Admin DTO: 본사 관리자가 사용하는 테마 관련 DTO들
|
||||||
|
* - Store Admin DTO: 매장 관리자가 사용하는 테마 관련 DTO들
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// HQ Admin DTO (본사)
|
||||||
|
// ========================================
|
||||||
data class ThemeCreateRequest(
|
data class ThemeCreateRequest(
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
@ -129,9 +141,9 @@ fun ThemeEntity.toAdminThemeDetailResponse(createdBy: OperatorInfo, updatedBy: O
|
|||||||
updatedBy = updatedBy
|
updatedBy = updatedBy
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ThemeIdListResponse(
|
// ========================================
|
||||||
val themeIds: List<Long>
|
// Store Admin DTO
|
||||||
)
|
// ========================================
|
||||||
|
|
||||||
data class ThemeInfoResponse(
|
data class ThemeInfoResponse(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
|||||||
@ -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<CommonApiResponse<ThemeInfoListResponse>> {
|
||||||
|
val response = themeService.findThemesByIds(request)
|
||||||
|
|
||||||
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
override fun findThemeInfoById(
|
||||||
|
@PathVariable id: Long
|
||||||
|
): ResponseEntity<CommonApiResponse<ThemeInfoResponse>> {
|
||||||
|
val response = themeService.findInfoById(id)
|
||||||
|
|
||||||
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/main/kotlin/roomescape/theme/web/PublicThemeDto.kt
Normal file
44
src/main/kotlin/roomescape/theme/web/PublicThemeDto.kt
Normal file
@ -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<Long>
|
||||||
|
)
|
||||||
|
|
||||||
|
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<ThemeInfoResponse>
|
||||||
|
)
|
||||||
|
|
||||||
|
fun List<ThemeEntity>.toInfoListResponse() = ThemeInfoListResponse(
|
||||||
|
themes = this.map { it.toInfoResponse() }
|
||||||
|
)
|
||||||
Loading…
x
Reference in New Issue
Block a user