Skip to content

Commit fde08cd

Browse files
authored
feat: Event API Admin 분리
1 parent 3e8ffd6 commit fde08cd

16 files changed

Lines changed: 747 additions & 371 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.back.api.event.controller;
2+
3+
import java.util.List;
4+
5+
import org.springframework.web.bind.annotation.PathVariable;
6+
import org.springframework.web.bind.annotation.RequestBody;
7+
8+
import com.back.api.event.dto.request.EventCreateRequest;
9+
import com.back.api.event.dto.request.EventUpdateRequest;
10+
import com.back.api.event.dto.response.AdminEventDashboardResponse;
11+
import com.back.api.event.dto.response.EventResponse;
12+
import com.back.global.config.swagger.ApiErrorCode;
13+
import com.back.global.response.ApiResponse;
14+
15+
import io.swagger.v3.oas.annotations.Operation;
16+
import io.swagger.v3.oas.annotations.Parameter;
17+
import io.swagger.v3.oas.annotations.tags.Tag;
18+
import jakarta.validation.Valid;
19+
20+
@Tag(name = "Event Admin API", description = "관리자용 이벤트 관리 및 현황 조회 API")
21+
public interface AdminEventApi {
22+
23+
@Operation(
24+
summary = "이벤트 생성 (관리자)",
25+
description = "새로운 이벤트를 생성합니다. 관리자 권한이 필요합니다."
26+
)
27+
@ApiErrorCode({
28+
"INVALID_EVENT_DATE",
29+
"DUPLICATE_EVENT",
30+
"EVENT_ALREADY_CLOSED",
31+
"PRE_REGISTER_NOT_OPEN"
32+
})
33+
ApiResponse<EventResponse> createEvent(@Valid @RequestBody EventCreateRequest request);
34+
35+
@Operation(
36+
summary = "이벤트 수정 (관리자)",
37+
description = "기존 이벤트 정보를 수정합니다. 관리자 권한이 필요합니다."
38+
)
39+
@ApiErrorCode({
40+
"NOT_FOUND_EVENT",
41+
"INVALID_EVENT_DATE",
42+
"DUPLICATE_EVENT",
43+
"EVENT_ALREADY_CLOSED",
44+
"PRE_REGISTER_NOT_OPEN"
45+
})
46+
ApiResponse<EventResponse> updateEvent(
47+
@Parameter(description = "수정할 이벤트 ID", example = "1")
48+
@PathVariable Long eventId,
49+
@Valid @RequestBody EventUpdateRequest request);
50+
51+
@Operation(
52+
summary = "이벤트 삭제 (관리자)",
53+
description = "이벤트를 삭제합니다. 관리자 권한이 필요합니다."
54+
)
55+
@ApiErrorCode({
56+
"NOT_FOUND_EVENT",
57+
"EVENT_ALREADY_CLOSED"
58+
})
59+
ApiResponse<Void> deleteEvent(
60+
@Parameter(description = "삭제할 이벤트 ID", example = "1")
61+
@PathVariable Long eventId);
62+
63+
@Operation(
64+
summary = "전체 이벤트 대시보드 현황 조회 (관리자)",
65+
description = "관리자 대시보드에서 모든 이벤트의 전체 현황을 조회합니다. "
66+
+ "각 이벤트별 상태, 사전등록 인원 수, 총 판매 좌석, 총 판매 금액을 포함합니다."
67+
)
68+
ApiResponse<List<AdminEventDashboardResponse>> getAllEventsDashboard();
69+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.back.api.event.controller;
2+
3+
import java.util.List;
4+
5+
import org.springframework.web.bind.annotation.DeleteMapping;
6+
import org.springframework.web.bind.annotation.GetMapping;
7+
import org.springframework.web.bind.annotation.PathVariable;
8+
import org.springframework.web.bind.annotation.PostMapping;
9+
import org.springframework.web.bind.annotation.PutMapping;
10+
import org.springframework.web.bind.annotation.RequestBody;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
import com.back.api.event.dto.request.EventCreateRequest;
15+
import com.back.api.event.dto.request.EventUpdateRequest;
16+
import com.back.api.event.dto.response.AdminEventDashboardResponse;
17+
import com.back.api.event.dto.response.EventResponse;
18+
import com.back.api.event.service.AdminEventService;
19+
import com.back.global.response.ApiResponse;
20+
21+
import jakarta.validation.Valid;
22+
import lombok.RequiredArgsConstructor;
23+
24+
@RestController
25+
@RequestMapping("/api/v1/admin/events")
26+
@RequiredArgsConstructor
27+
public class AdminEventController implements AdminEventApi {
28+
29+
private final AdminEventService adminEventService;
30+
31+
@Override
32+
@PostMapping
33+
public ApiResponse<EventResponse> createEvent(
34+
@Valid @RequestBody EventCreateRequest request) {
35+
EventResponse response = adminEventService.createEvent(request);
36+
return ApiResponse.created("이벤트가 생성되었습니다.", response);
37+
}
38+
39+
@Override
40+
@PutMapping("/{eventId}")
41+
public ApiResponse<EventResponse> updateEvent(
42+
@PathVariable Long eventId,
43+
@Valid @RequestBody EventUpdateRequest request) {
44+
EventResponse response = adminEventService.updateEvent(eventId, request);
45+
return ApiResponse.ok("이벤트가 수정되었습니다.", response);
46+
}
47+
48+
@Override
49+
@DeleteMapping("/{eventId}")
50+
public ApiResponse<Void> deleteEvent(
51+
@PathVariable Long eventId) {
52+
adminEventService.deleteEvent(eventId);
53+
return ApiResponse.noContent("이벤트가 삭제되었습니다.");
54+
}
55+
56+
@Override
57+
@GetMapping("/dashboard")
58+
public ApiResponse<List<AdminEventDashboardResponse>> getAllEventsDashboard() {
59+
List<AdminEventDashboardResponse> responses = adminEventService.getAllEventsDashboard();
60+
return ApiResponse.ok("이벤트 현황 조회 성공", responses);
61+
}
62+
}

backend/src/main/java/com/back/api/event/controller/EventApi.java

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55
import org.springframework.data.domain.Sort;
66
import org.springframework.data.web.PageableDefault;
77
import org.springframework.web.bind.annotation.PathVariable;
8-
import org.springframework.web.bind.annotation.RequestBody;
98
import org.springframework.web.bind.annotation.RequestParam;
109

11-
import com.back.api.event.dto.request.EventCreateRequest;
12-
import com.back.api.event.dto.request.EventUpdateRequest;
1310
import com.back.api.event.dto.response.EventListResponse;
1411
import com.back.api.event.dto.response.EventResponse;
1512
import com.back.domain.event.entity.EventCategory;
@@ -20,50 +17,9 @@
2017
import io.swagger.v3.oas.annotations.Operation;
2118
import io.swagger.v3.oas.annotations.Parameter;
2219
import io.swagger.v3.oas.annotations.tags.Tag;
23-
import jakarta.validation.Valid;
2420

25-
@Tag(name = "Event API", description = "이벤트 CRUD 및 조회 API")
21+
@Tag(name = "Event API", description = "이벤트 조회 API")
2622
public interface EventApi {
27-
@Operation(
28-
summary = "이벤트 생성",
29-
description = "새로운 이벤트를 생성합니다. 관리자 권한이 필요합니다."
30-
)
31-
@ApiErrorCode({
32-
"INVALID_EVENT_DATE",
33-
"DUPLICATE_EVENT",
34-
"EVENT_ALREADY_CLOSED",
35-
"PRE_REGISTER_NOT_OPEN"
36-
})
37-
ApiResponse<EventResponse> createEvent(@Valid @RequestBody EventCreateRequest request);
38-
39-
@Operation(
40-
summary = "이벤트 수정",
41-
description = "기존 이벤트 정보를 수정합니다. 관리자 권한이 필요합니다."
42-
)
43-
@ApiErrorCode({
44-
"NOT_FOUND_EVENT",
45-
"INVALID_EVENT_DATE",
46-
"DUPLICATE_EVENT",
47-
"EVENT_ALREADY_CLOSED",
48-
"PRE_REGISTER_NOT_OPEN"
49-
})
50-
ApiResponse<EventResponse> updateEvent(
51-
@Parameter(description = "수정할 이벤트 ID", example = "1")
52-
@PathVariable Long eventId,
53-
@Valid @RequestBody EventUpdateRequest request);
54-
55-
@Operation(
56-
summary = "이벤트 삭제",
57-
description = "이벤트를 삭제합니다. 관리자 권한이 필요합니다."
58-
)
59-
@ApiErrorCode({
60-
"NOT_FOUND_EVENT",
61-
"EVENT_ALREADY_CLOSED"
62-
})
63-
ApiResponse<Void> deleteEvent(
64-
@Parameter(description = "삭제할 이벤트 ID", example = "1")
65-
@PathVariable Long eventId);
66-
6723
@Operation(
6824
summary = "이벤트 단건 조회",
6925
description = "이벤트 ID로 상세 정보를 조회합니다. 이벤트 상세 페이지에서 사용됩니다."

backend/src/main/java/com/back/api/event/controller/EventController.java

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,19 @@
33
import org.springframework.data.domain.Page;
44
import org.springframework.data.domain.Pageable;
55
import org.springframework.data.web.PageableDefault;
6-
import org.springframework.web.bind.annotation.DeleteMapping;
76
import org.springframework.web.bind.annotation.GetMapping;
87
import org.springframework.web.bind.annotation.PathVariable;
9-
import org.springframework.web.bind.annotation.PostMapping;
10-
import org.springframework.web.bind.annotation.PutMapping;
11-
import org.springframework.web.bind.annotation.RequestBody;
128
import org.springframework.web.bind.annotation.RequestMapping;
139
import org.springframework.web.bind.annotation.RequestParam;
1410
import org.springframework.web.bind.annotation.RestController;
1511

16-
import com.back.api.event.dto.request.EventCreateRequest;
17-
import com.back.api.event.dto.request.EventUpdateRequest;
1812
import com.back.api.event.dto.response.EventListResponse;
1913
import com.back.api.event.dto.response.EventResponse;
2014
import com.back.api.event.service.EventService;
2115
import com.back.domain.event.entity.EventCategory;
2216
import com.back.domain.event.entity.EventStatus;
2317
import com.back.global.response.ApiResponse;
2418

25-
import jakarta.validation.Valid;
2619
import lombok.RequiredArgsConstructor;
2720

2821
@RestController
@@ -32,31 +25,6 @@ public class EventController implements EventApi {
3225

3326
private final EventService eventService;
3427

35-
@Override
36-
@PostMapping
37-
public ApiResponse<EventResponse> createEvent(
38-
@Valid @RequestBody EventCreateRequest request) {
39-
EventResponse response = eventService.createEvent(request);
40-
return ApiResponse.created("이벤트가 생성되었습니다.", response);
41-
}
42-
43-
@Override
44-
@PutMapping("/{eventId}")
45-
public ApiResponse<EventResponse> updateEvent(
46-
@PathVariable Long eventId,
47-
@Valid @RequestBody EventUpdateRequest request) {
48-
EventResponse response = eventService.updateEvent(eventId, request);
49-
return ApiResponse.ok("이벤트가 수정되었습니다.", response);
50-
}
51-
52-
@Override
53-
@DeleteMapping("/{eventId}")
54-
public ApiResponse<Void> deleteEvent(
55-
@PathVariable Long eventId) {
56-
eventService.deleteEvent(eventId);
57-
return ApiResponse.noContent("이벤트가 삭제되었습니다.");
58-
}
59-
6028
@Override
6129
@GetMapping("/{eventId}")
6230
public ApiResponse<EventResponse> getEvent(
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.back.api.event.dto.response;
2+
3+
import com.back.domain.event.entity.EventStatus;
4+
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
7+
@Schema(description = "관리자 대시보드용 이벤트 현황 조회 응답")
8+
public record AdminEventDashboardResponse(
9+
10+
@Schema(description = "이벤트 ID", example = "1")
11+
Long eventId,
12+
13+
@Schema(description = "이벤트 제목", example = "2025 BTS 콘서트")
14+
String title,
15+
16+
@Schema(description = "이벤트 상태", example = "PRE_OPEN")
17+
EventStatus status,
18+
19+
@Schema(description = "현재 사전등록 인원 수", example = "1523")
20+
Long preRegisterCount,
21+
22+
@Schema(description = "총 판매 좌석 수", example = "850")
23+
Long totalSoldSeats,
24+
25+
@Schema(description = "총 판매 금액", example = "127500000")
26+
Long totalSalesAmount
27+
) {
28+
public static AdminEventDashboardResponse of(
29+
Long eventId,
30+
String title,
31+
EventStatus status,
32+
Long preRegisterCount,
33+
Long totalSoldSeats,
34+
Long totalSalesAmount
35+
) {
36+
return new AdminEventDashboardResponse(
37+
eventId,
38+
title,
39+
status,
40+
preRegisterCount,
41+
totalSoldSeats,
42+
totalSalesAmount
43+
);
44+
}
45+
}

backend/src/main/java/com/back/api/event/dto/response/EventListResponse.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ public record EventListResponse(
4040
@Schema(description = "티켓팅 시작일시", example = "2026-01-25T10:00:00")
4141
LocalDateTime ticketOpenAt,
4242

43+
@Schema(description = "티켓팅 종료일시", example = "2026-01-30T23:59:59")
44+
LocalDateTime ticketCloseAt,
45+
4346
@Schema(description = "이벤트 상태 (READY: 준비중, PRE_OPEN: 사전등록중, QUEUE_READY: 대기열 준비, OPEN: 티켓팅 진행중, CLOSED: 마감)",
4447
example = "PRE_OPEN")
4548
EventStatus status,
@@ -59,6 +62,7 @@ public static EventListResponse from(Event event) {
5962
event.getPreOpenAt(),
6063
event.getPreCloseAt(),
6164
event.getTicketOpenAt(),
65+
event.getTicketCloseAt(),
6266
event.getStatus(),
6367
event.getCreateAt()
6468
);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.back.api.event.service;
2+
3+
import java.util.List;
4+
import java.util.stream.Collectors;
5+
6+
import org.springframework.stereotype.Service;
7+
import org.springframework.transaction.annotation.Transactional;
8+
9+
import com.back.api.event.dto.request.EventCreateRequest;
10+
import com.back.api.event.dto.request.EventUpdateRequest;
11+
import com.back.api.event.dto.response.AdminEventDashboardResponse;
12+
import com.back.api.event.dto.response.EventResponse;
13+
import com.back.domain.event.entity.Event;
14+
import com.back.domain.event.repository.EventRepository;
15+
import com.back.domain.preregister.entity.PreRegisterStatus;
16+
import com.back.domain.preregister.repository.PreRegisterRepository;
17+
import com.back.domain.seat.entity.SeatStatus;
18+
import com.back.domain.seat.repository.SeatRepository;
19+
import com.back.global.error.code.EventErrorCode;
20+
import com.back.global.error.exception.ErrorException;
21+
22+
import lombok.RequiredArgsConstructor;
23+
24+
@Service
25+
@RequiredArgsConstructor
26+
@Transactional(readOnly = true)
27+
public class AdminEventService {
28+
29+
private final EventService eventService;
30+
private final EventRepository eventRepository;
31+
private final PreRegisterRepository preRegisterRepository;
32+
private final SeatRepository seatRepository;
33+
34+
@Transactional
35+
public EventResponse createEvent(EventCreateRequest request) {
36+
return eventService.createEvent(request);
37+
}
38+
39+
@Transactional
40+
public EventResponse updateEvent(Long eventId, EventUpdateRequest request) {
41+
return eventService.updateEvent(eventId, request);
42+
}
43+
44+
@Transactional
45+
public void deleteEvent(Long eventId) {
46+
eventService.deleteEvent(eventId);
47+
}
48+
49+
public List<AdminEventDashboardResponse> getAllEventsDashboard() {
50+
List<Event> events = eventRepository.findAll();
51+
52+
return events.stream()
53+
.map(event -> {
54+
Long eventId = event.getId();
55+
56+
// 1. 이벤트별 현재 사전등록 인원 수 조회
57+
Long preRegisterCount = preRegisterRepository.countByEvent_IdAndPreRegisterStatus(
58+
eventId,
59+
PreRegisterStatus.REGISTERED
60+
);
61+
62+
// 2. 이벤트별 총 판매 좌석 조회 (SOLD 상태인 좌석)
63+
Long totalSoldSeats = seatRepository.countByEventIdAndSeatStatus(eventId, SeatStatus.SOLD);
64+
65+
// 3. 이벤트별 총 판매 금액 조회
66+
Long totalSalesAmount = seatRepository.sumPriceByEventIdAndSeatStatus(eventId, SeatStatus.SOLD);
67+
68+
return AdminEventDashboardResponse.of(
69+
event.getId(),
70+
event.getTitle(),
71+
event.getStatus(),
72+
preRegisterCount,
73+
totalSoldSeats,
74+
totalSalesAmount != null ? totalSalesAmount : 0L
75+
);
76+
})
77+
.collect(Collectors.toList());
78+
}
79+
}

0 commit comments

Comments
 (0)