diff --git a/.gitignore b/.gitignore index 59060b61..27242490 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ /.idea/ /Server/out/ /Server/src/main/resources/application-local.yml +/Server/logs/ +/logs/ + diff --git a/Server/build.gradle b/Server/build.gradle index edfad680..151cd640 100644 --- a/Server/build.gradle +++ b/Server/build.gradle @@ -34,8 +34,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' - + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0") runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' @@ -49,6 +48,10 @@ dependencies { annotationProcessor('org.projectlombok:lombok') implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3' + // prometheus + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'io.micrometer:micrometer-registry-prometheus' + } tasks.named('test') { diff --git a/Server/src/main/java/JGS/CasperEvent/CasperEventApplication.java b/Server/src/main/java/JGS/CasperEvent/CasperEventApplication.java index b5910477..07b2d937 100644 --- a/Server/src/main/java/JGS/CasperEvent/CasperEventApplication.java +++ b/Server/src/main/java/JGS/CasperEvent/CasperEventApplication.java @@ -2,10 +2,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableAspectJAutoProxy +@EnableScheduling +@EnableCaching public class CasperEventApplication { public static void main(String[] args) { diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/adminController/AdminController.java b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/adminController/AdminController.java index 8a09bc77..9fb05af1 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/adminController/AdminController.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/adminController/AdminController.java @@ -1,16 +1,21 @@ package JGS.CasperEvent.domain.event.controller.adminController; -import JGS.CasperEvent.domain.event.dto.RequestDto.AdminRequestDto; -import JGS.CasperEvent.domain.event.dto.RequestDto.lotteryEventDto.LotteryEventRequestDto; -import JGS.CasperEvent.domain.event.dto.RequestDto.rushEventDto.RushEventRequestDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.ImageUrlResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.*; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.AdminRushEventOptionResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.AdminRushEventResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.LotteryEventWinnerListResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.RushEventParticipantsListResponseDto; +import JGS.CasperEvent.domain.event.dto.request.AdminRequestDto; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.LotteryEventRequestDto; +import JGS.CasperEvent.domain.event.dto.request.rushEventDto.RushEventRequestDto; +import JGS.CasperEvent.domain.event.dto.response.ImageUrlResponseDto; +import JGS.CasperEvent.domain.event.dto.response.ParticipantsListResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.ExpectationsPagingResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; import JGS.CasperEvent.domain.event.service.adminService.AdminService; import JGS.CasperEvent.global.response.ResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -20,13 +25,18 @@ import java.util.List; +@Tag(name = "관리자 API", description = "관리자 관련 API 목록입니다.") @RestController @RequestMapping("/admin") @RequiredArgsConstructor public class AdminController { private final AdminService adminService; - // 어드민 생성 + @Operation(summary = "어드민 객체 생성", description = "아이디와 비밀번호를 이용해 어드민 객체를 생성합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Admin account created successfully."), + @ApiResponse(responseCode = "409", description = "The ID is already in use.") + }) @PostMapping("/join") public ResponseEntity postAdmin(@RequestBody @Valid AdminRequestDto adminRequestDto) { return ResponseEntity @@ -34,7 +44,11 @@ public ResponseEntity postAdmin(@RequestBody @Valid AdminRequestDto .body(adminService.postAdmin(adminRequestDto)); } - // 이미지 업로드 + @Operation(summary = "이미지 업로드", description = "AWS S3 버킷 이미지 업로드 요청입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Image uploaded successfully."), + @ApiResponse(responseCode = "400", description = "Failed to upload image.") + }) @PostMapping("/image") public ResponseEntity postImage(@RequestPart(value = "image") MultipartFile image) { return ResponseEntity @@ -42,15 +56,24 @@ public ResponseEntity postImage(@RequestPart(value = "image .body(adminService.postImage(image)); } - // 추첨 이벤트 조회 + @Operation(summary = "추첨 이벤트 조회", description = "현재 데이터베이스에 존재하는 추첨 이벤트를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Lottery event retrieved successfully."), + @ApiResponse(responseCode = "404", description = "No lottery event found in the database."), + @ApiResponse(responseCode = "409", description = "Multiple lottery events found in the database.") + }) @GetMapping("/event/lottery") - public ResponseEntity getLotteryEvent() { + public ResponseEntity getLotteryEvent() { return ResponseEntity .status(HttpStatus.OK) .body(adminService.getLotteryEvent()); } - // 추첨 이벤트 생성 + @Operation(summary = "추첨 이벤트 생성", description = "이벤트 시작 날짜, 종료 날짜, 당첨인원을 통해 추첨 이벤트를 생성합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Lottery event created successfully."), + @ApiResponse(responseCode = "409", description = "A lottery event already exists in the database.") + }) @PostMapping("/event/lottery") public ResponseEntity createLotteryEvent( @Valid @RequestBody LotteryEventRequestDto lotteryEventRequestDto) { @@ -59,9 +82,10 @@ public ResponseEntity createLotteryEvent( .body(adminService.createLotteryEvent(lotteryEventRequestDto)); } - // 추첨 이벤트 참여자 조회 + @Operation(summary = "추첨 이벤트 참여자 조회", description = "추첨 이벤트 참여자를 조회합니다. size, page를 통해 페이지네이션이 가능하며, 전화번호를 통해 검색할 수 있습니다.") + @ApiResponse(responseCode = "200", description = "Lottery event participants retrieved successfully.") @GetMapping("/event/lottery/participants") - public ResponseEntity getLotteryEventParticipants( + public ResponseEntity> getLotteryEventParticipants( @RequestParam(name = "size", required = false, defaultValue = "10") int size, @RequestParam(name = "page", required = false, defaultValue = "0") int page, @RequestParam(name = "number", required = false, defaultValue = "") String phoneNumber) { @@ -70,9 +94,13 @@ public ResponseEntity getLotteryEventPa .body(adminService.getLotteryEventParticipants(size, page, phoneNumber)); } - // 선착순 이벤트 생성 + @Operation(summary = "선착순 이벤트 생성", description = "선착순 이벤트와 선택지 정보를 통해 선착순 이벤트를 생성합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Rush event created successfully."), + @ApiResponse(responseCode = "409", description = "More than six rush events already exist in the database.") + }) @PostMapping("/event/rush") - public ResponseEntity createRushEvent( + public ResponseEntity createRushEvent( @RequestPart(value = "dto") RushEventRequestDto rushEventRequestDto, @RequestPart(value = "prizeImg") MultipartFile prizeImg, @RequestPart(value = "leftOptionImg") MultipartFile leftOptionImg, @@ -82,17 +110,19 @@ public ResponseEntity createRushEvent( .body(adminService.createRushEvent(rushEventRequestDto, prizeImg, leftOptionImg, rightOptionImg)); } - // 선착순 이벤트 전체 조회 + @Operation(summary = "선착순 이벤트 조회", description = "현재 데이터베이스에 존재하는 전체 선착순 이벤트를 조회합니다.") + @ApiResponse(responseCode = "200", description = "Rush events retrieved successfully.") @GetMapping("/event/rush") - public ResponseEntity> getRushEvents() { + public ResponseEntity> getRushEvents() { return ResponseEntity .status(HttpStatus.OK) .body(adminService.getRushEvents()); } - // 선착순 이벤트 참여자 조회 + @Operation(summary = "선착순 이벤트 참여자 조회", description = "선착순 이벤트 참여자를 조회합니다. rushEventId가 필요합니다. size, page를 통해 페이지네이션이 가능하며, 전화번호를 통해 검색할 수 있습니다.") + @ApiResponse(responseCode = "200", description = "Rush event participants retrieved successfully.") @GetMapping("/event/rush/{rushEventId}/participants") - public ResponseEntity getRushEventParticipants( + public ResponseEntity> getRushEventParticipants( @PathVariable("rushEventId") Long rushEventId, @RequestParam(name = "size", required = false, defaultValue = "10") int size, @RequestParam(name = "page", required = false, defaultValue = "0") int page, @@ -103,9 +133,10 @@ public ResponseEntity getRushEventParticip .body(adminService.getRushEventParticipants(rushEventId, size, page, option, phoneNumber)); } - // 선착순 이벤트 당첨자 조회 + @Operation(summary = "선착순 이벤트 당첨자 조회", description = "선착순 이벤트 당첨자를 조회합니다. rushEventId가 필요합니다. size, page를 통해 페이지네이션이 가능하며, 전화번호를 통해 검색할 수 있습니다.") + @ApiResponse(responseCode = "200", description = "Rush event winners retrieved successfully.") @GetMapping("/event/rush/{rushEventId}/winner") - public ResponseEntity getRushEventWinners( + public ResponseEntity> getRushEventWinners( @PathVariable("rushEventId") Long rushEventId, @RequestParam(name = "size", required = false, defaultValue = "10") int size, @RequestParam(name = "page", required = false, defaultValue = "0") int page, @@ -115,9 +146,13 @@ public ResponseEntity getRushEventWinners( .body(adminService.getRushEventWinners(rushEventId, size, page, phoneNumber)); } - // 선착순 이벤트 수정 + @Operation(summary = "선착순 이벤트 수정", description = "선착순 이벤트 정보를 통해 이벤트를 수정합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Rush event updated successfully."), + @ApiResponse(responseCode = "400", description = "Failed to update rush event.") + }) @PutMapping("/event/rush") - public ResponseEntity> updateRushEvent( + public ResponseEntity> updateRushEvent( @RequestBody List rushEventListRequestDto) { return ResponseEntity @@ -125,7 +160,11 @@ public ResponseEntity> updateRushEvent( .body(adminService.updateRushEvents(rushEventListRequestDto)); } - // 선착순 이벤트 삭제 + @Operation(summary = "선착순 이벤트를 삭제", description = "rushEventId를 통해 선착순 이벤트를 삭제합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Rush event deleted successfully."), + @ApiResponse(responseCode = "404", description = "No rush event found matching the provided ID.") + }) @DeleteMapping("/event/rush/{rushEventId}") public ResponseEntity deleteRushEvent(@PathVariable Long rushEventId) { return ResponseEntity @@ -133,15 +172,20 @@ public ResponseEntity deleteRushEvent(@PathVariable Long rushEventI .body(adminService.deleteRushEvent(rushEventId)); } - // 선착순 이벤트 선택지 조회 + @Operation(summary = "선착순 이벤트 선택지 조회", description = "rushEventId를 통해 선착순 이벤트의 선택지를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Rush event options retrieved successfully."), + @ApiResponse(responseCode = "404", description = "No rush event found matching the provided ID.") + }) @GetMapping("/event/rush/{rushEventId}/options") - public ResponseEntity getRushEventOptions(@PathVariable Long rushEventId) { + public ResponseEntity getRushEventOptions(@PathVariable Long rushEventId) { return ResponseEntity .status(HttpStatus.OK) .body(adminService.getRushEventOptions(rushEventId)); } - // 추첨 이벤트 삭제 + @Operation(summary = "선착순 이벤트 삭제", description = "현재 진행중인 선착순 이벤트를 삭제합니다.") + @ApiResponse(responseCode = "204", description = "Ongoing rush event deleted successfully.") @DeleteMapping("/event/lottery") public ResponseEntity deleteLotteryEvent() { adminService.deleteLotteryEvent(); @@ -149,25 +193,37 @@ public ResponseEntity deleteLotteryEvent() { } - // 추첨 이벤트 수정 + @Operation(summary = "추첨 이벤트 수정", description = "추첨 이벤트 정보를 통해 이벤트를 수정합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Lottery event updated successfully."), + @ApiResponse(responseCode = "400", description = "Failed to update lottery event.") + }) @PutMapping("/event/lottery") - public ResponseEntity updateLotteryEvent(@RequestBody @Valid LotteryEventRequestDto lotteryEventRequestDto) { - LotteryEventDetailResponseDto updatedLotteryEventDetailResponseDto = adminService.updateLotteryEvent(lotteryEventRequestDto); + public ResponseEntity updateLotteryEvent(@RequestBody @Valid LotteryEventRequestDto lotteryEventRequestDto) { + LotteryEventResponseDto updatedLotteryEventDetailResponseDto = adminService.updateLotteryEvent(lotteryEventRequestDto); return ResponseEntity.ok(updatedLotteryEventDetailResponseDto); } - // 추첨 이벤트 특정 사용자의 기대평 조회 + @Operation(summary = "추첨 이벤트 기대평 조회", description = "participantId를 통해 특정 참가자의 기대평을 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Lottery event expectations retrieved successfully."), + @ApiResponse(responseCode = "404", description = "User not found or did not participate.") + }) @GetMapping("/event/lottery/participants/{participantId}/expectations") - public ResponseEntity getLotteryEventExpectations(@PathVariable("participantId") Long participantId, - @RequestParam(name = "page", required = false, defaultValue = "0") int page, - @RequestParam(name = "size", required = false, defaultValue = "10") int size) { - LotteryEventExpectationsResponseDto lotteryEventExpectationResponseDtoList = adminService.getLotteryEventExpectations(page, size, participantId); + public ResponseEntity getLotteryEventExpectations(@PathVariable("participantId") Long participantId, + @RequestParam(name = "page", required = false, defaultValue = "0") int page, + @RequestParam(name = "size", required = false, defaultValue = "10") int size) { + ExpectationsPagingResponseDto lotteryEventExpectationResponseDtoList = adminService.getLotteryEventExpectations(page, size, participantId); return ResponseEntity.ok(lotteryEventExpectationResponseDtoList); } - // 추첨 이벤트 특정 기대평을 삭제 + @Operation(summary = "추첨 이벤트 기대평 삭제", description = "casperId를 통해 기대평을 삭제합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Lottery event expectation deleted successfully."), + @ApiResponse(responseCode = "404", description = "Casper bot with the provided casperId not found.") + }) @PatchMapping("/event/lottery/expectations/{casperId}") public ResponseEntity deleteLotteryEventExpectation(@PathVariable("casperId") Long casperId) { adminService.deleteLotteryEventExpectation(casperId); @@ -175,7 +231,11 @@ public ResponseEntity deleteLotteryEventExpectation(@PathVariable("casperI return ResponseEntity.noContent().build(); } - // 추첨 이벤트 당첨자 추첨 + @Operation(summary = "추첨 이벤트 당첨자 추첨", description = "추첨 이벤트의 당첨자를 추첨합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Winners picked successfully."), + @ApiResponse(responseCode = "409", description = "Winners have already been picked for this lottery event.") + }) @PostMapping("/event/lottery/winner") public ResponseEntity pickLotteryEventWinners() { return ResponseEntity @@ -183,17 +243,22 @@ public ResponseEntity pickLotteryEventWinners() { .body(adminService.pickLotteryEventWinners()); } - // 추첨 이벤트 당첨자 삭제 + @Operation(summary = "추첨 이벤트 당첨자 초기화", description = "추첨 이벤트의 당첨자 테이블을 삭제합니다.") + @ApiResponse(responseCode = "200", description = "Lottery event winners reset successfully.") @DeleteMapping("/event/lottery/winner") - public ResponseEntity deleteLotteryEventWinners(){ + public ResponseEntity deleteLotteryEventWinners() { return ResponseEntity .status(HttpStatus.OK) .body(adminService.deleteLotteryEventWinners()); } - // 추첨 이벤트 당첨자 조회 + @Operation(summary = "추첨 이벤트 당첨자 조회", description = "추첨 이벤트의 당첨자를 조회합니다. size, page를 통해 페이지네이션이 가능하며, 전화번호를 통해 검색할 수 있습니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Lottery event winners retrieved successfully."), + @ApiResponse(responseCode = "400", description = "Lottery event has not yet been drawn.") + }) @GetMapping("/event/lottery/winner") - public ResponseEntity getWinners( + public ResponseEntity> getWinners( @RequestParam(name = "size", required = false, defaultValue = "10") int size, @RequestParam(name = "page", required = false, defaultValue = "0") int page, @RequestParam(name = "number", required = false, defaultValue = "") String phoneNumber) { diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/EventController.java b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/EventController.java index 3183dbe5..8549826e 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/EventController.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/EventController.java @@ -1,9 +1,10 @@ package JGS.CasperEvent.domain.event.controller.eventController; -import JGS.CasperEvent.domain.event.dto.ResponseDto.TotalEventDateResponseDto; +import JGS.CasperEvent.domain.event.dto.response.TotalEventDateResponseDto; import JGS.CasperEvent.domain.event.service.eventService.EventService; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -19,6 +20,8 @@ public class EventController { @GetMapping("/total") public ResponseEntity getTotalEventDate() { TotalEventDateResponseDto totalEventDateResponseDto = eventService.getTotalEventDate(); - return ResponseEntity.ok(totalEventDateResponseDto); + return ResponseEntity + .status(HttpStatus.OK) + .body(totalEventDateResponseDto); } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/LotteryEventController.java b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/LotteryEventController.java index 8223063a..53afd790 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/LotteryEventController.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/LotteryEventController.java @@ -1,12 +1,16 @@ package JGS.CasperEvent.domain.event.controller.eventController; -import JGS.CasperEvent.domain.event.dto.RequestDto.lotteryEventDto.CasperBotRequestDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.CasperBotResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.LotteryEventResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.LotteryParticipantResponseDto; -import JGS.CasperEvent.domain.event.service.redisService.RedisService; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.CasperBotRequestDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.CasperBotResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventResponseDto; +import JGS.CasperEvent.domain.event.service.redisService.LotteryEventRedisService; import JGS.CasperEvent.domain.event.service.eventService.LotteryEventService; import JGS.CasperEvent.global.entity.BaseUser; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -17,56 +21,73 @@ import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; -import java.nio.file.attribute.UserPrincipalNotFoundException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.List; +@Tag(name = "추첨 이벤트 API", description = "추첨 이벤트 (Lottery Event) 관련 API 목록입니다.") @RestController @RequestMapping("/event/lottery") public class LotteryEventController { private final LotteryEventService lotteryEventService; - private final RedisService redisService; + private final LotteryEventRedisService lotteryEventRedisService; @Autowired - public LotteryEventController(LotteryEventService lotteryEventService, RedisService redisService) { + public LotteryEventController(LotteryEventService lotteryEventService, LotteryEventRedisService lotteryEventRedisService) { this.lotteryEventService = lotteryEventService; - this.redisService = redisService; + this.lotteryEventRedisService = lotteryEventRedisService; } - // 추첨 이벤트 조회 API + @Operation(summary = "추첨 이벤트 조회", description = "현재 진행 중인 추첨 이벤트의 정보를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Lottery event retrieval successful"), + @ApiResponse(responseCode = "404", description = "No lottery event found in the database"), + @ApiResponse(responseCode = "409", description = "More than one lottery event exists in the database") + }) @GetMapping - public ResponseEntity getLotteryEvent(){ + public ResponseEntity getLotteryEvent() { return ResponseEntity .status(HttpStatus.OK) .body(lotteryEventService.getLotteryEvent()); } - // 캐스퍼 봇 생성 API + @Operation(summary = "캐스퍼 봇 생성", description = "새로운 캐스퍼 봇을 생성합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Casper bot creation successful"), + @ApiResponse(responseCode = "404", description = "No lottery event found in the database"), + @ApiResponse(responseCode = "409", description = "More than one lottery event exists in the database") + }) @PostMapping("/casperBot") public ResponseEntity postCasperBot( HttpServletRequest request, - @RequestBody @Valid CasperBotRequestDto postCasperBot) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + @RequestBody @Valid CasperBotRequestDto postCasperBot) { BaseUser user = (BaseUser) request.getAttribute("user"); return ResponseEntity .status(HttpStatus.CREATED) .body(lotteryEventService.postCasperBot(user, postCasperBot)); } - // 응모 여부 조회 API + @Operation(summary = "응모 여부 조회", description = "현재 사용자의 응모 여부를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Application status retrieval successful"), + @ApiResponse(responseCode = "404", description = "User has not applied") + }) @GetMapping("/applied") - public ResponseEntity GetLotteryParticipant(HttpServletRequest request) throws UserPrincipalNotFoundException { + public ResponseEntity getLotteryParticipant(HttpServletRequest request) { BaseUser user = (BaseUser) request.getAttribute("user"); return ResponseEntity .status(HttpStatus.OK) .body(lotteryEventService.getLotteryParticipant(user)); } - // 최근 100개 캐스퍼 봇 조회 + @Operation(summary = "최근 100개 캐스퍼 봇 조회", description = "최근에 생성된 100개의 캐스퍼 봇을 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Recent Casper bots retrieval successful") + }) @GetMapping("/caspers") public ResponseEntity> getCasperBots() { return ResponseEntity.status(HttpStatus.OK) - .body(redisService.getRecentData()); + .body(lotteryEventRedisService.getRecentData()); } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventController.java b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventController.java index ce9eb3d6..4742bbfe 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventController.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventController.java @@ -1,13 +1,22 @@ package JGS.CasperEvent.domain.event.controller.eventController; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.*; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventListResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventOptionResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResultResponseDto; import JGS.CasperEvent.domain.event.service.eventService.RushEventService; import JGS.CasperEvent.global.entity.BaseUser; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +@Tag(name = "선착순 이벤트 API", description = "선착순 이벤트 (Rush Event) 관련 API 목록입니다.") @RestController @RequestMapping("/event/rush") public class RushEventController { @@ -17,62 +26,93 @@ public RushEventController(RushEventService rushEventService) { this.rushEventService = rushEventService; } - // 전체 선착순 이벤트 조회 + @Operation(summary = "메인화면 선착순 이벤트 리스트 조회", description = "메인화면에 들어갈 선착순 이벤트 6개와 서버 시간 등을 조회합니다.") + @ApiResponse(responseCode = "200", description = "Successfully retrieved the list of rush events.") @GetMapping public ResponseEntity getRushEventListAndServerTime() { - return ResponseEntity.ok(rushEventService.getAllRushEvents()); + return ResponseEntity + .status(HttpStatus.OK) + .body(rushEventService.getAllRushEvents()); } - // 밸런스 게임 참여 여부 조회 + @Operation(summary = "응모 여부 체크", description = "해당 유저가 오늘의 이벤트에 응모했는지 여부를 조회합니다.") + @ApiResponse(responseCode = "200", description = "Successfully checked user participation.") @GetMapping("/applied") public ResponseEntity checkUserParticipationInRushEvent(HttpServletRequest httpServletRequest) { BaseUser user = (BaseUser) httpServletRequest.getAttribute("user"); - return ResponseEntity.ok(rushEventService.isExists(user.getId())); + return ResponseEntity + .status(HttpStatus.OK) + .body(rushEventService.isExists(user.getPhoneNumber())); } - // 밸런스 게임 응모 + @Operation(summary = "선착순 이벤트 응모", description = "해당 유저가 오늘의 이벤트에 응모합니다. optionId 값이 필요합니다. optionId 값이 1이면 왼쪽 선택지, 2이면 오른쪽 선택지에 응모합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Successfully applied for the rush event."), + @ApiResponse(responseCode = "400", description = "Invalid option ID provided."), + @ApiResponse(responseCode = "401", description = "Unauthorized to apply for the event.") + }) @PostMapping("/options/{optionId}/apply") public ResponseEntity applyRushEvent(HttpServletRequest httpServletRequest, @PathVariable("optionId") int optionId) { BaseUser user = (BaseUser) httpServletRequest.getAttribute("user"); rushEventService.apply(user, optionId); - return ResponseEntity.noContent().build(); + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .build(); } - // 밸런스 게임 비율 조회 + @Operation(summary = "실시간 응모 비율 조회", description = "실시간으로 변경되는 응모 비율을 조회합니다.") + @ApiResponse(responseCode = "200", description = "Successfully retrieved the balance rate.") @GetMapping("/balance") - public ResponseEntity rushEventRate(HttpServletRequest httpServletRequest) { + public ResponseEntity rushEventRate(HttpServletRequest httpServletRequest) { BaseUser user = (BaseUser) httpServletRequest.getAttribute("user"); - RushEventRateResponseDto rushEventRateResponseDto = rushEventService.getRushEventRate(user); - return ResponseEntity.ok(rushEventRateResponseDto); + RushEventResultResponseDto rushEventRateResponseDto = rushEventService.getRushEventRate(user); + return ResponseEntity + .status(HttpStatus.OK) + .body(rushEventRateResponseDto); } - // 밸런스 게임 결과 조회 + @Operation(summary = "선착순 이벤트 결과를 조회합니다.", description = "이벤트가 끝나고 나서 최종 결과를 조회합니다.") + @ApiResponse(responseCode = "200", description = "Successfully retrieved the rush event result.") @GetMapping("/result") public ResponseEntity rushEventResult(HttpServletRequest httpServletRequest) { BaseUser user = (BaseUser) httpServletRequest.getAttribute("user"); RushEventResultResponseDto result = rushEventService.getRushEventResult(user); - return ResponseEntity.ok(result); + return ResponseEntity + .status(HttpStatus.OK) + .body(result); } - // 레디스에 오늘의 이벤트 등록 테스트 api + @Operation(summary = "오늘의 이벤트를 초기화합니다.", description = "오늘의 이벤트를 전부 초기화하고, 응모 여부도 전부 초기화합니다.") + @ApiResponse(responseCode = "204", description = "Successfully set today's event in Redis.") @GetMapping("/today/test") public ResponseEntity setTodayEvent() { - rushEventService.setTodayEventToRedis(); - return ResponseEntity.noContent().build(); + rushEventService.setRushEvents(); + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .build(); } - // 오늘의 이벤트 선택지 조회 + @Operation(summary = "오늘의 이벤트 옵션을 조회합니다.", description = "이벤트 참여자가 이벤트에 진입했을 때 보여질 옵션 선택지 정보를 조회합니다.") + @ApiResponse(responseCode = "200", description = "Successfully retrieved today's rush event options.") @GetMapping("/today") - public ResponseEntity getTodayEvent() { - MainRushEventOptionsResponseDto mainRushEventOptionsResponseDto = rushEventService.getTodayRushEventOptions(); - return ResponseEntity.ok(mainRushEventOptionsResponseDto); + public ResponseEntity getTodayEvent() { + RushEventResponseDto mainRushEventOptionsResponseDto = rushEventService.getTodayRushEventOptions(); + return ResponseEntity + .status(HttpStatus.OK) + .body(mainRushEventOptionsResponseDto); } - // 옵션 선택 결과 조회 + @Operation(summary = "옵션의 결과 Text를 조회합니다.", description = "유저가 응모를 했을 때 보여질 옵션의 상세 Text를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved the option result."), + @ApiResponse(responseCode = "400", description = "Invalid option ID provided.") + }) @GetMapping("/options/{optionId}/result") - public ResponseEntity getResultOption(@PathVariable("optionId") int optionId) { - ResultRushEventOptionResponseDto resultRushEventOptionResponseDto = rushEventService.getRushEventOptionResult(optionId); - return ResponseEntity.ok(resultRushEventOptionResponseDto); + public ResponseEntity getResultOption(@PathVariable("optionId") int optionId) { + RushEventOptionResponseDto resultRushEventOptionResponseDto = rushEventService.getRushEventOptionResult(optionId); + return ResponseEntity + .status(HttpStatus.OK) + .body(resultRushEventOptionResponseDto); } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/AdminRequestDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/AdminRequestDto.java deleted file mode 100644 index 1f7c0821..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/AdminRequestDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.RequestDto; - -import jakarta.validation.constraints.NotNull; -import lombok.Getter; - -@Getter -public class AdminRequestDto { - - @NotNull - private String adminId; - - @NotNull - private String password; - -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/lotteryEventDto/LotteryEventRequestDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/lotteryEventDto/LotteryEventRequestDto.java deleted file mode 100644 index c8823502..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/lotteryEventDto/LotteryEventRequestDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.RequestDto.lotteryEventDto; - -import jakarta.validation.constraints.NotNull; -import lombok.Getter; - -import java.time.LocalDate; -import java.time.LocalTime; - -@Getter -public class LotteryEventRequestDto { - - @NotNull(message = "이벤트 시작 날짜를 지정하세요.") - private LocalDate startDate; - - @NotNull(message = "이벤트 시작 시간을 지정하세요.") - private LocalTime startTime; - - @NotNull(message = "이벤트 종료 날짜를 지정하세요.") - private LocalDate endDate; - - @NotNull(message = "이벤트 시작 시간을 지정하세요.") - private LocalTime endTime; - - @NotNull(message = "당첨인원 수를 지정하세요.") - private int winnerCount; -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/rushEventDto/RushEventOptionRequestDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/rushEventDto/RushEventOptionRequestDto.java deleted file mode 100644 index bdc49e20..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/rushEventDto/RushEventOptionRequestDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.RequestDto.rushEventDto; - -import JGS.CasperEvent.global.enums.Position; -import lombok.Getter; -import lombok.ToString; - -@ToString -@Getter -public class RushEventOptionRequestDto { - private Long rushOptionId; - private Position position; - private String mainText; - private String subText; - private String resultMainText; - private String resultSubText; - private String imageUrl; -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventDetailResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventDetailResponseDto.java deleted file mode 100644 index d7237273..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventDetailResponseDto.java +++ /dev/null @@ -1,37 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; -import JGS.CasperEvent.global.enums.EventStatus; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; - -public record LotteryEventDetailResponseDto( - LocalDate startDate, LocalTime startTime, - LocalDate endDate, LocalTime endTime, - int appliedCount, int winnerCount, - EventStatus status, - LocalDateTime createdAt, LocalDateTime updatedAt) { - - public static LotteryEventDetailResponseDto of(LotteryEvent event) { - - EventStatus status; - LocalDateTime now = LocalDateTime.now(); - - if (now.isBefore(event.getStartDateTime())) status = EventStatus.BEFORE; - else if (now.isAfter(event.getEndDateTime())) status = EventStatus.AFTER; - else status = EventStatus.DURING; - - return new LotteryEventDetailResponseDto( - event.getStartDateTime().toLocalDate(), - event.getStartDateTime().toLocalTime(), - event.getEndDateTime().toLocalDate(), - event.getEndDateTime().toLocalTime(), - event.getTotalAppliedCount(), - event.getWinnerCount(), - status, - event.getCreatedAt(), - event.getUpdatedAt()); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventExpectationResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventExpectationResponseDto.java deleted file mode 100644 index 42f7be75..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventExpectationResponseDto.java +++ /dev/null @@ -1,25 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.casperBot.CasperBot; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; - -public record LotteryEventExpectationResponseDto(Long casperId, String expectation, LocalDate createdDate, - LocalTime createdTime) { - - public static LotteryEventExpectationResponseDto of(CasperBot casperBot) { - - LocalDateTime createdAt = casperBot.getCreatedAt(); - LocalDate createdDate = createdAt.toLocalDate(); - LocalTime createdTime = createdAt.toLocalTime(); - - return new LotteryEventExpectationResponseDto( - casperBot.getCasperId(), - casperBot.getExpectation(), - createdDate, - createdTime - ); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventExpectationsResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventExpectationsResponseDto.java deleted file mode 100644 index 264370ae..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventExpectationsResponseDto.java +++ /dev/null @@ -1,7 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto; - -import java.util.List; - -public record LotteryEventExpectationsResponseDto(List expectations, - Boolean isLastPage, long totalExpectations) { -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventParticipantsListResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventParticipantsListResponseDto.java deleted file mode 100644 index fa26a30e..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventParticipantsListResponseDto.java +++ /dev/null @@ -1,6 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto; - -import java.util.List; - -public record LotteryEventParticipantsListResponseDto(List participantsList, Boolean isLastPage, long totalParticipants) { -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventParticipantsResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventParticipantsResponseDto.java deleted file mode 100644 index 35acc0ab..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventParticipantsResponseDto.java +++ /dev/null @@ -1,25 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; - -import java.time.LocalDate; -import java.time.LocalTime; - -public record LotteryEventParticipantsResponseDto( - Long id, String phoneNumber, int linkClickedCounts, - int expectation, int appliedCount, - LocalDate createdDate, LocalTime createdTime) { - - public static LotteryEventParticipantsResponseDto of(LotteryParticipants participant) { - return new LotteryEventParticipantsResponseDto( - participant.getId(), - participant.getBaseUser().getId(), - participant.getLinkClickedCount(), - participant.getExpectations(), - participant.getAppliedCount(), - participant.getBaseUser().getCreatedAt().toLocalDate(), - participant.getBaseUser().getCreatedAt().toLocalTime() - ); - } - -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventResponseDto.java deleted file mode 100644 index f9bfb9c7..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryEventResponseDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; - -public record LotteryEventResponseDto(LocalDateTime serverDateTime, LocalDateTime eventStartDate, - LocalDateTime eventEndDate, - long activePeriod) { - public static LotteryEventResponseDto of(LotteryEvent lotteryEvent, LocalDateTime serverDateTime) { - return new LotteryEventResponseDto( - serverDateTime, - lotteryEvent.getStartDateTime(), - lotteryEvent.getEndDateTime(), - ChronoUnit.DAYS.between(lotteryEvent.getStartDateTime(), lotteryEvent.getEndDateTime()) - ); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryParticipantResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryParticipantResponseDto.java deleted file mode 100644 index e29d8564..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/LotteryParticipantResponseDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto; -import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; - -import java.time.LocalDateTime; - - -public record LotteryParticipantResponseDto(int linkClickedCount, int expectations, int appliedCount, CasperBotResponseDto casperBot, - LocalDateTime createdAt, LocalDateTime updatedAt) { - public static LotteryParticipantResponseDto of(LotteryParticipants lotteryParticipants, CasperBotResponseDto casperBotResponseDto) { - return new LotteryParticipantResponseDto( - lotteryParticipants.getLinkClickedCount(), - lotteryParticipants.getExpectations(), - lotteryParticipants.getAppliedCount(), - casperBotResponseDto, - lotteryParticipants.getBaseUser().getCreatedAt(), - lotteryParticipants.getBaseUser().getUpdatedAt() - ); - } -} \ No newline at end of file diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/AdminRushEventOptionResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/AdminRushEventOptionResponseDto.java deleted file mode 100644 index bdac054c..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/AdminRushEventOptionResponseDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.event.RushEvent; - -import java.util.HashSet; -import java.util.Set; - -public record AdminRushEventOptionResponseDto(Set options) { - public static AdminRushEventOptionResponseDto of(RushEvent rushEvent){ - Set optionResponseDtoList = new HashSet<>(); - optionResponseDtoList.add(RushEventOptionResponseDto.of(rushEvent.getLeftOption())); - optionResponseDtoList.add(RushEventOptionResponseDto.of(rushEvent.getRightOption())); - return new AdminRushEventOptionResponseDto(optionResponseDtoList); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/AdminRushEventResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/AdminRushEventResponseDto.java deleted file mode 100644 index 459d710e..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/AdminRushEventResponseDto.java +++ /dev/null @@ -1,42 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.event.RushEvent; -import JGS.CasperEvent.global.enums.EventStatus; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Set; -import java.util.stream.Collectors; - -public record AdminRushEventResponseDto(Long rushEventId, LocalDate eventDate, - LocalTime startTime, LocalTime endTime, - int winnerCount, String prizeImageUrl, - String prizeDescription, LocalDateTime createdAt, LocalDateTime updatedAt, - EventStatus status, Set options) { - public static AdminRushEventResponseDto of(RushEvent rushEvent){ - Set options = rushEvent.getOptions().stream() - .map(RushEventOptionResponseDto::of) - .collect(Collectors.toSet()); - - LocalDateTime now = LocalDateTime.now(); - EventStatus status; - if (now.isBefore(rushEvent.getStartDateTime())) status = EventStatus.BEFORE; - else if (now.isAfter(rushEvent.getEndDateTime())) status = EventStatus.AFTER; - else status = EventStatus.DURING; - - return new AdminRushEventResponseDto( - rushEvent.getRushEventId(), - rushEvent.getStartDateTime().toLocalDate(), - rushEvent.getStartDateTime().toLocalTime(), - rushEvent.getEndDateTime().toLocalTime(), - rushEvent.getWinnerCount(), - rushEvent.getPrizeImageUrl(), - rushEvent.getPrizeDescription(), - rushEvent.getCreatedAt(), - rushEvent.getUpdatedAt(), - status, - options - ); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/LotteryEventWinnerListResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/LotteryEventWinnerListResponseDto.java deleted file mode 100644 index 83826efa..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/LotteryEventWinnerListResponseDto.java +++ /dev/null @@ -1,7 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import java.util.List; - -public record LotteryEventWinnerListResponseDto(List participantsList, - Boolean isLastPage, long totalParticipants) { -} \ No newline at end of file diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/LotteryEventWinnerResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/LotteryEventWinnerResponseDto.java deleted file mode 100644 index 25bcd113..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/LotteryEventWinnerResponseDto.java +++ /dev/null @@ -1,25 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.participants.LotteryWinners; - -import java.time.LocalDate; -import java.time.LocalTime; - -public record LotteryEventWinnerResponseDto( - Long id, String phoneNumber, int linkClickedCounts, - int expectation, int appliedCount, long ranking, - LocalDate createdDate, LocalTime createdTime) { - - public static LotteryEventWinnerResponseDto of(LotteryWinners lotteryWinner) { - return new LotteryEventWinnerResponseDto( - lotteryWinner.getId(), - lotteryWinner.getPhoneNumber(), - lotteryWinner.getLinkClickedCount(), - lotteryWinner.getExpectation(), - lotteryWinner.getAppliedCount(), - lotteryWinner.getRanking(), - lotteryWinner.getCreatedAt().toLocalDate(), - lotteryWinner.getCreatedAt().toLocalTime() - ); - } -} \ No newline at end of file diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/MainRushEventOptionResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/MainRushEventOptionResponseDto.java deleted file mode 100644 index b55446d0..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/MainRushEventOptionResponseDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -public record MainRushEventOptionResponseDto(String mainText, - String subText) { - - public static MainRushEventOptionResponseDto of(RushEventOptionResponseDto rushEventOptionResponseDto) { - return new MainRushEventOptionResponseDto( - rushEventOptionResponseDto.mainText(), - rushEventOptionResponseDto.subText() - ); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/MainRushEventOptionsResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/MainRushEventOptionsResponseDto.java deleted file mode 100644 index 6b1294c4..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/MainRushEventOptionsResponseDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - - -public record MainRushEventOptionsResponseDto(MainRushEventOptionResponseDto leftOption, - MainRushEventOptionResponseDto rightOption) { - - public MainRushEventOptionsResponseDto(MainRushEventOptionResponseDto leftOption, MainRushEventOptionResponseDto rightOption) { - this.leftOption = leftOption; - this.rightOption = rightOption; - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/MainRushEventResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/MainRushEventResponseDto.java deleted file mode 100644 index c73bde04..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/MainRushEventResponseDto.java +++ /dev/null @@ -1,27 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.event.RushEvent; -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -public class MainRushEventResponseDto { - private Long rushEventId; - private LocalDateTime startDateTime; - private LocalDateTime endDateTime; - - public MainRushEventResponseDto(Long rushEventId, LocalDateTime startDateTime, LocalDateTime endDateTime) { - this.rushEventId = rushEventId; - this.startDateTime = startDateTime; - this.endDateTime = endDateTime; - } - - public static MainRushEventResponseDto of (RushEvent rushEvent) { - return new MainRushEventResponseDto( - rushEvent.getRushEventId(), - rushEvent.getStartDateTime(), - rushEvent.getEndDateTime() - ); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/ResultRushEventOptionResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/ResultRushEventOptionResponseDto.java deleted file mode 100644 index 00ff31c5..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/ResultRushEventOptionResponseDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -public record ResultRushEventOptionResponseDto(String mainText, String resultMainText, String resultSubText) { - public static ResultRushEventOptionResponseDto of(RushEventOptionResponseDto rushEventOptionResponseDto) { - return new ResultRushEventOptionResponseDto( - rushEventOptionResponseDto.mainText(), - rushEventOptionResponseDto.resultMainText(), - rushEventOptionResponseDto.resultSubText() - ); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventOptionResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventOptionResponseDto.java deleted file mode 100644 index 5d09bf73..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventOptionResponseDto.java +++ /dev/null @@ -1,36 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.event.RushOption; -import JGS.CasperEvent.global.enums.Position; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; - -import java.time.LocalDateTime; - -public record RushEventOptionResponseDto(long optionId, String mainText, - String subText, String resultMainText, - String resultSubText, String imageUrl, - Position position, - @JsonSerialize(using = LocalDateTimeSerializer.class) - @JsonDeserialize(using = LocalDateTimeDeserializer.class) - LocalDateTime createdAt, - @JsonSerialize(using = LocalDateTimeSerializer.class) - @JsonDeserialize(using = LocalDateTimeDeserializer.class) - LocalDateTime updatedAt) { - public static RushEventOptionResponseDto of(RushOption rushOption) { - return new RushEventOptionResponseDto( - rushOption.getOptionId(), - rushOption.getMainText(), - rushOption.getSubText(), - rushOption.getResultMainText(), - rushOption.getResultSubText(), - rushOption.getImageUrl(), - rushOption.getPosition(), - rushOption.getCreatedAt(), - rushOption.getUpdatedAt() - ); - } - -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventParticipantResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventParticipantResponseDto.java deleted file mode 100644 index 6d40224a..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventParticipantResponseDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; - -import java.time.LocalDate; -import java.time.LocalTime; - -public record RushEventParticipantResponseDto(Long id, String phoneNumber, - int balanceGameChoice, LocalDate createdDate, LocalTime createdTime, - Long rank) { - public static RushEventParticipantResponseDto of(RushParticipants rushParticipants, Long rank){ - return new RushEventParticipantResponseDto( - rushParticipants.getId(), - rushParticipants.getBaseUser().getId(), - rushParticipants.getOptionId(), - rushParticipants.getCreatedAt().toLocalDate(), - rushParticipants.getCreatedAt().toLocalTime(), - rank - ); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventParticipantsListResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventParticipantsListResponseDto.java deleted file mode 100644 index 655c4b2e..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventParticipantsListResponseDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import java.util.List; -public record RushEventParticipantsListResponseDto(List participantsList, Boolean isLastPage, long totalParticipants) { -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventRateResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventRateResponseDto.java deleted file mode 100644 index 13001088..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventRateResponseDto.java +++ /dev/null @@ -1,4 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -public record RushEventRateResponseDto(int optionId, long leftOption, long rightOption) { -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventResponseDto.java deleted file mode 100644 index b73c65ae..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventResponseDto.java +++ /dev/null @@ -1,41 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import JGS.CasperEvent.domain.event.entity.event.RushEvent; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; - -import java.time.LocalDateTime; -import java.util.Set; -import java.util.stream.Collectors; - -public record RushEventResponseDto(Long rushEventId, - @JsonSerialize(using = LocalDateTimeSerializer.class) - @JsonDeserialize(using = LocalDateTimeDeserializer.class) - LocalDateTime startDateTime, - - @JsonSerialize(using = LocalDateTimeSerializer.class) - @JsonDeserialize(using = LocalDateTimeDeserializer.class) - LocalDateTime endDateTime, - - int winnerCount, String prizeImageUrl, - String prizeDescription, - Set options){ - - public static RushEventResponseDto of (RushEvent rushEvent){ - Set options = rushEvent.getOptions().stream() - .map(RushEventOptionResponseDto::of) - .collect(Collectors.toSet()); - - return new RushEventResponseDto( - rushEvent.getRushEventId(), - rushEvent.getStartDateTime(), - rushEvent.getEndDateTime(), - rushEvent.getWinnerCount(), - rushEvent.getPrizeImageUrl(), - rushEvent.getPrizeDescription(), - options - ); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventResultResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventResultResponseDto.java deleted file mode 100644 index acba931f..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventResultResponseDto.java +++ /dev/null @@ -1,22 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class RushEventResultResponseDto { - private final long leftOption; - private final long rightOption; - private final long rank; - private final long totalParticipants; - private final boolean isWinner; - - public RushEventResultResponseDto(RushEventRateResponseDto rushEventRateResponseDto, long rank, long totalParticipants, boolean isWinner) { - this.leftOption = rushEventRateResponseDto.leftOption(); - this.rightOption = rushEventRateResponseDto.rightOption(); - this.rank = rank; - this.totalParticipants = totalParticipants; - this.isWinner = isWinner; - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/AdminRequestDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/AdminRequestDto.java new file mode 100644 index 00000000..9c0ae770 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/AdminRequestDto.java @@ -0,0 +1,21 @@ +package JGS.CasperEvent.domain.event.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@Getter +@Builder +@EqualsAndHashCode +@Schema(description = "관리자 계정 생성 요청을 위한 데이터입니다.") +public class AdminRequestDto { + + @NotNull + @Schema(description = "관리자 아이디", example = "adminId") + private String adminId; + + @NotNull + @Schema(description = "관리자 인증을 위한 비밀번호", example = "password") + private String password; + +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/lotteryEventDto/CasperBotRequestDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/lotteryEventDto/CasperBotRequestDto.java similarity index 66% rename from Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/lotteryEventDto/CasperBotRequestDto.java rename to Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/lotteryEventDto/CasperBotRequestDto.java index 9a85979d..7698a6c3 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/lotteryEventDto/CasperBotRequestDto.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/lotteryEventDto/CasperBotRequestDto.java @@ -1,5 +1,6 @@ -package JGS.CasperEvent.domain.event.dto.RequestDto.lotteryEventDto; +package JGS.CasperEvent.domain.event.dto.request.lotteryEventDto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; @@ -10,37 +11,46 @@ @Getter @Builder @EqualsAndHashCode +@Schema(description = "캐스퍼 봇 생성 요청을 위한 데이터입니다.") public class CasperBotRequestDto { @NotNull(message = "눈 모양 값은 필수 필드입니다.") @Min(value = 0, message = "눈 모양 값이 부적절합니다.") @Max(value = 7, message = "눈 모양 값이 부적절합니다.") + @Schema(description = "눈 모양 값", example = "1") private Integer eyeShape; @NotNull(message = "눈 위치 값은 필수 필드입니다.") @Min(value = 0, message = "눈 위치 값이 부적절합니다.") @Max(value = 2, message = "눈 위치 값이 부적절합니다.") + @Schema(description = "눈 위치 값", example = "1") private Integer eyePosition; @NotNull(message = "입 모양 값은 필수 필드입니다.") @Min(value = 0, message = "입 모양 값이 부적절합니다.") @Max(value = 4, message = "입 모양 값이 부적절합니다.") + @Schema(description = "입 모양 값", example = "2") private Integer mouthShape; @NotNull(message = "색깔 값은 필수 필드입니다.") @Min(value = 0, message = "색깔 값이 부적절합니다.") @Max(value = 17, message = "색깔 값이 부적절합니다.") + @Schema(description = "색깔 값", example = "8") private Integer color; @NotNull(message = "스티커 값은 필수 필드입니다.") @Min(value = 0, message = "스티커 값이 부적절합니다.") @Max(value = 4, message = "스티커 값이 부적절합니다.") + @Schema(description = "스티커 값", example = "1") private Integer sticker; - @NotNull(message = "이름은 필수 필드입니다. ") + @NotNull(message = "이름은 필수 필드입니다.") + @Schema(description = "캐스퍼 봇의 이름", example = "MyCasperBot") private String name; + @Schema(description = "기대평", example = "캐스퍼 정말 기대되요!") private String expectation; + @Schema(description = "추천인 ID", example = "referralId") private String referralId = ""; } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/lotteryEventDto/LotteryEventRequestDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/lotteryEventDto/LotteryEventRequestDto.java new file mode 100644 index 00000000..31ffceea --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/lotteryEventDto/LotteryEventRequestDto.java @@ -0,0 +1,37 @@ +package JGS.CasperEvent.domain.event.dto.request.lotteryEventDto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalTime; + +@Getter +@Builder +@EqualsAndHashCode +@Schema(description = "추첨 이벤트를 생성하기 위한 요청 데이터입니다.") +public class LotteryEventRequestDto { + + @NotNull(message = "이벤트 시작 날짜를 지정하세요.") + @Schema(description = "이벤트의 시작 날짜", example = "2024-09-01") + private LocalDate startDate; + + @NotNull(message = "이벤트 시작 시간을 지정하세요.") + @Schema(description = "이벤트의 시작 시간", example = "14:00:00") + private LocalTime startTime; + + @NotNull(message = "이벤트 종료 날짜를 지정하세요.") + @Schema(description = "이벤트의 종료 날짜", example = "2024-09-30") + private LocalDate endDate; + + @NotNull(message = "이벤트 종료 시간을 지정하세요.") + @Schema(description = "이벤트의 종료 시간", example = "18:00:00") + private LocalTime endTime; + + @NotNull(message = "당첨인원 수를 지정하세요.") + @Schema(description = "당첨 인원 수", example = "10") + private int winnerCount; +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/rushEventDto/RushEventOptionRequestDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/rushEventDto/RushEventOptionRequestDto.java new file mode 100644 index 00000000..9e9451af --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/rushEventDto/RushEventOptionRequestDto.java @@ -0,0 +1,43 @@ +package JGS.CasperEvent.domain.event.dto.request.rushEventDto; + +import io.swagger.v3.oas.annotations.media.Schema; +import JGS.CasperEvent.global.enums.Position; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@Builder +@Schema(description = "선착순 이벤트 선택지를 생성하기 위한 요청 데이터입니다.") +public class RushEventOptionRequestDto { + + @Schema(description = "선착순 이벤트 선택지 ID", example = "1") + @NotNull(message = "선착순 이벤트 선택지 ID는 필수 값입니다.") + private Long rushOptionId; + + @Schema(description = "선택지의 위치", example = "LEFT", implementation = Position.class) + @NotNull(message = "선택지의 위치는 필수 값입니다.") + private Position position; + + @Schema(description = "선택지 메인 텍스트", example = "첫 차는 저렴해야 한다") + @NotNull(message = "선택지 메인 텍스트는 필수 값입니다.") + private String mainText; + + @Schema(description = "선택지 서브 텍스트", example = "가성비 좋게 저렴한 차로 시작해서 차근히 업그레이드하고 싶어") + @NotNull(message = "선택지 서브 텍스트는 필수 값입니다.") + private String subText; + + @Schema(description = "선택지 결과 메인 텍스트", example = "가성비 좋은 도심형 전기차") + @NotNull(message = "선택지 결과 메인 텍스트는 필수 값입니다.") + private String resultMainText; + + @Schema(description = "선택지 결과 서브 텍스트", example = "캐스퍼 일렉트릭은 전기차 평균보다 30% 저렴해요 첫 차로 캐스퍼 일렉트릭 어떤가요?") + @NotNull(message = "선택지 결과 서브 텍스트는 필수 값입니다.") + private String resultSubText; + + @Schema(description = "선택지 이미지 URL", example = "https://example.com/image.png") + @NotNull(message = "선택지 이미지 URL은 필수 값입니다.") + private String imageUrl; +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/rushEventDto/RushEventRequestDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/rushEventDto/RushEventRequestDto.java similarity index 64% rename from Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/rushEventDto/RushEventRequestDto.java rename to Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/rushEventDto/RushEventRequestDto.java index ed21c1b9..93b1ed7d 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/RequestDto/rushEventDto/RushEventRequestDto.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/request/rushEventDto/RushEventRequestDto.java @@ -1,9 +1,12 @@ -package JGS.CasperEvent.domain.event.dto.RequestDto.rushEventDto; +package JGS.CasperEvent.domain.event.dto.request.rushEventDto; +import io.swagger.v3.oas.annotations.media.Schema; import JGS.CasperEvent.global.enums.CustomErrorCode; import JGS.CasperEvent.global.enums.Position; import JGS.CasperEvent.global.error.exception.CustomException; import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; @@ -13,29 +16,40 @@ @ToString @Getter -//todo 검증 항목 추가 필요 +@Builder +@EqualsAndHashCode +@Schema(description = "선착순 이벤트 요청을 위한 데이터입니다.") public class RushEventRequestDto { + + @Schema(description = "선착순 이벤트 ID", example = "1") @NotNull(message = "선착순 이벤트 아이디는 필수 항목입니다.") private Long rushEventId; + @Schema(description = "이벤트 날짜", example = "2024-08-20") @NotNull(message = "이벤트 시작 날짜는 필수 항목입니다.") private LocalDate eventDate; + @Schema(description = "이벤트 시작 시간", example = "14:00:00") @NotNull(message = "이벤트 시작 시간은 필수 항목입니다.") private LocalTime startTime; - @NotNull(message = "이벤트 종료 시간 필수 항목입니다.") + @Schema(description = "이벤트 종료 시간", example = "16:00:00") + @NotNull(message = "이벤트 종료 시간은 필수 항목입니다.") private LocalTime endTime; + @Schema(description = "당첨자 수", example = "3") @NotNull(message = "당첨자 수는 필수 항목입니다.") private int winnerCount; + @Schema(description = "상품 사진 URL", example = "https://example.com/image.png") @NotNull(message = "상품 사진은 필수 항목입니다.") private String prizeImageUrl; + @Schema(description = "상품 상세 설명", example = "올리브영 1만원권") @NotNull(message = "상품 상세 정보는 필수 항목입니다.") private String prizeDescription; + @Schema(description = "선택지 목록") private Set options; public RushEventOptionRequestDto getLeftOptionRequestDto() { diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/ImageUrlResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/ImageUrlResponseDto.java similarity index 50% rename from Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/ImageUrlResponseDto.java rename to Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/ImageUrlResponseDto.java index d1c49e1d..6420276a 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/ImageUrlResponseDto.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/ImageUrlResponseDto.java @@ -1,4 +1,4 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto; +package JGS.CasperEvent.domain.event.dto.response; public record ImageUrlResponseDto(String imageUrl) { } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/ParticipantsListResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/ParticipantsListResponseDto.java new file mode 100644 index 00000000..500c7212 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/ParticipantsListResponseDto.java @@ -0,0 +1,6 @@ +package JGS.CasperEvent.domain.event.dto.response; + +import java.util.List; + +public record ParticipantsListResponseDto(List participantsList, Boolean isLastPage, long totalParticipants) { +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/TotalEventDateResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/TotalEventDateResponseDto.java similarity index 71% rename from Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/TotalEventDateResponseDto.java rename to Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/TotalEventDateResponseDto.java index 44fb6411..d48e3eb2 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/TotalEventDateResponseDto.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/TotalEventDateResponseDto.java @@ -1,4 +1,4 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto; +package JGS.CasperEvent.domain.event.dto.response; import java.time.LocalDate; diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/CasperBotResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/CasperBotResponseDto.java similarity index 91% rename from Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/CasperBotResponseDto.java rename to Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/CasperBotResponseDto.java index 7ec812ea..d6f27ff8 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/lotteryEventResponseDto/CasperBotResponseDto.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/CasperBotResponseDto.java @@ -1,4 +1,4 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto; +package JGS.CasperEvent.domain.event.dto.response.lottery; import JGS.CasperEvent.domain.event.entity.casperBot.CasperBot; import org.springframework.data.annotation.Id; diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/ExpectationsPagingResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/ExpectationsPagingResponseDto.java new file mode 100644 index 00000000..e7feb4dd --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/ExpectationsPagingResponseDto.java @@ -0,0 +1,7 @@ +package JGS.CasperEvent.domain.event.dto.response.lottery; + +import java.util.List; + +public record ExpectationsPagingResponseDto(List expectations, + Boolean isLastPage, long totalExpectations) { +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/LotteryEventParticipantResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/LotteryEventParticipantResponseDto.java new file mode 100644 index 00000000..1ee354a8 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/LotteryEventParticipantResponseDto.java @@ -0,0 +1,110 @@ +package JGS.CasperEvent.domain.event.dto.response.lottery; + +import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; +import JGS.CasperEvent.domain.event.entity.participants.LotteryWinners; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LotteryEventParticipantResponseDto { + private Long id; + private String phoneNumber; + + // 이름이 비슷하니 통일 필요 + private int linkClickedCount; + private int expectations; + + // 디테일에서 쓰는 변수 + private int linkClickedCounts; + private int expectation; + + private int appliedCount; + private Long ranking; + + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + private LocalDate createdDate; + private LocalTime createdTime; + + + private LotteryEventParticipantResponseDto( + int linkClickedCount, int expectations, int appliedCount, + LocalDateTime createdAt, LocalDateTime updatedAt) { + this.linkClickedCount = linkClickedCount; + this.expectations = expectations; + this.appliedCount = appliedCount; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public static LotteryEventParticipantResponseDto of(LotteryParticipants lotteryParticipants) { + return new LotteryEventParticipantResponseDto( + lotteryParticipants.getLinkClickedCount(), + lotteryParticipants.getExpectations(), + lotteryParticipants.getAppliedCount(), + lotteryParticipants.getCreatedAt(), + lotteryParticipants.getUpdatedAt() + ); + } + + private LotteryEventParticipantResponseDto( + Long id, String phoneNumber, int linkClickedCounts, + int expectation, int appliedCount, + LocalDate createdDate, LocalTime createdTime) { + this.id = id; + this.phoneNumber =phoneNumber; + this.linkClickedCounts = linkClickedCounts; + this.expectation = expectation; + this.appliedCount = appliedCount; + this.createdDate = createdDate; + this.createdTime = createdTime; + } + + public static LotteryEventParticipantResponseDto withDetail(LotteryParticipants lotteryParticipants) { + return new LotteryEventParticipantResponseDto( + lotteryParticipants.getId(), + lotteryParticipants.getBaseUser().getPhoneNumber(), + lotteryParticipants.getLinkClickedCount(), + lotteryParticipants.getExpectations(), + lotteryParticipants.getAppliedCount(), + lotteryParticipants.getCreatedAt().toLocalDate(), + lotteryParticipants.getCreatedAt().toLocalTime() + ); + } + + private LotteryEventParticipantResponseDto(Long id, String phoneNumber, + int linkClickedCounts, int expectation, + int appliedCount, Long ranking, + LocalDate createdDate, LocalTime createdTime) { + this.id = id; + this.phoneNumber = phoneNumber; + this.linkClickedCounts = linkClickedCounts; + this.expectation = expectation; + this.appliedCount = appliedCount; + this.ranking = ranking; + this.createdDate = createdDate; + this.createdTime = createdTime; + } + + // LotteryEventWinnerResponseDto + public static LotteryEventParticipantResponseDto winner(LotteryWinners lotteryWinner) { + return new LotteryEventParticipantResponseDto( + lotteryWinner.getId(), + lotteryWinner.getPhoneNumber(), + lotteryWinner.getLinkClickedCount(), + lotteryWinner.getExpectation(), + lotteryWinner.getAppliedCount(), + lotteryWinner.getRanking(), + lotteryWinner.getCreatedAt().toLocalDate(), + lotteryWinner.getCreatedAt().toLocalTime() + ); + } + + +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/LotteryEventResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/LotteryEventResponseDto.java new file mode 100644 index 00000000..41394bb4 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/lottery/LotteryEventResponseDto.java @@ -0,0 +1,111 @@ +package JGS.CasperEvent.domain.event.dto.response.lottery; + +import JGS.CasperEvent.domain.event.entity.casperBot.CasperBot; +import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; +import JGS.CasperEvent.global.enums.EventStatus; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Getter +public class LotteryEventResponseDto { + private LocalDateTime serverDateTime; + private LocalDateTime eventStartDate; + private LocalDateTime eventEndDate; + + private LocalDate startDate; + private LocalTime startTime; + private LocalDate endDate; + private LocalTime endTime; + + private int winnerCount; + private EventStatus status; + + + private Long appliedCount; + private Long activePeriod; + + private Long casperId; + private String expectation; + + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + private LocalDate createdDate; + private LocalTime createdTime; + + private LotteryEventResponseDto(LocalDateTime serverDateTime, + LocalDateTime eventStartDate, + LocalDateTime eventEndDate) { + this.serverDateTime = serverDateTime; + this.eventStartDate = eventStartDate; + this.eventEndDate = eventEndDate; + this.activePeriod = ChronoUnit.DAYS.between(eventStartDate, eventEndDate); + } + + public static LotteryEventResponseDto of(LotteryEvent lotteryEvent, LocalDateTime serverDateTime) { + return new LotteryEventResponseDto( + serverDateTime, + lotteryEvent.getStartDateTime(), + lotteryEvent.getEndDateTime() + ); + } + + private LotteryEventResponseDto(LocalDate startDate, LocalTime startTime, + LocalDate endDate, LocalTime endTime, + Long appliedCount, int winnerCount, + EventStatus status, + LocalDateTime createdAt, LocalDateTime updatedAt) { + this.startDate = startDate; + this.startTime = startTime; + this.endDate = endDate; + this.endTime = endTime; + this.appliedCount = appliedCount; + this.winnerCount = winnerCount; + this.status = status; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public static LotteryEventResponseDto withDetail(LotteryEvent event, Long appliedCount) { + EventStatus status; + LocalDateTime now = LocalDateTime.now(); + + if (now.isBefore(event.getStartDateTime())) status = EventStatus.BEFORE; + else if (now.isAfter(event.getEndDateTime())) status = EventStatus.AFTER; + else status = EventStatus.DURING; + + return new LotteryEventResponseDto( + event.getStartDateTime().toLocalDate(), + event.getStartDateTime().toLocalTime(), + event.getEndDateTime().toLocalDate(), + event.getEndDateTime().toLocalTime(), + appliedCount, + event.getWinnerCount(), + status, + event.getCreatedAt(), + event.getUpdatedAt()); + } + + private LotteryEventResponseDto(Long casperId, String expectation, + LocalDate createdDate, LocalTime createdTime) { + this.casperId = casperId; + this.expectation = expectation; + this.createdDate = createdDate; + this.createdTime = createdTime; + } + + public static LotteryEventResponseDto withExpectation(CasperBot casperBot) { + return new LotteryEventResponseDto( + casperBot.getCasperId(), + casperBot.getExpectation(), + casperBot.getCreatedAt().toLocalDate(), + casperBot.getCreatedAt().toLocalTime() + ); + } +} \ No newline at end of file diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventListResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventListResponseDto.java similarity index 60% rename from Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventListResponseDto.java rename to Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventListResponseDto.java index ec8b0749..4b2f367c 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/rushEventResponseDto/RushEventListResponseDto.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventListResponseDto.java @@ -1,5 +1,6 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto; +package JGS.CasperEvent.domain.event.dto.response.rush; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Getter; import java.time.LocalDate; @@ -7,15 +8,16 @@ import java.util.List; @Getter +@JsonInclude(JsonInclude.Include.NON_NULL) public class RushEventListResponseDto { - private List events; + private List events; private LocalDateTime serverTime; private Long todayEventId; private LocalDate eventStartDate; private LocalDate eventEndDate; private Long activePeriod; - public RushEventListResponseDto(List events, LocalDateTime serverTime, Long todayEventId, LocalDate eventStartDate, LocalDate eventEndDate, Long activePeriod) { + public RushEventListResponseDto(List events, LocalDateTime serverTime, Long todayEventId, LocalDate eventStartDate, LocalDate eventEndDate, Long activePeriod) { this.events = events; this.serverTime = serverTime; this.todayEventId = todayEventId; diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventOptionResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventOptionResponseDto.java new file mode 100644 index 00000000..658d6711 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventOptionResponseDto.java @@ -0,0 +1,107 @@ +package JGS.CasperEvent.domain.event.dto.response.rush; + +import JGS.CasperEvent.domain.event.entity.event.RushOption; +import JGS.CasperEvent.global.enums.Position; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Getter +@ToString +@JsonInclude(JsonInclude.Include.NON_NULL) +public class RushEventOptionResponseDto { + private Long optionId; + private String mainText; + private String subText; + private String resultMainText; + private String resultSubText; + private String imageUrl; + private Position position; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + + private RushEventOptionResponseDto(Long optionId, String mainText, + String subText, String resultMainText, + String resultSubText, String imageUrl, + Position position, LocalDateTime createdAt, + LocalDateTime updatedAt) { + this.optionId = optionId; + this.mainText = mainText; + this.subText = subText; + this.resultSubText = resultSubText; + this.resultMainText = resultMainText; + this.imageUrl = imageUrl; + this.position = position; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public static RushEventOptionResponseDto of(Long optionId, String mainText, + String subText, String resultMainText, + String resultSubText, String imageUrl, + Position position, LocalDateTime createdAt, + LocalDateTime updatedAt) { + return new RushEventOptionResponseDto( + optionId, mainText, subText, resultMainText, + resultSubText, imageUrl, position, createdAt, updatedAt); + } + + + // RushEventOptionResponseDto + public static RushEventOptionResponseDto of(RushOption rushOption) { + return new RushEventOptionResponseDto( + rushOption.getOptionId(), + rushOption.getMainText(), + rushOption.getSubText(), + rushOption.getResultMainText(), + rushOption.getResultSubText(), + rushOption.getImageUrl(), + rushOption.getPosition(), + rushOption.getCreatedAt(), + rushOption.getUpdatedAt() + ); + } + + private RushEventOptionResponseDto(String mainText, String subTest) { + this.mainText = mainText; + this.subText = subTest; + } + + // MainRushEventOptionResponseDto + public static RushEventOptionResponseDto inMain(RushEventOptionResponseDto rushEventOptionResponseDto) { + return new RushEventOptionResponseDto( + rushEventOptionResponseDto.getMainText(), + rushEventOptionResponseDto.getSubText() + ); + } + + public static RushEventOptionResponseDto inMain(String mainText, String subText) { + return new RushEventOptionResponseDto(mainText, subText); + } + + private RushEventOptionResponseDto(String mainText, String resultMainText, String resultSubText) { + this.mainText = mainText; + this.resultMainText = resultMainText; + this.resultSubText = resultSubText; + } + + // ResultRushEventOptionResponseDto + public static RushEventOptionResponseDto inResult(RushEventOptionResponseDto rushEventOptionResponseDto) { + return new RushEventOptionResponseDto( + rushEventOptionResponseDto.getMainText(), + rushEventOptionResponseDto.getResultMainText(), + rushEventOptionResponseDto.getResultSubText() + ); + } + + public static RushEventOptionResponseDto inResult(String mainText, String resultMainText, String resultSubText) { + return new RushEventOptionResponseDto( + mainText, resultMainText, resultSubText + ); + } + + +} \ No newline at end of file diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventParticipantResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventParticipantResponseDto.java new file mode 100644 index 00000000..4e4b113d --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventParticipantResponseDto.java @@ -0,0 +1,43 @@ +package JGS.CasperEvent.domain.event.dto.response.rush; + +import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalTime; + +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class RushEventParticipantResponseDto { + private Long id; + private String phoneNumber; + private LocalDate createdDate; + private LocalTime createdTime; + private int balanceGameChoice; + private Long rank; + + + private RushEventParticipantResponseDto(Long id, String phoneNumber, + int balanceGameChoice, LocalDate createdDate, + LocalTime createdTime, Long rank) { + this.id = id; + this.phoneNumber = phoneNumber; + this.balanceGameChoice = balanceGameChoice; + this.createdDate = createdDate; + this.createdTime = createdTime; + this.rank = rank; + } + + // RushEventParticipantResponseDto + public static RushEventParticipantResponseDto result(RushParticipants rushParticipants, Long rank) { + return new RushEventParticipantResponseDto( + rushParticipants.getId(), + rushParticipants.getBaseUser().getPhoneNumber(), + rushParticipants.getOptionId(), + rushParticipants.getCreatedAt().toLocalDate(), + rushParticipants.getCreatedAt().toLocalTime(), + rank + ); + } +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventResponseDto.java new file mode 100644 index 00000000..3e8241ad --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventResponseDto.java @@ -0,0 +1,169 @@ +package JGS.CasperEvent.domain.event.dto.response.rush; + +import JGS.CasperEvent.domain.event.entity.event.RushEvent; +import JGS.CasperEvent.global.enums.EventStatus; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import lombok.ToString; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +@Getter +@ToString +@JsonInclude(JsonInclude.Include.NON_NULL) +public class RushEventResponseDto { + private Long rushEventId; + private LocalDate eventDate; + private LocalTime startTime; + private LocalTime endTime; + private int winnerCount; + private String prizeImageUrl; + private String prizeDescription; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private EventStatus status; + + private Set options; + private RushEventOptionResponseDto leftOption; + private RushEventOptionResponseDto rightOption; + + private LocalDateTime startDateTime; + private LocalDateTime endDateTime; + + private RushEventResponseDto(Long rushEventId, LocalDateTime startDateTime, + LocalDateTime endDateTime, int winnerCount, + String prizeImageUrl, String prizeDescription, + Set options) { + this.rushEventId = rushEventId; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.winnerCount = winnerCount; + this.prizeImageUrl = prizeImageUrl; + this.prizeDescription = prizeDescription; + this.options = options; + } + + + public static RushEventResponseDto of(Long rushEventId, LocalDateTime startDateTime, LocalDateTime endDateTime, + int winnerCount, String prizeImageUrl, + String prizeDescription, + Set options) { + return new RushEventResponseDto( + rushEventId, startDateTime, endDateTime, winnerCount, prizeImageUrl, + prizeDescription, options); + } + + // RushEventResponseDto + public static RushEventResponseDto of(RushEvent rushEvent) { + Set options = rushEvent.getOptions().stream() + .map(RushEventOptionResponseDto::of) + .collect(Collectors.toSet()); + + return new RushEventResponseDto( + rushEvent.getRushEventId(), + rushEvent.getStartDateTime(), + rushEvent.getEndDateTime(), + rushEvent.getWinnerCount(), + rushEvent.getPrizeImageUrl(), + rushEvent.getPrizeDescription(), + options + ); + } + + private RushEventResponseDto(Long rushEventId, LocalDate eventDate, + LocalTime startTime, LocalTime endTime, + int winnerCount, String prizeImageUrl, + String prizeDescription, LocalDateTime createdAt, + LocalDateTime updatedAt, EventStatus status, + Set options) { + this.rushEventId = rushEventId; + this.eventDate = eventDate; + this.startTime = startTime; + this.endTime = endTime; + this.winnerCount = winnerCount; + this.prizeImageUrl = prizeImageUrl; + this.prizeDescription = prizeDescription; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.status = status; + this.options = options; + } + + // AdminRushEventResponseDto + public static RushEventResponseDto withDetail(RushEvent rushEvent) { + Set options = rushEvent.getOptions().stream() + .map(RushEventOptionResponseDto::of) + .collect(Collectors.toSet()); + + LocalDateTime now = LocalDateTime.now(); + EventStatus status; + if (now.isBefore(rushEvent.getStartDateTime())) status = EventStatus.BEFORE; + else if (now.isAfter(rushEvent.getEndDateTime())) status = EventStatus.AFTER; + else status = EventStatus.DURING; + + return new RushEventResponseDto( + rushEvent.getRushEventId(), + rushEvent.getStartDateTime().toLocalDate(), + rushEvent.getStartDateTime().toLocalTime(), + rushEvent.getEndDateTime().toLocalTime(), + rushEvent.getWinnerCount(), + rushEvent.getPrizeImageUrl(), + rushEvent.getPrizeDescription(), + rushEvent.getCreatedAt(), + rushEvent.getUpdatedAt(), + status, + options + ); + } + + private RushEventResponseDto(Long rushEventId, LocalDateTime startDateTime, + LocalDateTime endDateTime) { + this.rushEventId = rushEventId; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + } + + // MainRushEventResponseDto + public static RushEventResponseDto withMain(Long rushEventId, LocalDateTime startDateTime, LocalDateTime endDateTime) { + return new RushEventResponseDto( + rushEventId, startDateTime, endDateTime + ); + } + + public static RushEventResponseDto withMain(RushEvent rushEvent) { + return new RushEventResponseDto( + rushEvent.getRushEventId(), + rushEvent.getStartDateTime(), + rushEvent.getEndDateTime() + ); + } + + private RushEventResponseDto(Set options) { + this.options = options; + } + + // AdminRushEventOptionResponseDto + public static RushEventResponseDto withOptions(RushEvent rushEvent) { + Set optionResponseDtoList = new HashSet<>(); + optionResponseDtoList.add(RushEventOptionResponseDto.of(rushEvent.getLeftOption())); + optionResponseDtoList.add(RushEventOptionResponseDto.of(rushEvent.getRightOption())); + return new RushEventResponseDto(optionResponseDtoList); + } + + private RushEventResponseDto(RushEventOptionResponseDto leftOption, + RushEventOptionResponseDto rightOption) { + this.leftOption = leftOption; + this.rightOption = rightOption; + } + + // MainRushEventOptionsResponseDto + public static RushEventResponseDto withMainOption(RushEventOptionResponseDto leftOption, + RushEventOptionResponseDto rightOption) { + return new RushEventResponseDto(leftOption, rightOption); + } +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventResultResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventResultResponseDto.java new file mode 100644 index 00000000..1c55c70e --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/response/rush/RushEventResultResponseDto.java @@ -0,0 +1,47 @@ +package JGS.CasperEvent.domain.event.dto.response.rush; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; + +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class RushEventResultResponseDto { + @JsonInclude(JsonInclude.Include.ALWAYS) + private Integer optionId; + private Long leftOption; + private Long rightOption; + @JsonInclude(JsonInclude.Include.ALWAYS) + private Long rank; + @JsonInclude(JsonInclude.Include.ALWAYS) + private Long totalParticipants; + @JsonInclude(JsonInclude.Include.ALWAYS) + private Boolean isWinner; + + private RushEventResultResponseDto(Integer optionId, Long leftOption, Long rightOption) { + this.optionId = optionId; + this.leftOption = leftOption; + this.rightOption = rightOption; + } + + // RushEventRateResponseDto + public static RushEventResultResponseDto of(Integer optionId, Long leftOption, Long rightOption) { + return new RushEventResultResponseDto(optionId, leftOption, rightOption); + } + + private RushEventResultResponseDto(Integer optionId, Long leftOption, + Long rightOption, Long rank, + Long totalParticipants, Boolean isWinner) { + this.optionId = optionId; + this.leftOption = leftOption; + this.rightOption = rightOption; + this.rank = rank; + this.totalParticipants = totalParticipants; + this.isWinner = isWinner; + } + + public static RushEventResultResponseDto withDetail(Integer optionId, Long leftOption, + Long rightOption, Long rank, + Long totalParticipants, Boolean isWinner){ + return new RushEventResultResponseDto(optionId, leftOption, rightOption, rank, totalParticipants, isWinner); + } +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/admin/Admin.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/admin/Admin.java index bea80329..84370479 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/admin/Admin.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/admin/Admin.java @@ -1,11 +1,14 @@ package JGS.CasperEvent.domain.event.entity.admin; -import JGS.CasperEvent.global.entity.BaseEntity; import JGS.CasperEvent.global.entity.BaseUser; import JGS.CasperEvent.global.enums.Role; import jakarta.persistence.Entity; +import lombok.EqualsAndHashCode; +import lombok.Getter; @Entity +@Getter +@EqualsAndHashCode(callSuper = false) public class Admin extends BaseUser { private String password; diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/casperBot/CasperBot.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/casperBot/CasperBot.java index 8bc7c5fe..18f26a44 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/casperBot/CasperBot.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/casperBot/CasperBot.java @@ -1,8 +1,9 @@ package JGS.CasperEvent.domain.event.entity.casperBot; -import JGS.CasperEvent.domain.event.dto.RequestDto.lotteryEventDto.CasperBotRequestDto; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.CasperBotRequestDto; import JGS.CasperEvent.global.entity.BaseEntity; import jakarta.persistence.*; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; @@ -10,6 +11,7 @@ @Entity @Getter @ToString +@EqualsAndHashCode public class CasperBot extends BaseEntity { @Id diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/BaseEvent.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/BaseEvent.java index 9ebf46fc..343295c5 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/BaseEvent.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/BaseEvent.java @@ -23,14 +23,12 @@ public class BaseEvent extends BaseEntity { @JsonDeserialize(using = LocalDateTimeDeserializer.class) protected LocalDateTime endDateTime; protected int winnerCount; - protected int totalAppliedCount; // 기본 생성자에서 디폴트 값 설정 public BaseEvent() { this.startDateTime = LocalDateTime.now(); this.endDateTime = LocalDateTime.now().plusMinutes(10); this.winnerCount = 0; // 기본 우승자 수를 0으로 설정 - this.totalAppliedCount = 0; } // 특정 값을 설정할 수 있는 생성자 @@ -38,10 +36,5 @@ public BaseEvent(LocalDateTime startDateTime, LocalDateTime endDateTime, int win this.startDateTime = startDateTime; this.endDateTime = endDateTime; this.winnerCount = winnerCount; - this.totalAppliedCount = 0; - } - - public void addAppliedCount() { - this.totalAppliedCount++; } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/LotteryEvent.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/LotteryEvent.java index cf4825c1..9425dba7 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/LotteryEvent.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/LotteryEvent.java @@ -1,12 +1,14 @@ package JGS.CasperEvent.domain.event.entity.event; import jakarta.persistence.*; +import lombok.EqualsAndHashCode; import lombok.Getter; import java.time.LocalDateTime; @Entity @Getter +@EqualsAndHashCode(callSuper = false) public class LotteryEvent extends BaseEvent { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushEvent.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushEvent.java index 0da7678c..bdc7d545 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushEvent.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushEvent.java @@ -1,12 +1,13 @@ package JGS.CasperEvent.domain.event.entity.event; -import JGS.CasperEvent.domain.event.dto.RequestDto.rushEventDto.RushEventRequestDto; +import JGS.CasperEvent.domain.event.dto.request.rushEventDto.RushEventRequestDto; import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; import JGS.CasperEvent.global.enums.CustomErrorCode; import JGS.CasperEvent.global.enums.Position; import JGS.CasperEvent.global.error.exception.CustomException; import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; @@ -17,6 +18,7 @@ @Entity @Getter @ToString +@EqualsAndHashCode(callSuper = false) public class RushEvent extends BaseEvent { private String prizeImageUrl; private String prizeDescription; @@ -25,11 +27,11 @@ public class RushEvent extends BaseEvent { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long rushEventId; - @OneToMany(fetch = FetchType.EAGER, mappedBy = "rushEvent", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "rushEvent", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference private final Set options = new HashSet<>(); - @OneToMany(fetch = FetchType.EAGER, mappedBy = "rushEvent", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "rushEvent", cascade = CascadeType.ALL, orphanRemoval = true) private Set rushParticipants; public RushEvent() { diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushOption.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushOption.java index fbea57fe..a9fe43ce 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushOption.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushOption.java @@ -1,16 +1,19 @@ package JGS.CasperEvent.domain.event.entity.event; -import JGS.CasperEvent.domain.event.dto.RequestDto.rushEventDto.RushEventOptionRequestDto; +import JGS.CasperEvent.domain.event.dto.request.rushEventDto.RushEventOptionRequestDto; import JGS.CasperEvent.global.entity.BaseEntity; import JGS.CasperEvent.global.enums.Position; import com.fasterxml.jackson.annotation.JsonBackReference; import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; @Entity @Getter @NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(exclude = {"rushEvent"}, callSuper = false) +@ToString(exclude = {"rushEvent"}) public class RushOption extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/LotteryParticipants.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/LotteryParticipants.java index 6f2ec115..563c21ab 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/LotteryParticipants.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/LotteryParticipants.java @@ -3,6 +3,7 @@ import JGS.CasperEvent.global.entity.BaseEntity; import JGS.CasperEvent.global.entity.BaseUser; import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.Getter; @@ -13,10 +14,10 @@ public class LotteryParticipants extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @OneToOne // mappedBy 이용하면 둘 다 저장 안해도 됨 + @ManyToOne(fetch = FetchType.LAZY) // mappedBy 이용하면 둘 다 저장 안해도 됨 @JoinColumn(name = "base_user_id") - //todo: 왜이런지 알아보기 @JsonBackReference + @JsonIgnore private BaseUser baseUser; private int linkClickedCount; diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/LotteryWinners.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/LotteryWinners.java index 1a3f98cc..5245343f 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/LotteryWinners.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/LotteryWinners.java @@ -11,7 +11,7 @@ @Entity @Getter public class LotteryWinners { - private long id; + private Long id; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -26,7 +26,7 @@ public class LotteryWinners { public LotteryWinners(LotteryParticipants lotteryParticipants) { this.id = lotteryParticipants.getId(); - this.phoneNumber = lotteryParticipants.getBaseUser().getId(); + this.phoneNumber = lotteryParticipants.getBaseUser().getPhoneNumber(); this.linkClickedCount = lotteryParticipants.getLinkClickedCount(); this.expectation = lotteryParticipants.getExpectations(); this.appliedCount = lotteryParticipants.getAppliedCount(); diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/RushParticipants.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/RushParticipants.java index 94111d30..32ba6d44 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/RushParticipants.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/RushParticipants.java @@ -14,12 +14,13 @@ public class RushParticipants extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private int optionId; - @OneToOne + + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "base_user_id") @JsonBackReference private BaseUser baseUser; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "rush_event_id") private RushEvent rushEvent; diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/AdminRepository.java b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/AdminRepository.java index d7189cc0..c74eebd7 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/AdminRepository.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/AdminRepository.java @@ -8,5 +8,6 @@ @Repository public interface AdminRepository extends JpaRepository { - Optional findByIdAndPassword(String id, String password); + Optional findByPhoneNumberAndPassword(String id, String password); + Optional findByPhoneNumber(String id); } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/CasperBotRepository.java b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/CasperBotRepository.java index de81cb13..04384d07 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/CasperBotRepository.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/CasperBotRepository.java @@ -8,8 +8,6 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; - @Repository public interface CasperBotRepository extends JpaRepository { @Query("SELECT c FROM CasperBot c WHERE c.phoneNumber = :phoneNumber AND c.isDeleted = false AND c.expectation <> ''") diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushEventRepository.java b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushEventRepository.java index 1913977f..797b3926 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushEventRepository.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushEventRepository.java @@ -11,7 +11,7 @@ @Repository public interface RushEventRepository extends JpaRepository { - @Query("SELECT e FROM RushEvent e WHERE DATE(e.startDateTime) = :eventDate") + @Query("SELECT e FROM RushEvent e WHERE DATE(CONVERT_TZ(e.startDateTime, '+00:00', '+09:00')) = :eventDate") List findByEventDate(@Param("eventDate") LocalDate eventDate); RushEvent findByRushEventId(Long rushEventId); diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/LotteryParticipantsRepository.java b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/LotteryParticipantsRepository.java index b2a2327c..fe469e5c 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/LotteryParticipantsRepository.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/LotteryParticipantsRepository.java @@ -9,16 +9,20 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository public interface LotteryParticipantsRepository extends JpaRepository { Optional findByBaseUser(BaseUser baseUser); - @Query("SELECT p FROM LotteryParticipants p WHERE p.baseUser.id LIKE :id%") + @Query("SELECT p FROM LotteryParticipants p WHERE p.baseUser.phoneNumber LIKE :id%") Page findByBaseUser_Id(@Param("id") String id, Pageable pageable); - @Query("SELECT COUNT(p) FROM LotteryParticipants p WHERE p.baseUser.id LIKE :id%") + @Query("SELECT COUNT(p) FROM LotteryParticipants p WHERE p.baseUser.phoneNumber LIKE :id%") long countByBaseUser_Id(@Param("id") String id); + + @Query("SELECT lp.id, lp.appliedCount From LotteryParticipants lp") + List findIdAndAppliedCounts(); } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/RushParticipantsRepository.java b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/RushParticipantsRepository.java index e3904ef2..29baf9ca 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/RushParticipantsRepository.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/RushParticipantsRepository.java @@ -12,7 +12,7 @@ @Repository public interface RushParticipantsRepository extends JpaRepository { - boolean existsByRushEvent_RushEventIdAndBaseUser_Id(Long eventId, String userId); + boolean existsByRushEvent_RushEventIdAndBaseUser_PhoneNumber(Long eventId, String userId); long countByRushEvent_RushEventIdAndOptionId(Long eventId, int optionId); @@ -21,7 +21,7 @@ public interface RushParticipantsRepository extends JpaRepository getOptionIdByUserId(@Param("userId") String userId); Page findByRushEvent_RushEventId(Long rushEventId, Pageable pageable); - @Query("SELECT p FROM RushParticipants p WHERE p.rushEvent.rushEventId = :rushEventId AND p.baseUser.id LIKE :baseUserId%") + @Query("SELECT p FROM RushParticipants p WHERE p.rushEvent.rushEventId = :rushEventId AND p.baseUser.phoneNumber LIKE :baseUserId%") Page findByRushEvent_RushEventIdAndBaseUser_Id(@Param("rushEventId") Long rushEventId, @Param("baseUserId") String baseUserId, Pageable pageable); Page findByRushEvent_RushEventIdAndOptionId(Long rushEventId, int optionId, Pageable pageable); - @Query("SELECT p FROM RushParticipants p WHERE p.rushEvent.rushEventId = :rushEventId AND p.optionId = :optionId AND p.baseUser.id LIKE :baseUserId%") + @Query("SELECT p FROM RushParticipants p WHERE p.rushEvent.rushEventId = :rushEventId AND p.optionId = :optionId AND p.baseUser.phoneNumber LIKE :baseUserId%") Page findByRushEvent_RushEventIdAndOptionIdAndBaseUser_Id(@Param("rushEventId") Long rushEventId, @Param("optionId") int optionId, @Param("baseUserId") String baseUserId, Pageable pageable); @@ -60,7 +60,7 @@ Page findWinnerByEventIdAndOptionId( @Query("SELECT rp FROM RushParticipants rp " + "WHERE rp.rushEvent.rushEventId = :eventId " + "AND rp.optionId = :optionId " + - "AND rp.baseUser.id LIKE :phoneNumber% " + + "AND rp.baseUser.phoneNumber LIKE :phoneNumber% " + "ORDER BY rp.id ASC ") Page findWinnerByEventIdAndOptionIdAndPhoneNumber( @Param("eventId") Long eventId, @Param("optionId") int optionId, @Param("phoneNumber") String phoneNumber, Pageable pageable @@ -74,15 +74,15 @@ Page findWinnerByEventIdAndOptionIdAndPhoneNumber( @Query("SELECT rp FROM RushParticipants rp " + "WHERE rp.rushEvent.rushEventId = :eventId " + - "AND rp.baseUser.id LIKE :phoneNumber% " + + "AND rp.baseUser.phoneNumber LIKE :phoneNumber% " + "ORDER BY rp.id ASC ") Page findByWinnerByEventIdAndPhoneNumber(@Param("eventId") Long eventId, @Param("phoneNumber") String phoneNumber, Pageable pageable); - @Query("SELECT COUNT(p) FROM RushParticipants p WHERE p.rushEvent.rushEventId = :rushEventId AND p.optionId = :optionId AND p.baseUser.id LIKE :baseUserId%") + @Query("SELECT COUNT(p) FROM RushParticipants p WHERE p.rushEvent.rushEventId = :rushEventId AND p.optionId = :optionId AND p.baseUser.phoneNumber LIKE :baseUserId%") long countByRushEvent_RushEventIdAndOptionIdAndBaseUser_Id(@Param("rushEventId") Long rushEventId, @Param("optionId") int optionId, @Param("baseUserId") String baseUserId); long countByRushEvent_RushEventId(long rushEventId); - @Query("SELECT COUNT(p) FROM RushParticipants p WHERE p.rushEvent.rushEventId = :rushEventId AND p.baseUser.id LIKE :baseUserId%") + @Query("SELECT COUNT(p) FROM RushParticipants p WHERE p.rushEvent.rushEventId = :rushEventId AND p.baseUser.phoneNumber LIKE :baseUserId%") long countByRushEvent_RushEventIdAndBaseUser_Id(@Param("rushEventId") Long rushEventId, @Param("baseUserId") String baseUserId); } \ No newline at end of file diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/adminService/AdminService.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/adminService/AdminService.java index 47800f8f..01e49b1d 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/service/adminService/AdminService.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/adminService/AdminService.java @@ -1,12 +1,17 @@ package JGS.CasperEvent.domain.event.service.adminService; -import JGS.CasperEvent.domain.event.dto.RequestDto.AdminRequestDto; -import JGS.CasperEvent.domain.event.dto.RequestDto.lotteryEventDto.LotteryEventRequestDto; -import JGS.CasperEvent.domain.event.dto.RequestDto.rushEventDto.RushEventOptionRequestDto; -import JGS.CasperEvent.domain.event.dto.RequestDto.rushEventDto.RushEventRequestDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.ImageUrlResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.*; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.*; +import JGS.CasperEvent.domain.event.dto.request.AdminRequestDto; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.LotteryEventRequestDto; +import JGS.CasperEvent.domain.event.dto.request.rushEventDto.RushEventOptionRequestDto; +import JGS.CasperEvent.domain.event.dto.request.rushEventDto.RushEventRequestDto; +import JGS.CasperEvent.domain.event.dto.response.ImageUrlResponseDto; +import JGS.CasperEvent.domain.event.dto.response.ParticipantsListResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.CasperBotResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.ExpectationsPagingResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; import JGS.CasperEvent.domain.event.entity.admin.Admin; import JGS.CasperEvent.domain.event.entity.casperBot.CasperBot; import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; @@ -23,12 +28,11 @@ import JGS.CasperEvent.domain.event.repository.participantsRepository.LotteryParticipantsRepository; import JGS.CasperEvent.domain.event.repository.participantsRepository.LotteryWinnerRepository; import JGS.CasperEvent.domain.event.repository.participantsRepository.RushParticipantsRepository; +import JGS.CasperEvent.domain.event.service.eventService.EventCacheService; import JGS.CasperEvent.global.enums.CustomErrorCode; import JGS.CasperEvent.global.enums.Position; import JGS.CasperEvent.global.enums.Role; import JGS.CasperEvent.global.error.exception.CustomException; -import JGS.CasperEvent.global.error.exception.TooManyLotteryEventException; -import JGS.CasperEvent.global.error.exception.TooManyRushEventException; import JGS.CasperEvent.global.response.ResponseDto; import JGS.CasperEvent.global.service.S3Service; import lombok.RequiredArgsConstructor; @@ -43,7 +47,6 @@ import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; import static JGS.CasperEvent.global.util.RepositoryErrorHandler.findByIdOrElseThrow; @@ -61,30 +64,35 @@ public class AdminService { private final CasperBotRepository casperBotRepository; private final LotteryWinnerRepository lotteryWinnerRepository; private final RedisTemplate casperBotRedisTemplate; + private final EventCacheService eventCacheService; + private final Random random = new Random(); + // 어드민 인증 public Admin verifyAdmin(AdminRequestDto adminRequestDto) { - return adminRepository.findByIdAndPassword(adminRequestDto.getAdminId(), adminRequestDto.getPassword()).orElseThrow(NoSuchElementException::new); + return adminRepository.findByPhoneNumberAndPassword(adminRequestDto.getAdminId(), adminRequestDto.getPassword()).orElseThrow(NoSuchElementException::new); } + // 어드민 생성 public ResponseDto postAdmin(AdminRequestDto adminRequestDto) { String adminId = adminRequestDto.getAdminId(); - //Todo: 비밀번호 암호화 필요 String password = adminRequestDto.getPassword(); - Admin admin = adminRepository.findById(adminId).orElse(null); + Admin admin = adminRepository.findByPhoneNumber(adminId).orElse(null); if (admin != null) throw new CustomException("이미 등록된 ID입니다.", CustomErrorCode.CONFLICT); adminRepository.save(new Admin(adminId, password, Role.ADMIN)); - return ResponseDto.of("관리자 생성 성공"); + return new ResponseDto("관리자 생성 성공"); } + // 이미지 업로드 public ImageUrlResponseDto postImage(MultipartFile image) { return new ImageUrlResponseDto(s3Service.upload(image)); } + // 추첨 이벤트 생성 public LotteryEventResponseDto createLotteryEvent(LotteryEventRequestDto lotteryEventRequestDto) { - if (lotteryEventRepository.count() >= 1) throw new TooManyLotteryEventException(); + if (lotteryEventRepository.count() >= 1) throw new CustomException(CustomErrorCode.TOO_MANY_LOTTERY_EVENT); LotteryEvent lotteryEvent = lotteryEventRepository.save(new LotteryEvent( LocalDateTime.of(lotteryEventRequestDto.getStartDate(), lotteryEventRequestDto.getStartTime()), @@ -92,16 +100,18 @@ public LotteryEventResponseDto createLotteryEvent(LotteryEventRequestDto lottery lotteryEventRequestDto.getWinnerCount() )); + eventCacheService.setLotteryEvent(); return LotteryEventResponseDto.of(lotteryEvent, LocalDateTime.now()); } - public LotteryEventDetailResponseDto getLotteryEvent() { - return LotteryEventDetailResponseDto.of( - getCurrentLotteryEvent() - ); + // 추첨 이벤트 조회 + public LotteryEventResponseDto getLotteryEvent() { + long appliedCount = lotteryParticipantsRepository.count(); + return LotteryEventResponseDto.withDetail(getCurrentLotteryEvent(), appliedCount); } - public LotteryEventParticipantsListResponseDto getLotteryEventParticipants(int size, int page, String phoneNumber) { + // 추첨 이벤트 참여자 조회 + public ParticipantsListResponseDto getLotteryEventParticipants(int size, int page, String phoneNumber) { Pageable pageable = PageRequest.of(page, size); Page lotteryParticipantsPage = null; @@ -114,25 +124,24 @@ public LotteryEventParticipantsListResponseDto getLotteryEventParticipants(int s count = lotteryParticipantsRepository.countByBaseUser_Id(phoneNumber); } - List lotteryEventParticipantsResponseDtoList = new ArrayList<>(); + List lotteryEventParticipantsResponseDtoList = new ArrayList<>(); for (LotteryParticipants lotteryParticipant : lotteryParticipantsPage) { lotteryEventParticipantsResponseDtoList.add( - LotteryEventParticipantsResponseDto.of(lotteryParticipant) + LotteryEventParticipantResponseDto.withDetail(lotteryParticipant) ); } Boolean isLastPage = !lotteryParticipantsPage.hasNext(); - return new LotteryEventParticipantsListResponseDto(lotteryEventParticipantsResponseDtoList, isLastPage, count); + return new ParticipantsListResponseDto<>(lotteryEventParticipantsResponseDtoList, isLastPage, count); } - public AdminRushEventResponseDto createRushEvent(RushEventRequestDto rushEventRequestDto, MultipartFile prizeImg, MultipartFile leftOptionImg, MultipartFile rightOptionImg) { - if (rushEventRepository.count() >= 6) throw new TooManyRushEventException(); - + // 선착순 이벤트 생성 + public RushEventResponseDto createRushEvent(RushEventRequestDto rushEventRequestDto, MultipartFile prizeImg, MultipartFile leftOptionImg, MultipartFile rightOptionImg) { + if (rushEventRepository.count() >= 6) throw new CustomException(CustomErrorCode.TOO_MANY_RUSH_EVENT); String prizeImgSrc = s3Service.upload(prizeImg); String leftOptionImgSrc = s3Service.upload(leftOptionImg); String rightOptionImgSrc = s3Service.upload(rightOptionImg); - // Img s3 저장 RushEvent rushEvent = rushEventRepository.save( new RushEvent( LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getStartTime()), @@ -145,41 +154,40 @@ public AdminRushEventResponseDto createRushEvent(RushEventRequestDto rushEventRe RushEventOptionRequestDto leftOption = rushEventRequestDto.getLeftOptionRequestDto(); RushEventOptionRequestDto rightOption = rushEventRequestDto.getRightOptionRequestDto(); - RushOption leftRushOption = rushOptionRepository.save(new RushOption( - rushEvent, - leftOption.getMainText(), - leftOption.getSubText(), - leftOption.getResultMainText(), - leftOption.getResultSubText(), - leftOptionImgSrc, - Position.LEFT - )); - - RushOption rightRushOption = rushOptionRepository.save(new RushOption( - rushEvent, - rightOption.getMainText(), - rightOption.getSubText(), - rightOption.getResultMainText(), - rightOption.getResultSubText(), - rightOptionImgSrc, - Position.RIGHT - )); + RushOption leftRushOption = createAndSaveRushOption(rushEvent, leftOption, leftOptionImgSrc, Position.LEFT); + RushOption rightRushOption = createAndSaveRushOption(rushEvent, rightOption, rightOptionImgSrc, Position.RIGHT); rushEvent.addOption(leftRushOption, rightRushOption); + return RushEventResponseDto.withDetail(rushEvent); + } + + public RushOption createAndSaveRushOption(RushEvent rushEvent, RushEventOptionRequestDto optionDto, String imgSrc, Position position) { + + RushOption rushOption = new RushOption( + rushEvent, + optionDto.getMainText(), + optionDto.getSubText(), + optionDto.getResultMainText(), + optionDto.getResultSubText(), + imgSrc, + position + ); - return AdminRushEventResponseDto.of(rushEvent); + return rushOptionRepository.save(rushOption); } - public List getRushEvents() { + // 선착순 이벤트 조회 + public List getRushEvents() { List rushEvents = rushEventRepository.findAll(); - List rushEventResponseDtoList = new ArrayList<>(); + List rushEventResponseDtoList = new ArrayList<>(); for (RushEvent rushEvent : rushEvents) { - rushEventResponseDtoList.add(AdminRushEventResponseDto.of(rushEvent)); + rushEventResponseDtoList.add(RushEventResponseDto.withDetail(rushEvent)); } return rushEventResponseDtoList; } - public RushEventParticipantsListResponseDto getRushEventParticipants(long rushEventId, int size, int page, int optionId, String phoneNumber) { + // 선착순 이벤트 참여자 조회 + public ParticipantsListResponseDto getRushEventParticipants(long rushEventId, int size, int page, int optionId, String phoneNumber) { Pageable pageable = PageRequest.of(page, size); Page rushParticipantsPage = null; @@ -209,19 +217,20 @@ public RushEventParticipantsListResponseDto getRushEventParticipants(long rushEv List rushEventParticipantResponseDtoList = new ArrayList<>(); for (RushParticipants rushParticipant : rushParticipantsPage) { - String userId = rushParticipant.getBaseUser().getId(); + String userId = rushParticipant.getBaseUser().getPhoneNumber(); int userChoice = rushParticipant.getOptionId(); long rank = rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(rushEventId, userId, userChoice); rushEventParticipantResponseDtoList.add( - RushEventParticipantResponseDto.of(rushParticipant, rank) + RushEventParticipantResponseDto.result(rushParticipant, rank) ); } Boolean isLastPage = !rushParticipantsPage.hasNext(); - return new RushEventParticipantsListResponseDto(rushEventParticipantResponseDtoList, isLastPage, count); + return new ParticipantsListResponseDto<>(rushEventParticipantResponseDtoList, isLastPage, count); } - public RushEventParticipantsListResponseDto getRushEventWinners(long rushEventId, int size, int page, String phoneNumber) { + // 선착순 이벤트 당첨자 조회 + public ParticipantsListResponseDto getRushEventWinners(long rushEventId, int size, int page, String phoneNumber) { Page rushParticipantsPage = null; RushEvent rushEvent = findByIdOrElseThrow(rushEventRepository, rushEventId, CustomErrorCode.NO_RUSH_EVENT); @@ -234,6 +243,7 @@ public RushEventParticipantsListResponseDto getRushEventWinners(long rushEventId boolean isPhoneNumberEmpty = phoneNumber.isEmpty(); int winnerOptionId; + if (leftSelect > rightSelect) winnerOptionId = 1; else if (leftSelect < rightSelect) winnerOptionId = 2; else winnerOptionId = 0; @@ -257,27 +267,29 @@ public RushEventParticipantsListResponseDto getRushEventWinners(long rushEventId List rushEventParticipantResponseDtoList = new ArrayList<>(); for (RushParticipants rushParticipant : rushParticipantsPage) { - String userId = rushParticipant.getBaseUser().getId(); + String userId = rushParticipant.getBaseUser().getPhoneNumber(); int userChoice = rushParticipant.getOptionId(); long rank = rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(rushEventId, userId, userChoice); rushEventParticipantResponseDtoList.add( - RushEventParticipantResponseDto.of(rushParticipant, rank) + RushEventParticipantResponseDto.result(rushParticipant, rank) ); } Boolean isLastPage = !rushParticipantsPage.hasNext(); long totalParticipants = rushParticipantsList.size(); - return new RushEventParticipantsListResponseDto(rushEventParticipantResponseDtoList, isLastPage, totalParticipants); + return new ParticipantsListResponseDto<>(rushEventParticipantResponseDtoList, isLastPage, totalParticipants); } + // 선착순 이벤트 삭제 @Transactional public void deleteLotteryEvent() { LotteryEvent currentLotteryEvent = getCurrentLotteryEvent(); lotteryEventRepository.deleteById(currentLotteryEvent.getLotteryEventId()); } + // 추첨 이벤트 업데이트 @Transactional - public LotteryEventDetailResponseDto updateLotteryEvent(LotteryEventRequestDto lotteryEventRequestDto) { + public LotteryEventResponseDto updateLotteryEvent(LotteryEventRequestDto lotteryEventRequestDto) { LotteryEvent currentLotteryEvent = getCurrentLotteryEvent(); LocalDateTime now = LocalDateTime.now(); @@ -306,10 +318,12 @@ else if (newStartDateTime.isBefore(now)) { // 필드 업데이트 currentLotteryEvent.updateLotteryEvent(newStartDateTime, newEndDateTime, lotteryEventRequestDto.getWinnerCount()); - - return LotteryEventDetailResponseDto.of(currentLotteryEvent); + eventCacheService.setLotteryEvent(); + Long appliedCount = lotteryParticipantsRepository.count(); + return LotteryEventResponseDto.withDetail(currentLotteryEvent, appliedCount); } + // 추첨 이벤트 조회 private LotteryEvent getCurrentLotteryEvent() { List lotteryEventList = lotteryEventRepository.findAll(); @@ -324,6 +338,7 @@ private LotteryEvent getCurrentLotteryEvent() { return lotteryEventList.get(0); } + // 추첨 이벤트 당첨자 추첨 @Transactional public ResponseDto pickLotteryEventWinners() { if (lotteryWinnerRepository.count() > 1) throw new CustomException(CustomErrorCode.LOTTERY_EVENT_ALREADY_DRAWN); @@ -331,43 +346,65 @@ public ResponseDto pickLotteryEventWinners() { int winnerCount = lotteryEvent.getWinnerCount(); - List lotteryParticipants = lotteryParticipantsRepository.findAll(); - Set lotteryEventWinners = new HashSet<>(); + List lotteryParticipants = lotteryParticipantsRepository.findIdAndAppliedCounts(); + + if (winnerCount >= lotteryParticipants.size()) { + Long winnerId; + for (Object[] lotteryParticipant : lotteryParticipants) { + winnerId = (Long) lotteryParticipant[0]; + lotteryWinnerRepository.save(new LotteryWinners( + lotteryParticipantsRepository.findById(winnerId).orElseThrow( + () -> new CustomException(CustomErrorCode.USER_NOT_FOUND) + ) + )); - int totalWeight; - Random random = new Random(); - while (lotteryEventWinners.size() < winnerCount) { - totalWeight = 0; - for (LotteryParticipants lotteryParticipant : lotteryParticipants) { - totalWeight += lotteryParticipant.getAppliedCount(); } + return new ResponseDto("추첨이 완료되었습니다."); + } - int randomValue = random.nextInt(totalWeight) + 1; + int appliedCount; + List appliedParticipants = new ArrayList<>(); - int cumulativeSum = 0; - for (LotteryParticipants lotteryParticipant : lotteryParticipants) { - cumulativeSum += lotteryParticipant.getAppliedCount(); - if(randomValue <= cumulativeSum){ - lotteryEventWinners.add(lotteryParticipant); - lotteryParticipants.remove(lotteryParticipant); - break; - } + for (Object[] lotteryParticipant : lotteryParticipants) { + appliedCount = (int) lotteryParticipant[1]; + for (int i = 0; i < appliedCount; i++) { + appliedParticipants.add((long) lotteryParticipant[0]); } } - for (LotteryParticipants lotteryEventWinner : lotteryEventWinners) { - lotteryWinnerRepository.save(new LotteryWinners(lotteryEventWinner)); + // Fisher-Yates Shuffle Algorithm + for (int i = appliedParticipants.size() - 1; i > 0; i--) { + int j = random.nextInt(i + 1); + Long temp = appliedParticipants.get(i); + appliedParticipants.set(i, appliedParticipants.get(j)); + appliedParticipants.set(j, temp); } + Set lotteryEventWinners = new HashSet<>(); + while (lotteryEventWinners.size() < winnerCount) { + Long winnerId = appliedParticipants.remove(0); + if (lotteryEventWinners.contains(winnerId)) continue; + lotteryEventWinners.add(winnerId); + } + + List winnersToSave = lotteryEventWinners.stream() + .map(winnerId -> new LotteryWinners(lotteryParticipantsRepository.findById(winnerId).orElseThrow(() -> + new CustomException(CustomErrorCode.USER_NOT_FOUND) + ))).toList(); + + lotteryWinnerRepository.saveAll(winnersToSave); + return new ResponseDto("추첨이 완료되었습니다."); } - public ResponseDto deleteLotteryEventWinners(){ + // 당첨자 명단 삭제 + public ResponseDto deleteLotteryEventWinners() { lotteryWinnerRepository.deleteAll(); return new ResponseDto("당첨자 명단을 삭제했습니다."); } - public LotteryEventWinnerListResponseDto getLotteryEventWinners(int size, int page, String phoneNumber) { + // 추첨 이벤트 당첨자 명단 조회 + public ParticipantsListResponseDto getLotteryEventWinners(int size, int page, String phoneNumber) { Pageable pageable = PageRequest.of(page, size); if (lotteryWinnerRepository.count() == 0) throw new CustomException(CustomErrorCode.LOTTERY_EVENT_NOT_DRAWN); @@ -381,66 +418,93 @@ public LotteryEventWinnerListResponseDto getLotteryEventWinners(int size, int pa count = lotteryWinnerRepository.countByPhoneNumber(phoneNumber); } - List lotteryEventWinnerResponseDto = new ArrayList<>(); + List lotteryEventWinnerResponseDto = new ArrayList<>(); for (LotteryWinners lotteryWinners : lotteryWinnersPage) { lotteryEventWinnerResponseDto.add( - LotteryEventWinnerResponseDto.of(lotteryWinners) + LotteryEventParticipantResponseDto.winner(lotteryWinners) ); } Boolean isLastPage = !lotteryWinnersPage.hasNext(); - return new LotteryEventWinnerListResponseDto(lotteryEventWinnerResponseDto, isLastPage, count); + return new ParticipantsListResponseDto<>(lotteryEventWinnerResponseDto, isLastPage, count); } + // 선착순 이벤트 업데이트 @Transactional - public List updateRushEvents(List rushEventRequestDtoList) { + public List updateRushEvents(List rushEventRequestDtoList) { LocalDateTime now = LocalDateTime.now(); for (RushEventRequestDto rushEventRequestDto : rushEventRequestDtoList) { RushEvent rushEvent = rushEventRepository.findByRushEventId(rushEventRequestDto.getRushEventId()); - - LocalDateTime curStartDateTime = rushEvent.getStartDateTime(); - LocalDateTime curEndDateTime = rushEvent.getEndDateTime(); LocalDateTime startDateTime = LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getStartTime()); LocalDateTime endDateTime = LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getEndTime()); - if (!Objects.equals(curStartDateTime, startDateTime) || !Objects.equals(curEndDateTime, endDateTime)) { - // 종료 날짜가 시작 날짜보다 뒤인지 체크 - if (endDateTime.isBefore(startDateTime)) { - throw new CustomException(CustomErrorCode.EVENT_END_TIME_BEFORE_START_TIME); - } - - if (curStartDateTime.isBefore(now) && curEndDateTime.isAfter(now)) { - // 현재 진행 중인 이벤트인 경우 - if (!curStartDateTime.equals(startDateTime)) { - throw new CustomException(CustomErrorCode.EVENT_IN_PROGRESS_CANNOT_CHANGE_START_TIME); - } - if (endDateTime.isBefore(now)) { - throw new CustomException(CustomErrorCode.EVENT_IN_PROGRESS_END_TIME_BEFORE_NOW); - } - } - - // 이벤트가 시작 전인 경우 - else if (startDateTime.isBefore(now)) { - throw new CustomException(CustomErrorCode.EVENT_BEFORE_START_TIME); - } - } - - RushOption leftOption = rushEvent.getLeftOption(); - RushOption rightOption = rushEvent.getRightOption(); - rushEvent.updateRushEvent(rushEventRequestDto); - leftOption.updateRushOption(rushEventRequestDto.getLeftOptionRequestDto()); - rightOption.updateRushOption(rushEventRequestDto.getRightOptionRequestDto()); + validateEventTimes(rushEvent, startDateTime, endDateTime, now); + updateRushEvent(rushEvent, rushEventRequestDto); } + List rushEvents = rushEventRepository.findAll(); - List rushEventResponseDtoList = new ArrayList<>(); + List rushEventResponseDtoList = new ArrayList<>(); for (RushEvent rushEvent : rushEvents) { - rushEventResponseDtoList.add(AdminRushEventResponseDto.of(rushEvent)); + rushEventResponseDtoList.add(RushEventResponseDto.withDetail(rushEvent)); } + return rushEventResponseDtoList; } + // 이벤트 시간 유효성 검사 + private void validateEventTimes(RushEvent rushEvent, LocalDateTime startDateTime, LocalDateTime endDateTime, LocalDateTime now) { + LocalDateTime curStartDateTime = rushEvent.getStartDateTime(); + LocalDateTime curEndDateTime = rushEvent.getEndDateTime(); + + if (!Objects.equals(curStartDateTime, startDateTime) || !Objects.equals(curEndDateTime, endDateTime)) { + checkEndTimeBeforeStartTime(startDateTime, endDateTime); + checkEventInProgress(rushEvent, startDateTime, endDateTime, now); + checkEventBeforeStartTime(startDateTime, now); + } + } + + // 현재 시간이 종료 시간보다 앞서는 경우 + private void checkEndTimeBeforeStartTime(LocalDateTime startDateTime, LocalDateTime endDateTime) { + if (endDateTime.isBefore(startDateTime)) { + throw new CustomException(CustomErrorCode.EVENT_END_TIME_BEFORE_START_TIME); + } + } + + // 이벤트가 현재 진행중인지 확인 + private void checkEventInProgress(RushEvent rushEvent, LocalDateTime startDateTime, LocalDateTime endDateTime, LocalDateTime now) { + LocalDateTime curStartDateTime = rushEvent.getStartDateTime(); + LocalDateTime curEndDateTime = rushEvent.getEndDateTime(); + + if (curStartDateTime.isBefore(now) && curEndDateTime.isAfter(now)) { + if (!curStartDateTime.equals(startDateTime)) { + throw new CustomException(CustomErrorCode.EVENT_IN_PROGRESS_CANNOT_CHANGE_START_TIME); + } + if (endDateTime.isBefore(now)) { + throw new CustomException(CustomErrorCode.EVENT_IN_PROGRESS_END_TIME_BEFORE_NOW); + } + } + } + + // 이벤트가 시작 전인지 확인 + private void checkEventBeforeStartTime(LocalDateTime startDateTime, LocalDateTime now) { + if (startDateTime.isBefore(now)) { + throw new CustomException(CustomErrorCode.EVENT_BEFORE_START_TIME); + } + } + + // 선착순 이벤트 업데이트 + private void updateRushEvent(RushEvent rushEvent, RushEventRequestDto rushEventRequestDto) { + RushOption leftOption = rushEvent.getLeftOption(); + RushOption rightOption = rushEvent.getRightOption(); + + rushEvent.updateRushEvent(rushEventRequestDto); + leftOption.updateRushOption(rushEventRequestDto.getLeftOptionRequestDto()); + rightOption.updateRushOption(rushEventRequestDto.getRightOptionRequestDto()); + } + + // 선착순 이벤트 삭제 @Transactional public ResponseDto deleteRushEvent(Long rushEventId) { RushEvent rushEvent = rushEventRepository.findById(rushEventId).orElseThrow(() -> new CustomException(CustomErrorCode.NO_RUSH_EVENT)); @@ -452,57 +516,53 @@ public ResponseDto deleteRushEvent(Long rushEventId) { if (now.isAfter(startDateTime) && now.isBefore(endDateTime)) throw new CustomException(CustomErrorCode.EVENT_IN_PROGRESS_CANNOT_DELETE); rushEventRepository.delete(rushEvent); - return ResponseDto.of("요청에 성공하였습니다."); + return new ResponseDto("요청에 성공하였습니다."); } - public AdminRushEventOptionResponseDto getRushEventOptions(Long rushEventId) { - return AdminRushEventOptionResponseDto.of( + // 선착순 이벤트 선택지 조회 + public RushEventResponseDto getRushEventOptions(Long rushEventId) { + return RushEventResponseDto.withOptions( rushEventRepository.findById(rushEventId).orElseThrow( () -> new CustomException(CustomErrorCode.NO_RUSH_EVENT) ) ); } - public LotteryEventExpectationsResponseDto getLotteryEventExpectations(int page, int size, Long participantId) { + // 기대평 조회 + public ExpectationsPagingResponseDto getLotteryEventExpectations(int page, int size, Long participantId) { LotteryParticipants lotteryParticipant = lotteryParticipantsRepository.findById(participantId).orElseThrow( () -> new CustomException(CustomErrorCode.USER_NOT_FOUND) ); Pageable pageable = PageRequest.of(page, size); - Page casperBotPage = casperBotRepository.findByPhoneNumberAndActiveExpectations(lotteryParticipant.getBaseUser().getId(), pageable); + Page casperBotPage = casperBotRepository.findByPhoneNumberAndActiveExpectations(lotteryParticipant.getBaseUser().getPhoneNumber(), pageable); // DTO로 변환합니다. - List lotteryEventExpectationResponseDtoList = casperBotPage.getContent().stream() - .map(casperBot -> new LotteryEventExpectationResponseDto( - casperBot.getCasperId(), - casperBot.getExpectation(), - casperBot.getCreatedAt().toLocalDate(), - casperBot.getCreatedAt().toLocalTime() - )) - .collect(Collectors.toList()); + List lotteryEventExpectationResponseDtoList = casperBotPage.getContent().stream() + .map(LotteryEventResponseDto::withExpectation).toList(); // 마지막 페이지 여부 계산 boolean isLastPage = casperBotPage.isLast(); // 결과를 반환합니다. - return new LotteryEventExpectationsResponseDto(lotteryEventExpectationResponseDtoList, isLastPage, casperBotPage.getTotalElements()); + return new ExpectationsPagingResponseDto(lotteryEventExpectationResponseDtoList, isLastPage, casperBotPage.getTotalElements()); } + // 부적절한 기대평 삭제 @Transactional public void deleteLotteryEventExpectation(Long casperId) { CasperBot casperBot = casperBotRepository.findById(casperId).orElseThrow( () -> new CustomException(CustomErrorCode.CASPERBOT_NOT_FOUND) ); - // todo: 전체 설정에서 가져오도록 변경 final String LIST_KEY = "recentData"; // 긍정적인 문구 리스트 List positiveMessages = List.of("사랑해 캐스퍼", "캐스퍼 최고!", "캐스퍼와 함께해요!", "캐스퍼 짱!", "캐스퍼는 나의 친구!"); // 랜덤으로 긍정적인 문구 선택 - String randomPositiveMessage = positiveMessages.get(new Random().nextInt(positiveMessages.size())); + String randomPositiveMessage = positiveMessages.get(random.nextInt(positiveMessages.size())); // isDeleted = true 로 업데이트 casperBot.deleteExpectation(); @@ -527,8 +587,7 @@ public void deleteLotteryEventExpectation(Long casperId) { ); } return data; - }) - .collect(Collectors.toList()); + }).toList(); // Redis에서 현재 리스트를 삭제합니다. casperBotRedisTemplate.delete(LIST_KEY); diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/EventCacheService.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/EventCacheService.java new file mode 100644 index 00000000..ff88ed7d --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/EventCacheService.java @@ -0,0 +1,128 @@ +package JGS.CasperEvent.domain.event.service.eventService; + +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; +import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; +import JGS.CasperEvent.domain.event.entity.event.RushEvent; +import JGS.CasperEvent.domain.event.repository.eventRepository.LotteryEventRepository; +import JGS.CasperEvent.domain.event.repository.eventRepository.RushEventRepository; +import JGS.CasperEvent.domain.event.repository.participantsRepository.RushParticipantsRepository; +import JGS.CasperEvent.global.enums.CustomErrorCode; +import JGS.CasperEvent.global.error.exception.CustomException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class EventCacheService { + + private final CacheManager cacheManager; + private final RushEventRepository rushEventRepository; + private final LotteryEventRepository lotteryEventRepository; + private final RushParticipantsRepository rushParticipantsRepository; + + @Cacheable(value = "ongoingLotteryEvent") + public LotteryEvent getLotteryEvent(){ + return fetchOngoingLotteryEvent(); + } + + @CachePut(value = "ongoingLotteryEvent") + public LotteryEvent setLotteryEvent() { + return fetchOngoingLotteryEvent(); + } + + private LotteryEvent fetchOngoingLotteryEvent() { + // 오늘 날짜에 해당하는 모든 이벤트 꺼내옴 + List lotteryEventList = lotteryEventRepository.findAll(); + + if (lotteryEventList.isEmpty()) { + throw new CustomException(CustomErrorCode.NO_LOTTERY_EVENT); + } + + if (lotteryEventList.size() > 1) { + throw new CustomException(CustomErrorCode.TOO_MANY_LOTTERY_EVENT); + } + + return lotteryEventList.get(0); + } + + @Cacheable(value = "todayRushEventCache", key = "#today") + public RushEventResponseDto getTodayEvent(LocalDate today) { + log.info("오늘의 이벤트 캐싱 {}", today); + // 오늘 날짜에 해당하는 모든 이벤트 꺼내옴 + return fetchTodayRushEvent(today); + } + + @CachePut(value = "todayRushEventCache", key = "#today") + public RushEventResponseDto setCacheValue(LocalDate today) { + log.info("이벤트 업데이트 {}", today); + return fetchTodayRushEvent(today); + } + + private RushEventResponseDto fetchTodayRushEvent(LocalDate today) { + // 오늘 날짜에 해당하는 모든 이벤트 꺼내옴 + List rushEventList = rushEventRepository.findByEventDate(today); + + if (rushEventList.isEmpty()) { + throw new CustomException("선착순 이벤트가 존재하지않습니다.", CustomErrorCode.NO_RUSH_EVENT); + } + + if (rushEventList.size() > 1) { + throw new CustomException("선착순 이벤트가 2개 이상 존재합니다.", CustomErrorCode.MULTIPLE_RUSH_EVENTS_FOUND); + } + + return RushEventResponseDto.of(rushEventList.get(0)); + } + + @Cacheable(value = "allRushEventCache") + public List getAllRushEvent() { + log.info("전체 이벤트 캐싱"); + // 오늘 날짜에 해당하는 모든 이벤트 꺼내옴 + return fetchAllRushEvent(); + } + + //todo: 어드민 선착순 이벤트 변경 시 메서드 호출 + @CachePut(value = "allRushEventCache") + public List setAllRushEvent() { + log.info("이벤트 변경 캐싱"); + return fetchAllRushEvent(); + } + + private List fetchAllRushEvent() { + // DB에서 모든 RushEvent 가져오기 + List rushEventList = rushEventRepository.findAll(); + + // RushEvent를 DTO로 전환 + return rushEventList.stream() + .map(RushEventResponseDto::withMain) + .toList(); + } + + // phoneNumber와 date 따른 optionId 캐싱 + @Cacheable(value = "userOptionCache", key = "#today + ':' + #phoneNumber") + public int getOptionId(LocalDate today, String phoneNumber) { + return fetchOptionId(phoneNumber); + } + + // userOptionCache 전체 초기화 + public void clearUserOptionCache() { + Cache userOptionCache = cacheManager.getCache("userOptionCache"); + if (userOptionCache != null) { + userOptionCache.clear(); + } + } + + private int fetchOptionId(String phoneNumber) { + Optional optionId = rushParticipantsRepository.getOptionIdByUserId(phoneNumber); + return optionId.orElseThrow(() -> new CustomException("유저가 응모한 선택지가 존재하지 않습니다.", CustomErrorCode.USER_NOT_FOUND)); + } +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/EventService.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/EventService.java index 5d93f2ba..47ea7284 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/EventService.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/EventService.java @@ -1,6 +1,6 @@ package JGS.CasperEvent.domain.event.service.eventService; -import JGS.CasperEvent.domain.event.dto.ResponseDto.TotalEventDateResponseDto; +import JGS.CasperEvent.domain.event.dto.response.TotalEventDateResponseDto; import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; import JGS.CasperEvent.domain.event.entity.event.RushEvent; import JGS.CasperEvent.domain.event.repository.eventRepository.LotteryEventRepository; diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventService.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventService.java index dfa9bb17..9b47915d 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventService.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventService.java @@ -1,35 +1,29 @@ package JGS.CasperEvent.domain.event.service.eventService; -import JGS.CasperEvent.domain.event.dto.RequestDto.lotteryEventDto.CasperBotRequestDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.CasperBotResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.LotteryEventResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.LotteryParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.CasperBotRequestDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.CasperBotResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventResponseDto; import JGS.CasperEvent.domain.event.entity.casperBot.CasperBot; import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; import JGS.CasperEvent.domain.event.repository.CasperBotRepository; import JGS.CasperEvent.domain.event.repository.eventRepository.LotteryEventRepository; import JGS.CasperEvent.domain.event.repository.participantsRepository.LotteryParticipantsRepository; -import JGS.CasperEvent.domain.event.service.redisService.RedisService; +import JGS.CasperEvent.domain.event.service.redisService.LotteryEventRedisService; import JGS.CasperEvent.global.entity.BaseUser; import JGS.CasperEvent.global.enums.CustomErrorCode; import JGS.CasperEvent.global.error.exception.CustomException; -import JGS.CasperEvent.global.error.exception.LotteryEventNotExists; import JGS.CasperEvent.global.jwt.repository.UserRepository; import JGS.CasperEvent.global.util.AESUtils; import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; -import java.nio.file.attribute.UserPrincipalNotFoundException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; -import java.util.List; import java.util.Optional; @Service @@ -37,38 +31,39 @@ @RequiredArgsConstructor public class LotteryEventService { + private static final Logger log = LoggerFactory.getLogger(LotteryEventService.class); private final UserRepository userRepository; - private final LotteryEventRepository lotteryEventRepository; private final LotteryParticipantsRepository lotteryParticipantsRepository; private final CasperBotRepository casperBotRepository; - private final RedisService redisService; + private final LotteryEventRedisService lotteryEventRedisService; private final SecretKey secretKey; + private final EventCacheService eventCacheService; + private final LotteryEventRepository lotteryEventRepository; - public CasperBotResponseDto postCasperBot(BaseUser user, CasperBotRequestDto casperBotRequestDto) throws CustomException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + public CasperBotResponseDto postCasperBot(BaseUser user, CasperBotRequestDto casperBotRequestDto) throws CustomException { + LotteryEvent lotteryEvent = eventCacheService.getLotteryEvent(); LotteryParticipants participants = registerUserIfNeed(user, casperBotRequestDto); - LotteryEvent lotteryEvent = getEvent(); - - CasperBot casperBot = casperBotRepository.save(new CasperBot(casperBotRequestDto, user.getId())); - lotteryEvent.addAppliedCount(); + CasperBot casperBot = casperBotRepository.save(new CasperBot(casperBotRequestDto, user.getPhoneNumber())); participants.updateCasperId(casperBot.getCasperId()); if (!casperBot.getExpectation().isEmpty()) { participants.expectationAdded(); - lotteryEvent.addAppliedCount(); } CasperBotResponseDto casperBotDto = CasperBotResponseDto.of(casperBot); - redisService.addData(casperBotDto); + lotteryEventRedisService.addData(casperBotDto); + eventCacheService.setLotteryEvent(); + lotteryEventRepository.save(lotteryEvent); return casperBotDto; } - public LotteryParticipantResponseDto getLotteryParticipant(BaseUser user) throws UserPrincipalNotFoundException { + public LotteryEventParticipantResponseDto getLotteryParticipant(BaseUser user) { LotteryParticipants participant = lotteryParticipantsRepository.findByBaseUser(user) - .orElseThrow(() -> new UserPrincipalNotFoundException("응모 내역이 없습니다.")); - return LotteryParticipantResponseDto.of(participant, getCasperBot(participant.getCasperId())); + .orElseThrow(() -> new CustomException("응모 내역이 없습니다.", CustomErrorCode.USER_NOT_FOUND)); + return LotteryEventParticipantResponseDto.of(participant); } public CasperBotResponseDto getCasperBot(Long casperId) { @@ -78,45 +73,36 @@ public CasperBotResponseDto getCasperBot(Long casperId) { } - public LotteryParticipants registerUserIfNeed(BaseUser user, CasperBotRequestDto casperBotRequestDto) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + public LotteryParticipants registerUserIfNeed(BaseUser user, CasperBotRequestDto casperBotRequestDto) { LotteryParticipants participant = lotteryParticipantsRepository.findByBaseUser(user).orElse(null); if (participant == null) { participant = new LotteryParticipants(user); lotteryParticipantsRepository.save(participant); + addReferralAppliedCount(casperBotRequestDto); } - if (casperBotRequestDto.getReferralId() != null) { + return participant; + } + + private void addReferralAppliedCount(CasperBotRequestDto casperBotRequestDto) { + String encryptedReferralId = casperBotRequestDto.getReferralId(); + if (encryptedReferralId == null) return; + try { String referralId = AESUtils.decrypt(casperBotRequestDto.getReferralId(), secretKey); Optional referralParticipant = lotteryParticipantsRepository.findByBaseUser( - userRepository.findById(referralId).orElse(null) + userRepository.findByPhoneNumber(referralId).orElse(null) ); referralParticipant.ifPresent(LotteryParticipants::linkClickedCountAdded); + } catch (Exception e) { + log.debug(e.getLocalizedMessage()); } - user.updateLotteryParticipants(participant); - userRepository.save(user); - - return participant; } public LotteryEventResponseDto getLotteryEvent() { - LotteryEvent lotteryEvent = getEvent(); + LotteryEvent lotteryEvent = eventCacheService.getLotteryEvent(); return LotteryEventResponseDto.of(lotteryEvent, LocalDateTime.now()); } - - private LotteryEvent getEvent() { - List lotteryEventList = lotteryEventRepository.findAll(); - - if (lotteryEventList.isEmpty()) { - throw new CustomException("현재 진행중인 lotteryEvent가 존재하지 않습니다.", CustomErrorCode.NO_LOTTERY_EVENT); - } - - if (lotteryEventList.size() > 1) { - throw new CustomException("현재 진행중인 lotteryEvent가 2개 이상입니다.", CustomErrorCode.TOO_MANY_LOTTERY_EVENT); - } - - return lotteryEventList.get(0); - } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventScheduler.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventScheduler.java deleted file mode 100644 index 0150eac1..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventScheduler.java +++ /dev/null @@ -1,33 +0,0 @@ -package JGS.CasperEvent.domain.event.service.eventService; - -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.RushEventResponseDto; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; - -import java.time.LocalDate; - -@Service -@EnableScheduling -@RequiredArgsConstructor -public class RushEventScheduler { - - private final RushEventService rushEventService; - private final RedisTemplate rushEventRedisTemplate; - - // 매일 12시에 스케줄된 작업을 실행합니다. - @Scheduled(cron = "0 0 12 * * ?") - public void fetchDailyEvents() { - // 오늘의 날짜를 구합니다. - LocalDate today = LocalDate.now(); - - // EventService를 통해 오늘의 이벤트를 가져옵니다. - RushEventResponseDto todayEvent = rushEventService.getTodayRushEvent(today); - - // 가져온 이벤트에 대한 추가 작업을 수행합니다. - // 예: 캐싱, 로그 기록, 알림 발송 등 - rushEventRedisTemplate.opsForValue().set("todayEvent", todayEvent); - } -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventService.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventService.java index aba48962..5f6d9416 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventService.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventService.java @@ -1,19 +1,22 @@ package JGS.CasperEvent.domain.event.service.eventService; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.*; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventListResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventOptionResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResultResponseDto; import JGS.CasperEvent.domain.event.entity.event.RushEvent; import JGS.CasperEvent.domain.event.entity.event.RushOption; import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; import JGS.CasperEvent.domain.event.repository.eventRepository.RushEventRepository; import JGS.CasperEvent.domain.event.repository.eventRepository.RushOptionRepository; import JGS.CasperEvent.domain.event.repository.participantsRepository.RushParticipantsRepository; +import JGS.CasperEvent.domain.event.service.redisService.RushEventRedisService; import JGS.CasperEvent.global.entity.BaseUser; import JGS.CasperEvent.global.enums.CustomErrorCode; import JGS.CasperEvent.global.enums.Position; import JGS.CasperEvent.global.error.exception.CustomException; import JGS.CasperEvent.global.util.RepositoryErrorHandler; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,36 +32,34 @@ public class RushEventService { private final RushEventRepository rushEventRepository; private final RushParticipantsRepository rushParticipantsRepository; - private final RedisTemplate rushEventRedisTemplate; private final RushOptionRepository rushOptionRepository; + private final EventCacheService eventCacheService; + private final RushEventRedisService rushEventRedisService; @Transactional public RushEventListResponseDto getAllRushEvents() { + LocalDate today = LocalDate.now(); + // 오늘의 선착순 이벤트 꺼내오기 - RushEventResponseDto todayEvent = getTodayRushEvent(); + RushEventResponseDto todayEvent = eventCacheService.getTodayEvent(today); - // DB에서 모든 RushEvent 가져오기 - List rushEventList = rushEventRepository.findAll(); + // 모든 이벤트 꺼내오기 + List mainRushEventDtoList = eventCacheService.getAllRushEvent(); // 선착순 이벤트 전체 시작 날짜와 종료 날짜 구하기 - List dates = rushEventList.stream().map(rushEvent -> rushEvent.getStartDateTime().toLocalDate()).sorted().toList(); + List dates = mainRushEventDtoList.stream().map(rushEvent -> rushEvent.getStartDateTime().toLocalDate()).sorted().toList(); LocalDate totalStartDate = dates.get(0); LocalDate totalEndDate = dates.get(dates.size() - 1); // 전체 이벤트 기간 구하기 - long activePeriod = totalStartDate.until(totalEndDate).getDays() + 1; - - // RushEvent를 DTO로 전환 - List mainRushEventDtoList = rushEventList.stream() - .map(MainRushEventResponseDto::of) - .toList(); - + long activePeriod = totalStartDate.until(totalEndDate).getDays() + 1L; + // DTO 리스트와 서버 시간을 담은 RushEventListAndServerTimeResponse 객체 생성 후 반환 return new RushEventListResponseDto( mainRushEventDtoList, LocalDateTime.now(), - todayEvent.rushEventId(), + todayEvent.getRushEventId(), totalStartDate, totalEndDate, activePeriod @@ -67,16 +68,18 @@ public RushEventListResponseDto getAllRushEvents() { // 응모 여부 조회 public boolean isExists(String userId) { - Long todayEventId = getTodayRushEvent().rushEventId(); - return rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_Id(todayEventId, userId); + LocalDate today = LocalDate.now(); + Long todayEventId = eventCacheService.getTodayEvent(today).getRushEventId(); + return rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_PhoneNumber(todayEventId, userId); } @Transactional public void apply(BaseUser user, int optionId) { - Long todayEventId = getTodayRushEvent().rushEventId(); + LocalDate today = LocalDate.now(); + Long todayEventId = eventCacheService.getTodayEvent(today).getRushEventId(); // 이미 응모한 회원인지 검증 - if (rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_Id(todayEventId, user.getId())) { + if (rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_PhoneNumber(todayEventId, user.getPhoneNumber())) { throw new CustomException("이미 응모한 회원입니다.", CustomErrorCode.CONFLICT); } @@ -89,103 +92,101 @@ public void apply(BaseUser user, int optionId) { } // 진행중인 게임의 응모 비율 반환 - public RushEventRateResponseDto getRushEventRate(BaseUser user) { - Long todayEventId = getTodayRushEvent().rushEventId(); - Optional optionId = rushParticipantsRepository.getOptionIdByUserId(user.getId()); + public RushEventResultResponseDto getRushEventRate(BaseUser user) { + LocalDate today = LocalDate.now(); + Long todayEventId = eventCacheService.getTodayEvent(today).getRushEventId(); + Optional optionId = rushParticipantsRepository.getOptionIdByUserId(user.getPhoneNumber()); + long leftOptionCount = rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(todayEventId, 1); long rightOptionCount = rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(todayEventId, 2); - return new RushEventRateResponseDto( - optionId.orElseThrow(() -> new CustomException("유저가 응모한 선택지가 존재하지 않습니다.", CustomErrorCode.USER_NOT_FOUND)), + return RushEventResultResponseDto.of(optionId.orElseThrow(() -> new CustomException("유저가 응모한 선택지가 존재하지 않습니다.", CustomErrorCode.USER_NOT_FOUND)), leftOptionCount, rightOptionCount); } // 이벤트 결과를 반환 - // 해당 요청은 무조건 응모한 유저일 때만 요청 가능하다고 가정 + // 응모하지 않은 유저가 요청하는 경우가 존재 -> 응모 비율만 반환하도록 수정 @Transactional public RushEventResultResponseDto getRushEventResult(BaseUser user) { - RushEventResponseDto todayRushEvent = getTodayRushEvent(); + LocalDate today = LocalDate.now(); + RushEventResponseDto todayRushEvent = eventCacheService.getTodayEvent(today); + Long todayEventId = todayRushEvent.getRushEventId(); // 최종 선택 비율을 조회 // TODO: 레디스에 캐시 - RushEventRateResponseDto rushEventRateResponseDto = getRushEventRate(user); - long leftOption = rushEventRateResponseDto.leftOption(); - long rightOption = rushEventRateResponseDto.rightOption(); + long leftOption = rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(todayEventId, 1); + long rightOption = rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(todayEventId, 2); + + Optional optionIdOptional = rushParticipantsRepository.getOptionIdByUserId(user.getPhoneNumber()); + if (optionIdOptional.isEmpty()) { + return RushEventResultResponseDto.withDetail( + null, + leftOption, + rightOption, + null, + null, + null + ); + } + + // optionId 를 꺼냄 + int optionId = optionIdOptional.get(); // 동점인 경우 if (leftOption == rightOption) { // 전체 참여자에서 등수 계산하기 - long rank = rushParticipantsRepository.findUserRankByEventIdAndUserId(todayRushEvent.rushEventId(), user.getId()); + long rank = rushParticipantsRepository.findUserRankByEventIdAndUserId(todayRushEvent.getRushEventId(), user.getPhoneNumber()); // 각 옵션 선택지를 더하여 전체 참여자 수 구하기 - long totalParticipants = rushEventRateResponseDto.leftOption() + rushEventRateResponseDto.rightOption(); + long totalParticipants = leftOption + rightOption; // 당첨 여부 - boolean isWinner = rank <= todayRushEvent.winnerCount(); + boolean isWinner = rank <= todayRushEvent.getWinnerCount(); - return new RushEventResultResponseDto(rushEventRateResponseDto, rank, totalParticipants, isWinner); + return RushEventResultResponseDto.withDetail(optionId, leftOption, rightOption, rank, totalParticipants, isWinner); } - // 해당 유저가 선택한 옵션을 가져옴 - int optionId = rushEventRateResponseDto.optionId(); - long totalParticipants = (optionId == 1 ? leftOption : rightOption); // eventId, userId, optionId 를 이용하여 해당 유저가 응모한 선택지에서 등수를 가져옴 - long rank = rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(todayRushEvent.rushEventId(), user.getId(), optionId); + long rank = rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(todayRushEvent.getRushEventId(), user.getPhoneNumber(), optionId); // 해당 유저가 선택한 옵션이 패배한 경우 if ((optionId == 1 && leftOption < rightOption) || (optionId == 2 && leftOption > rightOption)) { - return new RushEventResultResponseDto(rushEventRateResponseDto, rank, totalParticipants, false); - } - - // 당첨 여부 - boolean isWinner = rank <= todayRushEvent.winnerCount(); - - return new RushEventResultResponseDto(rushEventRateResponseDto, rank, totalParticipants, isWinner); - } - - @Transactional - // 오늘의 이벤트를 DB에 꺼내서 반환 - public RushEventResponseDto getTodayRushEvent(LocalDate today) { - // 오늘 날짜에 해당하는 모든 이벤트 꺼내옴 - List rushEventList = rushEventRepository.findByEventDate(today); - if (rushEventList.isEmpty()) { - throw new CustomException("선착순 이벤트가 존재하지않습니다.", CustomErrorCode.NO_RUSH_EVENT); + return RushEventResultResponseDto.withDetail(optionId, leftOption, rightOption, rank, totalParticipants, false); } - if (rushEventList.size() > 1) { - throw new CustomException("선착순 이벤트가 2개 이상 존재합니다.", CustomErrorCode.MULTIPLE_RUSH_EVENTS_FOUND); - } - - return RushEventResponseDto.of(rushEventList.get(0)); - } - - // 오늘의 이벤트 꺼내오기 - private RushEventResponseDto getTodayRushEvent() { - RushEventResponseDto todayEvent = rushEventRedisTemplate.opsForValue().get("todayEvent"); - if (todayEvent == null) { - throw new CustomException("오늘의 이벤트가 Redis에 없습니다.", CustomErrorCode.TODAY_RUSH_EVENT_NOT_FOUND); - } + // 당첨 여부 + boolean isWinner = rank <= todayRushEvent.getWinnerCount(); - return todayEvent; + return RushEventResultResponseDto.withDetail(optionId, leftOption, rightOption, rank, totalParticipants, isWinner); } @Transactional - public void setTodayEventToRedis() { + public void setRushEvents() { // 테이블 초기화 rushParticipantsRepository.deleteAllInBatch(); rushOptionRepository.deleteAllInBatch(); rushEventRepository.deleteAllInBatch(); // 오늘의 날짜를 기준으로 시간 설정 - LocalDateTime startDateTime = LocalDateTime.now().minusDays(2).withHour(22).withMinute(0).withSecond(0).withNano(0); - LocalDateTime endDateTime = startDateTime.plusMinutes(10); + LocalDateTime startDateTime = LocalDateTime.now().minusDays(2).plusSeconds(20).withNano(0); + LocalDateTime endDateTime = startDateTime.plusSeconds(20); + + // 각 이벤트에 대응하는 텍스트들 + String[][] texts = { + {"첫 차는 저렴해야 한다", "가성비 좋게 저렴한 차로 시작해서 차근히 업그레이드하고 싶어", "가성비 좋은 도심형 전기차", "캐스퍼 일렉트릭은 전기차 평균보다 30% 저렴해요 첫 차로 캐스퍼 일렉트릭 어떤가요?", "첫 차는 안전해야 한다", "처음 사는 차인 만큼 안전한 차를 사서 오래 타고 싶어", "가성비 좋은 도심형 전기차", "캐스퍼는 작고 귀엽기만 하다고 생각했나요? 이젠 현대 스마트센스 안전옵션까지 지원해요"}, + {"온라인 쇼핑이 편해서 좋다", "편한게 최고야! 집에서 인터넷으로 쇼핑할래", "현대 유일 온라인 예약", "차 살 때도 온라인 쇼핑을! 오직 온라인에서만 구매할 수 있는 캐스퍼 일렉트릭", "오프라인 쇼핑이 확실해서 좋다", "살 거면 제대로 사야지! 직접 보고 나서 판단할래", "캐스퍼 스튜디오 송파", "캐스퍼 일렉트릭을 원하는 시간에 직접 만나볼 수 있는 무인 전시 스튜디오"}, + {"텐트 치고 캠핑하기", "캠핑은 텐트가 근본이지!", "V2L로 캠핑 준비 끝", "캠핑장 전기 눈치싸움은 이제 그만! 차에 직접 220V 전원을 연결할 수 있어요", "차 안에서 차박하기", "가벼운 짐으로 차에서 잠드는 낭만이 좋아", "풀 폴딩으로 공간활용", "모든 시트가 완전히 접혀서 나만의 작은 방으로 만들 수 있어요"}, + {"평생 주차 무료로 하기", "요즘 주차장은 너무 비싸 주차비 걱정은 그만 하고 싶어", "전기차는 주차비 혜택 받아요", "공영주차장 50% 할인 정책으로 주차비 부담을 덜 수 있어요", "평생 주유 무료로 하기", "기름값이 너무 많이 올랐어 주유비가 많이 들어 고민이야", "전기차는 기름값 걱정 없어요", "주유비 부담 없이 마음껏 드라이브를 즐길 수 있어요"}, + {"무채색 차가 좋다", "검은색, 흰색, 회색! 오래 타려면 무난한 게 최고야", "기본 색감도 다채롭게", "무채색 컬러도 매트부터 메탈릭까지 다양한 질감으로 구성했어요", "컬러풀한 차가 좋다", "무채색은 지루해! 내가 좋아하는 색으로 고를래", "신규 색상 5종 출시", "기존 캐스퍼 색상 라인업에 새로운 5종을 추가! 내 차의 개성을 뽐내봐요"}, + {"주말에는 바다로 가기", "아까운 주말엔 국내여행이라도 다녀오고 싶어", "충전 한 번에 315km", "엔트리급 전기차의 주행거리 혁신 한 번 충전으로 서울에서 강릉까지 왕복도 거뜬해요", "주말에는 도심 드라이브", "평일에 너무 피곤했으니 오랜만에 동네 드라이브나 할래", "충전 한 번에 315km", "엔트리급 전기차의 주행거리 혁신 한 번 충전으로 서울에서 강릉까지 왕복도 거뜬해요"} + }; List rushEvents = new ArrayList<>(); - for (int i = 0; i < 6; i++) { + for (int i = 0; i < texts.length; i++) { // 각 이벤트의 날짜를 오늘 기준으로 설정 RushEvent rushEvent = new RushEvent( startDateTime.plusDays(i), // 이벤트 시작 날짜 @@ -198,20 +199,20 @@ public void setTodayEventToRedis() { // RushOption 생성 RushOption option1 = new RushOption( rushEvent, - "Option 1 Main Text for Event " + (i + 1), - "Option 1 Sub Text for Event " + (i + 1), - "Option 1 Result Main Text for Event " + (i + 1), - "Option 1 Result Sub Text for Event " + (i + 1), + texts[i][0], // Option 1 Main Text + texts[i][1], // Option 1 Sub Text + texts[i][2], // Option 1 Result Main Text + texts[i][3], // Option 1 Result Sub Text "http://example.com/option1-image" + (i + 1) + ".jpg", Position.LEFT ); RushOption option2 = new RushOption( rushEvent, - "Option 2 Main Text for Event " + (i + 1), - "Option 2 Sub Text for Event " + (i + 1), - "Option 2 Result Main Text for Event " + (i + 1), - "Option 2 Result Sub Text for Event " + (i + 1), + texts[i][4], // Option 2 Main Text + texts[i][5], // Option 2 Sub Text + texts[i][6], // Option 2 Result Main Text + texts[i][7], // Option 2 Result Sub Text "http://example.com/option2-image" + (i + 1) + ".jpg", Position.RIGHT ); @@ -228,49 +229,46 @@ public void setTodayEventToRedis() { rushEvents.add(rushEvent); } - // 세 번째로 생성된 RushEvent를 Redis에 저장 - rushEventRedisTemplate.opsForValue().set("todayEvent", RushEventResponseDto.of(rushEvents.get(2))); + eventCacheService.setCacheValue(LocalDate.now()); + eventCacheService.setAllRushEvent(); + rushEventRedisService.clearAllrushEventRate(); } + // 오늘의 이벤트 옵션 정보를 반환 - public MainRushEventOptionsResponseDto getTodayRushEventOptions() { - RushEventResponseDto todayEvent = getTodayRushEvent(); - Set options = todayEvent.options(); + public RushEventResponseDto getTodayRushEventOptions() { + LocalDate today = LocalDate.now(); + RushEventResponseDto todayEvent = eventCacheService.getTodayEvent(today); + Set options = todayEvent.getOptions(); RushEventOptionResponseDto leftOption = options.stream() - .filter(option -> option.position() == Position.LEFT) + .filter(option -> option.getPosition() == Position.LEFT) .findFirst() .orElseThrow(() -> new CustomException("왼쪽 선택지가 존재하지 않습니다.", CustomErrorCode.INVALID_RUSH_EVENT_OPTIONS_COUNT)); RushEventOptionResponseDto rightOption = options.stream() - .filter(option -> option.position() == Position.RIGHT) + .filter(option -> option.getPosition() == Position.RIGHT) .findFirst() .orElseThrow(() -> new CustomException("오른쪽 선택지가 존재하지 않습니다.", CustomErrorCode.INVALID_RUSH_EVENT_OPTIONS_COUNT)); - return new MainRushEventOptionsResponseDto( - MainRushEventOptionResponseDto.of(leftOption), - MainRushEventOptionResponseDto.of(rightOption) - ); + return RushEventResponseDto.withMainOption(leftOption, rightOption); } - public ResultRushEventOptionResponseDto getRushEventOptionResult(int optionId) { + public RushEventOptionResponseDto getRushEventOptionResult(int optionId) { Position position = Position.of(optionId); - RushEventResponseDto todayEvent = getTodayRushEvent(); - Set options = todayEvent.options(); + LocalDate today = LocalDate.now(); + RushEventResponseDto todayEvent = eventCacheService.getTodayEvent(today); + Set options = todayEvent.getOptions(); if (options.size() != 2) { throw new CustomException("해당 이벤트의 선택지가 2개가 아닙니다.", CustomErrorCode.INVALID_RUSH_EVENT_OPTIONS_COUNT); } RushEventOptionResponseDto selectedOption = options.stream() - .filter(option -> option.position() == position) + .filter(option -> option.getPosition() == position) .findFirst() .orElseThrow(() -> new CustomException("사용자가 선택한 선택지가 존재하지 않습니다.", CustomErrorCode.NO_RUSH_EVENT_OPTION)); - return new ResultRushEventOptionResponseDto( - selectedOption.mainText(), - selectedOption.resultMainText(), - selectedOption.resultSubText() - ); + return RushEventOptionResponseDto.inResult(selectedOption); } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/redisService/RedisService.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/redisService/LotteryEventRedisService.java similarity index 81% rename from Server/src/main/java/JGS/CasperEvent/domain/event/service/redisService/RedisService.java rename to Server/src/main/java/JGS/CasperEvent/domain/event/service/redisService/LotteryEventRedisService.java index 3de8b601..88e99dfe 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/service/redisService/RedisService.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/redisService/LotteryEventRedisService.java @@ -1,6 +1,6 @@ package JGS.CasperEvent.domain.event.service.redisService; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.CasperBotResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.CasperBotResponseDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -8,14 +8,14 @@ import java.util.List; @Service -public class RedisService { +public class LotteryEventRedisService { private static final String LIST_KEY = "recentData"; private static final int MAX_SIZE = 100; private final RedisTemplate redisTemplate; @Autowired - public RedisService(RedisTemplate redisTemplate) { + public LotteryEventRedisService(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/redisService/RushEventRedisService.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/redisService/RushEventRedisService.java new file mode 100644 index 00000000..e3ffffe7 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/redisService/RushEventRedisService.java @@ -0,0 +1,56 @@ +package JGS.CasperEvent.domain.event.service.redisService; + +import JGS.CasperEvent.domain.event.repository.participantsRepository.RushParticipantsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Set; + +@Service +@RequiredArgsConstructor +public class RushEventRedisService { + + private final RedisTemplate redisTemplate; + private final RushParticipantsRepository rushParticipantsRepository; + + public Long getOptionCount(Long eventId, int optionId) { + String redisKey = getRedisKey(eventId, optionId); + + // Redis에서 값 조회 + String cachedCount = redisTemplate.opsForValue().get(redisKey); + + if (cachedCount != null) { + // Redis에 값이 있으면 캐시된 값 반환 + return Long.valueOf(cachedCount); + } else { + // Redis에 값이 없으면 DB에서 값 조회 + long countFromDb = rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(eventId, optionId); + + // 조회한 값을 Redis에 저장 + redisTemplate.opsForValue().set(redisKey, String.valueOf(countFromDb)); + + return countFromDb; + } + } + + public void incrementOptionCount(Long eventId, int optionId) { + String redisKey = getRedisKey(eventId, optionId); + + // Redis에서 해당 키의 값을 증가시킴 + redisTemplate.opsForValue().increment(redisKey); + } + + private String getRedisKey(Long eventId, int optionId) { + return "rushEvent:" + eventId + ":option:" + optionId; + } + + public void clearAllrushEventRate() { + // 특정 rushEventId에 대한 모든 옵션 키들을 가져와 삭제 + String pattern = "rushEvent:*" + ":option:*"; + Set keys = redisTemplate.keys(pattern); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); + } + } +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/health/api/HealthController.java b/Server/src/main/java/JGS/CasperEvent/domain/health/api/HealthController.java index 058b61ed..95bd4629 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/health/api/HealthController.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/health/api/HealthController.java @@ -1,16 +1,22 @@ package JGS.CasperEvent.domain.health.api; +import JGS.CasperEvent.global.response.ResponseDto; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/health") +@ResponseBody public class HealthController { @GetMapping - public ResponseEntity health(){ - return ResponseEntity.ok().body("Server OK"); + public ResponseEntity health(){ + return ResponseEntity + .status(HttpStatus.OK) + .body(new ResponseDto("Server OK")); } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/url/controller/UrlController.java b/Server/src/main/java/JGS/CasperEvent/domain/url/controller/UrlController.java index 3503e936..47891334 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/url/controller/UrlController.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/url/controller/UrlController.java @@ -3,6 +3,8 @@ import JGS.CasperEvent.domain.url.dto.ShortenUrlResponseDto; import JGS.CasperEvent.domain.url.service.UrlService; import JGS.CasperEvent.global.entity.BaseUser; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -26,19 +28,25 @@ public UrlController(UrlService urlService) { this.urlService = urlService; } + @Operation(summary = "공유 링크 생성", description = "사용자가 공유할 URL을 단축 링크로 생성합니다.") + @ApiResponse(responseCode = "201", description = "Short URL creation successful") @PostMapping - public ResponseEntity generateShortUrl(HttpServletRequest request) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + public ResponseEntity generateShortUrl(HttpServletRequest request) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { BaseUser user = (BaseUser) request.getAttribute("user"); return ResponseEntity .status(HttpStatus.CREATED) .body(urlService.generateShortUrl(user)); } + @Operation(summary = "공유 링크 접속", description = "단축 링크를 통해 원본 URL로 리다이렉트합니다.") + @ApiResponse(responseCode = "302", description = "Redirect successful") @GetMapping("/{encodedId}") - public ResponseEntity redirectOriginalUrl(@PathVariable String encodedId){ + public ResponseEntity redirectOriginalUrl(@PathVariable String encodedId) { + String originalUrl = urlService.getOriginalUrl(encodedId); return ResponseEntity .status(HttpStatus.FOUND) - .header("Location", urlService.getOriginalUrl(encodedId)) + .header("Location", originalUrl) .build(); } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/url/service/UrlService.java b/Server/src/main/java/JGS/CasperEvent/domain/url/service/UrlService.java index dcb53cfa..a26b3520 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/url/service/UrlService.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/url/service/UrlService.java @@ -6,7 +6,7 @@ import JGS.CasperEvent.global.entity.BaseUser; import JGS.CasperEvent.global.util.AESUtils; import JGS.CasperEvent.global.util.Base62Utils; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -16,9 +16,9 @@ import javax.crypto.SecretKey; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.NoSuchElementException; @Service +@RequiredArgsConstructor public class UrlService { @Value("${client.url}") @@ -33,15 +33,10 @@ public class UrlService { private final UrlRepository urlRepository; private final SecretKey secretKey; - @Autowired - public UrlService(UrlRepository urlRepository, SecretKey secretKey) { - this.urlRepository = urlRepository; - this.secretKey = secretKey; - } - //todo: 테스트 끝나면 수정필요 + // 단축 url 생성 public ShortenUrlResponseDto generateShortUrl(BaseUser user) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { - String encryptedUserId = AESUtils.encrypt(user.getId(), secretKey); + String encryptedUserId = AESUtils.encrypt(user.getPhoneNumber(), secretKey); String originalUrl = clientUrl + "?" + "referralId=" + encryptedUserId; String originalLocalUrl = localClientUrl + "?" + "referralId=" + encryptedUserId; @@ -62,9 +57,10 @@ public ShortenUrlResponseDto generateShortUrl(BaseUser user) throws NoSuchPaddin return new ShortenUrlResponseDto(shortenUrl, shortenLocalUrl); } - public String getOriginalUrl(String encodedId){ + // 원본 url 조회 테스트 + public String getOriginalUrl(String encodedId) { Long urlId = Base62Utils.decode(encodedId); - Url url = urlRepository.findById(urlId).orElseThrow(NoSuchElementException::new); + Url url = urlRepository.findById(urlId).orElseGet(() -> new Url(clientUrl)); return url.getOriginalUrl(); } diff --git a/Server/src/main/java/JGS/CasperEvent/global/config/CacheConfig.java b/Server/src/main/java/JGS/CasperEvent/global/config/CacheConfig.java new file mode 100644 index 00000000..7eed87ab --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/global/config/CacheConfig.java @@ -0,0 +1,16 @@ +package JGS.CasperEvent.global.config; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CacheConfig { + + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager("todayRushEventCache", "ongoingLotteryEvent", "allRushEventCache", "userOptionCache"); + } + +} diff --git a/Server/src/main/java/JGS/CasperEvent/global/config/RedisConfig.java b/Server/src/main/java/JGS/CasperEvent/global/config/RedisConfig.java index ee04b77d..9e499b3a 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/config/RedisConfig.java +++ b/Server/src/main/java/JGS/CasperEvent/global/config/RedisConfig.java @@ -1,7 +1,6 @@ package JGS.CasperEvent.global.config; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.CasperBotResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.RushEventResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.CasperBotResponseDto; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; @@ -25,25 +24,15 @@ public class RedisConfig { @Bean public RedisConnectionFactory redisConnectionFactory(){ RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port); - LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration); - return lettuceConnectionFactory; + return new LettuceConnectionFactory(redisStandaloneConfiguration); } @Bean - public RedisTemplate CasperBotRedisTemplate(){ + public RedisTemplate casperBotRedisTemplate(){ RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } - - @Bean - public RedisTemplate RushEventRedisTemplate(){ - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(redisConnectionFactory()); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); - return redisTemplate; - } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/config/SecurityConfig.java b/Server/src/main/java/JGS/CasperEvent/global/config/SecurityConfig.java index 90dbf9ad..d2721d4d 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/config/SecurityConfig.java +++ b/Server/src/main/java/JGS/CasperEvent/global/config/SecurityConfig.java @@ -1,11 +1,13 @@ package JGS.CasperEvent.global.config; import JGS.CasperEvent.global.util.AESUtils; +import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.crypto.SecretKey; +import java.security.Key; @Configuration public class SecurityConfig { @@ -13,8 +15,17 @@ public class SecurityConfig { @Value("${spring.encryption.key}") private String encryptionKey; + @Value("${spring.jwt.secretKey}") + private String secretKey; + @Bean public SecretKey secretKey() { return AESUtils.stringToKey(encryptionKey); } + + @Bean + public Key jwtKey(){ + byte[] secret = secretKey.getBytes(); + return Keys.hmacShaKeyFor(secret); + } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/config/SwaggerConfig.java b/Server/src/main/java/JGS/CasperEvent/global/config/SwaggerConfig.java index 388701b2..d9e4ca97 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/config/SwaggerConfig.java +++ b/Server/src/main/java/JGS/CasperEvent/global/config/SwaggerConfig.java @@ -2,23 +2,25 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SwaggerConfig { @Bean - public OpenAPI openAPI() { - return new OpenAPI() - .components(new Components()) - .info(apiInfo()); - } + public OpenAPI api() { + SecurityScheme apiKey = new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("Authorization"); - private Info apiInfo() { - return new Info() - .title("API Test") // API의 제목 - .description("Let's practice Swagger UI") // API에 대한 설명 - .version("1.0.0"); // API의 버전 + SecurityRequirement securityRequirement = new SecurityRequirement() + .addList("Bearer Token"); + + return new OpenAPI() + .components(new Components().addSecuritySchemes("Bearer Token", apiKey)) + .addSecurityItem(securityRequirement); } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/config/WebConfig.java b/Server/src/main/java/JGS/CasperEvent/global/config/WebConfig.java index 1dd6cdb4..0cd3f777 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/config/WebConfig.java +++ b/Server/src/main/java/JGS/CasperEvent/global/config/WebConfig.java @@ -1,6 +1,7 @@ package JGS.CasperEvent.global.config; import JGS.CasperEvent.domain.event.service.adminService.AdminService; +import JGS.CasperEvent.global.interceptor.RequestInterceptor; import JGS.CasperEvent.global.jwt.filter.JwtAuthorizationFilter; import JGS.CasperEvent.global.jwt.filter.JwtUserFilter; import JGS.CasperEvent.global.jwt.filter.VerifyAdminFilter; @@ -15,13 +16,11 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; -import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - @Configuration public class WebConfig implements WebMvcConfigurer { - @Bean public FilterRegistrationBean corsFilterRegistrationBean() { FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); @@ -46,7 +45,7 @@ public UrlBasedCorsConfigurationSource corsConfigurationSource() { } @Bean - public FilterRegistrationBean verifyUserFilter(ObjectMapper mapper, UserService userService) { + public FilterRegistrationBean verifyUserFilter(ObjectMapper mapper, UserService userService) { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new VerifyUserFilter(mapper, userService)); @@ -56,7 +55,7 @@ public FilterRegistrationBean verifyUserFilter(ObjectMapper mapper, UserService } @Bean - public FilterRegistrationBean jwtFilter(JwtProvider provider, ObjectMapper mapper) { + public FilterRegistrationBean jwtFilter(JwtProvider provider, ObjectMapper mapper) { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new JwtUserFilter(provider, mapper)); @@ -66,7 +65,7 @@ public FilterRegistrationBean jwtFilter(JwtProvider provider, ObjectMapper mappe } @Bean - public FilterRegistrationBean verifyAdminFilter(ObjectMapper mapper, AdminService adminService) { + public FilterRegistrationBean verifyAdminFilter(ObjectMapper mapper, AdminService adminService) { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(); @@ -77,7 +76,7 @@ public FilterRegistrationBean verifyAdminFilter(ObjectMapper mapper, AdminServic } @Bean - public FilterRegistrationBean jwtAdminFilter(JwtProvider provider, ObjectMapper mapper) { + public FilterRegistrationBean jwtAdminFilter(JwtProvider provider, ObjectMapper mapper) { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new JwtUserFilter(provider, mapper)); @@ -86,7 +85,7 @@ public FilterRegistrationBean jwtAdminFilter(JwtProvider provider, ObjectMapper return filterRegistrationBean; } @Bean - public FilterRegistrationBean jwtAuthorizationFilter(JwtProvider provider, ObjectMapper mapper) { + public FilterRegistrationBean jwtAuthorizationFilter(JwtProvider provider, ObjectMapper mapper) { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new JwtAuthorizationFilter(provider, mapper)); @@ -94,5 +93,10 @@ public FilterRegistrationBean jwtAuthorizationFilter(JwtProvider provider, Objec return filterRegistrationBean; } + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new RequestInterceptor()) + .addPathPatterns("/**"); // 모든 경로에 대해 인터셉터 적용 + } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/entity/BaseUser.java b/Server/src/main/java/JGS/CasperEvent/global/entity/BaseUser.java index 038655cb..9cbbe0ac 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/entity/BaseUser.java +++ b/Server/src/main/java/JGS/CasperEvent/global/entity/BaseUser.java @@ -3,34 +3,41 @@ import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; import JGS.CasperEvent.global.enums.Role; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; import lombok.EqualsAndHashCode; import lombok.Getter; +import java.util.List; + @Getter @Entity @EqualsAndHashCode(callSuper = false) @Inheritance(strategy = InheritanceType.JOINED) public class BaseUser extends BaseEntity { + + @Id - String id; + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Column(unique = true) + String phoneNumber; Role role; @JsonManagedReference - @OneToOne(mappedBy = "baseUser", cascade = CascadeType.ALL) - private LotteryParticipants lotteryParticipants; + @OneToMany(mappedBy = "baseUser", cascade = CascadeType.ALL) + @JsonIgnore + private List lotteryParticipants; @JsonManagedReference - @OneToOne(mappedBy = "baseUser", cascade = CascadeType.ALL) - private RushParticipants rushParticipants; - - public void updateLotteryParticipants(LotteryParticipants lotteryParticipant) { - this.lotteryParticipants = lotteryParticipant; - } + @OneToMany(mappedBy = "baseUser", cascade = CascadeType.ALL) + @JsonIgnore + private List rushParticipants; - public BaseUser(String id, Role role) { - this.id = id; + public BaseUser(String phoneNumber, Role role) { + this.phoneNumber = phoneNumber; this.role = role; } diff --git a/Server/src/main/java/JGS/CasperEvent/global/enums/CustomErrorCode.java b/Server/src/main/java/JGS/CasperEvent/global/enums/CustomErrorCode.java index 7ca81da5..1530b86f 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/enums/CustomErrorCode.java +++ b/Server/src/main/java/JGS/CasperEvent/global/enums/CustomErrorCode.java @@ -5,7 +5,7 @@ @Getter public enum CustomErrorCode { NO_RUSH_EVENT("선착순 이벤트를 찾을 수 없습니다.", 404), - NO_LOTTERY_EVENT("선착순 이벤트를 찾을 수 없습니다.", 404), + NO_LOTTERY_EVENT("추첨 이벤트를 찾을 수 없습니다.", 404), NO_RUSH_EVENT_OPTION("해당 밸런스 게임 선택지를 찾을 수 없습니다.", 404), INVALID_PARAMETER("잘못된 파라미터 입력입니다.", 400), CASPERBOT_NOT_FOUND("배지를 찾을 수 없습니다.", 404), @@ -24,6 +24,7 @@ public enum CustomErrorCode { INVALID_RUSH_EVENT_OPTION_ID("옵션 ID는 1 또는 2여야 합니다.", 400), EMPTY_FILE("유효하지 않은 파일입니다.", 422), TOO_MANY_LOTTERY_EVENT("현재 진행중인 추첨 이벤트가 2개 이상입니다.", 409), + TOO_MANY_RUSH_EVENT("현재 진행중인 선착순 이벤트가 6개 이상입니다.", 409), EVENT_IN_PROGRESS_CANNOT_CHANGE_START_TIME("현재 진행 중인 이벤트의 시작 시간을 변경할 수 없습니다.", 400), EVENT_IN_PROGRESS_END_TIME_BEFORE_NOW("현재 진행 중인 이벤트의 종료 시간을 현재 시간보다 이전으로 설정할 수 없습니다.", 400), EVENT_BEFORE_START_TIME("이벤트 시작 시간은 현재 시간 이후로 설정해야 합니다.", 400), diff --git a/Server/src/main/java/JGS/CasperEvent/global/enums/EventStatus.java b/Server/src/main/java/JGS/CasperEvent/global/enums/EventStatus.java index 84ddd1c0..8f837d93 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/enums/EventStatus.java +++ b/Server/src/main/java/JGS/CasperEvent/global/enums/EventStatus.java @@ -8,9 +8,9 @@ public enum EventStatus { DURING(2), AFTER(3); - private final int eventStatus; + private final int statusNum; - EventStatus(int eventStatus){ - this.eventStatus = eventStatus; + EventStatus(int statusNum){ + this.statusNum = statusNum; } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/enums/Position.java b/Server/src/main/java/JGS/CasperEvent/global/enums/Position.java index 25c7a205..4b4e1551 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/enums/Position.java +++ b/Server/src/main/java/JGS/CasperEvent/global/enums/Position.java @@ -8,15 +8,15 @@ public enum Position { LEFT(1), RIGHT(2); - private final int position; + private final int positionNum; - Position(int position) { - this.position = position; + Position(int positionNum) { + this.positionNum = positionNum; } public static Position of(int position) { for (Position pos : Position.values()) { - if (pos.getPosition() == position) { + if (pos.getPositionNum() == position) { return pos; } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/error/GlobalExceptionHandler.java b/Server/src/main/java/JGS/CasperEvent/global/error/GlobalExceptionHandler.java index d6ba3665..ce2addb4 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/error/GlobalExceptionHandler.java +++ b/Server/src/main/java/JGS/CasperEvent/global/error/GlobalExceptionHandler.java @@ -2,53 +2,32 @@ import JGS.CasperEvent.global.enums.CustomErrorCode; import JGS.CasperEvent.global.error.exception.CustomException; -import JGS.CasperEvent.global.error.exception.TooManyLotteryEventException; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.JDBCConnectionException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.MissingRequestCookieException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; - -import java.nio.file.attribute.UserPrincipalNotFoundException; - @RestControllerAdvice +@Slf4j public class GlobalExceptionHandler { @ExceptionHandler(CustomException.class) public ResponseEntity handler(CustomException e){ + log.error("CustomException: {} {}", e.getErrorCode(), e.getMessage()); return ResponseEntity .status(HttpStatus.valueOf(e.getErrorCode().getStatus())) .body(ErrorResponse.of(e.getErrorCode(), e.getMessage())); } - @ExceptionHandler(MissingRequestCookieException.class) - public ResponseEntity missingCookieHandler(){ - return ResponseEntity - .status(HttpStatus.UNAUTHORIZED) - .body(ErrorResponse.of(CustomErrorCode.UNAUTHORIZED)); - } - - @ExceptionHandler(UserPrincipalNotFoundException.class) - public ResponseEntity userPrincipalNotFoundHandler(){ - return ResponseEntity - .status(HttpStatus.CONFLICT) - .body(ErrorResponse.of(CustomErrorCode.USER_NOT_FOUND)); - } - - @ExceptionHandler(TooManyLotteryEventException.class) - public ResponseEntity tooManyLotteryEventExceptionHandler(){ - return ResponseEntity - .status(HttpStatus.NOT_FOUND) - .body(ErrorResponse.of(CustomErrorCode.LOTTERY_EVENT_ALREADY_EXISTS)); - } - @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e){ - BindingResult bindingResult = e.getBindingResult(); + log.error("MethodArgumentNotValidException: [{}]", e.getMessage()); + BindingResult bindingResult = e.getBindingResult(); StringBuilder builder = new StringBuilder(); for (FieldError fieldError : bindingResult.getFieldErrors()) { builder.append(fieldError.getDefaultMessage()); @@ -63,10 +42,27 @@ public ResponseEntity methodArgumentNotValidExceptionHandler(Meth } @ExceptionHandler(RuntimeException.class) - public ResponseEntity RuntimeExceptionHandler(RuntimeException e){ + public ResponseEntity runtimeExceptionHandler(RuntimeException e){ + log.error("RuntimeException: [{}]", e.getMessage()); return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body(ErrorResponse.of(CustomErrorCode.BAD_REQUEST, e.getMessage())); } + @ExceptionHandler(JDBCConnectionException.class) + public ResponseEntity handleJDBCConnectionException(JDBCConnectionException e) { + log.error("JDBCConnectionException: [{}]", e.getMessage()); + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(ErrorResponse.of(CustomErrorCode.BAD_REQUEST, e.getMessage())); + + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllExceptions(Exception e) { + log.error("Exception: {}", e.getMessage()); + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(ErrorResponse.of(CustomErrorCode.BAD_REQUEST, e.getMessage())); + } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/error/exception/LotteryEventNotExists.java b/Server/src/main/java/JGS/CasperEvent/global/error/exception/LotteryEventNotExists.java deleted file mode 100644 index 314b513d..00000000 --- a/Server/src/main/java/JGS/CasperEvent/global/error/exception/LotteryEventNotExists.java +++ /dev/null @@ -1,4 +0,0 @@ -package JGS.CasperEvent.global.error.exception; - -public class LotteryEventNotExists extends RuntimeException{ -} diff --git a/Server/src/main/java/JGS/CasperEvent/global/error/exception/TooManyLotteryEventException.java b/Server/src/main/java/JGS/CasperEvent/global/error/exception/TooManyLotteryEventException.java deleted file mode 100644 index 94c742cc..00000000 --- a/Server/src/main/java/JGS/CasperEvent/global/error/exception/TooManyLotteryEventException.java +++ /dev/null @@ -1,5 +0,0 @@ -package JGS.CasperEvent.global.error.exception; - -public class TooManyLotteryEventException extends RuntimeException { - -} diff --git a/Server/src/main/java/JGS/CasperEvent/global/error/exception/TooManyRushEventException.java b/Server/src/main/java/JGS/CasperEvent/global/error/exception/TooManyRushEventException.java deleted file mode 100644 index 8f48f25d..00000000 --- a/Server/src/main/java/JGS/CasperEvent/global/error/exception/TooManyRushEventException.java +++ /dev/null @@ -1,4 +0,0 @@ -package JGS.CasperEvent.global.error.exception; - -public class TooManyRushEventException extends RuntimeException{ -} diff --git a/Server/src/main/java/JGS/CasperEvent/global/interceptor/RequestInterceptor.java b/Server/src/main/java/JGS/CasperEvent/global/interceptor/RequestInterceptor.java new file mode 100644 index 00000000..ffdbabd4 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/global/interceptor/RequestInterceptor.java @@ -0,0 +1,46 @@ +package JGS.CasperEvent.global.interceptor; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.UUID; + +@Component +@Slf4j +public class RequestInterceptor implements HandlerInterceptor { + + private static final String REQUEST_ID = "requestId"; + + @Override + public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { + // UUID를 사용해 고유한 requestId 생성 + String requestId = UUID.randomUUID().toString(); + + // MDC에 requestId 추가하여 로깅 시 포함되도록 설정 + MDC.put(REQUEST_ID, requestId); + + String requestURI = request.getMethod() + " " + request.getRequestURL(); + + String queryString = request.getQueryString(); + + log.info("Request [{}] QueryString [{}]", requestURI, queryString); + + // 요청의 헤더에 requestId 추가 (선택 사항) + response.addHeader(REQUEST_ID, requestId); + + return true; // 다음 인터셉터나 컨트롤러로 요청 전달 + } + + @Override + public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) { + log.info("Response {} [{}]", response.getStatus(), handler); + + // 요청이 완료된 후 MDC에서 requestId 제거 + MDC.remove(REQUEST_ID); + } +} \ No newline at end of file diff --git a/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/JwtAuthorizationFilter.java b/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/JwtAuthorizationFilter.java index f9b7cf1e..9c5ccc36 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/JwtAuthorizationFilter.java +++ b/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/JwtAuthorizationFilter.java @@ -32,12 +32,18 @@ public class JwtAuthorizationFilter implements Filter { "/event/rush", "/event/lottery/caspers", "/admin/join", "/admin/auth", "/h2", "/h2/*", "/swagger-ui/*", "/v3/api-docs", "/v3/api-docs/*", - "/event/lottery", "/link/*", "/event/total" + "/event/lottery", "/link/*", "/event/total", + "/actuator", "/actuator/*" }; private final String[] blackListUris = new String[]{ "/event/rush/*", "/event/lottery/casperBot" }; + private final String[] exceptionUris = new String[]{ + "/event/rush/today/test" + }; + + private final JwtProvider jwtProvider; private final ObjectMapper objectMapper; @@ -48,7 +54,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String requestUri = httpServletRequest.getRequestURI(); - if (whiteListCheck(requestUri) && !blackListCheck(requestUri)) { + // 예외 리스트 체크 + if (exceptionCheck(requestUri) || (whiteListCheck(requestUri) && !blackListCheck(requestUri))) { chain.doFilter(request, response); return; } @@ -62,7 +69,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String token = getToken(httpServletRequest); BaseUser user = getAuthenticateUser(token); verifyAuthorization(requestUri, user); - log.info("값 : {}", user.getId()); + log.info("값 : {}", user.getPhoneNumber()); httpServletRequest.setAttribute("user", user); chain.doFilter(request, response); } catch (JsonParseException e) { @@ -80,6 +87,10 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } } + private boolean exceptionCheck(String uri) { + return PatternMatchUtils.simpleMatch(exceptionUris, uri); + } + private boolean whiteListCheck(String uri) { return PatternMatchUtils.simpleMatch(whiteListUris, uri); } diff --git a/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/VerifyAdminFilter.java b/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/VerifyAdminFilter.java index 19d97a11..bbfbb73c 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/VerifyAdminFilter.java +++ b/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/VerifyAdminFilter.java @@ -1,6 +1,6 @@ package JGS.CasperEvent.global.jwt.filter; -import JGS.CasperEvent.domain.event.dto.RequestDto.AdminRequestDto; +import JGS.CasperEvent.domain.event.dto.request.AdminRequestDto; import JGS.CasperEvent.domain.event.entity.admin.Admin; import JGS.CasperEvent.domain.event.service.adminService.AdminService; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/VerifyUserFilter.java b/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/VerifyUserFilter.java index d935f314..8422493f 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/VerifyUserFilter.java +++ b/Server/src/main/java/JGS/CasperEvent/global/jwt/filter/VerifyUserFilter.java @@ -31,7 +31,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha request.setAttribute(AUTHENTICATE_USER, user); chain.doFilter(request, response); } catch (Exception e) { - log.error("Fail User Verify"); + log.error("Fail User Verify: {}", e.getMessage()); HttpServletResponse httpServletResponse = (HttpServletResponse) response; httpServletResponse.sendError(HttpStatus.BAD_REQUEST.value(), e.getMessage()); } diff --git a/Server/src/main/java/JGS/CasperEvent/global/jwt/repository/UserRepository.java b/Server/src/main/java/JGS/CasperEvent/global/jwt/repository/UserRepository.java index 8dbf311e..a262c44f 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/jwt/repository/UserRepository.java +++ b/Server/src/main/java/JGS/CasperEvent/global/jwt/repository/UserRepository.java @@ -2,6 +2,8 @@ import JGS.CasperEvent.global.entity.BaseUser; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { + Optional findByPhoneNumber(String phoneNumber); } diff --git a/Server/src/main/java/JGS/CasperEvent/global/jwt/service/UserService.java b/Server/src/main/java/JGS/CasperEvent/global/jwt/service/UserService.java index 536f0fb7..56573740 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/jwt/service/UserService.java +++ b/Server/src/main/java/JGS/CasperEvent/global/jwt/service/UserService.java @@ -15,7 +15,7 @@ public class UserService { private final UserRepository userRepository; public BaseUser verifyUser(UserLoginDto userLoginDto) { - return userRepository.findById(userLoginDto.getPhoneNumber()).orElseGet( + return userRepository.findByPhoneNumber(userLoginDto.getPhoneNumber()).orElseGet( () -> userRepository.save(new BaseUser(userLoginDto.getPhoneNumber(), Role.USER))); } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/jwt/util/JwtProvider.java b/Server/src/main/java/JGS/CasperEvent/global/jwt/util/JwtProvider.java index 69745bb0..d4cb645a 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/jwt/util/JwtProvider.java +++ b/Server/src/main/java/JGS/CasperEvent/global/jwt/util/JwtProvider.java @@ -3,7 +3,7 @@ import JGS.CasperEvent.global.jwt.dto.Jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import java.security.Key; @@ -11,9 +11,10 @@ import java.util.Map; @Component +@RequiredArgsConstructor public class JwtProvider { - public static final byte[] secret = "JaeYoungSecretKeyJaeYoungSecretKeyJaeYoungSecretKey".getBytes(); - private final Key key = Keys.hmacShaKeyFor(secret); + + private final Key jwtKey; public Jwt createJwt(Map claims) { String accessToken = createToken(claims, getExpireDateAccessToken()); @@ -26,13 +27,13 @@ private String createToken(Map claims, Date expireDate) { return Jwts.builder() .setClaims(claims) .setExpiration(expireDate) - .signWith(key) + .signWith(jwtKey) .compact(); } public Claims getClaims(String token) { return Jwts.parserBuilder() - .setSigningKey(key) + .setSigningKey(jwtKey) .build() .parseClaimsJws(token) .getBody(); diff --git a/Server/src/main/java/JGS/CasperEvent/global/response/CustomErrorResponse.java b/Server/src/main/java/JGS/CasperEvent/global/response/CustomErrorResponse.java deleted file mode 100644 index e7f2adc0..00000000 --- a/Server/src/main/java/JGS/CasperEvent/global/response/CustomErrorResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package JGS.CasperEvent.global.response; - -import JGS.CasperEvent.global.error.exception.CustomException; - -public record CustomErrorResponse(String message) { - public static CustomErrorResponse returnError(CustomException e){ - return new CustomErrorResponse(e.getMessage()); - } - - public static CustomErrorResponse returnError(String message){ - return new CustomErrorResponse(message); - } -} \ No newline at end of file diff --git a/Server/src/main/java/JGS/CasperEvent/global/response/ListResponse.java b/Server/src/main/java/JGS/CasperEvent/global/response/ListResponse.java deleted file mode 100644 index ec790574..00000000 --- a/Server/src/main/java/JGS/CasperEvent/global/response/ListResponse.java +++ /dev/null @@ -1,88 +0,0 @@ -package JGS.CasperEvent.global.response; - - -import com.fasterxml.jackson.annotation.JsonInclude; -import io.swagger.v3.oas.annotations.media.Schema; -import org.springframework.data.domain.Page; - -import java.util.List; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@Schema(title = "리스트 응답 래핑 DTO") -public class ListResponse { - private final List result; - private final Metadata metadata; - - public ListResponse(List result, Metadata metadata) { - this.result = result; - this.metadata = metadata; - } - - public List getResult() { - return result; - } - - public Metadata getMetadata() { - return metadata; - } - - public static ListResponse create(Page page) { - return new ListResponse<>(page.toList(), Metadata.create(page)); - } - - public static ListResponse create(List list) { - return new ListResponse<>(list, null); - } - - public static ListResponse create(List list, Page page) { - return new ListResponse<>(list, Metadata.create(page)); - } - - public static ListResponse create(List list, Long totalCount, Integer size, String lastId) { - return new ListResponse<>(list, Metadata.create(totalCount, size, lastId)); - } - - @Schema(title = "검색 메타데이터") - public static class Metadata { - private final Long totalCount; - private final Integer totalPageCount; - private final Integer size; - public Integer getCurrentPageNumber() { - return currentPageNumber; - } - private final Integer currentPageNumber; - private final String lastId; - - public Metadata(Long totalCount, Integer totalPageCount, Integer size, Integer currentPageNumber, String lastId) { - this.totalCount = totalCount; - this.totalPageCount = totalPageCount; - this.size = size; - this.currentPageNumber = currentPageNumber; - this.lastId = lastId; - } - - public Long getTotalCount() { - return totalCount; - } - - public Integer getTotalPageCount() { - return totalPageCount; - } - - public Integer getSize() { - return size; - } - - public String getLastId() { - return lastId; - } - - public static Metadata create(Page page) { - return new Metadata(page.getTotalElements(), page.getTotalPages(), page.getSize(), page.getNumber(),""); - } - - public static Metadata create(Long totalCount, Integer size, String lastId) { - return new Metadata(totalCount, 0, size, 0, lastId); - } - } -} \ No newline at end of file diff --git a/Server/src/main/java/JGS/CasperEvent/global/response/ResponseDto.java b/Server/src/main/java/JGS/CasperEvent/global/response/ResponseDto.java index 97f4408b..4ef85a74 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/response/ResponseDto.java +++ b/Server/src/main/java/JGS/CasperEvent/global/response/ResponseDto.java @@ -1,8 +1,5 @@ package JGS.CasperEvent.global.response; public record ResponseDto(String message) { - public static ResponseDto of(String message) { - return new ResponseDto(message); - } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/service/S3Service.java b/Server/src/main/java/JGS/CasperEvent/global/service/S3Service.java index 87531748..e0518f9a 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/service/S3Service.java +++ b/Server/src/main/java/JGS/CasperEvent/global/service/S3Service.java @@ -36,7 +36,11 @@ public String upload(MultipartFile image) { } private String uploadImage(MultipartFile image) { - this.validateImageFileExtension(image.getOriginalFilename()); + String originalFilename = image.getOriginalFilename(); + if (originalFilename == null) { + throw new AmazonS3Exception("파일명이 null입니다."); + } + this.validateImageFileExtension(originalFilename); try { return this.uploadImageToS3(image); } catch (IOException e) { @@ -59,7 +63,7 @@ private void validateImageFileExtension(String filename) { } private String uploadImageToS3(MultipartFile image) throws IOException { - String originalFilename = image.getOriginalFilename(); //원본 파일 명 + String originalFilename = image.getOriginalFilename(); String extension = Objects.requireNonNull(originalFilename).substring(originalFilename.lastIndexOf(".") + 1); //확장자 명 String s3FileName = "image/" + UUID.randomUUID().toString().substring(0, 10) + originalFilename; //변경된 파일 명 @@ -67,23 +71,17 @@ private String uploadImageToS3(MultipartFile image) throws IOException { InputStream is = image.getInputStream(); byte[] bytes = IOUtils.toByteArray(is); - ObjectMetadata metadata = new ObjectMetadata(); //metadata 생성 + ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType("image/" + extension); metadata.setContentLength(bytes.length); - ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); - - try { + try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, s3FileName, byteArrayInputStream, metadata); -// .withCannedAcl(CannedAccessControlList.PublicRead); - //실제로 S3에 이미지 데이터를 넣는 부분이다. - amazonS3.putObject(putObjectRequest); // put image to S3 + amazonS3.putObject(putObjectRequest); + is.close(); } catch (Exception e) { throw new AmazonS3Exception("이미지 업로드에 실패했습니다."); - } finally { - byteArrayInputStream.close(); - is.close(); } return amazonS3.getUrl(bucketName, s3FileName).toString(); diff --git a/Server/src/main/java/JGS/CasperEvent/global/util/AESUtils.java b/Server/src/main/java/JGS/CasperEvent/global/util/AESUtils.java index e6bbec9d..6d91c92e 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/util/AESUtils.java +++ b/Server/src/main/java/JGS/CasperEvent/global/util/AESUtils.java @@ -8,6 +8,9 @@ public class AESUtils { + private AESUtils() { + } + public static SecretKey stringToKey(String keyString) { byte[] decodedKey = keyString.getBytes(); return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); diff --git a/Server/src/main/java/JGS/CasperEvent/global/util/Base62Utils.java b/Server/src/main/java/JGS/CasperEvent/global/util/Base62Utils.java index a32e7839..556adb2f 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/util/Base62Utils.java +++ b/Server/src/main/java/JGS/CasperEvent/global/util/Base62Utils.java @@ -3,6 +3,9 @@ public class Base62Utils { private static final String BASE62_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + private Base62Utils() { + } + public static String encode(long number) { if (number == 0) return Character.toString(BASE62_CHARS.charAt(0)); diff --git a/Server/src/main/java/JGS/CasperEvent/global/util/RepositoryErrorHandler.java b/Server/src/main/java/JGS/CasperEvent/global/util/RepositoryErrorHandler.java index f4894091..6deb261d 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/util/RepositoryErrorHandler.java +++ b/Server/src/main/java/JGS/CasperEvent/global/util/RepositoryErrorHandler.java @@ -5,6 +5,10 @@ import org.springframework.data.jpa.repository.JpaRepository; public class RepositoryErrorHandler { + + private RepositoryErrorHandler() { + } + public static T findByIdOrElseThrow(JpaRepository repository, ID id, CustomErrorCode customErrorCode) { return repository.findById(id).orElseThrow( () -> new EntityNotFoundException(customErrorCode.name()) diff --git a/Server/src/main/java/JGS/CasperEvent/global/util/UserUtil.java b/Server/src/main/java/JGS/CasperEvent/global/util/UserUtil.java deleted file mode 100644 index a3ffb0cb..00000000 --- a/Server/src/main/java/JGS/CasperEvent/global/util/UserUtil.java +++ /dev/null @@ -1,12 +0,0 @@ -package JGS.CasperEvent.global.util; - -import java.util.concurrent.atomic.AtomicLong; - -public class UserUtil { - //TODO: 스프링 서버 뻗으면 캐스퍼 아이디 0부터 다시 시작함 - private static final AtomicLong counter = new AtomicLong(0); - public static long generateId(){ - return counter.incrementAndGet(); - } - -} diff --git a/Server/src/main/resources/application-prod.yml b/Server/src/main/resources/application-prod.yml index c1da7f6f..59c99538 100644 --- a/Server/src/main/resources/application-prod.yml +++ b/Server/src/main/resources/application-prod.yml @@ -4,6 +4,8 @@ spring: username: ${SPRING_DATASOURCE_USERNAME} password: ${SPRING_DATASOURCE_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: ${DB_MAX_POOL_SIZE:10} # 기본값 10, 환경변수 DB_MAX_POOL_SIZE로 대체 가능 jpa: hibernate: ddl-auto: update @@ -29,9 +31,22 @@ spring: servlet: multipart: max-file-size: 10MB + jwt: + secretKey: + ${JWT_SECRET_KEY} + client: url: ${CLIENT_URL} localUrl: ${LOCAL_CLIENT_URL} shortenUrlService: - url: ${SPRING_SERVER_URL} \ No newline at end of file + url: ${SPRING_SERVER_URL} + +management: + endpoints: + web: + exposure: + include: prometheus, health, info + metrics: + tags: + application: ${spring.application.name} diff --git a/Server/src/main/resources/application.yml b/Server/src/main/resources/application.yml index 19292914..695816b1 100644 --- a/Server/src/main/resources/application.yml +++ b/Server/src/main/resources/application.yml @@ -1,4 +1,4 @@ spring: - jpa: - show-sql: true + application: + name: hybrid-jgs # 데이터베이스 설정은 공통으로 지정하지 않음 diff --git a/Server/src/main/resources/logback-spring.xml b/Server/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..5b0cd143 --- /dev/null +++ b/Server/src/main/resources/logback-spring.xml @@ -0,0 +1,144 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n + + + + + + + + ./logs/info.log + + ./logs/info.%d{yyyy-MM-dd}.%i.log.gz + 10MB + 30 + 1GB + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n + + + INFO + ACCEPT + DENY + + + + + + ./logs/warn.log + + ./logs/warn.%d{yyyy-MM-dd}.%i.log.gz + 10MB + 30 + 1GB + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n + + + WARN + ACCEPT + DENY + + + + + + ./logs/error.log + + ./logs/error.%d{yyyy-MM-dd}.%i.log.gz + 10MB + 30 + 1GB + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n + + + ERROR + ACCEPT + DENY + + + + + + + + + + + + + + + + + /logs/info.log + + /logs/info.%d{yyyy-MM-dd}.%i.log.gz + 10MB + 30 + 1GB + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n + + + INFO + ACCEPT + DENY + + + + + + /logs/warn.log + + /logs/warn.%d{yyyy-MM-dd}.%i.log.gz + 10MB + 30 + 1GB + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n + + + WARN + ACCEPT + DENY + + + + + + /logs/error.log + + /logs/error.%d{yyyy-MM-dd}.%i.log.gz + 10MB + 30 + 1GB + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n + + + ERROR + ACCEPT + DENY + + + + + + + + + + + + diff --git a/Server/src/test/java/JGS/CasperEvent/CasperEventApplicationTests.java b/Server/src/test/java/JGS/CasperEvent/CasperEventApplicationTests.java index f2636be9..9923f5a1 100644 --- a/Server/src/test/java/JGS/CasperEvent/CasperEventApplicationTests.java +++ b/Server/src/test/java/JGS/CasperEvent/CasperEventApplicationTests.java @@ -37,7 +37,7 @@ public void setup(){ void HealthTest() throws Exception { mockMvc.perform(get("/health")) .andExpect(status().isOk()) - .andExpect(content().string("Server OK")) + .andExpect(jsonPath("$.message").value("Server OK")) .andDo(print()); } diff --git a/Server/src/test/java/JGS/CasperEvent/domain/event/controller/adminController/AdminControllerTest.java b/Server/src/test/java/JGS/CasperEvent/domain/event/controller/adminController/AdminControllerTest.java index 88897ad1..c90e3341 100644 --- a/Server/src/test/java/JGS/CasperEvent/domain/event/controller/adminController/AdminControllerTest.java +++ b/Server/src/test/java/JGS/CasperEvent/domain/event/controller/adminController/AdminControllerTest.java @@ -1,55 +1,745 @@ package JGS.CasperEvent.domain.event.controller.adminController; +import JGS.CasperEvent.domain.event.dto.request.AdminRequestDto; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.CasperBotRequestDto; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.LotteryEventRequestDto; +import JGS.CasperEvent.domain.event.dto.request.rushEventDto.RushEventOptionRequestDto; +import JGS.CasperEvent.domain.event.dto.request.rushEventDto.RushEventRequestDto; +import JGS.CasperEvent.domain.event.dto.response.ImageUrlResponseDto; +import JGS.CasperEvent.domain.event.dto.response.ParticipantsListResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.ExpectationsPagingResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; +import JGS.CasperEvent.domain.event.entity.admin.Admin; +import JGS.CasperEvent.domain.event.entity.casperBot.CasperBot; +import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; +import JGS.CasperEvent.domain.event.entity.event.RushEvent; +import JGS.CasperEvent.domain.event.entity.event.RushOption; +import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; +import JGS.CasperEvent.domain.event.entity.participants.LotteryWinners; +import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; +import JGS.CasperEvent.domain.event.service.adminService.AdminService; +import JGS.CasperEvent.global.entity.BaseUser; +import JGS.CasperEvent.global.enums.Position; +import JGS.CasperEvent.global.enums.Role; +import JGS.CasperEvent.global.jwt.service.UserService; +import JGS.CasperEvent.global.jwt.util.JwtProvider; +import JGS.CasperEvent.global.response.ResponseDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.security.Keys; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; -import java.util.UUID; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.lenient; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@SpringBootTest -@AutoConfigureMockMvc -@ActiveProfiles("local") -public class AdminControllerTest { +@WebMvcTest(value = AdminController.class) +class AdminControllerTest { @Autowired private MockMvc mockMvc; + @Autowired + ObjectMapper objectMapper; + + @MockBean + private AdminService adminService; + @MockBean + private UserService userService; + + private Admin admin; + private String adminId; + private String password; + private String accessToken; + private BaseUser user; + + private CasperBotRequestDto casperBotRequestDto; + private CasperBot casperBot; + private LotteryEvent lotteryEvent; + private LotteryEventRequestDto lotteryEventRequestDto; + private LotteryEventResponseDto lotteryEventResponseDto; + private LotteryParticipants lotteryParticipants; + private LotteryEventParticipantResponseDto lotteryEventParticipantsResponseDto; + private ParticipantsListResponseDto lotteryEventParticipantsListResponseDto; + private LotteryEventResponseDto lotteryEventDetailResponseDto; + private ExpectationsPagingResponseDto expectationsPagingResponseDto; + private LotteryEventResponseDto lotteryEventExpectationResponseDto; + private ParticipantsListResponseDto lotteryEventWinnerListResponseDto; + private LotteryEventParticipantResponseDto lotteryEventWinnerResponseDto; + private LotteryWinners lotteryWinners; - @Nested - @DisplayName("어드민 테스트") - class AdminTest{ - @Test - @DisplayName("어드민 생성 성공 테스트") - void createAdminSuccessTest() throws Exception { - //given - String adminId = UUID.randomUUID().toString(); - String adminRequest = String.format(""" - { - "adminId": "%s", - "password": "password" - } - """, adminId); - - //when - ResultActions perform = mockMvc.perform(post("/admin/join") - .contentType(MediaType.APPLICATION_JSON) - .content(adminRequest)); - - //then - perform - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.message").value("관리자 생성 성공")) - .andDo(print()); + + private RushEventRequestDto rushEventRequestDto; + private RushEventOptionRequestDto leftOptionRequestDto; + private RushEventOptionRequestDto rightOptionRequestDto; + private RushEventResponseDto adminRushEventResponseDto; + private RushEvent rushEvent; + private RushOption leftOption; + private RushOption rightOption; + private RushParticipants rushParticipants; + private RushEventParticipantResponseDto rushEventParticipantResponseDto; + private ParticipantsListResponseDto rushEventParticipantsListResponseDto; + + @TestConfiguration + static class TestConfig{ + @Bean + public JwtProvider jwtProvider(){ + String secretKey = "mockKEymockKEymockKEymockKEymockKEymockKEymockKEy"; + byte[] secret = secretKey.getBytes(); + return new JwtProvider(Keys.hmacShaKeyFor(secret)); } } + + @BeforeEach + void setUp() throws Exception { + this.adminId = "adminId"; + this.password = "password"; + + admin = new Admin(adminId, password, Role.ADMIN); + given(adminService.verifyAdmin(any())).willReturn(admin); + + user = spy(new BaseUser("010-0000-0000", Role.USER)); + lenient().when(user.getCreatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + lenient().when(user.getUpdatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + given(userService.verifyUser(any())).willReturn(user); + // 엑세스 토큰 설정 + this.accessToken = getToken(adminId, password); + + // 추첨 이벤트 설정 + this.lotteryEvent = new LotteryEvent(LocalDateTime.of(2000, 9, 27, 0, 0, 0), LocalDateTime.of(2100, 9, 27, 0, 0, 0), 315); + + // 추첨 이벤트 생성 DTO + this.lotteryEventRequestDto = LotteryEventRequestDto.builder() + .startDate(LocalDate.of(2000, 9, 27)) + .startTime(LocalTime.of(0, 0, 0)) + .endDate(LocalDate.of(2100, 9, 27)) + .endTime(LocalTime.of(0, 0, 0)) + .winnerCount(315) + .build(); + + // 추첨 이벤트 응답 DTO + this.lotteryEventResponseDto = LotteryEventResponseDto.of(lotteryEvent, LocalDateTime.of(2024, 8, 15, 0, 0, 0)); + + // 추첨 이벤트 참여자 객체 + LotteryParticipants realLotteryParticipants = new LotteryParticipants(user); + this.lotteryParticipants = spy(realLotteryParticipants); + doReturn(1L).when(lotteryParticipants).getId(); + doReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)).when(lotteryParticipants).getCreatedAt(); + doReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)).when(lotteryParticipants).getUpdatedAt(); + + + // 추첨 이벤트 참여자 응답 DTO + this.lotteryEventParticipantsResponseDto = LotteryEventParticipantResponseDto.withDetail(lotteryParticipants); + + // 추첨 이벤트 참여자 리스트 응답 DTO + List participants = new ArrayList<>(); + participants.add(lotteryEventParticipantsResponseDto); + this.lotteryEventParticipantsListResponseDto = new ParticipantsListResponseDto<>(participants, true, 1); + + // 추첨 이벤트 상세 응답 DTO + lotteryEventDetailResponseDto = LotteryEventResponseDto.withDetail(lotteryEvent, 1L); + + // 캐스퍼 봇 + casperBotRequestDto = CasperBotRequestDto.builder() + .eyeShape(0) + .eyePosition(0) + .mouthShape(0) + .color(0) + .sticker(0) + .name("name") + .expectation("expectation") + .referralId("QEszP1K8IqcapUHAVwikXA==").build(); + + casperBot = spy(new CasperBot(casperBotRequestDto, "010-0000-0000")); + lenient().when(casperBot.getCreatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + lenient().when(casperBot.getUpdatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + + // 추첨 이벤트 당첨자 엔티티 + lotteryWinners = spy(new LotteryWinners(lotteryParticipants)); + doReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)).when(lotteryWinners).getCreatedAt(); + doReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)).when(lotteryWinners).getUpdatedAt(); + + + // 추첨 이벤트 당첨자 응답 DTO + lotteryEventWinnerResponseDto = LotteryEventParticipantResponseDto.winner(lotteryWinners); + + // 추첨 이벤트 당첨자 리스트 응답 DTO + List lotteryEventWinnerResponseDtoList = new ArrayList<>(); + lotteryEventWinnerResponseDtoList.add(lotteryEventWinnerResponseDto); + lotteryEventWinnerListResponseDto = new ParticipantsListResponseDto<>(lotteryEventWinnerResponseDtoList, true, 1); + + // 추첨 이벤트 기대평 응답 DTO + lotteryEventExpectationResponseDto = LotteryEventResponseDto.withExpectation(casperBot); + + // 추첨 이벤트 기대평 리스트 응답 DTO + List lotteryEventExpectationResponseDtoList = new ArrayList<>(); + lotteryEventExpectationResponseDtoList.add(lotteryEventExpectationResponseDto); + expectationsPagingResponseDto = new ExpectationsPagingResponseDto(lotteryEventExpectationResponseDtoList, true, 1); + + + // 선착순 이벤트 왼쪽 옵션 + leftOptionRequestDto = RushEventOptionRequestDto.builder() + .rushOptionId(1L) + .position(Position.LEFT) + .mainText("main text 1") + .subText("sub text 1") + .resultMainText("result main text 1") + .resultSubText("result sub text 1") + .imageUrl("image url 1").build(); + + // 선착순 이벤트 오른쪽 옵션 + rightOptionRequestDto = RushEventOptionRequestDto.builder() + .rushOptionId(1L) + .position(Position.RIGHT) + .mainText("main text 2") + .subText("sub text 2") + .resultMainText("result main text 2") + .resultSubText("result sub text 2") + .imageUrl("image url 2").build(); + + + // 선착순 이벤트 생성 요청 DTO + Set options = new HashSet<>(); + options.add(leftOptionRequestDto); + options.add(rightOptionRequestDto); + + this.rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.of(2024, 8, 15)) + .startTime(LocalTime.of(0, 0, 0)) + .endTime(LocalTime.of(23, 59, 59)) + .winnerCount(315) + .prizeImageUrl("prize img") + .prizeDescription("prize description") + .options(options).build(); + + // 선착순 이벤트 + rushEvent = new RushEvent( + LocalDateTime.of(2024, 8, 15, 0, 0, 0), + LocalDateTime.of(2024, 8, 15, 23, 59, 59), + 315, + "prize image url", + "prize description" + ); + + leftOption = RushOption.builder() + .optionId(1L) + .rushEvent(rushEvent) + .mainText("main text 1") + .subText("sub text 1") + .resultMainText("result main text 1") + .resultSubText("result sub text 1") + .imageUrl("image url 1") + .position(Position.LEFT).build(); + + rightOption = RushOption.builder() + .optionId(2L) + .rushEvent(rushEvent) + .mainText("main text 2") + .subText("sub text 2") + .resultMainText("result main text 2") + .resultSubText("result sub text 2") + .imageUrl("image url 2") + .position(Position.RIGHT).build(); + + + rushEvent.addOption(leftOption, rightOption); + + // 선착순 이벤트 조회 응답 DTO + adminRushEventResponseDto = RushEventResponseDto.withDetail(rushEvent); + + // 선착순 이벤트 참여자 엔티티 + rushParticipants = spy(new RushParticipants(user, rushEvent, 1)); + lenient().when(rushParticipants.getCreatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + lenient().when(rushParticipants.getUpdatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + + // 선착순 이벤트 참여자 응답 DTO + rushEventParticipantResponseDto = RushEventParticipantResponseDto.result(rushParticipants, 1L); + + // 선착순 이벤트 참여자 리스트 조회 응답 DTO + List rushEventParticipantResponseDtoList = new ArrayList<>(); + rushEventParticipantResponseDtoList.add(rushEventParticipantResponseDto); + rushEventParticipantsListResponseDto = new ParticipantsListResponseDto<>(rushEventParticipantResponseDtoList, true, 1); + } + + + @Test + @DisplayName("어드민 생성 성공 테스트") + void postAdminSuccessTest() throws Exception { + //given + AdminRequestDto adminRequestDto = AdminRequestDto.builder().adminId(adminId).password(password).build(); + String requestBody = objectMapper.writeValueAsString(adminRequestDto); + + given(adminService.postAdmin(adminRequestDto)).willReturn(new ResponseDto("관리자 생성 성공")); + //when + ResultActions perform = mockMvc.perform(post("/admin/join").contentType(APPLICATION_JSON).content(requestBody)); + + //then + perform.andExpect(status().isCreated()).andExpect(jsonPath("$.message").value("관리자 생성 성공")).andDo(print()); + } + + @Test + @DisplayName("이미지 업로드 성공 테스트") + void postImageSuccessTest() throws Exception { + //given + given(adminService.postImage(any())).willReturn(new ImageUrlResponseDto("https://image.url")); + MockMultipartFile image = new MockMultipartFile("image", "image.png", "png", "<>".getBytes()); + //when + ResultActions perform = mockMvc.perform(multipart("/admin/image").file(image).header("Authorization", accessToken) + .contentType(MediaType.MULTIPART_FORM_DATA)); + + //then + perform.andExpect(status().isCreated()).andExpect(jsonPath("$.imageUrl").value("https://image.url")).andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 조회 성공 테스트") + void getLotteryEventSuccessTest() throws Exception { + //given + given(adminService.getLotteryEvent()).willReturn(LotteryEventResponseDto.withDetail(lotteryEvent, 1L)); + + //when + ResultActions perform = mockMvc.perform(get("/admin/event/lottery").header("Authorization", accessToken).contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.startDate").value("2000-09-27")) + .andExpect(jsonPath("$.startTime").value("00:00:00")) + .andExpect(jsonPath("$.endDate").value("2100-09-27")) + .andExpect(jsonPath("$.endTime").value("00:00:00")) + .andExpect(jsonPath("$.appliedCount").value(1L)) + .andExpect(jsonPath("$.winnerCount").value(315)) + .andExpect(jsonPath("$.status").value("DURING")) + .andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 생성 성공 테스트") + void createLotteryEventSuccessTest() throws Exception { + //given + given(adminService.createLotteryEvent(lotteryEventRequestDto)).willReturn(lotteryEventResponseDto); + String requestBody = objectMapper.writeValueAsString(lotteryEventRequestDto); + //when + ResultActions perform = mockMvc.perform(post("/admin/event/lottery") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON) + .content(requestBody)); + + //then + perform.andExpect(status().isCreated()) + .andExpect(jsonPath("$.serverDateTime").value("2024-08-15T00:00:00")) + .andExpect(jsonPath("$.eventStartDate").value("2000-09-27T00:00:00")) + .andExpect(jsonPath("$.eventEndDate").value("2100-09-27T00:00:00")) + .andExpect(jsonPath("$.activePeriod").value("36524")) + .andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 참여자 조회 성공 테스트") + void getLotteryEventParticipantsSuccessTest() throws Exception { + //given + given(adminService.getLotteryEventParticipants(anyInt(), anyInt(), anyString())) + .willReturn(lotteryEventParticipantsListResponseDto); + //when + ResultActions perform = mockMvc.perform(get("/admin/event/lottery/participants") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.participantsList[0].phoneNumber").value("010-0000-0000")) + .andExpect(jsonPath("$.participantsList[0].linkClickedCounts").value(0)) + .andExpect(jsonPath("$.participantsList[0].expectation").value(0)) + .andExpect(jsonPath("$.participantsList[0].appliedCount").value(1)) + .andExpect(jsonPath("$.participantsList[0].createdDate").value("2000-09-27")) + .andExpect(jsonPath("$.participantsList[0].createdTime").value("00:00:00")) + .andExpect(jsonPath("$.isLastPage").value(true)) + .andExpect(jsonPath("$.totalParticipants").value(1)) + .andDo(print()); + } + + @Test + @DisplayName("선착순 이벤트 생성 성공 이벤트") + void createRushEventSuccessTest() throws Exception { + //given + given(adminService.createRushEvent(any(), any(), any(), any())) + .willReturn(adminRushEventResponseDto); + String requestDto = objectMapper.writeValueAsString(rushEventRequestDto); + //when + MockMultipartFile dto = new MockMultipartFile("dto", "dto", "application/json", requestDto.getBytes()); + MockMultipartFile prizeImage = new MockMultipartFile("prizeImg", "prizeImage.png", "image/png", "<>".getBytes()); + MockMultipartFile leftOptionImg = new MockMultipartFile("leftOptionImg", "leftOptionImg.png", "image/png", "<>".getBytes()); + MockMultipartFile rightOptionImg = new MockMultipartFile("rightOptionImg", "rightOptionImg.png", "image/png", "<>".getBytes()); + + ResultActions perform = mockMvc.perform(multipart("/admin/event/rush") + .file(dto) + .file(prizeImage) + .file(leftOptionImg) + .file(rightOptionImg) + .header("Authorization", accessToken) + .contentType(MediaType.MULTIPART_FORM_DATA)); + + //then + perform.andExpect(status().isCreated()) + .andExpect(jsonPath("$.eventDate").value("2024-08-15")) + .andExpect(jsonPath("$.startTime").value("00:00:00")) + .andExpect(jsonPath("$.endTime").value("23:59:59")) + .andExpect(jsonPath("$.winnerCount").value(315)) + .andExpect(jsonPath("$.prizeImageUrl").value("prize image url")) + .andExpect(jsonPath("$.prizeDescription").value("prize description")) + .andExpect(jsonPath("$.status").value("AFTER")) +// .andExpect(jsonPath("$.options[0].optionId").value(1)) +// .andExpect(jsonPath("$.options[0].mainText").value("main text 1")) +// .andExpect(jsonPath("$.options[0].subText").value("sub text 1")) +// .andExpect(jsonPath("$.options[0].resultMainText").value("result main text 1")) +// .andExpect(jsonPath("$.options[0].resultSubText").value("result sub text 1")) +// .andExpect(jsonPath("$.options[0].imageUrl").value("image url 1")) +// .andExpect(jsonPath("$.options[0].position").value("LEFT")) +// .andExpect(jsonPath("$.options[1].optionId").value(2)) +// .andExpect(jsonPath("$.options[1].mainText").value("main text 2")) +// .andExpect(jsonPath("$.options[1].subText").value("sub text 2")) +// .andExpect(jsonPath("$.options[1].resultMainText").value("result main text 2")) +// .andExpect(jsonPath("$.options[1].resultSubText").value("result sub text 2")) +// .andExpect(jsonPath("$.options[1].imageUrl").value("image url 2")) +// .andExpect(jsonPath("$.options[1].position").value("RIGHT")) + + .andDo(print()); + } + + @Test + @DisplayName("선착순 이벤트 전체 조회 성공 테스트") + void getRushEventsSuccessTest() throws Exception { + //given + List rushEvents = new ArrayList<>(); + rushEvents.add(adminRushEventResponseDto); + given(adminService.getRushEvents()).willReturn(rushEvents); + + //when + ResultActions perform = mockMvc.perform(get("/admin/event/rush") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$[0].eventDate").value("2024-08-15")) + .andExpect(jsonPath("$[0].startTime").value("00:00:00")) + .andExpect(jsonPath("$[0].endTime").value("23:59:59")) + .andExpect(jsonPath("$[0].winnerCount").value(315)) + .andExpect(jsonPath("$[0].prizeImageUrl").value("prize image url")) + .andExpect(jsonPath("$[0].prizeDescription").value("prize description")) + .andExpect(jsonPath("$[0].status").value("AFTER")) + .andExpect(jsonPath("$[0].options").isArray()) + .andDo(print()); + } + + @Test + @DisplayName("선착순 이벤트 참여자 조회 성공 테스트") + void getRushEventParticipantsSuccessTest() throws Exception { + //given + given(adminService.getRushEventParticipants(anyLong(), anyInt(), anyInt(), anyInt(), anyString())) + .willReturn(rushEventParticipantsListResponseDto); + + //when + ResultActions perform = mockMvc.perform(get("/admin/event/rush/1/participants") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.participantsList[0].phoneNumber").value("010-0000-0000")) + .andExpect(jsonPath("$.participantsList[0].balanceGameChoice").value(1)) + .andExpect(jsonPath("$.participantsList[0].createdDate").value("2000-09-27")) + .andExpect(jsonPath("$.participantsList[0].createdTime").value("00:00:00")) + .andExpect(jsonPath("$.participantsList[0].rank").value(1)) + .andExpect(jsonPath("$.isLastPage").value(true)) + .andExpect(jsonPath("$.totalParticipants").value(1)) + .andDo(print()); + } + + @Test + @DisplayName("선착순 이벤트 당첨자 조회 성공 테스트") + void getRushEventWinnersSuccessTest() throws Exception { + //given + given(adminService.getRushEventWinners(anyLong(), anyInt(), anyInt(), anyString())) + .willReturn(rushEventParticipantsListResponseDto); + + //when + ResultActions perform = mockMvc.perform(get("/admin/event/rush/1/winner") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.participantsList[0].createdDate").value("2000-09-27")) + .andExpect(jsonPath("$.participantsList[0].rank").value(1)) + .andExpect(jsonPath("$.participantsList[0].phoneNumber").value("010-0000-0000")) + .andExpect(jsonPath("$.isLastPage").value(true)) + .andExpect(jsonPath("$.totalParticipants").value(1)) + .andDo(print()); + } + + @Test + @DisplayName("선착순 이벤트 수정 성공 테스트") + void updateRushEventSuccessTest() throws Exception { + //given + List rushEventRequestDtoList = new ArrayList<>(); + rushEventRequestDtoList.add(rushEventRequestDto); + + List adminRushEventResponseDtoList = new ArrayList<>(); + adminRushEventResponseDtoList.add(adminRushEventResponseDto); + + given(adminService.updateRushEvents(anyList())) + .willReturn(adminRushEventResponseDtoList); + + String requestBody = objectMapper.writeValueAsString(rushEventRequestDtoList); + //when + ResultActions perform = mockMvc.perform(put("/admin/event/rush") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON) + .content(requestBody)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$[0].eventDate").value("2024-08-15")) + .andExpect(jsonPath("$[0].startTime").value("00:00:00")) + .andExpect(jsonPath("$[0].endTime").value("23:59:59")) + .andExpect(jsonPath("$[0].winnerCount").value(315)) + .andExpect(jsonPath("$[0].prizeImageUrl").value("prize image url")) + .andExpect(jsonPath("$[0].prizeDescription").value("prize description")) + .andExpect(jsonPath("$[0].status").value("AFTER")) + .andDo(print()); + } + + @Test + @DisplayName("선착순 이벤트 삭제 성공 테스트") + void deleteRushEventSuccessTest() throws Exception { + //given + ResponseDto responseDto = new ResponseDto("요청에 성공하였습니다."); + given(adminService.deleteRushEvent(1L)).willReturn(responseDto); + + //when + ResultActions perform = mockMvc.perform(delete("/admin/event/rush/1") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("요청에 성공하였습니다.")) + .andDo(print()); + } + + @Test + @DisplayName("선착순 이벤트 선택지 조회 성공 테스트") + void getRushEventOptionsSuccessTest() throws Exception { + //given + RushEventResponseDto adminRushEventOptionResponseDto = RushEventResponseDto.withOptions(rushEvent); + given(adminService.getRushEventOptions(1L)) + .willReturn(adminRushEventOptionResponseDto); + //when + ResultActions perform = mockMvc.perform(get("/admin/event/rush/1/options") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) +// .andExpect(jsonPath("$.options[0].optionId").value(2)) +// .andExpect(jsonPath("$.options[0].mainText").value("main text 2")) +// .andExpect(jsonPath("$.options[0].subText").value("sub text 2")) +// .andExpect(jsonPath("$.options[0].resultMainText").value("result main text 2")) +// .andExpect(jsonPath("$.options[0].resultSubText").value("result sub text 2")) +// .andExpect(jsonPath("$.options[0].imageUrl").value("image url 2")) +// .andExpect(jsonPath("$.options[0].position").value("RIGHT")) +// .andExpect(jsonPath("$.options[1].optionId").value(1)) +// .andExpect(jsonPath("$.options[1].mainText").value("main text 1")) +// .andExpect(jsonPath("$.options[1].subText").value("sub text 1")) +// .andExpect(jsonPath("$.options[1].resultMainText").value("result main text 1")) +// .andExpect(jsonPath("$.options[1].resultSubText").value("result sub text 1")) +// .andExpect(jsonPath("$.options[1].imageUrl").value("image url 1")) +// .andExpect(jsonPath("$.options[1].position").value("LEFT")) + .andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 삭제 성공 테스트") + void deleteLotteryEventSuccessTest() throws Exception { + //given + + //when + ResultActions perform = mockMvc.perform(delete("/admin/event/lottery") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isNoContent()) + .andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 수정 성공 테스트") + void updateLotteryEventSuccessTest() throws Exception { + //given + given(adminService.updateLotteryEvent(lotteryEventRequestDto)) + .willReturn(lotteryEventDetailResponseDto); + String requestBody = objectMapper.writeValueAsString(lotteryEventRequestDto); + + //when + ResultActions perform = mockMvc.perform(put("/admin/event/lottery") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON) + .content(requestBody)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.startDate").value("2000-09-27")) + .andExpect(jsonPath("$.startTime").value("00:00:00")) + .andExpect(jsonPath("$.endDate").value("2100-09-27")) + .andExpect(jsonPath("$.endTime").value("00:00:00")) + .andExpect(jsonPath("$.appliedCount").value(1L)) + .andExpect(jsonPath("$.winnerCount").value(315)) + .andExpect(jsonPath("$.status").value("DURING")) + .andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 특정 사용자의 기대평 조회 성공 테스트") + void getLotteryEventExpectationsSuccessTest() throws Exception { + //given + given(adminService.getLotteryEventExpectations(anyInt(), anyInt(), anyLong())) + .willReturn(expectationsPagingResponseDto); + + //when + ResultActions perform = mockMvc.perform(get("/admin/event/lottery/participants/1/expectations") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.expectations[0].expectation").value("expectation")) + .andExpect(jsonPath("$.expectations[0].createdDate").value("2000-09-27")) + .andExpect(jsonPath("$.expectations[0].createdTime").value("00:00:00")) + .andExpect(jsonPath("$.isLastPage").value(true)) + .andExpect(jsonPath("$.totalExpectations").value(1)) + .andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 특정 기대평 삭제 성공 테스트") + void deleteLotteryEventExpectationSuccessTest() throws Exception { + //given + + //when + ResultActions perform = mockMvc.perform(patch("/admin/event/lottery/expectations/1") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isNoContent()) + .andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 당첨자 추첨 성공 테스트") + void pickLotteryEventWinnerSuccessTest() throws Exception { + //given + given(adminService.pickLotteryEventWinners()) + .willReturn(new ResponseDto("추첨이 완료되었습니다.")); + + //when + ResultActions perform = mockMvc.perform(post("/admin/event/lottery/winner") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isCreated()) + .andExpect(jsonPath("$.message").value("추첨이 완료되었습니다.")) + .andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 당첨자 삭제 성공 테스트") + void deleteLotteryEventWinners() throws Exception { + //given + given(adminService.deleteLotteryEventWinners()) + .willReturn(new ResponseDto("당첨자 명단을 삭제했습니다.")); + + //when + ResultActions perform = mockMvc.perform(delete("/admin/event/lottery/winner") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("당첨자 명단을 삭제했습니다.")) + .andDo(print()); + } + + @Test + @DisplayName("추첨 이벤트 당첨자 조회 성공 테스트") + void getWinnersSuccessTest() throws Exception { + //given + given(adminService.getLotteryEventWinners(anyInt(), anyInt(), anyString())) + .willReturn(lotteryEventWinnerListResponseDto); + + //when + ResultActions perform = mockMvc.perform(get("/admin/event/lottery/winner") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.participantsList[0].id").value(1)) + .andExpect(jsonPath("$.participantsList[0].phoneNumber").value("010-0000-0000")) + .andExpect(jsonPath("$.participantsList[0].linkClickedCounts").value(0)) + .andExpect(jsonPath("$.participantsList[0].expectation").value(0)) + .andExpect(jsonPath("$.participantsList[0].appliedCount").value(1)) + .andExpect(jsonPath("$.participantsList[0].ranking").value(0)) + .andExpect(jsonPath("$.participantsList[0].createdDate").value("2000-09-27")) + .andExpect(jsonPath("$.participantsList[0].createdTime").value("00:00:00")) + .andExpect(jsonPath("$.isLastPage").value(true)) + .andExpect(jsonPath("$.totalParticipants").value(1)) + .andDo(print()); + } + + String getToken(String id, String password) throws Exception { + String requestBody = String.format(""" + { + "adminId": "%s", + "password": "%s" + } + """, id, password); + + ResultActions perform = mockMvc.perform(post("/admin/auth").contentType(APPLICATION_JSON).content(requestBody)); + + String jsonString = perform.andReturn().getResponse().getContentAsString(); + String tokenPrefix = "\"accessToken\":\""; + int start = jsonString.indexOf(tokenPrefix) + tokenPrefix.length(); + int end = jsonString.indexOf("\"", start); + + return "Bearer " + jsonString.substring(start, end); + } } \ No newline at end of file diff --git a/Server/src/test/java/JGS/CasperEvent/domain/event/controller/eventController/LotteryEventControllerTest.java b/Server/src/test/java/JGS/CasperEvent/domain/event/controller/eventController/LotteryEventControllerTest.java index 4cf07d8e..54875995 100644 --- a/Server/src/test/java/JGS/CasperEvent/domain/event/controller/eventController/LotteryEventControllerTest.java +++ b/Server/src/test/java/JGS/CasperEvent/domain/event/controller/eventController/LotteryEventControllerTest.java @@ -1,32 +1,34 @@ package JGS.CasperEvent.domain.event.controller.eventController; -import JGS.CasperEvent.domain.event.dto.RequestDto.lotteryEventDto.CasperBotRequestDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.CasperBotResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.LotteryEventResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.lotteryEventResponseDto.LotteryParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.CasperBotRequestDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.CasperBotResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventResponseDto; import JGS.CasperEvent.domain.event.entity.casperBot.CasperBot; +import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; import JGS.CasperEvent.domain.event.service.adminService.AdminService; import JGS.CasperEvent.domain.event.service.eventService.LotteryEventService; -import JGS.CasperEvent.domain.event.service.redisService.RedisService; +import JGS.CasperEvent.domain.event.service.redisService.LotteryEventRedisService; import JGS.CasperEvent.global.entity.BaseUser; import JGS.CasperEvent.global.enums.Role; import JGS.CasperEvent.global.jwt.service.UserService; import JGS.CasperEvent.global.jwt.util.JwtProvider; import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.security.Keys; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; @@ -38,8 +40,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(LotteryEventController.class) -@Import(JwtProvider.class) -public class LotteryEventControllerTest { +class LotteryEventControllerTest { @Autowired private MockMvc mockMvc; @Autowired @@ -52,7 +53,7 @@ public class LotteryEventControllerTest { @MockBean private AdminService adminService; @MockBean - private RedisService redisService; + private LotteryEventRedisService lotteryEventRedisService; private BaseUser user; private String phoneNumber; @@ -63,6 +64,16 @@ public class LotteryEventControllerTest { private LotteryParticipants lotteryParticipants; private LotteryEventResponseDto lotteryEventResponseDto; + @TestConfiguration + static class TestConfig { + @Bean + public JwtProvider jwtProvider() { + String secretKey = "mockKEymockKEymockKEymockKEymockKEymockKEymockKEy"; + byte[] secret = secretKey.getBytes(); + return new JwtProvider(Keys.hmacShaKeyFor(secret)); + } + } + @BeforeEach void setUp() throws Exception { this.phoneNumber = "010-0000-0000"; @@ -78,11 +89,14 @@ void setUp() throws Exception { this.accessToken = getToken(this.phoneNumber); // 추첨 이벤트 조회 - lotteryEventResponseDto = new LotteryEventResponseDto( - LocalDateTime.of(2024, 8, 15, 0, 0, 0), + LotteryEvent lotteryEvent = new LotteryEvent( LocalDateTime.of(2024, 8, 1, 0, 0, 0), LocalDateTime.of(2024, 8, 31, 0, 0, 0), - ChronoUnit.DAYS.between(LocalDateTime.of(2024, 8, 1, 0, 0, 0), LocalDateTime.of(2024, 8, 31, 0, 0, 0)) + 315 + ); + lotteryEventResponseDto = LotteryEventResponseDto.of( + lotteryEvent, + LocalDateTime.of(2024, 8, 1, 0, 0, 0) ); casperBotRequest = CasperBotRequestDto.builder() @@ -93,7 +107,8 @@ void setUp() throws Exception { .sticker(0) .name("name") .expectation("expectation") - .referralId("QEszP1K8IqcapUHAVwikXA==").build(); + .referralId("QEszP1K8IqcapUHAVwikXA==") + .build(); casperBot = new CasperBot(casperBotRequest, "010-0000-0000"); @@ -112,7 +127,7 @@ void getLotteryEventAndServerTime() throws Exception { //then perform.andExpect(status().isOk()) - .andExpect(jsonPath("$.serverDateTime").value("2024-08-15T00:00:00")) + .andExpect(jsonPath("$.serverDateTime").value("2024-08-01T00:00:00")) .andExpect(jsonPath("$.eventStartDate").value("2024-08-01T00:00:00")) .andExpect(jsonPath("$.eventEndDate").value("2024-08-31T00:00:00")) .andDo(print()); @@ -146,10 +161,10 @@ void postCasperBot() throws Exception { @Test @DisplayName("응모 여부 조회 성공 테스트") - void GetLotteryParticipantsSuccessTest() throws Exception { + void getLotteryParticipantsSuccessTest() throws Exception { //given given(lotteryEventService.getLotteryParticipant(user)) - .willReturn(LotteryParticipantResponseDto.of(lotteryParticipants, casperBotResponse)); + .willReturn(LotteryEventParticipantResponseDto.of(lotteryParticipants)); //when ResultActions perform = mockMvc.perform(get("/event/lottery/applied") @@ -161,13 +176,6 @@ void GetLotteryParticipantsSuccessTest() throws Exception { .andExpect(jsonPath("$.linkClickedCount").value(0)) .andExpect(jsonPath("$.expectations").value(0)) .andExpect(jsonPath("$.appliedCount").value(1)) - .andExpect(jsonPath("$.casperBot.eyeShape").value(0)) - .andExpect(jsonPath("$.casperBot.eyePosition").value(0)) - .andExpect(jsonPath("$.casperBot.mouthShape").value(0)) - .andExpect(jsonPath("$.casperBot.color").value(0)) - .andExpect(jsonPath("$.casperBot.sticker").value(0)) - .andExpect(jsonPath("$.casperBot.name").value("name")) - .andExpect(jsonPath("$.casperBot.expectation").value("expectation")) .andDo(print()); } @@ -179,7 +187,7 @@ void getCasperBotsSuccessTest() throws Exception { for (int i = 0; i < 100; i++) { recentData.add(casperBotResponse); } - given(redisService.getRecentData()) + given(lotteryEventRedisService.getRecentData()) .willReturn(recentData); //when diff --git a/Server/src/test/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventControllerTest.java b/Server/src/test/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventControllerTest.java index cc2ab2ff..09bfcc5e 100644 --- a/Server/src/test/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventControllerTest.java +++ b/Server/src/test/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventControllerTest.java @@ -1,6 +1,9 @@ package JGS.CasperEvent.domain.event.controller.eventController; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.*; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventListResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventOptionResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResultResponseDto; import JGS.CasperEvent.domain.event.service.adminService.AdminService; import JGS.CasperEvent.domain.event.service.eventService.RushEventService; import JGS.CasperEvent.global.entity.BaseUser; @@ -9,13 +12,15 @@ import JGS.CasperEvent.global.error.exception.CustomException; import JGS.CasperEvent.global.jwt.service.UserService; import JGS.CasperEvent.global.jwt.util.JwtProvider; +import io.jsonwebtoken.security.Keys; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; @@ -32,8 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(RushEventController.class) -@Import(JwtProvider.class) -public class RushEventControllerTest { +class RushEventControllerTest { @Autowired private MockMvc mockMvc; @@ -50,6 +54,16 @@ public class RushEventControllerTest { private String phoneNumber; private String accessToken; + @TestConfiguration + static class TestConfig { + @Bean + public JwtProvider jwtProvider() { + String secretKey = "mockKEymockKEymockKEymockKEymockKEymockKEymockKEy"; + byte[] secret = secretKey.getBytes(); + return new JwtProvider(Keys.hmacShaKeyFor(secret)); + } + } + @BeforeEach void setUp() throws Exception { this.phoneNumber = "010-0000-0000"; @@ -63,12 +77,12 @@ void setUp() throws Exception { // Mock 데이터 설정 RushEventListResponseDto rushEventListResponseDto = new RushEventListResponseDto( Arrays.asList( - new MainRushEventResponseDto(37L, LocalDateTime.of(2024, 8, 11, 22, 0), LocalDateTime.of(2024, 8, 11, 22, 10)), - new MainRushEventResponseDto(38L, LocalDateTime.of(2024, 8, 12, 22, 0), LocalDateTime.of(2024, 8, 12, 22, 10)), - new MainRushEventResponseDto(39L, LocalDateTime.of(2024, 8, 13, 22, 0), LocalDateTime.of(2024, 8, 13, 22, 10)), - new MainRushEventResponseDto(40L, LocalDateTime.of(2024, 8, 14, 22, 0), LocalDateTime.of(2024, 8, 14, 22, 10)), - new MainRushEventResponseDto(41L, LocalDateTime.of(2024, 8, 15, 22, 0), LocalDateTime.of(2024, 8, 15, 22, 10)), - new MainRushEventResponseDto(42L, LocalDateTime.of(2024, 8, 16, 22, 0), LocalDateTime.of(2024, 8, 16, 22, 10)) + RushEventResponseDto.withMain(37L, LocalDateTime.of(2024, 8, 11, 22, 0), LocalDateTime.of(2024, 8, 11, 22, 10)), + RushEventResponseDto.withMain(38L, LocalDateTime.of(2024, 8, 12, 22, 0), LocalDateTime.of(2024, 8, 12, 22, 10)), + RushEventResponseDto.withMain(39L, LocalDateTime.of(2024, 8, 13, 22, 0), LocalDateTime.of(2024, 8, 13, 22, 10)), + RushEventResponseDto.withMain(40L, LocalDateTime.of(2024, 8, 14, 22, 0), LocalDateTime.of(2024, 8, 14, 22, 10)), + RushEventResponseDto.withMain(41L, LocalDateTime.of(2024, 8, 15, 22, 0), LocalDateTime.of(2024, 8, 15, 22, 10)), + RushEventResponseDto.withMain(42L, LocalDateTime.of(2024, 8, 16, 22, 0), LocalDateTime.of(2024, 8, 16, 22, 10)) ), LocalDateTime.of(2024, 8, 12, 13, 46, 29, 48782), 37L, @@ -79,14 +93,14 @@ void setUp() throws Exception { given(rushEventService.getAllRushEvents()).willReturn(rushEventListResponseDto); - MainRushEventOptionsResponseDto mainRushEventOptionsResponseDto = new MainRushEventOptionsResponseDto( - new MainRushEventOptionResponseDto("leftMainText", "leftSubText"), - new MainRushEventOptionResponseDto("rightMainText", "rightSubText") + RushEventResponseDto mainRushEventOptionsResponseDto = RushEventResponseDto.withMainOption( + RushEventOptionResponseDto.inMain("leftMainText", "leftSubText"), + RushEventOptionResponseDto.inMain("rightMainText", "rightSubText") ); given(rushEventService.getTodayRushEventOptions()).willReturn(mainRushEventOptionsResponseDto); - ResultRushEventOptionResponseDto resultRushEventOptionResponseDto = new ResultRushEventOptionResponseDto( + RushEventOptionResponseDto resultRushEventOptionResponseDto = RushEventOptionResponseDto.inResult( "mainText", "resultMainText", "resultSubText" @@ -102,18 +116,20 @@ void setUp() throws Exception { willThrow(new CustomException("이미 응모한 회원입니다.", CustomErrorCode.CONFLICT)) .given(rushEventService).apply(any(BaseUser.class), eq(1)); - RushEventRateResponseDto rushEventRateResponseDto = new RushEventRateResponseDto( + RushEventResultResponseDto rushEventRateResponseDto = RushEventResultResponseDto.of( 1, - 315, - 1000 + 315L, + 1000L ); given(rushEventService.getRushEventRate(any())).willReturn(rushEventRateResponseDto); - RushEventResultResponseDto rushEventResultResponseDto = new RushEventResultResponseDto( - rushEventRateResponseDto, + RushEventResultResponseDto rushEventResultResponseDto = RushEventResultResponseDto.withDetail( 1, - 1000, + 315L, + 1000L, + 1L, + 1000L, true ); @@ -122,7 +138,7 @@ void setUp() throws Exception { @Test @DisplayName("메인화면 선착순 이벤트 전체 조회 API 테스트") - public void getRushEventListAndServerTime() throws Exception { + void getRushEventListAndServerTime() throws Exception { // when ResultActions perform = mockMvc.perform(get("/event/rush") .contentType(MediaType.APPLICATION_JSON)); @@ -142,10 +158,7 @@ public void getRushEventListAndServerTime() throws Exception { @Test @DisplayName("오늘의 선착순 이벤트 조회 API 성공 테스트") - public void getTodayEventTest() throws Exception { - // given - String accessToken = this.accessToken; - + void getTodayEventTest() throws Exception { // when ResultActions perform = mockMvc.perform(get("/event/rush/today") .header("Authorization", accessToken) @@ -163,8 +176,7 @@ public void getTodayEventTest() throws Exception { @Test @DisplayName("응모 성공 테스트 - Option ID 2") - public void applyRushEvent_Success() throws Exception { - String accessToken = this.accessToken; + void applyRushEvent_Success() throws Exception { int optionId = 2; ResultActions perform = mockMvc.perform(post("/event/rush/options/{optionId}/apply", optionId) @@ -172,13 +184,12 @@ public void applyRushEvent_Success() throws Exception { .contentType(MediaType.APPLICATION_JSON)); perform.andExpect(status().isNoContent()) // 204 No Content 응답 확인 - .andDo(print()); -} + .andDo(print()); + } @Test @DisplayName("응모 실패 테스트 - Option ID 1") - public void applyRushEvent_Failure_AlreadyApplied() throws Exception { - String accessToken = this.accessToken; + void applyRushEvent_Failure_AlreadyApplied() throws Exception { int optionId = 1; ResultActions perform = mockMvc.perform(post("/event/rush/options/{optionId}/apply", optionId) @@ -193,10 +204,8 @@ public void applyRushEvent_Failure_AlreadyApplied() throws Exception { @Test @DisplayName("선택지 결과 조회 성공 테스트") - public void getResultOptionTest() throws Exception { + void getResultOptionTest() throws Exception { // given - String accessToken = this.accessToken; - int optionId = 1; // when @@ -214,10 +223,7 @@ public void getResultOptionTest() throws Exception { @Test @DisplayName("밸런스 게임 비율 조회 API 테스트") - public void getRushEventRateTest() throws Exception { - // given - String accessToken = this.accessToken; - + void getRushEventRateTest() throws Exception { // when ResultActions perform = mockMvc.perform(get("/event/rush/balance") .header("Authorization", accessToken) @@ -233,10 +239,7 @@ public void getRushEventRateTest() throws Exception { @Test @DisplayName("밸런스 게임 최종 결과 조회 API 테스트") - public void getRushEventResultTest() throws Exception { - // given - String accessToken = this.accessToken; - + void getRushEventResultTest() throws Exception { // when ResultActions perform = mockMvc.perform(get("/event/rush/result") .header("Authorization", accessToken) @@ -244,11 +247,12 @@ public void getRushEventResultTest() throws Exception { // then perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.optionId").value(1)) .andExpect(jsonPath("$.leftOption").value(315)) .andExpect(jsonPath("$.rightOption").value(1000)) .andExpect(jsonPath("$.rank").value(1)) .andExpect(jsonPath("$.totalParticipants").value(1000)) - .andExpect(jsonPath("$.winner").value(true)) + .andExpect(jsonPath("$.isWinner").value(true)) .andDo(print()); } diff --git a/Server/src/test/java/JGS/CasperEvent/domain/event/service/adminService/AdminServiceTest.java b/Server/src/test/java/JGS/CasperEvent/domain/event/service/adminService/AdminServiceTest.java new file mode 100644 index 00000000..fc310d1f --- /dev/null +++ b/Server/src/test/java/JGS/CasperEvent/domain/event/service/adminService/AdminServiceTest.java @@ -0,0 +1,1649 @@ +package JGS.CasperEvent.domain.event.service.adminService; + +import JGS.CasperEvent.domain.event.dto.request.AdminRequestDto; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.CasperBotRequestDto; +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.LotteryEventRequestDto; +import JGS.CasperEvent.domain.event.dto.request.rushEventDto.RushEventOptionRequestDto; +import JGS.CasperEvent.domain.event.dto.request.rushEventDto.RushEventRequestDto; +import JGS.CasperEvent.domain.event.dto.response.ImageUrlResponseDto; +import JGS.CasperEvent.domain.event.dto.response.ParticipantsListResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.CasperBotResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.ExpectationsPagingResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventOptionResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; +import JGS.CasperEvent.domain.event.entity.admin.Admin; +import JGS.CasperEvent.domain.event.entity.casperBot.CasperBot; +import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; +import JGS.CasperEvent.domain.event.entity.event.RushEvent; +import JGS.CasperEvent.domain.event.entity.event.RushOption; +import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; +import JGS.CasperEvent.domain.event.entity.participants.LotteryWinners; +import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; +import JGS.CasperEvent.domain.event.repository.AdminRepository; +import JGS.CasperEvent.domain.event.repository.CasperBotRepository; +import JGS.CasperEvent.domain.event.repository.eventRepository.LotteryEventRepository; +import JGS.CasperEvent.domain.event.repository.eventRepository.RushEventRepository; +import JGS.CasperEvent.domain.event.repository.eventRepository.RushOptionRepository; +import JGS.CasperEvent.domain.event.repository.participantsRepository.LotteryParticipantsRepository; +import JGS.CasperEvent.domain.event.repository.participantsRepository.LotteryWinnerRepository; +import JGS.CasperEvent.domain.event.repository.participantsRepository.RushParticipantsRepository; +import JGS.CasperEvent.domain.event.service.eventService.EventCacheService; +import JGS.CasperEvent.global.entity.BaseUser; +import JGS.CasperEvent.global.enums.CustomErrorCode; +import JGS.CasperEvent.global.enums.EventStatus; +import JGS.CasperEvent.global.enums.Position; +import JGS.CasperEvent.global.enums.Role; +import JGS.CasperEvent.global.error.exception.CustomException; +import JGS.CasperEvent.global.response.ResponseDto; +import JGS.CasperEvent.global.service.S3Service; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.mock.web.MockMultipartFile; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.*; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class AdminServiceTest { + + @Mock + private AdminRepository adminRepository; + @Mock + private LotteryEventRepository lotteryEventRepository; + @Mock + private RushEventRepository rushEventRepository; + @Mock + private LotteryParticipantsRepository lotteryParticipantsRepository; + @Mock + private RushParticipantsRepository rushParticipantsRepository; + @Mock + private RushOptionRepository rushOptionRepository; + @Mock + private S3Service s3Service; + @Mock + private CasperBotRepository casperBotRepository; + @Mock + private LotteryWinnerRepository lotteryWinnerRepository; + @Mock + private RedisTemplate casperBotRedisTemplate; + @Mock + private ListOperations listOperations; + @Mock + private EventCacheService eventCacheService; + + + private RushEvent rushEvent; + private RushOption leftOption; + private RushOption rightOption; + + private Admin admin; + private BaseUser user; + private LotteryEvent lotteryEvent; + private LotteryEventRequestDto lotteryEventRequestDto; + private LotteryParticipants lotteryParticipants; + private CasperBotRequestDto casperBotRequestDto; + private CasperBot casperBot; + + private RushEventRequestDto rushEventRequestDto; + private RushEventOptionRequestDto leftOptionRequestDto; + private RushEventOptionRequestDto rightOptionRequestDto; + private RushParticipants rushParticipant; + + @InjectMocks + AdminService adminService; + + @BeforeEach + void setUp() { + // 어드민 객체 + admin = new Admin("adminId", "password", Role.ADMIN); + + // 유저 객체 + user = spy(new BaseUser("010-0000-0000", Role.USER)); + lenient().when(user.getCreatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + lenient().when(user.getUpdatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + + // 추첨 이벤트 생성 요청 DTO + lotteryEventRequestDto = LotteryEventRequestDto.builder() + .startDate(LocalDate.of(2000, 9, 27)) + .startTime(LocalTime.of(0, 0)) + .endDate(LocalDate.of(2100, 9, 27)) + .endTime(LocalTime.of(0, 0)) + .winnerCount(315) + .build(); + + // 추첨 이벤트 엔티티 + lotteryEvent = new LotteryEvent( + LocalDateTime.of(lotteryEventRequestDto.getStartDate(), lotteryEventRequestDto.getStartTime()), + LocalDateTime.of(lotteryEventRequestDto.getEndDate(), lotteryEventRequestDto.getEndTime()), + lotteryEventRequestDto.getWinnerCount() + ); + + // 추첨 이벤트 참여자 엔티티 + lotteryParticipants = spy(new LotteryParticipants(user)); + lenient().when(lotteryParticipants.getId()).thenReturn(1L); + lenient().when(lotteryParticipants.getCreatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + lenient().when(lotteryParticipants.getUpdatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + + // 선착순 이벤트 옵션 요청 DTO + leftOptionRequestDto = RushEventOptionRequestDto.builder() + .rushOptionId(1L) + .position(Position.LEFT) + .mainText("Main Text 1") + .subText("Sub Text 1") + .resultMainText("Result Main Text 1") + .resultSubText("Result Sub Text 1") + .imageUrl("http://example.com/image.jpg").build(); + + rightOptionRequestDto = RushEventOptionRequestDto.builder() + .rushOptionId(1L) + .position(Position.RIGHT) + .mainText("Main Text 2") + .subText("Sub Text 2") + .resultMainText("Result Main Text 2") + .resultSubText("Result Sub Text 2") + .imageUrl("http://example.com/image.jpg").build(); + + Set options = new HashSet<>(); + options.add(leftOptionRequestDto); + options.add(rightOptionRequestDto); + + // 선착순 이벤트 요청 DTO + rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.of(2024, 8, 15)) + .startTime(LocalTime.of(0, 0)) + .endTime(LocalTime.of(23, 59)) + .winnerCount(100) + .prizeImageUrl("http://example.com/image.jpg") + .prizeDescription("This is a detailed description of the prize.") + .options(options) + .build(); + + // 선착순 이벤트 객체 + rushEvent = new RushEvent( + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getStartTime()), + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getEndTime()), + rushEventRequestDto.getWinnerCount(), + "http://example.com/image.jpg", + rushEventRequestDto.getPrizeDescription() + ); + + // 선착순 이벤트 선택지 객체 + leftOption = new RushOption( + rushEvent, + leftOptionRequestDto.getMainText(), + leftOptionRequestDto.getSubText(), + leftOptionRequestDto.getResultMainText(), + leftOptionRequestDto.getResultSubText(), + "http://example.com/image.jpg", + Position.LEFT + ); + + rightOption = new RushOption( + rushEvent, + rightOptionRequestDto.getMainText(), + rightOptionRequestDto.getSubText(), + rightOptionRequestDto.getResultMainText(), + rightOptionRequestDto.getResultSubText(), + "http://example.com/image.jpg", + Position.RIGHT + ); + + // 선착순 이벤트 참여자 + rushParticipant = spy(new RushParticipants(user, rushEvent, 1)); + lenient().when(rushParticipant.getCreatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + lenient().when(rushParticipant.getUpdatedAt()).thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + + + // 캐스퍼 봇 생성 + casperBotRequestDto = CasperBotRequestDto.builder() + . + + eyeShape(0) + . + + eyePosition(0) + . + + mouthShape(0) + . + + color(0) + . + + sticker(0) + . + + name("name") + . + + expectation("expectation") + . + + referralId("QEszP1K8IqcapUHAVwikXA=="). + + build(); + + casperBot = + spy(new CasperBot(casperBotRequestDto, "010-0000-0000")); + lenient(). + when(casperBot.getCasperId()). + thenReturn(1L); + lenient(). + when(casperBot.getCreatedAt()). + thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + lenient(). + when(casperBot.getUpdatedAt()). + thenReturn(LocalDateTime.of(2000, 9, 27, 0, 0, 0)); + } + + @Test + @DisplayName("어드민 인증 테스트 - 성공") + void verifyAdminTest_Success() { + //given + AdminRequestDto adminRequestDto = AdminRequestDto.builder() + .adminId("adminId") + .password("password") + .build(); + + given(adminRepository.findByPhoneNumberAndPassword("adminId", "password")).willReturn(Optional.ofNullable(admin)); + + //when + admin = adminService.verifyAdmin(adminRequestDto); + + //then + assertThat(admin.getRole()).isEqualTo(Role.ADMIN); + assertThat(admin.getPhoneNumber()).isEqualTo("adminId"); + assertThat(admin.getPassword()).isEqualTo("password"); + } + + @Test + @DisplayName("어드민 생성 테스트 - 성공") + void postAdminTest_Success() { + //given + AdminRequestDto adminRequestDto = AdminRequestDto.builder() + .adminId("adminId") + .password("password") + .build(); + + //when + ResponseDto responseDto = adminService.postAdmin(adminRequestDto); + + //then + assertThat(responseDto.message()).isEqualTo("관리자 생성 성공"); + } + + @Test + @DisplayName("어드민 생성 테스트 - 실패 (중복 아이디 존재)") + void postAdminTest_Failure_DuplicatedId() { + //given + AdminRequestDto adminRequestDto = AdminRequestDto.builder() + .adminId("adminId") + .password("password") + .build(); + + given(adminRepository.findByPhoneNumber("adminId")).willReturn(Optional.ofNullable(admin)); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.postAdmin(adminRequestDto) + ); + + //then + assertEquals(CustomErrorCode.CONFLICT, customException.getErrorCode()); + assertEquals("이미 등록된 ID입니다.", customException.getMessage()); + } + + @Test + @DisplayName("이미지 업로드 성공 테스트") + void postImageTest_Success() { + //given + MockMultipartFile image = new MockMultipartFile("image", "image.png", "png", "<>".getBytes()); + given(s3Service.upload(image)).willReturn("www.image.com"); + + //when + ImageUrlResponseDto imageUrlResponseDto = adminService.postImage(image); + + //then + assertThat(imageUrlResponseDto.imageUrl()).isEqualTo("www.image.com"); + } + + @Test + @DisplayName("추첨 이벤트 생성 테스트 - 성공") + void createLotteryEventTest_Success() { + //given + given(lotteryEventRepository.count()).willReturn(0L); + given(lotteryEventRepository.save(lotteryEvent)).willReturn(lotteryEvent); + + //when + LotteryEventResponseDto lotteryEventResponseDto = adminService.createLotteryEvent(lotteryEventRequestDto); + + //then + assertThat(lotteryEventResponseDto.getServerDateTime()).isNotNull(); + assertThat(lotteryEventResponseDto.getEventStartDate()).isEqualTo("2000-09-27T00:00"); + assertThat(lotteryEventResponseDto.getEventEndDate()).isEqualTo("2100-09-27T00:00"); + assertThat(lotteryEventResponseDto.getActivePeriod()).isEqualTo(36524); + } + + @Test + @DisplayName("추첨 이벤트 생성 테스트 - 실패 (데이터베이스에 추첨 이벤트가 존재할 때)") + void createLotteryEventTest_Failure_TooManyLotteryEvent() { + //given + given(lotteryEventRepository.count()).willReturn(1L); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.createLotteryEvent(lotteryEventRequestDto) + ); + + //then + assertEquals(CustomErrorCode.TOO_MANY_LOTTERY_EVENT, customException.getErrorCode()); + assertEquals("현재 진행중인 추첨 이벤트가 2개 이상입니다.", customException.getMessage()); + } + + @Test + @DisplayName("추첨 이벤트 조회 성공 테스트") + void getLotteryEventTest_Success() { + //given + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + + //when + + LotteryEventResponseDto lotteryEventResponseDto = adminService.getLotteryEvent(); + + //then + assertThat(lotteryEventResponseDto.getStartDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(lotteryEventResponseDto.getStartTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(lotteryEventResponseDto.getEndDate()).isEqualTo(LocalDate.of(2100, 9, 27)); + assertThat(lotteryEventResponseDto.getEndTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(lotteryEventResponseDto.getAppliedCount()).isZero(); + assertThat(lotteryEventResponseDto.getWinnerCount()).isEqualTo(315); + assertThat(lotteryEventResponseDto.getStatus()).isEqualTo(EventStatus.DURING); + } + + @Test + @DisplayName("추첨 이벤트 조회 테스트 - 실패 (데이터베이스에 추첨 이벤트가 없을 때)") + void getLotteryEvent_Failure_NoLotteryEvent() { + //given + List lotteryEventList = new ArrayList<>(); + given(lotteryEventRepository.findAll()) + .willReturn(lotteryEventList); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.getLotteryEvent() + ); + + //then + assertEquals(CustomErrorCode.NO_LOTTERY_EVENT, customException.getErrorCode()); + assertEquals("현재 진행중인 lotteryEvent가 존재하지 않습니다.", customException.getMessage()); + } + + @Test + @DisplayName("추첨 이벤트 조회 테스트 - 실패 (데이터베이스에 추첨 이벤트가 2개 이상 존재)") + void getLotteryEvent_Failure_TooManyLotteryEvent() { + //given + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + lotteryEventList.add(lotteryEvent); + given(lotteryEventRepository.findAll()) + .willReturn(lotteryEventList); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.getLotteryEvent() + ); + + //then + assertEquals(CustomErrorCode.TOO_MANY_LOTTERY_EVENT, customException.getErrorCode()); + assertEquals("현재 진행중인 lotteryEvent가 2개 이상입니다.", customException.getMessage()); + } + + @Test + @DisplayName("추첨 이벤트 참여자 조회 성공 테스트 (전화번호가 없을 때)") + void getLotteryEventParticipantsTest_Success_withoutPhoneNumber() { + //given + List lotteryParticipantsList = new ArrayList<>(); + lotteryParticipantsList.add(lotteryParticipants); + Page lotteryParticipantsPage = new PageImpl<>(lotteryParticipantsList); + + + given(lotteryParticipantsRepository.findAll(any(Pageable.class))).willReturn(lotteryParticipantsPage); + given(lotteryParticipantsRepository.count()).willReturn(1L); + + //when + ParticipantsListResponseDto lotteryEventParticipantsListResponseDto = adminService.getLotteryEventParticipants(10, 0, ""); + LotteryEventParticipantResponseDto retrievedParticipant = lotteryEventParticipantsListResponseDto.participantsList().get(0); + + //then + assertThat(lotteryEventParticipantsListResponseDto.isLastPage()).isTrue(); + assertThat(lotteryEventParticipantsListResponseDto.totalParticipants()).isEqualTo(1); + + assertThat(retrievedParticipant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(retrievedParticipant.getLinkClickedCounts()).isZero(); + assertThat(retrievedParticipant.getExpectation()).isZero(); + assertThat(retrievedParticipant.getAppliedCount()).isEqualTo(1); + assertThat(retrievedParticipant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(retrievedParticipant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0, 0)); + } + + @Test + @DisplayName("추첨 이벤트 참여자 조회 성공 테스트 (전화번호가 있을 때)") + void getLotteryEventParticipantsTest_Success_withPhoneNumber() { + //given + List lotteryParticipantsList = new ArrayList<>(); + lotteryParticipantsList.add(lotteryParticipants); + Page lotteryParticipantsPage = new PageImpl<>(lotteryParticipantsList); + + + given(lotteryParticipantsRepository.findByBaseUser_Id(eq("010-0000-0000"), any(Pageable.class))).willReturn(lotteryParticipantsPage); + given(lotteryParticipantsRepository.countByBaseUser_Id("010-0000-0000")).willReturn(1L); + + //when + ParticipantsListResponseDto lotteryEventParticipantsListResponseDto = adminService.getLotteryEventParticipants(10, 0, "010-0000-0000"); + LotteryEventParticipantResponseDto retrievedParticipant = lotteryEventParticipantsListResponseDto.participantsList().get(0); + + //then + assertThat(lotteryEventParticipantsListResponseDto.isLastPage()).isTrue(); + assertThat(lotteryEventParticipantsListResponseDto.totalParticipants()).isEqualTo(1); + + assertThat(retrievedParticipant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(retrievedParticipant.getLinkClickedCounts()).isZero(); + assertThat(retrievedParticipant.getExpectation()).isZero(); + assertThat(retrievedParticipant.getAppliedCount()).isEqualTo(1); + assertThat(retrievedParticipant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(retrievedParticipant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0, 0)); + } + + @Test + @DisplayName("선착순 이벤트 생성 테스트 - 성공") + void createRushEventTest_Success() { + //given + MockMultipartFile prizeImg = new MockMultipartFile("prizeImg", "prizeImage.png", "png", "<>".getBytes()); + MockMultipartFile leftOptionImg = new MockMultipartFile("leftOptionImg", "leftOptionImage.png", "png", "<>".getBytes()); + MockMultipartFile rightOptionImg = new MockMultipartFile("rightOptionImg", "rightOptionImage.png", "png", "<>".getBytes()); + + + given(rushEventRepository.count()).willReturn(1L); + given(rushEventRepository.save(rushEvent)).willReturn(rushEvent); + given(rushOptionRepository.save(leftOption)).willReturn(leftOption); + given(rushOptionRepository.save(rightOption)).willReturn(rightOption); + + given(s3Service.upload(any())).willReturn("http://example.com/image.jpg"); + + + //when + RushEventResponseDto adminRushEventResponseDto = adminService.createRushEvent(rushEventRequestDto, prizeImg, leftOptionImg, rightOptionImg); + + //then + assertThat(adminRushEventResponseDto.getEventDate()).isEqualTo(LocalDate.of(2024, 8, 15)); + assertThat(adminRushEventResponseDto.getStartTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(adminRushEventResponseDto.getEndTime()).isEqualTo(LocalTime.of(23, 59)); + assertThat(adminRushEventResponseDto.getWinnerCount()).isEqualTo(100); + assertThat(adminRushEventResponseDto.getPrizeImageUrl()).isEqualTo("http://example.com/image.jpg"); + assertThat(adminRushEventResponseDto.getPrizeDescription()).isEqualTo("This is a detailed description of the prize."); + assertThat(adminRushEventResponseDto.getStatus()).isEqualTo(EventStatus.AFTER); + + Set options = adminRushEventResponseDto.getOptions(); + + boolean firstOptionFound = false; + boolean secondOptionFound = false; + + for (RushEventOptionResponseDto option : options) { + if (option.getMainText().equals("Main Text 2") && + option.getSubText().equals("Sub Text 2") && + option.getResultMainText().equals("Result Main Text 2") && + option.getResultSubText().equals("Result Sub Text 2") && + option.getImageUrl().equals("http://example.com/image.jpg") && + option.getPosition().equals(Position.RIGHT)) { + firstOptionFound = true; + } else if (option.getMainText().equals("Main Text 1") && + option.getSubText().equals("Sub Text 1") && + option.getResultMainText().equals("Result Main Text 1") && + option.getResultSubText().equals("Result Sub Text 1") && + option.getImageUrl().equals("http://example.com/image.jpg") && + option.getPosition().equals(Position.LEFT)) { + secondOptionFound = true; + } + } + + + assertThat(firstOptionFound).isTrue(); + assertThat(secondOptionFound).isTrue(); + } + + @Test + @DisplayName("선착순 이벤트 생성 테스트 - 실패 (데이터베이스에 이미 6개의 선착순 이벤트가 존재할 때)") + void createRushEventTest_Failure_TooManyRushEvent() { + //given + given(rushEventRepository.count()).willReturn(6L); + MockMultipartFile prizeImg = new MockMultipartFile("prizeImg", "prizeImage.png", "png", "<>".getBytes()); + MockMultipartFile leftOptionImg = new MockMultipartFile("leftOptionImg", "leftOptionImage.png", "png", "<>".getBytes()); + MockMultipartFile rightOptionImg = new MockMultipartFile("rightOptionImg", "rightOptionImage.png", "png", "<>".getBytes()); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.createRushEvent(rushEventRequestDto, prizeImg, leftOptionImg, rightOptionImg) + ); + + //then + assertEquals(CustomErrorCode.TOO_MANY_RUSH_EVENT, customException.getErrorCode()); + assertEquals("현재 진행중인 선착순 이벤트가 6개 이상입니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 조회 테스트 - 성공") + void getRushEventsTest_Success() { + //given + rushEvent.addOption(leftOption, rightOption); + List rushEventList = new ArrayList<>(); + rushEventList.add(rushEvent); + given(rushEventRepository.findAll()).willReturn(rushEventList); + + //when + List rushEvents = adminService.getRushEvents(); + + //then + RushEventResponseDto firstEvent = rushEvents.get(0); + assertThat(firstEvent.getEventDate()).isEqualTo(LocalDate.of(2024, 8, 15)); + assertThat(firstEvent.getStartTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(firstEvent.getEndTime()).isEqualTo(LocalTime.of(23, 59)); + assertThat(firstEvent.getWinnerCount()).isEqualTo(100); + assertThat(firstEvent.getPrizeImageUrl()).isEqualTo("http://example.com/image.jpg"); + assertThat(firstEvent.getPrizeDescription()).isEqualTo("This is a detailed description of the prize."); + assertThat(firstEvent.getStatus()).isEqualTo(EventStatus.AFTER); + + Set options = firstEvent.getOptions(); + + + boolean firstOptionFound = false; + boolean secondOptionFound = false; + for (RushEventOptionResponseDto option : options) { + System.out.println("option = " + option); + if (option.getMainText().equals("Main Text 2") && + option.getSubText().equals("Sub Text 2") && + option.getResultMainText().equals("Result Main Text 2") && + option.getResultSubText().equals("Result Sub Text 2") && + option.getImageUrl().equals("http://example.com/image.jpg") && + option.getPosition().equals(Position.RIGHT)) { + firstOptionFound = true; + } else if (option.getMainText().equals("Main Text 1") && + option.getSubText().equals("Sub Text 1") && + option.getResultMainText().equals("Result Main Text 1") && + option.getResultSubText().equals("Result Sub Text 1") && + option.getImageUrl().equals("http://example.com/image.jpg") && + option.getPosition().equals(Position.LEFT)) { + secondOptionFound = true; + } + } + + assertThat(firstOptionFound).isTrue(); + assertThat(secondOptionFound).isTrue(); + } + + @Test + @DisplayName("선착순 이벤트 참여자 조회 테스트 - 성공 (전화번호가 존재하고 결과가 동점이 아닌 경우") + void getRushEventParticipantsTest_Success_withPhoneNumberAndOptionId() { + //given + List rushParticipantsList = new ArrayList<>(); + rushParticipantsList.add(rushParticipant); + Page rushParticipantsPage = new PageImpl<>(rushParticipantsList); + + given(rushParticipantsRepository.findByRushEvent_RushEventIdAndOptionIdAndBaseUser_Id(eq(1L), eq(1), eq("010-0000-0000"), any(Pageable.class))) + .willReturn(rushParticipantsPage); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionIdAndBaseUser_Id(1L, 1, "010-0000-0000")) + .willReturn(1L); + + //when + ParticipantsListResponseDto rushEventParticipants = adminService.getRushEventParticipants(1, 1, 0, 1, "010-0000-0000"); + + //then + assertThat(rushEventParticipants.isLastPage()).isTrue(); + assertThat(rushEventParticipants.totalParticipants()).isEqualTo(1); + + List participantsList = rushEventParticipants.participantsList(); + + RushEventParticipantResponseDto participant = participantsList.get(0); + + assertThat(participant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(participant.getBalanceGameChoice()).isEqualTo(1); + assertThat(participant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(participant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(participant.getRank()).isZero(); + + } + + @Test + @DisplayName("선착순 이벤트 참여자 조회 테스트 - 성공 (전화번호가 존재하지 않고 결과가 동점인 경우") + void getRushEventParticipantsTest_Success_withoutPhoneNumberAndOptionId() { + //given + List rushParticipantsList = new ArrayList<>(); + rushParticipantsList.add(rushParticipant); + Page rushParticipantsPage = new PageImpl<>(rushParticipantsList); + + given(rushParticipantsRepository.findByRushEvent_RushEventId(eq(1L), any(Pageable.class))) + .willReturn(rushParticipantsPage); + given(rushParticipantsRepository.countByRushEvent_RushEventId(1L)) + .willReturn(1L); + + //when + ParticipantsListResponseDto rushEventParticipants = adminService.getRushEventParticipants(1, 1, 0, 0, ""); + + //then + assertThat(rushEventParticipants.isLastPage()).isTrue(); + assertThat(rushEventParticipants.totalParticipants()).isEqualTo(1); + + List participantsList = rushEventParticipants.participantsList(); + + RushEventParticipantResponseDto participant = participantsList.get(0); + + assertThat(participant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(participant.getBalanceGameChoice()).isEqualTo(1); + assertThat(participant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(participant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(participant.getRank()).isZero(); + } + + @Test + @DisplayName("선착순 이벤트 참여자 조회 테스트 - 성공 (전화번호가 존재하지 않고 결과가 동점이 아닌 경우") + void getRushEventParticipantsTest_Success_withoutPhoneNumberWithOptionId() { + //given + List rushParticipantsList = new ArrayList<>(); + rushParticipantsList.add(rushParticipant); + Page rushParticipantsPage = new PageImpl<>(rushParticipantsList); + + given(rushParticipantsRepository.findByRushEvent_RushEventIdAndOptionId(eq(1L), eq(1), any(Pageable.class))) + .willReturn(rushParticipantsPage); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)) + .willReturn(1L); + + //when + ParticipantsListResponseDto rushEventParticipants = adminService.getRushEventParticipants(1, 1, 0, 1, ""); + + //then + assertThat(rushEventParticipants.isLastPage()).isTrue(); + assertThat(rushEventParticipants.totalParticipants()).isEqualTo(1); + + List participantsList = rushEventParticipants.participantsList(); + + RushEventParticipantResponseDto participant = participantsList.get(0); + + assertThat(participant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(participant.getBalanceGameChoice()).isEqualTo(1); + assertThat(participant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(participant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(participant.getRank()).isZero(); + } + + @Test + @DisplayName("선착순 이벤트 참여자 조회 테스트 - 성공 (전화번호가 존재하고 결과가 동점인 경우") + void getRushEventParticipantsTest_Success_witPhoneNumberAndWithoutOptionId() { + //given + List rushParticipantsList = new ArrayList<>(); + rushParticipantsList.add(rushParticipant); + Page rushParticipantsPage = new PageImpl<>(rushParticipantsList); + + given(rushParticipantsRepository.findByRushEvent_RushEventIdAndBaseUser_Id(eq(1L), eq("010-0000-0000"), any(Pageable.class))) + .willReturn(rushParticipantsPage); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndBaseUser_Id(1L, "010-0000-0000")) + .willReturn(1L); + + //when + ParticipantsListResponseDto rushEventParticipants = adminService.getRushEventParticipants(1, 1, 0, 0, "010-0000-0000"); + + //then + assertThat(rushEventParticipants.isLastPage()).isTrue(); + assertThat(rushEventParticipants.totalParticipants()).isEqualTo(1); + + List participantsList = rushEventParticipants.participantsList(); + + RushEventParticipantResponseDto participant = participantsList.get(0); + + assertThat(participant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(participant.getBalanceGameChoice()).isEqualTo(1); + assertThat(participant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(participant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(participant.getRank()).isZero(); + } + + @Test + @DisplayName("선착순 이벤트 당첨자 조회 테스트 - 성공 (전화번호가 존재하고 결과가 동점이 아닌 경우") + void getRushEventWinnersTest_Success_withPhoneNumberAndOptionId() { + //given + List rushParticipantsList = new ArrayList<>(); + rushParticipantsList.add(rushParticipant); + Page rushParticipantsPage = new PageImpl<>(rushParticipantsList); + + given(rushEventRepository.findById(1L)).willReturn(Optional.of(rushEvent)); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)) + .willReturn(2L); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)) + .willReturn(1L); + given(rushParticipantsRepository.findWinnerByEventIdAndOptionIdAndPhoneNumber(eq(1L), eq(1), eq("010-0000-0000"), any(Pageable.class))) + .willReturn(rushParticipantsPage); + + //when + ParticipantsListResponseDto rushEventWinners + = adminService.getRushEventWinners(1L, 1, 0, "010-0000-0000"); + + //then + assertThat(rushEventWinners.isLastPage()).isTrue(); + assertThat(rushEventWinners.totalParticipants()).isEqualTo(1); + + List participantsList = rushEventWinners.participantsList(); + + RushEventParticipantResponseDto participant = participantsList.get(0); + + assertThat(participant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(participant.getBalanceGameChoice()).isEqualTo(1); + assertThat(participant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(participant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(participant.getRank()).isZero(); + } + + @Test + @DisplayName("선착순 이벤트 당첨자 조회 테스트 - 성공 (전화번호가 존재하지 않고 결과가 동점인 경우") + void getRushEventWinnersTest_Success_withoutPhoneNumberAndOptionId() { + //given + List rushParticipantsList = new ArrayList<>(); + rushParticipantsList.add(rushParticipant); + Page rushParticipantsPage = new PageImpl<>(rushParticipantsList); + + given(rushEventRepository.findById(1L)).willReturn(Optional.of(rushEvent)); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)) + .willReturn(1L); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)) + .willReturn(1L); + given(rushParticipantsRepository.findWinnerByEventId(eq(1L), any(Pageable.class))) + .willReturn(rushParticipantsPage); + + //when + ParticipantsListResponseDto rushEventWinners + = adminService.getRushEventWinners(1L, 1, 0, ""); + + //then + assertThat(rushEventWinners.isLastPage()).isTrue(); + assertThat(rushEventWinners.totalParticipants()).isEqualTo(1); + + List participantsList = rushEventWinners.participantsList(); + + RushEventParticipantResponseDto participant = participantsList.get(0); + + assertThat(participant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(participant.getBalanceGameChoice()).isEqualTo(1); + assertThat(participant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(participant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(participant.getRank()).isZero(); + } + + @Test + @DisplayName("선착순 이벤트 당첨자 조회 테스트 - 성공 (전화번호가 존재하지 않고 결과가 동점이 아닌 경우") + void getRushEventWinnersTest_Success_withoutPhoneNumberAndWithOptionId() { + //given + List rushParticipantsList = new ArrayList<>(); + rushParticipantsList.add(rushParticipant); + Page rushParticipantsPage = new PageImpl<>(rushParticipantsList); + + given(rushEventRepository.findById(1L)).willReturn(Optional.of(rushEvent)); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)) + .willReturn(2L); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)) + .willReturn(1L); + given(rushParticipantsRepository.findWinnerByEventIdAndOptionId(eq(1L), eq(1), any(Pageable.class))) + .willReturn(rushParticipantsPage); + + //when + ParticipantsListResponseDto rushEventWinners + = adminService.getRushEventWinners(1L, 1, 0, ""); + + //then + assertThat(rushEventWinners.isLastPage()).isTrue(); + assertThat(rushEventWinners.totalParticipants()).isEqualTo(1); + + List participantsList = rushEventWinners.participantsList(); + + RushEventParticipantResponseDto participant = participantsList.get(0); + + assertThat(participant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(participant.getBalanceGameChoice()).isEqualTo(1); + assertThat(participant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(participant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(participant.getRank()).isZero(); + } + + @Test + @DisplayName("선착순 이벤트 당첨자 조회 테스트 - 성공 (전화번호가 존재하고 결과가 동점인 경우") + void getRushEventWinnersTest_Success_withPhoneNumberAndWithoutOptionId() { + //given + List rushParticipantsList = new ArrayList<>(); + rushParticipantsList.add(rushParticipant); + Page rushParticipantsPage = new PageImpl<>(rushParticipantsList); + + given(rushEventRepository.findById(1L)).willReturn(Optional.of(rushEvent)); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)) + .willReturn(1L); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)) + .willReturn(1L); + given(rushParticipantsRepository.findByWinnerByEventIdAndPhoneNumber(eq(1L), eq("010-0000-0000"), any(Pageable.class))) + .willReturn(rushParticipantsPage); + + //when + ParticipantsListResponseDto rushEventWinners + = adminService.getRushEventWinners(1L, 1, 0, "010-0000-0000"); + + //then + assertThat(rushEventWinners.isLastPage()).isTrue(); + assertThat(rushEventWinners.totalParticipants()).isEqualTo(1); + + List participantsList = rushEventWinners.participantsList(); + + RushEventParticipantResponseDto participant = participantsList.get(0); + + assertThat(participant.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(participant.getBalanceGameChoice()).isEqualTo(1); + assertThat(participant.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(participant.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(participant.getRank()).isZero(); + } + + @Test + @DisplayName("선착순 이벤트 삭제 - 성공") + void deleteLotteryEvent_Success() { + //given + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + + //when + adminService.deleteLotteryEvent(); + } + + @Test + @DisplayName("추첨 이벤트 업데이트 테스트 - 성공") + void updateLotteryEventTest_Success() { + //given + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + given(eventCacheService.setLotteryEvent()).willReturn(lotteryEvent); + + //when + LotteryEventResponseDto lotteryEventResponseDto = adminService.updateLotteryEvent(lotteryEventRequestDto); + + //then + assertThat(lotteryEventResponseDto.getStartDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(lotteryEventResponseDto.getStartTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(lotteryEventResponseDto.getEndDate()).isEqualTo(LocalDate.of(2100, 9, 27)); + assertThat(lotteryEventResponseDto.getEndTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(lotteryEventResponseDto.getAppliedCount()).isZero(); + assertThat(lotteryEventResponseDto.getWinnerCount()).isEqualTo(315); + assertThat(lotteryEventResponseDto.getStatus()).isEqualTo(EventStatus.DURING); + } + + @Test + @DisplayName("선착순 이벤트 업데이트 테스트 - 실패 (종료 날짜가 시작 날짜보다 앞서는 경우)") + void updateLotteryEventTest_Failure_EndBeforeStart() { + //given + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + + lotteryEventRequestDto = LotteryEventRequestDto.builder() + .startDate(LocalDate.of(2100, 9, 27)) + .startTime(LocalTime.of(0, 0)) + .endDate(LocalDate.of(2000, 9, 27)) + .endTime(LocalTime.of(0, 0)) + .winnerCount(315) + .build(); + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.updateLotteryEvent(lotteryEventRequestDto) + ); + + //then + assertEquals(CustomErrorCode.EVENT_END_TIME_BEFORE_START_TIME, customException.getErrorCode()); + assertEquals("종료 시간은 시작 시간 이후로 설정해야 합니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 업데이트 테스트 - 실패 (이벤트가 진행중일 때, 시작 날짜를 수정하는 경우)") + void updateLotteryEventTest_Failure_ModifyingOngoingEvent() { + //given + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + + lotteryEventRequestDto = LotteryEventRequestDto.builder() + .startDate(LocalDate.of(2050, 9, 27)) + .startTime(LocalTime.of(0, 0)) + .endDate(LocalDate.of(2100, 9, 27)) + .endTime(LocalTime.of(0, 0)) + .winnerCount(315) + .build(); + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.updateLotteryEvent(lotteryEventRequestDto) + ); + + //then + assertEquals(CustomErrorCode.EVENT_IN_PROGRESS_CANNOT_CHANGE_START_TIME, customException.getErrorCode()); + assertEquals("현재 진행 중인 이벤트의 시작 시간을 변경할 수 없습니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 업데이트 테스트 - 실패 (이벤트가 진행중일 때, 종료 날짜가 현재 시간보다 앞서는 경우)") + void updateLotteryEventTest_Failure_EndBeforeNow() { + //given + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + + lotteryEventRequestDto = LotteryEventRequestDto.builder() + .startDate(LocalDate.of(2000, 9, 27)) + .startTime(LocalTime.of(0, 0)) + .endDate(LocalDate.of(2001, 9, 27)) + .endTime(LocalTime.of(0, 0)) + .winnerCount(315) + .build(); + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.updateLotteryEvent(lotteryEventRequestDto) + ); + + //then + assertEquals(CustomErrorCode.EVENT_IN_PROGRESS_END_TIME_BEFORE_NOW, customException.getErrorCode()); + assertEquals("현재 진행 중인 이벤트의 종료 시간을 현재 시간보다 이전으로 설정할 수 없습니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 업데이트 테스트 - 실패 (이벤트가 시작 전일 때, 시작 날짜가 현재 시간보다 앞서는 경우)") + void updateLotteryEventTest_Failure_StartBeforeNow() { + //given + lotteryEventRequestDto = LotteryEventRequestDto.builder() + .startDate(LocalDate.of(2100, 9, 27)) + .startTime(LocalTime.of(0, 0)) + .endDate(LocalDate.of(2200, 9, 27)) + .endTime(LocalTime.of(0, 0)) + .winnerCount(315) + .build(); + + lotteryEvent = new LotteryEvent( + LocalDateTime.of(lotteryEventRequestDto.getStartDate(), lotteryEventRequestDto.getStartTime()), + LocalDateTime.of(lotteryEventRequestDto.getEndDate(), lotteryEventRequestDto.getEndTime()), + lotteryEventRequestDto.getWinnerCount() + ); + + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + + lotteryEventRequestDto = LotteryEventRequestDto.builder() + .startDate(LocalDate.of(2000, 9, 27)) + .startTime(LocalTime.of(0, 0)) + .endDate(LocalDate.of(2200, 9, 27)) + .endTime(LocalTime.of(0, 0)) + .winnerCount(315) + .build(); + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.updateLotteryEvent(lotteryEventRequestDto) + ); + + //then + assertEquals(CustomErrorCode.EVENT_BEFORE_START_TIME, customException.getErrorCode()); + assertEquals("이벤트 시작 시간은 현재 시간 이후로 설정해야 합니다.", customException.getMessage()); + } + + @Test + @DisplayName("추첨 이벤트 당첨자 추첨 테스트 - 성공") + void pickLotteryEventWinners_Success() { + //given + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + List lotteryParticipantsList = new ArrayList<>(); + + for (int i = 0; i < 400; i++) { + lotteryParticipantsList.add(lotteryParticipants); + } + + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + given(lotteryWinnerRepository.count()).willReturn(0L); + + List idAndAppliedCounts = new ArrayList<>(); + for (int i = 0; i < 400; i++) { + Object[] data = new Object[2]; + data[0] = (long) i; + data[1] = 2; + idAndAppliedCounts.add(data); + } + given(lotteryParticipantsRepository.findById(any())).willReturn(Optional.ofNullable(lotteryParticipants)); + given(lotteryParticipantsRepository.findIdAndAppliedCounts()).willReturn(idAndAppliedCounts); + + //when + ResponseDto responseDto = adminService.pickLotteryEventWinners(); + + //then + assertThat(responseDto.message()).isEqualTo("추첨이 완료되었습니다."); + } + + @Test + @DisplayName("추첨 이벤트 당첨자 추첨 테스트 - 성공 (당첨인원보다 신청인원이 적을 경우)") + void pickLotteryEventWinners_Success_ParticipantsIsLessThanWinners() { + //given + List lotteryEventList = new ArrayList<>(); + lotteryEventList.add(lotteryEvent); + List lotteryParticipantsList = new ArrayList<>(); + lotteryParticipantsList.add(lotteryParticipants); + + + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + given(lotteryWinnerRepository.count()).willReturn(0L); + List idAndAppliedCounts = new ArrayList<>(); + Object[] data = new Object[2]; + data[0] = 1L; + data[1] = 2; + idAndAppliedCounts.add(data); + given(lotteryParticipantsRepository.findById(1L)).willReturn(Optional.ofNullable(lotteryParticipants)); + given(lotteryParticipantsRepository.findIdAndAppliedCounts()).willReturn(idAndAppliedCounts); + + //when + ResponseDto responseDto = adminService.pickLotteryEventWinners(); + + //then + assertThat(responseDto.message()).isEqualTo("추첨이 완료되었습니다."); + } + + @Test + @DisplayName("추첨 이벤트 당첨자 추첨 테스트 - 실패 (이미 추첨이 완료된 경우)") + void pickLotteryEventWinnersTest_Failure_AlreadyDrown() { + //given + given(lotteryWinnerRepository.count()).willReturn(315L); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.pickLotteryEventWinners() + ); + + //then + assertEquals(CustomErrorCode.LOTTERY_EVENT_ALREADY_DRAWN, customException.getErrorCode()); + assertEquals("추첨 이벤트의 당첨자가 이미 추첨되었습니다.", customException.getMessage()); + } + + @Test + @DisplayName("당첨자 명단 삭제 테스트 - 성공") + void deleteLotteryEventWinnersTest_Success() { + //given + + //when + ResponseDto responseDto = adminService.deleteLotteryEventWinners(); + + //then + assertThat(responseDto.message()).isEqualTo("당첨자 명단을 삭제했습니다."); + } + + @Test + @DisplayName("당첨자 명단 조회 테스트 - 성공 (전화번호가 주어지지 않은 경우)") + void getLotteryEventWinnersTest_Success_WithoutPhoneNumber() { + //given + List lotteryWinnersList = new ArrayList<>(); + lotteryWinnersList.add(new LotteryWinners(lotteryParticipants)); + Page lotteryWinnersPage = new PageImpl<>(lotteryWinnersList); + + given(lotteryWinnerRepository.count()).willReturn(315L); + given(lotteryWinnerRepository.findAll(any(Pageable.class))) + .willReturn(lotteryWinnersPage); + + //when + ParticipantsListResponseDto lotteryEventWinners = adminService.getLotteryEventWinners(1, 0, ""); + + //then + LotteryEventParticipantResponseDto actualWinner = lotteryEventWinners.participantsList().get(0); + assertThat(actualWinner.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(actualWinner.getLinkClickedCounts()).isZero(); + assertThat(actualWinner.getExpectation()).isZero(); + assertThat(actualWinner.getAppliedCount()).isEqualTo(1); + assertThat(actualWinner.getRanking()).isZero(); + assertThat(actualWinner.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(actualWinner.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + + assertThat(lotteryEventWinners.isLastPage()).isTrue(); + + assertThat(lotteryEventWinners.totalParticipants()).isEqualTo(315); + } + + @Test + @DisplayName("당첨자 명단 조회 테스트 - 성공 (전화번호가 주어진 경우)") + void getLotteryEventWinnersTest_Success_WithPhoneNumber() { + //given + List lotteryWinnersList = new ArrayList<>(); + lotteryWinnersList.add(new LotteryWinners(lotteryParticipants)); + Page lotteryWinnersPage = new PageImpl<>(lotteryWinnersList); + + given(lotteryWinnerRepository.count()).willReturn(315L); + given(lotteryWinnerRepository.findByPhoneNumber(eq("010-0000-0000"), any(Pageable.class))) + .willReturn(lotteryWinnersPage); + given(lotteryWinnerRepository.countByPhoneNumber("010-0000-0000")).willReturn(1L); + + //when + ParticipantsListResponseDto lotteryEventWinners = adminService.getLotteryEventWinners(1, 0, "010-0000-0000"); + + //then + LotteryEventParticipantResponseDto actualWinner = lotteryEventWinners.participantsList().get(0); + assertThat(actualWinner.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(actualWinner.getLinkClickedCounts()).isZero(); + assertThat(actualWinner.getExpectation()).isZero(); + assertThat(actualWinner.getAppliedCount()).isEqualTo(1); + assertThat(actualWinner.getRanking()).isZero(); + assertThat(actualWinner.getCreatedDate()).isEqualTo(LocalDate.of(2000, 9, 27)); + assertThat(actualWinner.getCreatedTime()).isEqualTo(LocalTime.of(0, 0)); + + assertThat(lotteryEventWinners.isLastPage()).isTrue(); + + assertThat(lotteryEventWinners.totalParticipants()).isEqualTo(1); + } + + @Test + @DisplayName("추첨 이벤트 당첨자 조회 테스트 - 실패 (아직 추첨하지 않음)") + void getLotteryEventWinnersTest_Failure_NotDrawn() { + //given + given(lotteryWinnerRepository.count()).willReturn(0L); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.getLotteryEventWinners(1, 0, "") + ); + + //then + assertEquals(CustomErrorCode.LOTTERY_EVENT_NOT_DRAWN, customException.getErrorCode()); + assertEquals("추첨 이벤트가 아직 추첨되지 않았습니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 업데이트 테스트 - 성공") + void updateRushEventTest_Success() { + //given + rushEvent.addOption(leftOption, rightOption); + List rushEventRequestDtoList = new ArrayList<>(); + rushEventRequestDtoList.add(rushEventRequestDto); + + List rushEventList = new ArrayList<>(); + rushEventList.add(rushEvent); + + given(rushEventRepository.findByRushEventId(1L)).willReturn(rushEvent); + given(rushEventRepository.findAll()).willReturn(rushEventList); + + //when + List rushEventResponseDtoList = adminService.updateRushEvents(rushEventRequestDtoList); + + //then + + RushEventResponseDto actualRushEvent = rushEventResponseDtoList.iterator().next(); + assertThat(actualRushEvent.getEventDate()).isEqualTo(LocalDate.of(2024, 8, 15)); + assertThat(actualRushEvent.getStartTime()).isEqualTo(LocalTime.of(0, 0)); + assertThat(actualRushEvent.getEndTime()).isEqualTo(LocalTime.of(23, 59)); + assertThat(actualRushEvent.getWinnerCount()).isEqualTo(100); + assertThat(actualRushEvent.getPrizeImageUrl()).isEqualTo("http://example.com/image.jpg"); + assertThat(actualRushEvent.getPrizeDescription()).isEqualTo("This is a detailed description of the prize."); + assertThat(actualRushEvent.getStatus()).isEqualTo(EventStatus.AFTER); + + Set options = actualRushEvent.getOptions(); + + + boolean firstOptionFound = false; + boolean secondOptionFound = false; + + for (RushEventOptionResponseDto option : options) { + if (option.getMainText().equals("Main Text 2") && + option.getSubText().equals("Sub Text 2") && + option.getResultMainText().equals("Result Main Text 2") && + option.getResultSubText().equals("Result Sub Text 2") && + option.getImageUrl().equals("http://example.com/image.jpg") && + option.getPosition().equals(Position.RIGHT)) { + firstOptionFound = true; + } else if (option.getMainText().equals("Main Text 1") && + option.getSubText().equals("Sub Text 1") && + option.getResultMainText().equals("Result Main Text 1") && + option.getResultSubText().equals("Result Sub Text 1") && + option.getImageUrl().equals("http://example.com/image.jpg") && + option.getPosition().equals(Position.LEFT)) { + secondOptionFound = true; + } + } + + + assertThat(firstOptionFound).isTrue(); + assertThat(secondOptionFound).isTrue(); + } + + @Test + @DisplayName("선착순 이벤트 업데이트 테스트 - 실패 (종료 시간이 시작 시간보다 앞서는 경우)") + void updateRushEventTest_Failure_EndBeforeStart() { + //given + rushEvent.addOption(leftOption, rightOption); + List rushEventRequestDtoList = new ArrayList<>(); + + Set options = new HashSet<>(); + options.add(leftOptionRequestDto); + options.add(rightOptionRequestDto); + + rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.of(2024, 8, 15)) + .startTime(LocalTime.of(23, 59)) + .endTime(LocalTime.of(0, 0)) + .winnerCount(100) + .prizeImageUrl("http://example.com/image.jpg") + .prizeDescription("This is a detailed description of the prize.") + .options(options) + .build(); + + rushEventRequestDtoList.add(rushEventRequestDto); + + List rushEventList = new ArrayList<>(); + rushEventList.add(rushEvent); + + given(rushEventRepository.findByRushEventId(1L)).willReturn(rushEvent); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.updateRushEvents(rushEventRequestDtoList) + ); + + //then + assertEquals(CustomErrorCode.EVENT_END_TIME_BEFORE_START_TIME, customException.getErrorCode()); + assertEquals("종료 시간은 시작 시간 이후로 설정해야 합니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 업데이트 테스트 - 실패 (진행중인 이벤트의 시작 시간을 수정하는 경우)") + void updateRushEventTest_Failure_ModifyingOngoingEvent() { + //given + Set options = new HashSet<>(); + options.add(leftOptionRequestDto); + options.add(rightOptionRequestDto); + + rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.now()) + .startTime(LocalTime.of(0, 0)) + .endTime(LocalTime.of(23, 59)) + .winnerCount(100) + .prizeImageUrl("http://example.com/image.jpg") + .prizeDescription("This is a detailed description of the prize.") + .options(options) + .build(); + + rushEvent = new RushEvent( + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getStartTime()), + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getEndTime()), + rushEventRequestDto.getWinnerCount(), + "http://example.com/image.jpg", + rushEventRequestDto.getPrizeDescription() + ); + + rushEvent.addOption(leftOption, rightOption); + List rushEventRequestDtoList = new ArrayList<>(); + + rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.now()) + .startTime(LocalTime.of(23, 58)) + .endTime(LocalTime.of(23, 59)) + .winnerCount(100) + .prizeImageUrl("http://example.com/image.jpg") + .prizeDescription("This is a detailed description of the prize.") + .options(options) + .build(); + + rushEventRequestDtoList.add(rushEventRequestDto); + + List rushEventList = new ArrayList<>(); + rushEventList.add(rushEvent); + + given(rushEventRepository.findByRushEventId(1L)).willReturn(rushEvent); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.updateRushEvents(rushEventRequestDtoList) + ); + + //then + assertEquals(CustomErrorCode.EVENT_IN_PROGRESS_CANNOT_CHANGE_START_TIME, customException.getErrorCode()); + assertEquals("현재 진행 중인 이벤트의 시작 시간을 변경할 수 없습니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 업데이트 테스트 - 실패 (진행중인 이벤트의 종료 시간을 수정하는 경우)") + void updateRushEventTest_Failure_EndBeforeNow() { + //given + Set options = new HashSet<>(); + options.add(leftOptionRequestDto); + options.add(rightOptionRequestDto); + + rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.now()) + .startTime(LocalTime.of(0, 0)) + .endTime(LocalTime.of(23, 59)) + .winnerCount(100) + .prizeImageUrl("http://example.com/image.jpg") + .prizeDescription("This is a detailed description of the prize.") + .options(options) + .build(); + + rushEvent = new RushEvent( + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getStartTime()), + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getEndTime()), + rushEventRequestDto.getWinnerCount(), + "http://example.com/image.jpg", + rushEventRequestDto.getPrizeDescription() + ); + + rushEvent.addOption(leftOption, rightOption); + List rushEventRequestDtoList = new ArrayList<>(); + + rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.now()) + .startTime(LocalTime.of(0, 0)) + .endTime(LocalTime.now().minusSeconds(5)) + .winnerCount(100) + .prizeImageUrl("http://example.com/image.jpg") + .prizeDescription("This is a detailed description of the prize.") + .options(options) + .build(); + + rushEventRequestDtoList.add(rushEventRequestDto); + + List rushEventList = new ArrayList<>(); + rushEventList.add(rushEvent); + + given(rushEventRepository.findByRushEventId(1L)).willReturn(rushEvent); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.updateRushEvents(rushEventRequestDtoList) + ); + + //then + assertEquals(CustomErrorCode.EVENT_IN_PROGRESS_END_TIME_BEFORE_NOW, customException.getErrorCode()); + assertEquals("현재 진행 중인 이벤트의 종료 시간을 현재 시간보다 이전으로 설정할 수 없습니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 업데이트 테스트 - 실패 (이벤트 시작 전인 경우)") + void updateRushEventTest_Failure_StartBeforeNow() { + //given + Set options = new HashSet<>(); + options.add(leftOptionRequestDto); + options.add(rightOptionRequestDto); + + rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.of(2100, 1, 1)) + .startTime(LocalTime.of(0, 0)) + .endTime(LocalTime.of(23, 59)) + .winnerCount(100) + .prizeImageUrl("http://example.com/image.jpg") + .prizeDescription("This is a detailed description of the prize.") + .options(options) + .build(); + + rushEvent = new RushEvent( + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getStartTime()), + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getEndTime()), + rushEventRequestDto.getWinnerCount(), + "http://example.com/image.jpg", + rushEventRequestDto.getPrizeDescription() + ); + + rushEvent.addOption(leftOption, rightOption); + List rushEventRequestDtoList = new ArrayList<>(); + + rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.of(1945, 8, 15)) + .startTime(LocalTime.of(0, 0)) + .endTime(LocalTime.now().minusSeconds(5)) + .winnerCount(100) + .prizeImageUrl("http://example.com/image.jpg") + .prizeDescription("This is a detailed description of the prize.") + .options(options) + .build(); + + rushEventRequestDtoList.add(rushEventRequestDto); + + List rushEventList = new ArrayList<>(); + rushEventList.add(rushEvent); + + given(rushEventRepository.findByRushEventId(1L)).willReturn(rushEvent); + + //when + CustomException customException = assertThrows(CustomException.class, () -> + adminService.updateRushEvents(rushEventRequestDtoList) + ); + + //then + assertEquals(CustomErrorCode.EVENT_BEFORE_START_TIME, customException.getErrorCode()); + assertEquals("이벤트 시작 시간은 현재 시간 이후로 설정해야 합니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 삭제 테스트 - 성공") + void deleteRushEventTest_Success() { + //given + given(rushEventRepository.findById(1L)).willReturn(Optional.ofNullable(rushEvent)); + + //when + ResponseDto responseDto = adminService.deleteRushEvent(1L); + + //then + assertThat(responseDto.message()).isEqualTo("요청에 성공하였습니다."); + } + + @Test + @DisplayName("선착순 이벤트 삭제 테스트 - 실패 (아이디와 일치하는 이벤트가 없는 경우)") + void deleteRushEventTest_Failure_NoLotteryEvent() { + //given + given(rushEventRepository.findById(1L)).willReturn(Optional.empty()); + + //when + CustomException customException = assertThrows(CustomException.class, + () -> adminService.deleteRushEvent(1L) + ); + + //then + assertEquals(CustomErrorCode.NO_RUSH_EVENT, customException.getErrorCode()); + assertEquals("선착순 이벤트를 찾을 수 없습니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 삭제 테스트 - 실패 (진행중인 이벤트일 경우)") + void deleteRushEventTest_Failure_EventInProgress() { + //given + Set options = new HashSet<>(); + options.add(leftOptionRequestDto); + options.add(rightOptionRequestDto); + + rushEventRequestDto = RushEventRequestDto.builder() + .rushEventId(1L) + .eventDate(LocalDate.now()) + .startTime(LocalTime.of(0, 0)) + .endTime(LocalTime.of(23, 59)) + .winnerCount(100) + .prizeImageUrl("http://example.com/image.jpg") + .prizeDescription("This is a detailed description of the prize.") + .options(options) + .build(); + + rushEvent = new RushEvent( + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getStartTime()), + LocalDateTime.of(rushEventRequestDto.getEventDate(), rushEventRequestDto.getEndTime()), + rushEventRequestDto.getWinnerCount(), + "http://example.com/image.jpg", + rushEventRequestDto.getPrizeDescription() + ); + + given(rushEventRepository.findById(1L)).willReturn(Optional.ofNullable(rushEvent)); + + //when + CustomException customException = assertThrows(CustomException.class, + () -> adminService.deleteRushEvent(1L) + ); + + //then + assertEquals(CustomErrorCode.EVENT_IN_PROGRESS_CANNOT_DELETE, customException.getErrorCode()); + assertEquals("진행중인 이벤트를 삭제할 수 없습니다.", customException.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 선택지 조회 테스트 - 성공") + void getRushEventOptionsTest_Success() { + //given + rushEvent.addOption(leftOption, rightOption); + given(rushEventRepository.findById(1L)).willReturn(Optional.ofNullable(rushEvent)); + + //when + RushEventResponseDto rushEventOptions = adminService.getRushEventOptions(1L); + + //then + Set options = rushEventOptions.getOptions(); + + boolean firstOptionFound = false; + boolean secondOptionFound = false; + + for (RushEventOptionResponseDto option : options) { + if (option.getMainText().equals("Main Text 2") && + option.getSubText().equals("Sub Text 2") && + option.getResultMainText().equals("Result Main Text 2") && + option.getResultSubText().equals("Result Sub Text 2") && + option.getImageUrl().equals("http://example.com/image.jpg") && + option.getPosition().equals(Position.RIGHT)) { + firstOptionFound = true; + } else if (option.getMainText().equals("Main Text 1") && + option.getSubText().equals("Sub Text 1") && + option.getResultMainText().equals("Result Main Text 1") && + option.getResultSubText().equals("Result Sub Text 1") && + option.getImageUrl().equals("http://example.com/image.jpg") && + option.getPosition().equals(Position.LEFT)) { + secondOptionFound = true; + } + } + + + assertThat(firstOptionFound).isTrue(); + assertThat(secondOptionFound).isTrue(); + + } + + @Test + @DisplayName("선착순 이벤트 선택지 조회 테스트 - 실패 (이벤트 조회 실패)") + void getRushEventOptionsTest_Failure_NoRushEvent() { + //given + given(rushEventRepository.findById(1L)).willReturn(Optional.empty()); + + //when + CustomException customException = assertThrows(CustomException.class, + () -> adminService.getRushEventOptions(1L) + ); + + //then + assertEquals(CustomErrorCode.NO_RUSH_EVENT, customException.getErrorCode()); + assertEquals("선착순 이벤트를 찾을 수 없습니다.", customException.getMessage()); + } + + @Test + @DisplayName("기대평 조회 테스트 - 성공") + void getLotteryEventExpectationsTest_Success() { + //given + given(lotteryParticipantsRepository.findById(1L)).willReturn(Optional.ofNullable(lotteryParticipants)); + + List casperBotList = new ArrayList<>(); + casperBotList.add(casperBot); + Page casperBotPage = new PageImpl<>(casperBotList); + + given(casperBotRepository.findByPhoneNumberAndActiveExpectations(eq("010-0000-0000"), any(Pageable.class))) + .willReturn(casperBotPage); + + //when + ExpectationsPagingResponseDto lotteryEventExpectations = adminService.getLotteryEventExpectations(0, 1, 1L); + + //then + List expectations = lotteryEventExpectations.expectations(); + + boolean expectationFound = false; + + for (LotteryEventResponseDto exp : expectations) { + if (exp.getExpectation().equals("expectation") && + exp.getCreatedDate().equals(LocalDate.of(2000, 9, 27)) && + exp.getCreatedTime().equals(LocalTime.of(0, 0))) { + expectationFound = true; + break; + } + } + + assertThat(expectationFound).isTrue(); + assertThat(lotteryEventExpectations.isLastPage()).isTrue(); + assertThat(lotteryEventExpectations.totalExpectations()).isEqualTo(1); + } + + @Test + @DisplayName("기대평 조회 테스트 - 실패 (참여자 조회 실패)") + void getLotteryEventExpectationsTest_Failure_UserNotFound() { + //given + given(lotteryParticipantsRepository.findById(1L)) + .willReturn(Optional.empty()); + + //when + CustomException customException = assertThrows(CustomException.class, + () -> adminService.getLotteryEventExpectations(0, 1, 1L) + ); + + //then + assertEquals(CustomErrorCode.USER_NOT_FOUND, customException.getErrorCode()); + assertEquals("응모하지 않은 사용자입니다.", customException.getMessage()); + } + + @Test + @DisplayName("부적절한 기대평 삭제 테스트 - 성공") + void deleteLotteryEventExpectationTest_Success() { + //given + List casperBotResponseDtoList = new ArrayList<>(); + casperBotResponseDtoList.add(CasperBotResponseDto.of(casperBot)); + + given(casperBotRepository.findById(1L)).willReturn(Optional.ofNullable(casperBot)); + given(casperBotRedisTemplate.opsForList()).willReturn(listOperations); + given(casperBotRedisTemplate.opsForList().range(anyString(), eq(0L), eq(-1L))) + .willReturn(casperBotResponseDtoList); + + //when + adminService.deleteLotteryEventExpectation(1L); + + //then + + } +} diff --git a/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/EventCacheServiceTest.java b/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/EventCacheServiceTest.java new file mode 100644 index 00000000..077745fc --- /dev/null +++ b/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/EventCacheServiceTest.java @@ -0,0 +1,199 @@ +package JGS.CasperEvent.domain.event.service.eventService; + + +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; +import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; +import JGS.CasperEvent.domain.event.entity.event.RushEvent; +import JGS.CasperEvent.domain.event.repository.eventRepository.LotteryEventRepository; +import JGS.CasperEvent.domain.event.repository.eventRepository.RushEventRepository; +import JGS.CasperEvent.global.enums.CustomErrorCode; +import JGS.CasperEvent.global.error.exception.CustomException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class EventCacheServiceTest { + + @Mock + private RushEventRepository rushEventRepository; + + @Mock + private LotteryEventRepository lotteryEventRepository; + + @InjectMocks + EventCacheService eventCacheService; + + @Test + @DisplayName("추첨 이벤트 조회 테스트 - 성공") + void getLotteryEventTest_Success() { + //given + LotteryEvent lotteryEvent = new LotteryEvent(); + List lotteryEventList = List.of(lotteryEvent); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + + //when + LotteryEvent actualLotteryEvent = eventCacheService.getLotteryEvent(); + + //then + assertThat(actualLotteryEvent).isEqualTo(lotteryEvent); + } + + @Test + @DisplayName("추첨 이벤트 업데이트 테스트 - 성공") + void setLotteryEventTest_Success() { + //given + LotteryEvent lotteryEvent = new LotteryEvent(); + List lotteryEventList = List.of(lotteryEvent); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + + //when + LotteryEvent actualLotteryEvent = eventCacheService.setLotteryEvent(); + + //then + assertThat(actualLotteryEvent).isEqualTo(lotteryEvent); + } + + @Test + @DisplayName("추첨 이벤트 조회 테스트 - 실패 (이벤트 없음)") + void getLotteryEventTest_Failure_NoLotteryEvent() { + //given + given(lotteryEventRepository.findAll()).willReturn(new ArrayList<>()); + + //when + CustomException exception = assertThrows(CustomException.class, () -> + eventCacheService.getLotteryEvent() + ); + + //then + assertEquals(CustomErrorCode.NO_LOTTERY_EVENT, exception.getErrorCode()); + assertEquals("추첨 이벤트를 찾을 수 없습니다.", exception.getMessage()); + } + + @Test + @DisplayName("추첨 이벤트 조회 테스트 - 실패 (이벤트 2개 이상)") + void getLotteryEventTest_Failure_TooManyLotteryEvent() { + //given + List lotteryEventList = List.of( + new LotteryEvent(), new LotteryEvent() + ); + given(lotteryEventRepository.findAll()).willReturn(lotteryEventList); + + //when + CustomException exception = assertThrows(CustomException.class, () -> + eventCacheService.getLotteryEvent() + ); + + //then + assertEquals(CustomErrorCode.TOO_MANY_LOTTERY_EVENT, exception.getErrorCode()); + assertEquals("현재 진행중인 추첨 이벤트가 2개 이상입니다.", exception.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 조회 테스트 - 성공") + void getTodayEventTest_Success() { + //given + RushEvent rushEvent = new RushEvent(); + List rushEventList = List.of(rushEvent); + given(rushEventRepository.findByEventDate(any())).willReturn(rushEventList); + + //when + RushEventResponseDto rushEventResponseDto = eventCacheService.getTodayEvent(LocalDate.now()); + + //then + assertThat(rushEventResponseDto).isNotNull(); + } + + @Test + @DisplayName("선착순 이벤트 패치 테스트 - 성공") + void setTodayEventTest_Success() { + //given + RushEvent rushEvent = new RushEvent(); + List rushEventList = List.of(rushEvent); + given(rushEventRepository.findByEventDate(any())).willReturn(rushEventList); + + //when + RushEventResponseDto rushEventResponseDto = eventCacheService.setCacheValue(LocalDate.now()); + + //then + assertThat(rushEventResponseDto).isNotNull(); + } + + @Test + @DisplayName("선착순 이벤트 조회 테스트 - 실패 (이벤트 없음)") + void getRushEventTest_Failure_NoRushEvent() { + //given + given(rushEventRepository.findByEventDate(any())).willReturn(new ArrayList<>()); + + //when + CustomException exception = assertThrows(CustomException.class, () -> + eventCacheService.getTodayEvent(LocalDate.now()) + ); + + //then + assertEquals(CustomErrorCode.NO_RUSH_EVENT, exception.getErrorCode()); + assertEquals("선착순 이벤트가 존재하지않습니다.", exception.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 조회 테스트 - 실패 (이벤트 2개 이상)") + void getLotteryEventTest_Failure_MultipleRushEventsFound() { + //given + List rushEventList = List.of( + new RushEvent(), new RushEvent() + ); + given(rushEventRepository.findByEventDate(any())).willReturn(rushEventList); + + //when + CustomException exception = assertThrows(CustomException.class, () -> + eventCacheService.getTodayEvent(LocalDate.now()) + ); + + //then + assertEquals(CustomErrorCode.MULTIPLE_RUSH_EVENTS_FOUND, exception.getErrorCode()); + assertEquals("선착순 이벤트가 2개 이상 존재합니다.", exception.getMessage()); + } + + @Test + @DisplayName("선착순 이벤트 전체 조회 테스트 - 성공") + void getAllRushEventTest_Success() { + //given + RushEvent rushEvent = new RushEvent(); + List rushEventList = List.of(rushEvent); + given(rushEventRepository.findAll()).willReturn(rushEventList); + + //when + List allRushEvent = eventCacheService.getAllRushEvent(); + + //then + assertThat(allRushEvent).isNotNull(); + } + + @Test + @DisplayName("선착순 이벤트 세팅 테스트 - 성공") + void setAllRushEventTest_Success() { + //given + RushEvent rushEvent = new RushEvent(); + List rushEventList = List.of(rushEvent); + given(rushEventRepository.findAll()).willReturn(rushEventList); + + //when + List allRushEvent = eventCacheService.setAllRushEvent(); + + //then + assertThat(allRushEvent).isNotNull(); + } +} \ No newline at end of file diff --git a/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventServiceTest.java b/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventServiceTest.java new file mode 100644 index 00000000..d38d2b16 --- /dev/null +++ b/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventServiceTest.java @@ -0,0 +1,203 @@ +package JGS.CasperEvent.domain.event.service.eventService; + +import JGS.CasperEvent.domain.event.dto.request.lotteryEventDto.CasperBotRequestDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.CasperBotResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventParticipantResponseDto; +import JGS.CasperEvent.domain.event.dto.response.lottery.LotteryEventResponseDto; +import JGS.CasperEvent.domain.event.entity.casperBot.CasperBot; +import JGS.CasperEvent.domain.event.entity.event.LotteryEvent; +import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; +import JGS.CasperEvent.domain.event.repository.CasperBotRepository; +import JGS.CasperEvent.domain.event.repository.eventRepository.LotteryEventRepository; +import JGS.CasperEvent.domain.event.repository.participantsRepository.LotteryParticipantsRepository; +import JGS.CasperEvent.domain.event.service.redisService.LotteryEventRedisService; +import JGS.CasperEvent.global.entity.BaseUser; +import JGS.CasperEvent.global.enums.CustomErrorCode; +import JGS.CasperEvent.global.enums.Role; +import JGS.CasperEvent.global.error.exception.CustomException; +import JGS.CasperEvent.global.jwt.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import javax.crypto.*; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class LotteryEventServiceTest { + + @Mock + private UserRepository userRepository; + @Mock + private LotteryEventRepository lotteryEventRepository; + @Mock + private LotteryParticipantsRepository lotteryParticipantsRepository; + @Mock + private CasperBotRepository casperBotRepository; + @Mock + private LotteryEventRedisService lotteryEventRedisService; + @Mock + private EventCacheService eventCacheService; + + + @InjectMocks + LotteryEventService lotteryEventService; + + private BaseUser user; + + private LotteryEvent lotteryEvent; + private LotteryParticipants lotteryParticipants; + private CasperBotRequestDto casperBotRequestDto; + private CasperBot casperBot; + + @BeforeEach + void setUp() { + byte[] decodedKey = "I0EM1X1NeXKJv4Q+ifZllg==".getBytes(); + SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); + ReflectionTestUtils.setField(lotteryEventService, "secretKey", secretKey); + + // BaseUser 엔티티 + user = new BaseUser("010-0000-0000", Role.USER); + + // 추첨 이벤트 엔티티 + lotteryEvent = new LotteryEvent( + LocalDateTime.of(2000, 9, 27, 0, 0, 0), + LocalDateTime.of(2100, 9, 27, 0, 0, 0), + 363 + ); + + // 추첨 이벤트 참여자 엔티티 + lotteryParticipants = new LotteryParticipants(user); + + // 캐스퍼 봇 생성 요청 DTO + casperBotRequestDto = CasperBotRequestDto.builder() + .eyeShape(0) + .eyePosition(0) + .mouthShape(0) + .color(0) + .sticker(0) + .name("name") + .expectation("expectation") + .referralId("QEszP1K8IqcapUHAVwikXA==").build(); + + // 캐스퍼 봇 엔티티 + casperBot = new CasperBot(casperBotRequestDto, "010-0000-0000"); + } + + @Test + @DisplayName("캐스퍼 봇 등록 테스트 - 성공") + void postCasperBot_Success() { + //given + given(casperBotRepository.save(casperBot)).willReturn(casperBot); + given(eventCacheService.getLotteryEvent()).willReturn(lotteryEvent); + + //when + CasperBotResponseDto casperBotResponseDto = lotteryEventService.postCasperBot(user, casperBotRequestDto); + + //then + assertThat(casperBotResponseDto.eyeShape()).isZero(); + assertThat(casperBotResponseDto.eyePosition()).isZero(); + assertThat(casperBotResponseDto.mouthShape()).isZero(); + assertThat(casperBotResponseDto.color()).isZero(); + assertThat(casperBotResponseDto.sticker()).isZero(); + assertThat(casperBotResponseDto.name()).isEqualTo("name"); + assertThat(casperBotResponseDto.expectation()).isEqualTo("expectation"); + } + + @Test + @DisplayName("응모 내역 조회 테스트 - 성공") + void getLotteryParticipants_Success() { + //given + given(lotteryParticipantsRepository.findByBaseUser(user)) + .willReturn(Optional.ofNullable(lotteryParticipants)); + //when + LotteryEventParticipantResponseDto lotteryEventParticipantResponseDto = lotteryEventService.getLotteryParticipant(user); + + //then + assertThat(lotteryEventParticipantResponseDto).isNotNull(); + assertThat(lotteryEventParticipantResponseDto.getLinkClickedCount()).isZero(); + assertThat(lotteryEventParticipantResponseDto.getExpectations()).isZero(); + assertThat(lotteryEventParticipantResponseDto.getAppliedCount()).isEqualTo(1); + } + + @Test + @DisplayName("캐스퍼 봇 조회 테스트 - 실패") + void getCasperBotTest_Failure() { + //given + given(casperBotRepository.findById(2L)) + .willThrow(new CustomException("캐스퍼 봇이 없음", CustomErrorCode.CASPERBOT_NOT_FOUND)); + + //when + CustomException exception = assertThrows(CustomException.class, () -> + lotteryEventService.getCasperBot(2L) + ); + + //then + assertEquals(CustomErrorCode.CASPERBOT_NOT_FOUND, exception.getErrorCode()); + assertEquals("캐스퍼 봇이 없음", exception.getMessage()); + } + + @Test + @DisplayName("추첨 이벤트 조회 테스트 - 성공") + void getLotteryEventTest_Success() { + //given + given(eventCacheService.getLotteryEvent()).willReturn(lotteryEvent); + + //when + LotteryEventResponseDto lotteryEventResponseDto = lotteryEventService.getLotteryEvent(); + + //then + assertThat(lotteryEventResponseDto.getServerDateTime()).isNotNull(); + assertThat(lotteryEventResponseDto.getEventStartDate()).isEqualTo("2000-09-27T00:00"); + assertThat(lotteryEventResponseDto.getEventEndDate()).isEqualTo("2100-09-27T00:00"); + assertThat(lotteryEventResponseDto.getActivePeriod()).isEqualTo(36524); + + } + + @Test + @DisplayName("추첨 이벤트 조회 테스트 - 실패 (진행중인 이벤트 없음)") + void getLotteryEventTest_Failure_NoLotteryEvent() { + //given + given(eventCacheService.getLotteryEvent()).willThrow(new CustomException(CustomErrorCode.NO_LOTTERY_EVENT)); + + //when + CustomException exception = assertThrows(CustomException.class, () -> + lotteryEventService.getLotteryEvent() + ); + + //then + assertEquals(CustomErrorCode.NO_LOTTERY_EVENT, exception.getErrorCode()); + assertEquals("추첨 이벤트를 찾을 수 없습니다.", exception.getMessage()); + } + + @Test + @DisplayName("추첨 이벤트 조회 테스트 - 실패(2개 이상의 이벤트 존재)") + void getLotteryEventTest_Failure_TooManyLotteryEvent() { + //given + given(eventCacheService.getLotteryEvent()).willThrow(new CustomException(CustomErrorCode.TOO_MANY_LOTTERY_EVENT)); + + //when + CustomException exception = assertThrows(CustomException.class, () -> + lotteryEventService.getLotteryEvent() + ); + + //then + assertEquals(CustomErrorCode.TOO_MANY_LOTTERY_EVENT, exception.getErrorCode()); + assertEquals("현재 진행중인 추첨 이벤트가 2개 이상입니다.", exception.getMessage()); + + } +} \ No newline at end of file diff --git a/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/RushEventServiceTest.java b/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/RushEventServiceTest.java index 2b60c572..af10cad7 100644 --- a/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/RushEventServiceTest.java +++ b/Server/src/test/java/JGS/CasperEvent/domain/event/service/eventService/RushEventServiceTest.java @@ -1,28 +1,30 @@ package JGS.CasperEvent.domain.event.service.eventService; -import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.*; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventListResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventOptionResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResponseDto; +import JGS.CasperEvent.domain.event.dto.response.rush.RushEventResultResponseDto; import JGS.CasperEvent.domain.event.entity.event.RushEvent; import JGS.CasperEvent.domain.event.entity.event.RushOption; import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; import JGS.CasperEvent.domain.event.repository.eventRepository.RushEventRepository; import JGS.CasperEvent.domain.event.repository.eventRepository.RushOptionRepository; import JGS.CasperEvent.domain.event.repository.participantsRepository.RushParticipantsRepository; +import JGS.CasperEvent.domain.event.service.redisService.RushEventRedisService; import JGS.CasperEvent.global.entity.BaseUser; import JGS.CasperEvent.global.enums.CustomErrorCode; import JGS.CasperEvent.global.enums.Position; import JGS.CasperEvent.global.error.exception.CustomException; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -39,41 +41,42 @@ class RushEventServiceTest { @Mock private RushParticipantsRepository rushParticipantsRepository; @Mock - private RedisTemplate rushEventRedisTemplate; - @Mock private RushOptionRepository rushOptionRepository; - @Mock - private ValueOperations valueOperations; + private EventCacheService eventCacheService; + @Mock + private RushEventRedisService rushEventRedisService; + @InjectMocks RushEventService rushEventService; + private RushEvent rushEvent; + private RushEventResponseDto todayEvent; + + + @BeforeEach + void setUp() { + rushEvent = spy(new RushEvent(LocalDateTime.now(), LocalDateTime.now().plusDays(1), 315, "image-url", "prize-description")); + lenient().doReturn(1L).when(rushEvent).getRushEventId(); + RushOption leftOption = new RushOption(rushEvent, "leftMainText", "leftSubText", "resultMainText", "resultSubText", "leftImageUrl", Position.LEFT); + RushOption rightOption = new RushOption(rushEvent, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT); + + rushEvent.addOption(leftOption, rightOption); + + todayEvent = RushEventResponseDto.of(rushEvent); + } + @Test @DisplayName("모든 RushEvent 조회") void getAllRushEvents() { // given - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); - - List rushEventList = List.of( - new RushEvent(), - new RushEvent() + List rushEventList = List.of( + todayEvent, todayEvent ); - List mainRushEventResponseDtoList = rushEventList.stream() - .map(MainRushEventResponseDto::of).toList(); - - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(valueOperations.get("todayEvent")).willReturn(todayEvent); - given(rushEventRepository.findAll()).willReturn(rushEventList); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(eventCacheService.getAllRushEvent()).willReturn(rushEventList); // when RushEventListResponseDto allRushEvents = rushEventService.getAllRushEvents(); @@ -81,7 +84,7 @@ void getAllRushEvents() { // then assertNotNull(allRushEvents); assertEquals(2, allRushEvents.getEvents().size()); - assertEquals(allRushEvents.getTodayEventId(), 1); + assertEquals(1, allRushEvents.getTodayEventId()); } @Test @@ -89,22 +92,12 @@ void getAllRushEvents() { void isExists() { // given BaseUser user = new BaseUser(); - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - given(rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_Id(1L, user.getId())).willReturn(true); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_PhoneNumber(1L, user.getPhoneNumber())).willReturn(true); // when - boolean exists = rushEventService.isExists(user.getId()); + boolean exists = rushEventService.isExists(user.getPhoneNumber()); // then assertTrue(exists); @@ -115,21 +108,11 @@ void isExists() { void apply() { // given BaseUser user = new BaseUser(); - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - given(rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_Id(1L, user.getId())).willReturn(false); - RushEvent rushEvent = new RushEvent(); - given(rushEventRepository.findById(1L)).willReturn(Optional.of(rushEvent)); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_PhoneNumber(1L, user.getPhoneNumber())).willReturn(false); + RushEvent mockRushEvent = new RushEvent(); + given(rushEventRepository.findById(1L)).willReturn(Optional.of(mockRushEvent)); // when rushEventService.apply(user, 1); @@ -142,20 +125,11 @@ void apply() { @DisplayName("선착순 이벤트 응모 테스트 (이미 응모한 유저인 경우)") void apply2() { // given - BaseUser user = new BaseUser(); - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); + BaseUser user = spy(new BaseUser()); + given(user.getPhoneNumber()).willReturn("010-0000-0000"); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - given(rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_Id(1L, user.getId())).willReturn(true); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_PhoneNumber(1L, "010-0000-0000")).willReturn(true); // when & then CustomException exception = assertThrows(CustomException.class, () -> @@ -172,30 +146,20 @@ void apply2() { void getRushEventRate() { // given BaseUser user = new BaseUser(); - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - given(rushParticipantsRepository.getOptionIdByUserId(user.getId())).willReturn(Optional.of(1)); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.getOptionIdByUserId(user.getPhoneNumber())).willReturn(Optional.of(1)); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)).willReturn(100L); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)).willReturn(200L); // when - RushEventRateResponseDto result = rushEventService.getRushEventRate(user); + RushEventResultResponseDto result = rushEventService.getRushEventRate(user); // then assertNotNull(result); - assertEquals(1, result.optionId()); - assertEquals(100, result.leftOption()); - assertEquals(200, result.rightOption()); + assertEquals(1, result.getOptionId()); + assertEquals(100, result.getLeftOption()); + assertEquals(200, result.getRightOption()); } @Test @@ -203,33 +167,24 @@ void getRushEventRate() { void getRushEventResult() { // given BaseUser user = new BaseUser(); - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - given(rushParticipantsRepository.getOptionIdByUserId(user.getId())).willReturn(Optional.of(1)); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.getOptionIdByUserId(user.getPhoneNumber())).willReturn(Optional.of(1)); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)).willReturn(700L); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)).willReturn(500L); - given(rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(1L, user.getId(), 1)).willReturn(300L); + given(rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(1L, user.getPhoneNumber(), 1)).willReturn(300L); // when RushEventResultResponseDto result = rushEventService.getRushEventResult(user); - + // then assertNotNull(result); + assertEquals(1, result.getOptionId()); assertEquals(700, result.getLeftOption()); assertEquals(500, result.getRightOption()); assertEquals(700, result.getTotalParticipants()); assertEquals(300, result.getRank()); - assertTrue(result.isWinner()); + assertTrue(result.getIsWinner()); } @Test @@ -237,33 +192,24 @@ void getRushEventResult() { void getRushEventResult2() { // given BaseUser user = new BaseUser(); - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - given(rushParticipantsRepository.getOptionIdByUserId(user.getId())).willReturn(Optional.of(2)); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.getOptionIdByUserId(user.getPhoneNumber())).willReturn(Optional.of(2)); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)).willReturn(700L); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)).willReturn(500L); - given(rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(1L, user.getId(), 2)).willReturn(300L); + given(rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(1L, user.getPhoneNumber(), 2)).willReturn(300L); // when RushEventResultResponseDto result = rushEventService.getRushEventResult(user); // then assertNotNull(result); + assertEquals(2, result.getOptionId()); assertEquals(700, result.getLeftOption()); assertEquals(500, result.getRightOption()); assertEquals(500, result.getTotalParticipants()); assertEquals(300, result.getRank()); - assertFalse(result.isWinner()); + assertFalse(result.getIsWinner()); } @Test @@ -271,33 +217,24 @@ void getRushEventResult2() { void getRushEventResult3() { // given BaseUser user = new BaseUser(); - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - given(rushParticipantsRepository.getOptionIdByUserId(user.getId())).willReturn(Optional.of(1)); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.getOptionIdByUserId(user.getPhoneNumber())).willReturn(Optional.of(1)); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)).willReturn(700L); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)).willReturn(500L); - given(rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(1L, user.getId(), 1)).willReturn(400L); + given(rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(1L, user.getPhoneNumber(), 1)).willReturn(400L); // when RushEventResultResponseDto result = rushEventService.getRushEventResult(user); // then assertNotNull(result); + assertEquals(1, result.getOptionId()); assertEquals(700, result.getLeftOption()); assertEquals(500, result.getRightOption()); assertEquals(700, result.getTotalParticipants()); assertEquals(400, result.getRank()); - assertFalse(result.isWinner()); + assertFalse(result.getIsWinner()); } @Test @@ -305,32 +242,23 @@ void getRushEventResult3() { void getRushEventResult4() { // given BaseUser user = new BaseUser(); - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - given(rushParticipantsRepository.getOptionIdByUserId(user.getId())).willReturn(Optional.of(1)); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.getOptionIdByUserId(user.getPhoneNumber())).willReturn(Optional.of(1)); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)).willReturn(500L); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)).willReturn(500L); - given(rushParticipantsRepository.findUserRankByEventIdAndUserId(1L, user.getId())).willReturn(300L); + given(rushParticipantsRepository.findUserRankByEventIdAndUserId(1L, user.getPhoneNumber())).willReturn(300L); // when RushEventResultResponseDto result = rushEventService.getRushEventResult(user); // then assertNotNull(result); + assertEquals(1, result.getOptionId()); assertEquals(500, result.getLeftOption()); assertEquals(500, result.getRightOption()); assertEquals(1000, result.getTotalParticipants()); assertEquals(300, result.getRank()); - assertTrue(result.isWinner()); + assertTrue(result.getIsWinner()); } @Test @@ -338,131 +266,89 @@ void getRushEventResult4() { void getRushEventResult5() { // given BaseUser user = new BaseUser(); - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - new HashSet<>() - ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - given(rushParticipantsRepository.getOptionIdByUserId(user.getId())).willReturn(Optional.of(1)); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.getOptionIdByUserId(user.getPhoneNumber())).willReturn(Optional.of(1)); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)).willReturn(500L); given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)).willReturn(500L); - given(rushParticipantsRepository.findUserRankByEventIdAndUserId(1L, user.getId())).willReturn(400L); + given(rushParticipantsRepository.findUserRankByEventIdAndUserId(1L, user.getPhoneNumber())).willReturn(400L); // when RushEventResultResponseDto result = rushEventService.getRushEventResult(user); // then assertNotNull(result); + assertEquals(1, result.getOptionId()); assertEquals(500, result.getLeftOption()); assertEquals(500, result.getRightOption()); assertEquals(1000, result.getTotalParticipants()); assertEquals(400, result.getRank()); - assertFalse(result.isWinner()); + assertFalse(result.getIsWinner()); } @Test - @DisplayName("오늘의 선착순 이벤트 DB에서 가져오기 테스트") - void getTodayRushEvent() { + @DisplayName("선착순 이벤트 결과 조회 테스트 (응모하지 않았는데 결과 조회창으로 넘어가서 호출되는 경우)") + void getRushEventResult6() { // given - LocalDate today = LocalDate.now(); - RushEvent rushEvent = new RushEvent(); - given(rushEventRepository.findByEventDate(today)).willReturn(List.of(rushEvent)); - - // when - RushEventResponseDto result = rushEventService.getTodayRushEvent(today); - - // then - assertNotNull(result); - assertEquals(rushEvent.getRushEventId(), result.rushEventId()); - } - - @Test - @DisplayName("오늘의 선착순 이벤트 DB에서 가져오기 테스트 (Redis에 선착순 이벤트가 없는 경우)") - void getTodayRushEvent2() { - // given - LocalDate today = LocalDate.now(); - given(rushEventRepository.findByEventDate(today)).willReturn(List.of()); - - // when & then - CustomException exception = assertThrows(CustomException.class, () -> - rushEventService.getTodayRushEvent(today) - ); - - assertEquals(CustomErrorCode.NO_RUSH_EVENT, exception.getErrorCode()); - assertEquals("선착순 이벤트가 존재하지않습니다.", exception.getMessage()); - } + BaseUser user = new BaseUser(); - @Test - @DisplayName("오늘의 선착순 이벤트 DB에서 가져오기 테스트 (Redis에 선착순 이벤트가 2개 이상인 경우)") - void getTodayRushEvent3() { - // given - LocalDate today = LocalDate.now(); - RushEvent rushEvent1 = new RushEvent(); - RushEvent rushEvent2 = new RushEvent(); - given(rushEventRepository.findByEventDate(today)).willReturn(List.of( - rushEvent1, rushEvent2 - )); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); + given(rushParticipantsRepository.getOptionIdByUserId(user.getPhoneNumber())).willReturn(Optional.empty()); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 1)).willReturn(500L); + given(rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(1L, 2)).willReturn(500L); // when & then - CustomException exception = assertThrows(CustomException.class, () -> - rushEventService.getTodayRushEvent(today) - ); + RushEventResultResponseDto result = rushEventService.getRushEventResult(user); - assertEquals(CustomErrorCode.MULTIPLE_RUSH_EVENTS_FOUND, exception.getErrorCode()); - assertEquals("선착순 이벤트가 2개 이상 존재합니다.", exception.getMessage()); + // then + assertNotNull(result); + assertNull(result.getOptionId()); + assertEquals(500, result.getLeftOption()); + assertEquals(500, result.getRightOption()); + assertNull(result.getTotalParticipants()); + assertNull(result.getRank()); + assertNull(result.getIsWinner()); } @Test + @DisplayName("선착순 이벤트 테스트 API 테스트") void setTodayEventToRedis() { // given - RushEvent rushEvent = new RushEvent(); + RushEvent mockRushEvent = new RushEvent(); RushOption rushOption = new RushOption(); - given(rushEventRepository.save(any(RushEvent.class))).willReturn(rushEvent); + given(rushEventRepository.save(any(RushEvent.class))).willReturn(mockRushEvent); given(rushOptionRepository.save(any(RushOption.class))).willReturn(rushOption); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); // when - rushEventService.setTodayEventToRedis(); + rushEventService.setRushEvents(); // then verify(rushParticipantsRepository).deleteAllInBatch(); verify(rushOptionRepository).deleteAllInBatch(); verify(rushEventRepository).deleteAllInBatch(); - verify(rushEventRedisTemplate.opsForValue()).set(eq("todayEvent"), any(RushEventResponseDto.class)); } @Test + @DisplayName("오늘의 선착순 이벤트의 선택지 조회 테스트") void getTodayRushEventOptions() { // given - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - Set.of( - new RushEventOptionResponseDto(1L, "leftMainText", "leftSubText", "resultMainText", "resultSubText", "leftImageUrl", Position.LEFT, LocalDateTime.now(), LocalDateTime.now()), - new RushEventOptionResponseDto(2L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()) - ) - ); + RushEvent spyRushEvent = spy(new RushEvent(LocalDateTime.now(), LocalDateTime.now().plusDays(1), 315, "image-url", "prize-description")); + given(spyRushEvent.getRushEventId()).willReturn(1L); + RushOption leftOption = new RushOption(spyRushEvent, "leftMainText", "leftSubText", "resultMainText", "resultSubText", "leftImageUrl", Position.LEFT); + RushOption rightOption = new RushOption(spyRushEvent, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT); + + spyRushEvent.addOption(leftOption, rightOption); + + RushEventResponseDto todayRushEvent = RushEventResponseDto.of(spyRushEvent); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayRushEvent); // when - MainRushEventOptionsResponseDto result = rushEventService.getTodayRushEventOptions(); + RushEventResponseDto result = rushEventService.getTodayRushEventOptions(); // then assertNotNull(result); - assertEquals("leftMainText", result.leftOption().mainText()); - assertEquals("rightMainText", result.rightOption().mainText()); + assertEquals("leftMainText", result.getLeftOption().getMainText()); + assertEquals("rightMainText", result.getRightOption().getMainText()); } @Test @@ -470,28 +356,13 @@ void getTodayRushEventOptions() { void getRushEventOptionResult() { // given int optionId = 1; - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - Set.of( - new RushEventOptionResponseDto(1L, "leftMainText", "leftSubText", "resultMainText", "resultSubText", "leftImageUrl", Position.LEFT, LocalDateTime.now(), LocalDateTime.now()), - new RushEventOptionResponseDto(2L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()) - ) - ); - - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayEvent); // when - ResultRushEventOptionResponseDto result = rushEventService.getRushEventOptionResult(optionId); + RushEventOptionResponseDto result = rushEventService.getRushEventOptionResult(optionId); // then assertNotNull(result); - assertEquals("leftMainText", result.mainText()); + assertEquals("leftMainText", result.getMainText()); } @Test @@ -499,7 +370,8 @@ void getRushEventOptionResult() { void getRushEventOptionResult2() { // given int optionId = 1; - RushEventResponseDto todayEvent = new RushEventResponseDto( + + RushEventResponseDto todayRushEvent = RushEventResponseDto.of( 1L, LocalDateTime.now(), LocalDateTime.now().plusDays(1), @@ -507,18 +379,17 @@ void getRushEventOptionResult2() { "image-url", "prize-description", Set.of( - new RushEventOptionResponseDto(1L, "leftMainText", "leftSubText", "resultMainText", "resultSubText", "leftImageUrl", Position.LEFT, LocalDateTime.now(), LocalDateTime.now()), - new RushEventOptionResponseDto(2L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()), - new RushEventOptionResponseDto(3L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()) + RushEventOptionResponseDto.of(1L, "leftMainText", "leftSubText", "resultMainText", "resultSubText", "leftImageUrl", Position.LEFT, LocalDateTime.now(), LocalDateTime.now()), + RushEventOptionResponseDto.of(2L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()), + RushEventOptionResponseDto.of(3L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()) ) ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayRushEvent); // when & then CustomException exception = assertThrows(CustomException.class, () -> - rushEventService.getRushEventOptionResult(optionId) + rushEventService.getRushEventOptionResult(optionId) ); assertEquals(CustomErrorCode.INVALID_RUSH_EVENT_OPTIONS_COUNT, exception.getErrorCode()); @@ -530,18 +401,6 @@ void getRushEventOptionResult2() { void getRushEventOptionResult3() { // given int optionId = 3; - RushEventResponseDto todayEvent = new RushEventResponseDto( - 1L, - LocalDateTime.now(), - LocalDateTime.now().plusDays(1), - 315, - "image-url", - "prize-description", - Set.of( - new RushEventOptionResponseDto(1L, "leftMainText", "leftSubText", "resultMainText", "resultSubText", "leftImageUrl", Position.LEFT, LocalDateTime.now(), LocalDateTime.now()), - new RushEventOptionResponseDto(2L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()) - ) - ); // when & then CustomException exception = assertThrows(CustomException.class, () -> @@ -557,7 +416,8 @@ void getRushEventOptionResult3() { void getRushEventOptionResult4() { // given int optionId = 1; - RushEventResponseDto todayEvent = new RushEventResponseDto( + + RushEventResponseDto todayRushEvent = RushEventResponseDto.of( 1L, LocalDateTime.now(), LocalDateTime.now().plusDays(1), @@ -565,14 +425,12 @@ void getRushEventOptionResult4() { "image-url", "prize-description", Set.of( - new RushEventOptionResponseDto(2L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()), - new RushEventOptionResponseDto(3L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()) + RushEventOptionResponseDto.of(2L, "leftMainText", "leftSubText", "resultMainText", "resultSubText", "leftImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()), + RushEventOptionResponseDto.of(3L, "rightMainText", "rightSubText", "resultMainText", "resultSubText", "rightImageUrl", Position.RIGHT, LocalDateTime.now(), LocalDateTime.now()) ) ); - given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations); - given(rushEventRedisTemplate.opsForValue().get("todayEvent")).willReturn(todayEvent); - + given(eventCacheService.getTodayEvent(LocalDate.now())).willReturn(todayRushEvent); // when & then CustomException exception = assertThrows(CustomException.class, () -> rushEventService.getRushEventOptionResult(optionId) diff --git a/Server/src/test/java/JGS/CasperEvent/domain/url/controller/UrlControllerTest.java b/Server/src/test/java/JGS/CasperEvent/domain/url/controller/UrlControllerTest.java new file mode 100644 index 00000000..76eb3a53 --- /dev/null +++ b/Server/src/test/java/JGS/CasperEvent/domain/url/controller/UrlControllerTest.java @@ -0,0 +1,121 @@ +package JGS.CasperEvent.domain.url.controller; + +import JGS.CasperEvent.domain.event.service.adminService.AdminService; +import JGS.CasperEvent.domain.url.dto.ShortenUrlResponseDto; +import JGS.CasperEvent.domain.url.service.UrlService; +import JGS.CasperEvent.global.entity.BaseUser; +import JGS.CasperEvent.global.enums.Role; +import JGS.CasperEvent.global.jwt.service.UserService; +import JGS.CasperEvent.global.jwt.util.JwtProvider; +import io.jsonwebtoken.security.Keys; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(value = UrlController.class) +class UrlControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockBean + private UrlService urlService; + @MockBean + private UserService userService; + @MockBean + private AdminService adminService; + + private BaseUser user; + private String phoneNumber; + private String accessToken; + + @BeforeEach + void setUp() throws Exception { + phoneNumber = "010-0000-0000"; + user = new BaseUser(phoneNumber, Role.USER); + given(userService.verifyUser(any())).willReturn(user); + accessToken = getToken(phoneNumber); + } + + @TestConfiguration + static class TestConfig{ + @Bean + public JwtProvider jwtProvider(){ + String secretKey = "mockKEymockKEymockKEymockKEymockKEymockKEymockKEy"; + byte[] secret = secretKey.getBytes(); + return new JwtProvider(Keys.hmacShaKeyFor(secret)); + } + } + + @Test + @DisplayName("공유 링크 생성 테스트 - 성공") + void generateShortUrlTest_Success() throws Exception { + //given + ShortenUrlResponseDto shortenUrlResponseDto = new ShortenUrlResponseDto("shortenUrl1", "shortenUrl2"); + given(urlService.generateShortUrl(user)).willReturn(shortenUrlResponseDto); + + //when + ResultActions perform = mockMvc.perform(post("/link") + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isCreated()) + .andExpect(jsonPath("$.shortenUrl").value("shortenUrl1")) + .andExpect(jsonPath("$.shortenLocalUrl").value("shortenUrl2")) + .andDo(print()); + } + + @Test + @DisplayName("공유 링크 접속 테스트 - 성공") + void redirectOriginalUrl_Success() throws Exception { + //given + String encodedId = "encodedId"; + given(urlService.getOriginalUrl(encodedId)) + .willReturn(null); + + //when + ResultActions perform = mockMvc.perform(get("/link/" + encodedId) + .header("Authorization", accessToken) + .contentType(APPLICATION_JSON)); + + //then + perform.andExpect(status().isFound()) + .andDo(print()); + } + + String getToken(String phoneNumber) throws Exception { + String requestBody = String.format(""" + { + "phoneNumber": "%s" + } + """, phoneNumber); + + ResultActions perform = mockMvc.perform(post("/event/auth") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)); + + String jsonString = perform.andReturn().getResponse().getContentAsString(); + String tokenPrefix = "\"accessToken\":\""; + int start = jsonString.indexOf(tokenPrefix) + tokenPrefix.length(); + int end = jsonString.indexOf("\"", start); + + return "Bearer " + jsonString.substring(start, end); + } +} \ No newline at end of file diff --git a/Server/src/test/java/JGS/CasperEvent/domain/url/service/UrlServiceTest.java b/Server/src/test/java/JGS/CasperEvent/domain/url/service/UrlServiceTest.java new file mode 100644 index 00000000..336f92f5 --- /dev/null +++ b/Server/src/test/java/JGS/CasperEvent/domain/url/service/UrlServiceTest.java @@ -0,0 +1,101 @@ +package JGS.CasperEvent.domain.url.service; + +import JGS.CasperEvent.domain.url.dto.ShortenUrlResponseDto; +import JGS.CasperEvent.domain.url.entity.Url; +import JGS.CasperEvent.domain.url.repository.UrlRepository; +import JGS.CasperEvent.global.entity.BaseUser; +import JGS.CasperEvent.global.enums.Role; +import JGS.CasperEvent.global.jwt.util.JwtProvider; +import JGS.CasperEvent.global.util.AESUtils; +import io.jsonwebtoken.security.Keys; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.util.ReflectionTestUtils; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.spy; + +@ExtendWith(MockitoExtension.class) +class UrlServiceTest { + @Mock + private UrlRepository urlRepository; + + private BaseUser user; + private SecretKey secretKey; + + @InjectMocks + UrlService urlService; + + @BeforeEach + void setUp(){ + user = new BaseUser("010-0000-0000", Role.USER); + + byte[] decodedKey = "I0EM1X1NeXKJv4Q+ifZllg==".getBytes(); + secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); + ReflectionTestUtils.setField(urlService, "secretKey", secretKey); + ReflectionTestUtils.setField(urlService, "shortenBaseUrl", "baseUrl"); + + } + + @TestConfiguration + static class TestConfig{ + @Bean + public JwtProvider jwtProvider(){ + String secretKey = "mockKEymockKEymockKEymockKEymockKEymockKEymockKEy"; + byte[] secret = secretKey.getBytes(); + return new JwtProvider(Keys.hmacShaKeyFor(secret)); + } + } + + @Test + @DisplayName("단축 url 생성 테스트 - 성공") + void generateShortUrlTest_Success() throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + //given + Url originalUrl = spy(new Url(AESUtils.encrypt(user.getPhoneNumber(), secretKey))); + + given(urlRepository.save(any())).willReturn(originalUrl); + given(originalUrl.getId()).willReturn(1L); + + //when + ShortenUrlResponseDto shortenUrlResponseDto = urlService.generateShortUrl(user); + + //then + assertThat(shortenUrlResponseDto.shortenUrl()).isEqualTo("baseUrl/link/B"); + assertThat(shortenUrlResponseDto.shortenLocalUrl()).isEqualTo("baseUrl/link/B"); + } + + @Test + @DisplayName("원본 url 조회 테스트 - 성공") + void getOriginalUrlTest_Success() throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + //given + Url url = new Url(AESUtils.encrypt(user.getPhoneNumber(), secretKey)); + given(urlRepository.findById(any())).willReturn(Optional.of(url)); + + //when + String originalUrl = urlService.getOriginalUrl("B"); + + //then + assertThat(originalUrl).isEqualTo("GH379iuBZdNRV9uGEk1KWg=="); + + } + +} \ No newline at end of file diff --git a/Server/src/test/java/JGS/CasperEvent/global/jwt/service/UserServiceTest.java b/Server/src/test/java/JGS/CasperEvent/global/jwt/service/UserServiceTest.java new file mode 100644 index 00000000..76ab1390 --- /dev/null +++ b/Server/src/test/java/JGS/CasperEvent/global/jwt/service/UserServiceTest.java @@ -0,0 +1,60 @@ +package JGS.CasperEvent.global.jwt.service; + +import JGS.CasperEvent.global.entity.BaseUser; +import JGS.CasperEvent.global.enums.Role; +import JGS.CasperEvent.global.jwt.dto.UserLoginDto; +import JGS.CasperEvent.global.jwt.repository.UserRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class UserServiceTest { + @Mock + private UserRepository userRepository; + + @InjectMocks + UserService userService; + + @Test + @DisplayName("유저 식별 테스트 - 성공 (가입한 유저)") + void verifyUserTest_Success() { + //given + UserLoginDto userLoginDto = new UserLoginDto("010-0000-0000"); + BaseUser user = new BaseUser("010-0000-0000", Role.USER); + given(userRepository.findByPhoneNumber("010-0000-0000")).willReturn(Optional.of(user)); + + //when + BaseUser verifiedUser = userService.verifyUser(userLoginDto); + + //then + assertThat(verifiedUser.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(verifiedUser.getRole()).isEqualTo(Role.USER); + } + + @Test + @DisplayName("유저 식별 테스트 - 성공 (가입하지 않은 유저)") + void testName() { + //given + UserLoginDto userLoginDto = new UserLoginDto("010-0000-0000"); + BaseUser user = new BaseUser("010-0000-0000", Role.USER); + given(userRepository.findByPhoneNumber("010-0000-0000")).willReturn(Optional.empty()); + given(userRepository.save(user)).willReturn(user); + + //when + BaseUser verifiedUser = userService.verifyUser(userLoginDto); + + //then + assertThat(verifiedUser.getPhoneNumber()).isEqualTo("010-0000-0000"); + assertThat(verifiedUser.getRole()).isEqualTo(Role.USER); + } + +} \ No newline at end of file diff --git a/Server/src/test/java/JGS/CasperEvent/global/service/S3ServiceTest.java b/Server/src/test/java/JGS/CasperEvent/global/service/S3ServiceTest.java new file mode 100644 index 00000000..4164e1bb --- /dev/null +++ b/Server/src/test/java/JGS/CasperEvent/global/service/S3ServiceTest.java @@ -0,0 +1,195 @@ +package JGS.CasperEvent.global.service; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.AmazonS3Exception; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; + +@ExtendWith(MockitoExtension.class) +class S3ServiceTest { + @Mock + private AmazonS3 amazonS3; + + private String bucketName; + private MockMultipartFile image; + + @InjectMocks + S3Service s3Service; + + @BeforeEach + void setUp() { + bucketName = "s3Bucket"; + ReflectionTestUtils.setField(s3Service, "bucketName", bucketName); + } + + @Test + @DisplayName("이미지 업로드 테스트 - 성공") + void uploadTest_Success() throws MalformedURLException { + //given + URL url = new URL("http", "www.example.com", "/image.jpg"); + image = new MockMultipartFile("image", "image.png", "png", "<>".getBytes()); + given(amazonS3.getUrl(eq(bucketName), anyString())) + .willReturn(url); + //when + String imageUrl = s3Service.upload(image); + + //then + assertThat(imageUrl).isEqualTo("http://www.example.com/image.jpg"); + } + + @Test + @DisplayName("이미지 업로드 테스트 - 실패 (이미지 비어있음)") + void uploadTest_Failure_ImageEmpty() { + //given + image = new MockMultipartFile("image", "image.png", "png", new byte[0]); + + //when + AmazonS3Exception amazonS3Exception = assertThrows(AmazonS3Exception.class, () -> + s3Service.upload(image) + ); + + //then + assertThat("파일이 유효하지 않습니다.").isEqualTo(amazonS3Exception.getErrorMessage()); + } + + @Test + @DisplayName("이미지 업로드 테스트 - 실패 (원본 파일 확장자 없음)") + void uploadTest_Failure_ImageExtensionEmpty() { + //given + image = new MockMultipartFile("image", "image", "png", "<>".getBytes()); + + //when + AmazonS3Exception amazonS3Exception = assertThrows(AmazonS3Exception.class, () -> + s3Service.upload(image) + ); + + //then + assertThat("파일에 확장자가 존재하지 않습니다.").isEqualTo(amazonS3Exception.getErrorMessage()); + } + + @Test + @DisplayName("이미지 업로드 테스트 - 실패 (지원하지 않는 확장자)") + void uploadTest_Failure_ImageNameEmpty() { + //given + image = new MockMultipartFile("image", "image.html", "png", "<>".getBytes()); + + //when + AmazonS3Exception amazonS3Exception = assertThrows(AmazonS3Exception.class, () -> + s3Service.upload(image) + ); + + //then + assertThat("유효하지 않은 확장자입니다.").isEqualTo(amazonS3Exception.getErrorMessage()); + } + + @Test + @DisplayName("이미지 업로드 테스트 - 실패 (IOException 발생)") + void uploadTest_Failure_IOException() throws IOException { + //given + image = spy(new MockMultipartFile("image", "image.png", "png", "<>".getBytes())); + doThrow(new IOException()) + .when(image).getInputStream(); + //when + AmazonS3Exception amazonS3Exception = assertThrows(AmazonS3Exception.class, () -> + s3Service.upload(image) + ); + + //then + assertThat("io exception on image upload").isEqualTo(amazonS3Exception.getErrorMessage()); + } + + @Test + @DisplayName("이미지 업로드 테스트 - 실패 (버킷 업로드 실패)") + void uploadTest_Failure_BucketException() { + //given + image = new MockMultipartFile("image", "image.png", "png", "<>".getBytes()); + doThrow(new RuntimeException()).when(amazonS3).putObject(any()); + + //when + AmazonS3Exception amazonS3Exception = assertThrows(AmazonS3Exception.class, () -> + s3Service.upload(image) + ); + + //then + assertThat("이미지 업로드에 실패했습니다.").isEqualTo(amazonS3Exception.getErrorMessage()); + } + + @Test + @DisplayName("이미지 삭제 테스트 - 성공") + void deleteImageFromS3Test_Success() { + //when + s3Service.deleteImageFromS3("http://www.example.com/image.jpg"); + } + + @Test + @DisplayName("이미지 삭제 테스트 - 실패 (삭제 실패)") + void deleteImageFromS3Test_Failure_Exception() { + // given + doThrow(new RuntimeException()).when(amazonS3).deleteObject(any()); + + //when + AmazonS3Exception amazonS3Exception = assertThrows(AmazonS3Exception.class, () -> + s3Service.deleteImageFromS3("http://www.example.com/image.jpg") + ); + + //then + assertThat("이미지 삭제에 실패했습니다.").isEqualTo(amazonS3Exception.getErrorMessage()); + } + + @Test + @DisplayName("이미지 삭제 테스트 - 실패 (URL 오류)") + void deleteImageFromS3Test_Failure_WrongUrl() { + // given + String url = "www.example.com/image.jpg"; + + //when + AmazonS3Exception amazonS3Exception = assertThrows(AmazonS3Exception.class, () -> + s3Service.deleteImageFromS3(url) + ); + + //then + assertThat("이미지 삭제에 실패했습니다.").isEqualTo(amazonS3Exception.getErrorMessage()); + } + + @Test + @DisplayName("이미지 삭제 테스트 - 실패 (url 인코딩 실패)") + void deleteImageFromS3Test_Failure_UrlEncodingException() { + // given + String url = "https://www.example.com/image.jpg"; + + try (MockedStatic mockedUrlDecoder = Mockito.mockStatic(URLDecoder.class)) { + mockedUrlDecoder.when(() -> URLDecoder.decode(Mockito.anyString(), Mockito.anyString())) + .thenThrow(new UnsupportedEncodingException("Unsupported encoding")); + + // when + AmazonS3Exception amazonS3Exception = assertThrows(AmazonS3Exception.class, () -> + s3Service.deleteImageFromS3(url) + ); + + // then + assertThat(amazonS3Exception.getErrorMessage()).isEqualTo("이미지 삭제에 실패했습니다."); + } + } +} \ No newline at end of file