Skip to content

Commit 2d8609b

Browse files
authored
Feat: 관리자 대시보드 (#235)
* Feat: 관리자 대시보드 - 이벤트 페이지 조회 비즈니스 로직 구현 * Feat: 관리자 대시보드 - 티켓 페이지 조회 비즈니스 로직 구현 * Feat: 관리자 대시보드 - 특정 행사 상세보기 페이지 구현 및 특정 행사 삭제 로직 연결 * Feat: 관리자 대시보드 - 이벤트 관리에서 특정 해상에 대한 삭제 및 복구 기능 구현 * Feat: 관리자 대시보드 - 이벤트 관리에서 삭제 기능 개편(선택 삭제, 일괄 삭제) * Feat: 관리자 대시보드 - 모니터링을 위한 페이지 생성
1 parent 56494d4 commit 2d8609b

File tree

11 files changed

+1091
-119
lines changed

11 files changed

+1091
-119
lines changed

service/main-server/src/main/java/org/codeNbug/mainserver/domain/admin/controller/AdminController.java

+216-65
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.codeNbug.mainserver.domain.admin.dto.response;
2+
3+
import java.time.LocalDateTime;
4+
5+
import org.codeNbug.mainserver.domain.event.entity.Event;
6+
import org.codeNbug.mainserver.domain.event.entity.EventCategoryEnum;
7+
import org.codeNbug.mainserver.domain.event.entity.EventInformation;
8+
9+
import lombok.AllArgsConstructor;
10+
import lombok.Builder;
11+
import lombok.Getter;
12+
import lombok.NoArgsConstructor;
13+
14+
@Getter
15+
@Builder
16+
@NoArgsConstructor
17+
@AllArgsConstructor
18+
public class EventAdminDto {
19+
private Long eventId;
20+
private String title;
21+
private String category;
22+
private LocalDateTime eventStart;
23+
private LocalDateTime eventEnd;
24+
private Integer seatCount; // 총 티켓 수
25+
private Integer soldTickets; // 판매된 티켓 수
26+
private String status;
27+
28+
public static EventAdminDto fromEntity(Event event, int soldTickets) {
29+
EventInformation information = event.getInformation();
30+
31+
return EventAdminDto.builder()
32+
.eventId(event.getEventId())
33+
.title(information.getTitle())
34+
.category(event.getCategory().name())
35+
.eventStart(information.getEventStart())
36+
.eventEnd(information.getEventEnd())
37+
.seatCount(information.getSeatCount())
38+
.soldTickets(soldTickets)
39+
.status(event.getStatus().name())
40+
.build();
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.codeNbug.mainserver.domain.admin.dto.response;
2+
3+
import java.time.LocalDateTime;
4+
5+
import org.codeNbug.mainserver.domain.purchase.entity.PaymentStatusEnum;
6+
7+
import lombok.AllArgsConstructor;
8+
import lombok.Builder;
9+
import lombok.Getter;
10+
import lombok.NoArgsConstructor;
11+
12+
@Getter
13+
@Builder
14+
@NoArgsConstructor
15+
@AllArgsConstructor
16+
public class TicketAdminDto {
17+
private Long ticketId;
18+
private Long eventId;
19+
private String eventTitle;
20+
private String userName;
21+
private String userEmail;
22+
private String phoneNumber;
23+
private String seatInfo;
24+
private PaymentStatusEnum paymentStatus;
25+
private Integer amount;
26+
private LocalDateTime purchaseDate;
27+
}

service/main-server/src/main/java/org/codeNbug/mainserver/domain/admin/service/AdminService.java

+269
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@
55
import org.codeNbug.mainserver.domain.admin.dto.response.AdminLoginResponse;
66
import org.codeNbug.mainserver.domain.admin.dto.response.AdminSignupResponse;
77
import org.codeNbug.mainserver.domain.admin.dto.response.DashboardStatsResponse;
8+
import org.codeNbug.mainserver.domain.admin.dto.response.EventAdminDto;
89
import org.codeNbug.mainserver.domain.admin.dto.response.ModifyRoleResponse;
10+
import org.codeNbug.mainserver.domain.admin.dto.response.TicketAdminDto;
911
import org.codeNbug.mainserver.domain.event.entity.Event;
12+
import org.codeNbug.mainserver.domain.event.entity.EventStatusEnum;
1013
import org.codeNbug.mainserver.domain.manager.repository.EventRepository;
14+
import org.codeNbug.mainserver.domain.notification.entity.NotificationEnum;
15+
import org.codeNbug.mainserver.domain.notification.service.NotificationService;
16+
import org.codeNbug.mainserver.domain.purchase.entity.Purchase;
17+
import org.codeNbug.mainserver.domain.purchase.repository.PurchaseRepository;
1118
import org.codeNbug.mainserver.domain.ticket.repository.TicketRepository;
19+
import org.codeNbug.mainserver.global.exception.globalException.BadRequestException;
1220
import org.codeNbug.mainserver.global.exception.globalException.DuplicateEmailException;
1321
import org.codenbug.user.domain.user.entity.User;
1422
import org.codenbug.user.domain.user.repository.UserRepository;
@@ -23,9 +31,11 @@
2331
import lombok.RequiredArgsConstructor;
2432
import lombok.extern.slf4j.Slf4j;
2533

34+
import java.util.ArrayList;
2635
import java.util.HashMap;
2736
import java.util.List;
2837
import java.util.Map;
38+
import java.util.stream.Collectors;
2939

3040
/**
3141
* 관리자 관련 서비스
@@ -41,6 +51,8 @@ public class AdminService {
4151
private final TicketRepository ticketRepository;
4252
private final PasswordEncoder passwordEncoder;
4353
private final TokenService tokenService;
54+
private final NotificationService notificationService;
55+
private final PurchaseRepository purchaseRepository;
4456

4557
/**
4658
* 관리자 회원가입 서비스
@@ -314,4 +326,261 @@ else if ("sns".equals(userType)) {
314326
log.error(">> 유효하지 않은 사용자 타입: {}", userType);
315327
throw new IllegalArgumentException("유효하지 않은 사용자 타입입니다: " + userType);
316328
}
329+
330+
/**
331+
* 모든 이벤트 목록을 조회하고 각 이벤트의 티켓 정보를 포함하여 반환합니다.
332+
*
333+
* @return 이벤트 관리자 DTO 목록
334+
*/
335+
@Transactional(readOnly = true)
336+
public List<EventAdminDto> getAllEvents() {
337+
log.info(">> 모든 이벤트 목록 조회");
338+
339+
try {
340+
// 삭제되지 않은 이벤트만 조회
341+
List<Event> events = eventRepository.findAllByIsDeletedFalse();
342+
log.debug(">> 이벤트 목록 조회 완료: {} 개", events.size());
343+
344+
// 각 이벤트에 대한 정보와 티켓 정보를 포함한 DTO 생성
345+
List<EventAdminDto> eventDtos = events.stream()
346+
.map(event -> {
347+
// 해당 이벤트의 판매된 티켓 수 조회
348+
int soldTickets = ticketRepository.countPaidTicketsByEventId(event.getEventId());
349+
log.debug(">> 이벤트 ID={}, 판매된 티켓 수={}", event.getEventId(), soldTickets);
350+
351+
// DTO 생성
352+
return EventAdminDto.fromEntity(event, soldTickets);
353+
})
354+
.collect(Collectors.toList());
355+
356+
log.info(">> 이벤트 목록 조회 완료: {} 개의 이벤트", eventDtos.size());
357+
358+
return eventDtos;
359+
} catch (Exception e) {
360+
log.error(">> 이벤트 목록 조회 중 오류: {}", e.getMessage(), e);
361+
throw new RuntimeException("이벤트 목록 조회 중 오류가 발생했습니다: " + e.getMessage());
362+
}
363+
}
364+
365+
/**
366+
* 모든 티켓 목록을 조회합니다.
367+
*
368+
* @return 티켓 관리자 DTO 목록
369+
*/
370+
@Transactional(readOnly = true)
371+
public List<TicketAdminDto> getAllTickets() {
372+
log.info(">> 모든 티켓 목록 조회");
373+
374+
try {
375+
// 모든 티켓 조회
376+
List<TicketAdminDto> tickets = ticketRepository.findAllTicketsForAdmin();
377+
log.debug(">> 티켓 목록 조회 완료: {} 개", tickets.size());
378+
379+
log.info(">> 티켓 목록 조회 완료: {} 개의 티켓", tickets.size());
380+
381+
return tickets;
382+
} catch (Exception e) {
383+
log.error(">> 티켓 목록 조회 중 오류: {}", e.getMessage(), e);
384+
throw new RuntimeException("티켓 목록 조회 중 오류가 발생했습니다: " + e.getMessage());
385+
}
386+
}
387+
388+
/**
389+
* 특정 이벤트 정보를 조회합니다.
390+
*
391+
* @param eventId 조회할 이벤트 ID
392+
* @return 이벤트 관리자 DTO
393+
* @throws RuntimeException 이벤트를 찾을 수 없거나 오류 발생 시
394+
*/
395+
@Transactional(readOnly = true)
396+
public EventAdminDto getEvent(Long eventId) {
397+
log.info(">> 이벤트 상세 정보 조회: id={}", eventId);
398+
399+
try {
400+
// 이벤트 조회
401+
Event event = eventRepository.findById(eventId)
402+
.orElseThrow(() -> new RuntimeException("해당 이벤트를 찾을 수 없습니다: " + eventId));
403+
404+
// 해당 이벤트의 판매된 티켓 수 조회
405+
int soldTickets = ticketRepository.countPaidTicketsByEventId(eventId);
406+
log.debug(">> 이벤트 ID={}, 판매된 티켓 수={}", event.getEventId(), soldTickets);
407+
408+
// DTO 생성
409+
EventAdminDto eventDto = EventAdminDto.fromEntity(event, soldTickets);
410+
411+
log.info(">> 이벤트 상세 정보 조회 완료: id={}, 제목={}", eventId, eventDto.getTitle());
412+
413+
return eventDto;
414+
} catch (Exception e) {
415+
log.error(">> 이벤트 상세 정보 조회 중 오류: {}", e.getMessage(), e);
416+
throw new RuntimeException("이벤트 상세 정보 조회 중 오류가 발생했습니다: " + e.getMessage());
417+
}
418+
}
419+
420+
/**
421+
* 삭제 대기 중인 이벤트 목록을 조회합니다.
422+
*
423+
* @return 삭제 대기 중인 이벤트 목록
424+
*/
425+
@Transactional(readOnly = true)
426+
public List<EventAdminDto> getDeletedEvents() {
427+
log.info(">> 삭제 대기 중인 이벤트 목록 조회");
428+
429+
try {
430+
// 삭제된 이벤트 조회
431+
List<Event> events = eventRepository.findAllByIsDeletedTrue();
432+
log.debug(">> 삭제된 이벤트 목록 조회 완료: {} 개", events.size());
433+
434+
// 각 이벤트에 대한 정보와 티켓 정보를 포함한 DTO 생성
435+
List<EventAdminDto> eventDtos = events.stream()
436+
.map(event -> {
437+
// 해당 이벤트의 판매된 티켓 수 조회
438+
int soldTickets = ticketRepository.countPaidTicketsByEventId(event.getEventId());
439+
log.debug(">> 이벤트 ID={}, 판매된 티켓 수={}", event.getEventId(), soldTickets);
440+
441+
// DTO 생성
442+
return EventAdminDto.fromEntity(event, soldTickets);
443+
})
444+
.collect(Collectors.toList());
445+
446+
log.info(">> 삭제된 이벤트 목록 조회 완료: {} 개의 이벤트", eventDtos.size());
447+
448+
return eventDtos;
449+
} catch (Exception e) {
450+
log.error(">> 삭제된 이벤트 목록 조회 중 오류: {}", e.getMessage(), e);
451+
throw new RuntimeException("삭제된 이벤트 목록 조회 중 오류가 발생했습니다: " + e.getMessage());
452+
}
453+
}
454+
455+
/**
456+
* 삭제된 이벤트를 복구합니다.
457+
*
458+
* @param eventId 복구할 이벤트 ID
459+
* @param status 복구 후 설정할 이벤트 상태
460+
* @return 복구된 이벤트 정보
461+
* @throws IllegalAccessException 이벤트가 삭제되지 않은 경우
462+
*/
463+
@Transactional
464+
public EventAdminDto restoreEvent(Long eventId, EventStatusEnum status) throws IllegalAccessException {
465+
log.info(">> 이벤트 복구 처리 시작: eventId={}, status={}", eventId, status);
466+
467+
Event event = eventRepository.findById(eventId)
468+
.orElseThrow(() -> {
469+
log.error(">> 이벤트 복구 실패: 존재하지 않는 이벤트 - eventId={}", eventId);
470+
return new BadRequestException("존재하지 않는 이벤트입니다.");
471+
});
472+
473+
// 이벤트가 삭제되지 않았다면 400에러 전송
474+
if (!event.getIsDeleted()) {
475+
log.warn(">> 이벤트 복구 실패: 삭제되지 않은 이벤트 - eventId={}", eventId);
476+
throw new IllegalAccessException("삭제되지 않은 이벤트입니다.");
477+
}
478+
479+
// 이벤트 상태 변경
480+
event.setIsDeleted(false);
481+
event.setStatus(status);
482+
log.info(">> 이벤트 상태 변경 완료: eventId={}, status={}", eventId, status);
483+
484+
// 알림 처리는 메인 로직과 분리하여 예외 처리
485+
try {
486+
// 해당 이벤트 구매자들 조회
487+
List<Purchase> purchases = purchaseRepository.findAllByEventId(eventId);
488+
log.debug(">> 이벤트 구매자 조회 완료: eventId={}, 구매자 수={}", eventId, purchases.size());
489+
490+
// 모든 구매자에게 행사 복구 알림 전송
491+
String notificationContent = String.format(
492+
"[%s] 행사가 복구되었습니다. 예매 내역을 확인해주세요.",
493+
event.getInformation().getTitle()
494+
);
495+
496+
for (Purchase purchase : purchases) {
497+
try {
498+
Long userId = purchase.getUser().getUserId();
499+
notificationService.createNotification(
500+
userId,
501+
NotificationEnum.EVENT,
502+
notificationContent
503+
);
504+
log.debug(">> 알림 전송 완료: userId={}, eventId={}", userId, eventId);
505+
} catch (Exception e) {
506+
log.error(">> 행사 복구 알림 전송 실패. 사용자ID: {}, 구매ID: {}, 오류: {}",
507+
purchase.getUser().getUserId(), purchase.getId(), e.getMessage(), e);
508+
// 개별 사용자 알림 실패는 다른 사용자 알림이나 이벤트 복구에 영향을 주지 않음
509+
}
510+
}
511+
} catch (Exception e) {
512+
log.error(">> 행사 복구 알림 처리 실패. 이벤트ID: {}, 오류: {}", eventId, e.getMessage(), e);
513+
// 알림 전체 실패는 이벤트 복구에 영향을 주지 않도록 예외를 무시함
514+
}
515+
516+
// 복구된 이벤트 정보 반환
517+
int soldTickets = ticketRepository.countPaidTicketsByEventId(eventId);
518+
EventAdminDto eventDto = EventAdminDto.fromEntity(event, soldTickets);
519+
520+
log.info(">> 이벤트 복구 처리 완료: eventId={}", eventId);
521+
return eventDto;
522+
}
523+
524+
/**
525+
* 이벤트를 삭제 처리하는 메서드입니다.
526+
* 관리자 권한으로 이벤트를 삭제하고, 관련된 구매자들에게 알림을 전송합니다.
527+
*
528+
* @param eventId 삭제할 이벤트 ID
529+
* @throws IllegalAccessException 이미 삭제된 이벤트인 경우
530+
*/
531+
@Transactional
532+
public void deleteEvent(Long eventId) throws IllegalAccessException {
533+
log.info(">> 이벤트 삭제 처리 시작: eventId={}", eventId);
534+
535+
Event event = eventRepository.findById(eventId)
536+
.orElseThrow(() -> {
537+
log.error(">> 이벤트 삭제 실패: 존재하지 않는 이벤트 - eventId={}", eventId);
538+
return new BadRequestException("존재하지 않는 이벤트입니다.");
539+
});
540+
541+
// 이벤트가 이미 삭제되었다면 400에러 전송
542+
if (event.getIsDeleted()) {
543+
log.warn(">> 이벤트 삭제 실패: 이미 삭제된 이벤트 - eventId={}", eventId);
544+
throw new IllegalAccessException("이미 삭제된 이벤트입니다.");
545+
}
546+
547+
// 이벤트 상태 변경
548+
event.setIsDeleted(true);
549+
event.setStatus(EventStatusEnum.CANCELLED);
550+
log.info(">> 이벤트 상태 변경 완료: eventId={}, status=CANCELLED", eventId);
551+
552+
// 알림 처리는 메인 로직과 분리하여 예외 처리
553+
try {
554+
// 해당 이벤트 구매자들 조회
555+
List<Purchase> purchases = purchaseRepository.findAllByEventId(eventId);
556+
log.debug(">> 이벤트 구매자 조회 완료: eventId={}, 구매자 수={}", eventId, purchases.size());
557+
558+
// 모든 구매자에게 행사 취소 알림 전송
559+
String notificationContent = String.format(
560+
"[%s] 행사가 취소되었습니다. 예매 내역을 확인해주세요.",
561+
event.getInformation().getTitle()
562+
);
563+
564+
for (Purchase purchase : purchases) {
565+
try {
566+
Long userId = purchase.getUser().getUserId();
567+
notificationService.createNotification(
568+
userId,
569+
NotificationEnum.EVENT,
570+
notificationContent
571+
);
572+
log.debug(">> 알림 전송 완료: userId={}, eventId={}", userId, eventId);
573+
} catch (Exception e) {
574+
log.error(">> 행사 취소 알림 전송 실패. 사용자ID: {}, 구매ID: {}, 오류: {}",
575+
purchase.getUser().getUserId(), purchase.getId(), e.getMessage(), e);
576+
// 개별 사용자 알림 실패는 다른 사용자 알림이나 이벤트 취소에 영향을 주지 않음
577+
}
578+
}
579+
} catch (Exception e) {
580+
log.error(">> 행사 취소 알림 처리 실패. 이벤트ID: {}, 오류: {}", eventId, e.getMessage(), e);
581+
// 알림 전체 실패는 이벤트 취소에 영향을 주지 않도록 예외를 무시함
582+
}
583+
584+
log.info(">> 이벤트 삭제 처리 완료: eventId={}", eventId);
585+
}
317586
}

0 commit comments

Comments
 (0)