Skip to content

Commit

Permalink
Merge pull request #134 from softeerbootcamp4th/feature/131-admin-eve…
Browse files Browse the repository at this point in the history
…nt-delete

[feat] 이벤트 삭제 기능 구현(#131)
  • Loading branch information
blaxsior authored Aug 22, 2024
2 parents ab11004 + d52fb0b commit 7dd36ee
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public ResponseEntity<BriefEventPageDto> getEvents(
@PostMapping
@Operation(summary = "이벤트 생성", description = "관리자가 이벤트를 새롭게 등록한다", responses = {
@ApiResponse(responseCode = "201", description = "이벤트 생성 성공"),
@ApiResponse(responseCode = "4xx", description = "유저 측 실수로 이벤트 생성 실패")
@ApiResponse(responseCode = "4xx", description = "유저 측 실수로 이벤트 생성 실패",content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
public ResponseEntity<Void> createEvent(@Validated @RequestBody EventDto eventDto,
@Parameter(hidden = true) @AdminAnnotation AdminDto admin
Expand All @@ -80,24 +80,42 @@ public ResponseEntity<Void> createEvent(@Validated @RequestBody EventDto eventDt
* @return 해당 이벤트에 대한 정보
*/
@GetMapping("{eventId}")
@Operation(summary = "이벤트 데이터 획득", description = "이벤트 초기 정보를 받는다. 상세 정보, 이벤트 수정 모두에서 사용 가능", responses = {
@Operation(summary = "이벤트 획득", description = "이벤트 상세 정보를 받는다. 상세 정보, 이벤트 수정 모두에서 사용 가능", responses = {
@ApiResponse(responseCode = "200", description = "이벤트 정보를 정상적으로 받음"),
@ApiResponse(responseCode = "404", description = "대응되는 이벤트가 존재하지 않음")
@ApiResponse(responseCode = "404", description = "대응되는 이벤트가 존재하지 않음",content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
public ResponseEntity<EventDto> getEventData(
public ResponseEntity<EventDto> getEvent(
@PathVariable("eventId") String eventId
) {
EventDto eventInfo = eventService.getEventInfo(eventId);
return ResponseEntity.ok(eventInfo);
}

/**
*
* @param eventId 이벤트 ID. HD000000~로 시작하는 그것
* @return 해당 이벤트에 대한 정보
*/
@DeleteMapping("{eventId}")
@Operation(summary = "이벤트 제거", description = "이벤트를 제거한다. 시작 전의 이벤트만 삭제할 수 있다.", responses = {
@ApiResponse(responseCode = "200", description = "이벤트를 정상적으로 삭제"),
@ApiResponse(responseCode = "404", description = "이벤트를 찾을 수 없음",content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "400", description = "이벤트가 진행 중이거나 종료되어 삭제할 수 없음",content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
public ResponseEntity<Void> deleteEvent(
@PathVariable("eventId") String eventId
) {
eventService.deleteEvent(eventId);
return ResponseEntity.ok().build();
}

/**
* @param eventDto 수정된 이벤트 정보
*/
@PostMapping("/edit")
@Operation(summary = "이벤트 수정", description = "관리자가 이벤트를 수정한다", responses = {
@ApiResponse(responseCode = "200", description = "이벤트 생성 성공"),
@ApiResponse(responseCode = "4xx", description = "유저 측 실수로 이벤트 생성 실패")
@ApiResponse(responseCode = "4xx", description = "유저 측 실수로 이벤트 생성 실패",content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
public ResponseEntity<Void> editEvent(
@Validated({EventEditGroup.class}) @RequestBody EventDto eventDto) {
Expand All @@ -111,7 +129,7 @@ public ResponseEntity<Void> editEvent(
@PostMapping("/frame")
@Operation(summary = "이벤트 프레임 생성", description = "관리자가 이벤트 프레임을 새롭게 등록한다", responses = {
@ApiResponse(responseCode = "201", description = "이벤트 프레임 생성 성공"),
@ApiResponse(responseCode = "4xx", description = "이벤트 프레임 생성 실패")
@ApiResponse(responseCode = "4xx", description = "이벤트 프레임 생성 실패" ,content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
public ResponseEntity<Void> createEventFrame(@Valid @RequestBody EventFrameCreateRequest req) {
eventService.createEventFrame(req.getFrameId(), req.getName());
Expand Down
80 changes: 41 additions & 39 deletions src/main/java/hyundai/softeer/orange/common/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,60 @@
@Getter
public enum ErrorCode {
// 400 Bad Request
BAD_REQUEST(HttpStatus.BAD_REQUEST, false, "잘못된 요청입니다."),
INVALID_COMMENT(HttpStatus.BAD_REQUEST, false, "부정적인 표현을 사용하였습니다."),
INVALID_JSON(HttpStatus.BAD_REQUEST, false, "잘못된 JSON 형식입니다."),
INVALID_URL(HttpStatus.BAD_REQUEST, false, "유효하지 않은 URL입니다."),
INVALID_INPUT_EVENT_TIME(HttpStatus.BAD_REQUEST, false, "입력된 시간 중 일부가 조건에 맞지 않습니다."),
INVALID_EVENT_TIME(HttpStatus.BAD_REQUEST, false, "이벤트 시간이 아닙니다."),
INVALID_EVENT_TYPE(HttpStatus.BAD_REQUEST, false, "이벤트 타입이 지원되지 않습니다."),
EVENT_NOT_ENDED(HttpStatus.BAD_REQUEST, false, "이벤트가 아직 종료되지 않았습니다."),
EVENT_IS_DRAWING(HttpStatus.BAD_REQUEST, false, "현재 추첨이 진행되고 있는 이벤트입니다."),
DUPLICATED_POLICIES(HttpStatus.BAD_REQUEST,false,"정책에서 중복된 액션이 존재합니다."),
DUPLICATED_GRADES(HttpStatus.BAD_REQUEST,false,"추첨 이벤트 정보에 중복된 등수가 존재합니다."),
CANNOT_PARTICIPATE(HttpStatus.BAD_REQUEST,false,"현재 유저가 참여할 수 없는 이벤트입니다."),
BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."),
INVALID_COMMENT(HttpStatus.BAD_REQUEST, "부정적인 표현을 사용하였습니다."),
INVALID_JSON(HttpStatus.BAD_REQUEST, "잘못된 JSON 형식입니다."),
INVALID_URL(HttpStatus.BAD_REQUEST, "유효하지 않은 URL입니다."),
INVALID_INPUT_EVENT_TIME(HttpStatus.BAD_REQUEST, "입력된 시간 중 일부가 조건에 맞지 않습니다."),
INVALID_EVENT_TIME(HttpStatus.BAD_REQUEST, "이벤트 시간이 아닙니다."),
INVALID_EVENT_TYPE(HttpStatus.BAD_REQUEST, "이벤트 타입이 지원되지 않습니다."),
EVENT_NOT_ENDED(HttpStatus.BAD_REQUEST, "이벤트가 아직 종료되지 않았습니다."),
EVENT_IS_DRAWING(HttpStatus.BAD_REQUEST, "현재 추첨이 진행되고 있는 이벤트입니다."),
DUPLICATED_POLICIES(HttpStatus.BAD_REQUEST,"정책에서 중복된 액션이 존재합니다."),
DUPLICATED_GRADES(HttpStatus.BAD_REQUEST,"추첨 이벤트 정보에 중복된 등수가 존재합니다."),
CANNOT_PARTICIPATE(HttpStatus.BAD_REQUEST,"현재 유저가 참여할 수 없는 이벤트입니다."),
CANNOT_DELETE_EVENT_RUNNING(HttpStatus.BAD_REQUEST,"이벤트가 진행 중이므로 삭제할 수 없습니다"),
CANNOT_DELETE_EVENT_ENDED(HttpStatus.BAD_REQUEST,"이벤트가 종료되어 삭제할 수 없습니다"),


// 401 Unauthorized
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, false, "인증되지 않은 사용자입니다."),
AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED, false, "아이디 또는 비밀번호가 일치하지 않습니다"),
INVALID_AUTH_CODE(HttpStatus.UNAUTHORIZED, false, "인증번호가 일치하지 않습니다."),
SESSION_EXPIRED(HttpStatus.UNAUTHORIZED, false, "세션이 만료되었습니다."),
AUTH_CODE_EXPIRED(HttpStatus.UNAUTHORIZED, false, "인증번호가 만료되었거나 존재하지 않습니다."),
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증되지 않은 사용자입니다."),
AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED, "아이디 또는 비밀번호가 일치하지 않습니다"),
INVALID_AUTH_CODE(HttpStatus.UNAUTHORIZED, "인증번호가 일치하지 않습니다."),
SESSION_EXPIRED(HttpStatus.UNAUTHORIZED, "세션이 만료되었습니다."),
AUTH_CODE_EXPIRED(HttpStatus.UNAUTHORIZED, "인증번호가 만료되었거나 존재하지 않습니다."),

// 403 Forbidden
FORBIDDEN(HttpStatus.FORBIDDEN, false, "권한이 없습니다."),
FORBIDDEN(HttpStatus.FORBIDDEN, "권한이 없습니다."),

// 404 Not Found
USER_NOT_FOUND(HttpStatus.NOT_FOUND, false, "사용자를 찾을 수 없습니다."),
SHORT_URL_NOT_FOUND(HttpStatus.NOT_FOUND, false, "단축 URL을 찾을 수 없습니다."),
FCFS_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, false, "선착순 이벤트를 찾을 수 없습니다."),
DRAW_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, false, "추첨 이벤트를 찾을 수 없습니다."),
EVENT_FRAME_NOT_FOUND(HttpStatus.NOT_FOUND, false, "이벤트 프레임을 찾을 수 없습니다."),
EVENT_USER_NOT_FOUND(HttpStatus.NOT_FOUND, false, "이벤트 사용자를 찾을 수 없습니다."),
COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, false, "기대평을 찾을 수 없습니다."),
EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, false, "이벤트를 찾을 수 없습니다."),
EVENT_NOT_PARTICIPATED(HttpStatus.NOT_FOUND, false, "이벤트에 참여하지 않았습니다."),
TEMP_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, false, "임시 저장 된 이벤트가 없습니다."),
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다."),
SHORT_URL_NOT_FOUND(HttpStatus.NOT_FOUND, "단축 URL을 찾을 수 없습니다."),
FCFS_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, "선착순 이벤트를 찾을 수 없습니다."),
DRAW_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, "추첨 이벤트를 찾을 수 없습니다."),
EVENT_FRAME_NOT_FOUND(HttpStatus.NOT_FOUND, "이벤트 프레임을 찾을 수 없습니다."),
EVENT_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "이벤트 사용자를 찾을 수 없습니다."),
COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "기대평을 찾을 수 없습니다."),
EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, "이벤트를 찾을 수 없습니다."),
EVENT_NOT_PARTICIPATED(HttpStatus.NOT_FOUND, "이벤트에 참여하지 않았습니다."),
TEMP_EVENT_NOT_FOUND(HttpStatus.NOT_FOUND, "임시 저장 된 이벤트가 없습니다."),

// 405 Method Not Allowed
METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, false, "허용되지 않은 메서드입니다."),
METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 메서드입니다."),

// 409 Conflict
USER_ALREADY_EXISTS(HttpStatus.CONFLICT, false, "이미 존재하는 사용자입니다."),
SHORT_URL_ALREADY_EXISTS(HttpStatus.CONFLICT, false, "이미 존재하는 URL입니다."),
COMMENT_ALREADY_EXISTS(HttpStatus.CONFLICT, false, "이미 등록된 기대평입니다."),
ADMIN_USER_ALREADY_EXISTS(HttpStatus.CONFLICT, false, "이미 존재하는 관리자입니다."),
ALREADY_WINNER(HttpStatus.CONFLICT, false, "이미 당첨된 사용자입니다."),
ALREADY_PARTICIPATED(HttpStatus.CONFLICT, false, "이미 참여한 사용자입니다."),
PHONE_NUMBER_ALREADY_EXISTS(HttpStatus.CONFLICT, false, "이미 존재하는 전화번호입니다."),
ALREADY_DRAWN(HttpStatus.CONFLICT, false, "이미 추첨된 이벤트입니다."),
USER_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 사용자입니다."),
SHORT_URL_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 URL입니다."),
COMMENT_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 등록된 기대평입니다."),
ADMIN_USER_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 관리자입니다."),
ALREADY_WINNER(HttpStatus.CONFLICT, "이미 당첨된 사용자입니다."),
ALREADY_PARTICIPATED(HttpStatus.CONFLICT, "이미 참여한 사용자입니다."),
PHONE_NUMBER_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 전화번호입니다."),
ALREADY_DRAWN(HttpStatus.CONFLICT, "이미 추첨된 이벤트입니다."),

// 500 Internal Server Error
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, false, "서버에 오류가 발생하였습니다.");
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버에 오류가 발생하였습니다.");

private final HttpStatus httpStatus;
private final Boolean success;
private final String message;
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public boolean isEnded(Instant now) {
private Long eventFrameId;

// 원래는 one-to-one 관계이지만, JPA 동작에 의해 강제로 EAGER FETCH로 처리돰 -> one-to-many 로 관리
@OneToMany(mappedBy = "eventMetadata", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@OneToMany(mappedBy = "eventMetadata", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY)
private final List<DrawEvent> drawEventList = new ArrayList<>();

public void updateDrawEvent(DrawEvent drawEvent) {
Expand All @@ -94,7 +94,7 @@ public DrawEvent getDrawEvent() {
return drawEventList.get(0);
}

@OneToMany(mappedBy = "eventMetaData", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@OneToMany(mappedBy = "eventMetaData", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY)
private final List<FcfsEvent> fcfsEventList = new ArrayList<>();

public void addFcfsEvents(List<FcfsEvent> fcfsEventList) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -209,6 +210,28 @@ public BriefEventPageDto searchEvents(String search, String sortQuery, String ty
return BriefEventPageDto.from(eventPage);
}

/**
* 이벤트를 제거한다. 존재하지 않거나, 시작된 이벤트는 제거할 수 없다.
* @param eventId 이벤트의 ID 값
*/
@Transactional
public void deleteEvent(String eventId) {
EventMetadata metadata = emRepository.findFirstByEventId(eventId)
.orElseThrow(() -> new EventException(ErrorCode.EVENT_NOT_FOUND));

Instant startTime = metadata.getStartTime();
Instant endTime = metadata.getEndTime();
Instant now = Instant.now();

if(startTime.isBefore(now)) {
// 이벤트 중
if (endTime.isAfter(now)) throw new EventException(ErrorCode.CANNOT_DELETE_EVENT_RUNNING);
else throw new EventException(ErrorCode.CANNOT_DELETE_EVENT_ENDED);
}

emRepository.delete(metadata);
}

private Set<EventType> parseTypes(String typeQuery) {
Set<EventType> result = new HashSet<>();
if(typeQuery == null) return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public class DrawEvent {
private EventMetadata eventMetadata;

@OneToMany(mappedBy ="drawEvent",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE},
fetch = FetchType.EAGER) // 추첨 이벤트는 항상 policy와 함께 사용되므로 EAGER로 설정
private List<DrawEventScorePolicy> policyList = new ArrayList<>();

@OneToMany(mappedBy ="drawEvent",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE},
fetch = FetchType.EAGER) // 추첨 이벤트는 항상 metadata와 함께 사용되므로 EAGER로 설정
private List<DrawEventMetadata> metadataList = new ArrayList<>();

Expand Down
Loading

0 comments on commit 7dd36ee

Please sign in to comment.