Skip to content

Commit

Permalink
feat: 행사(Event) 생성 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
hyeonjerry committed Jul 28, 2023
1 parent a107610 commit 4381081
Show file tree
Hide file tree
Showing 10 changed files with 532 additions and 56 deletions.
15 changes: 14 additions & 1 deletion backend/emm-sale/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ include::{snippets}/find-profile/http-response.adoc[]
.HTTP response 설명
include::{snippets}/find-profile/response-fields.adoc[]


== Event

=== `GET` : 행사 상세정보 조회
Expand Down Expand Up @@ -140,6 +139,20 @@ include::{snippets}/find-participants/http-response.adoc[]
.HTTP response 설명
include::{snippets}/find-participants/response-fields.adoc[]

=== `POST` : 이벤트 생성

.HTTP request
include::{snippets}/find-participants/http-request.adoc[]

.HTTP request 설명
include::{snippets}/find-participants/request-fields.adoc[]

.HTTP response
include::{snippets}/find-participants/http-response.adoc[]

.HTTP response 설명
include::{snippets}/find-participants/response-fields.adoc[]

== Comment

=== `GET` : 댓글 모두 조회
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

import com.emmsale.base.BaseException;
import com.emmsale.base.BaseExceptionType;
import java.time.format.DateTimeParseException;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

Expand All @@ -18,6 +23,24 @@ public ResponseEntity<ExceptionResponse> handleException(final BaseException e)
return new ResponseEntity<>(ExceptionResponse.from(e), type.httpStatus());
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ExceptionResponse> handleValidationException(
final MethodArgumentNotValidException e) {
final String message = e.getBindingResult().getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining("\n"));
log.warn("[WARN] MESSAGE: {}", message);
return new ResponseEntity<>(new ExceptionResponse(message), HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(DateTimeParseException.class)
public ResponseEntity<ExceptionResponse> handleDateTimeParseException(
final DateTimeParseException e) {
final String message = "DateTime 입력 형식이 올바르지 않습니다.";
log.warn("[WARN] MESSAGE: " + message);
return new ResponseEntity<>(new ExceptionResponse(message), HttpStatus.BAD_REQUEST);
}

static class ExceptionResponse {

private final String message;
Expand Down
11 changes: 11 additions & 0 deletions backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@
import static java.net.URI.create;

import com.emmsale.event.application.EventService;
import com.emmsale.event.application.dto.EventCreateRequest;
import com.emmsale.event.application.dto.EventDetailResponse;
import com.emmsale.event.application.dto.EventParticipateRequest;
import com.emmsale.event.application.dto.EventResponse;
import com.emmsale.event.application.dto.ParticipantResponse;
import com.emmsale.member.domain.Member;
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand Down Expand Up @@ -62,4 +66,11 @@ public ResponseEntity<List<EventResponse>> findEvents(@RequestParam final int ye
@RequestParam(required = false) final String status) {
return ResponseEntity.ok(eventService.findEvents(LocalDate.now(), year, month, tag, status));
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public EventDetailResponse addEvent(
@RequestBody @Valid final EventCreateRequest request) {
return eventService.addEvent(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toUnmodifiableList;

import com.emmsale.event.application.dto.EventCreateRequest;
import com.emmsale.event.application.dto.EventDetailResponse;
import com.emmsale.event.application.dto.EventResponse;
import com.emmsale.event.application.dto.ParticipantResponse;
Expand All @@ -26,6 +27,8 @@
import com.emmsale.tag.domain.TagRepository;
import com.emmsale.tag.exception.TagException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -46,6 +49,12 @@ public class EventService {
private final EventTagRepository eventTagRepository;
private final TagRepository tagRepository;

private static void validateMemberNotAllowed(final Long memberId, final Member member) {
if (member.isNotMe(memberId)) {
throw new EventException(EventExceptionType.FORBIDDEN_PARTICIPATE_EVENT);
}
}

@Transactional(readOnly = true)
public EventDetailResponse findEvent(final Long id) {
final Event event = eventRepository.findById(id)
Expand All @@ -63,17 +72,11 @@ public Long participate(final Long eventId, final Long memberId, final Member me
return participant.getId();
}

private static void validateMemberNotAllowed(final Long memberId, final Member member) {
if (member.isNotMe(memberId)) {
throw new EventException(EventExceptionType.FORBIDDEN_PARTICIPATE_EVENT);
}
}

@Transactional(readOnly = true)
public List<EventResponse> findEvents(final LocalDate nowDate, final int year, final int month,
final String tagName, final String statusName) {
validateYearAndMonth(year, month);
List<Event> events = filterEventsByTag(tagName);
final List<Event> events = filterEventsByTag(tagName);

final EnumMap<EventStatus, List<Event>> sortAndGroupByStatus
= groupByEventStatus(nowDate, events, year, month);
Expand Down Expand Up @@ -102,7 +105,7 @@ private void validateYearAndMonth(final int year, final int month) {

private List<Event> filterEventsByTag(final String tagName) {
if (isExistTagName(tagName)) {
Tag tag = tagRepository.findByName(tagName)
final Tag tag = tagRepository.findByName(tagName)
.orElseThrow(() -> new TagException(NOT_FOUND_TAG));

return eventTagRepository.findEventTagsByTag(tag)
Expand Down Expand Up @@ -132,22 +135,22 @@ private EnumMap<EventStatus, List<Event>> groupByEventStatus(final LocalDate now

private boolean isOverlapToMonth(final int year, final int month,
final LocalDate eventStart, final LocalDate eventEnd) {
LocalDate monthStart = LocalDate.of(year, month, 1);
LocalDate monthEnd = LocalDate.of(year, month, monthStart.lengthOfMonth());
final LocalDate monthStart = LocalDate.of(year, month, 1);
final LocalDate monthEnd = LocalDate.of(year, month, monthStart.lengthOfMonth());

return (isBeforeOrEquals(eventStart, monthEnd) && isBeforeOrEquals(monthStart, eventEnd))
|| (isBeforeOrEquals(monthStart, eventStart) && isBeforeOrEquals(eventStart, monthEnd))
|| (isBeforeOrEquals(monthStart, eventEnd) && isBeforeOrEquals(eventEnd, monthEnd));
}

private boolean isBeforeOrEquals(LocalDate criteria, LocalDate comparison) {
private boolean isBeforeOrEquals(final LocalDate criteria, final LocalDate comparison) {
return criteria.isBefore(comparison) || criteria.isEqual(comparison);
}

private List<EventResponse> filterEventResponsesByStatus(final String statusName,
final EnumMap<EventStatus, List<Event>> sortAndGroupByEventStatus) {
if (isExistStatusName(statusName)) {
EventStatus status = EventStatus.from(statusName);
final EventStatus status = EventStatus.from(statusName);
return EventResponse.makeEventResponsesByStatus(status,
sortAndGroupByEventStatus.get(status));
}
Expand All @@ -158,5 +161,42 @@ private boolean isExistStatusName(final String statusName) {
return statusName != null;
}

public EventDetailResponse addEvent(final EventCreateRequest request) {
validateStartBeforeOrEqualEndDateTime(request.getStartDateTime(), request.getEndDateTime());

final Event event = getPersistentEvent(request);

final List<Tag> tags = getPersistTags(request);

for (final Tag tag : tags) {
event.addEventTag(tag);
}

return EventDetailResponse.from(event);
}

private void validateStartBeforeOrEqualEndDateTime(final LocalDateTime startDateTime,
final LocalDateTime endDateTime) {
if (startDateTime.isAfter(endDateTime)) {
throw new EventException(EventExceptionType.START_DATE_TIME_AFTER_END_DATE_TIME);
}
}

private List<Tag> getPersistTags(final EventCreateRequest request) {
if (request.getTags() == null || request.getTags().isEmpty()) {
return new ArrayList<>();
}

return request.getTags().stream()
.map(tagRequest -> tagRepository.findByName(tagRequest.getName())
.orElseThrow(() -> new EventException(EventExceptionType.NOT_FOUND_TAG)))
.collect(toList());
}

private Event getPersistentEvent(final EventCreateRequest request) {
final Event event = new Event(request.getName(), request.getLocation(),
request.getStartDateTime(), request.getEndDateTime(), request.getInformationUrl());

return eventRepository.save(event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.emmsale.event.application.dto;

import com.emmsale.tag.application.dto.TagRequest;
import java.time.LocalDateTime;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

@RequiredArgsConstructor
@Getter
public class EventCreateRequest {

private static final String DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";

@NotBlank(message = "행사의 이름을 입력해 주세요.")
private final String name;
@NotBlank(message = "행사의 장소를 입력해 주세요.")
private final String location;
@NotBlank(message = "행사의 상세 URL을 입력해 주세요.")
@Pattern(regexp = "(http.?://).*", message = "http:// 혹은 https://로 시작하는 주소를 입력해 주세요.")
private final String informationUrl;

@DateTimeFormat(pattern = DATE_TIME_FORMAT)
@NotNull(message = "행사의 시작 일시를 입력해 주세요.")
private final LocalDateTime startDateTime;
@DateTimeFormat(pattern = DATE_TIME_FORMAT)
@NotNull(message = "행사의 종료 일시를 입력해 주세요.")
private final LocalDateTime endDateTime;

private final List<TagRequest> tags;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.emmsale.event.exception.EventException;
import com.emmsale.event.exception.EventExceptionType;
import com.emmsale.member.domain.Member;
import com.emmsale.tag.domain.Tag;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
Expand Down Expand Up @@ -39,7 +40,7 @@ public class Event extends BaseEntity {
private LocalDateTime endDate;
@Column(nullable = false)
private String informationUrl;
@OneToMany(mappedBy = "event")
@OneToMany(mappedBy = "event", cascade = CascadeType.PERSIST)
private List<EventTag> tags = new ArrayList<>();
@OneToMany(mappedBy = "event")
private List<Comment> comments;
Expand All @@ -66,6 +67,12 @@ public Participant addParticipant(final Member member) {
return participant;
}

public EventTag addEventTag(final Tag tag) {
final EventTag eventTag = new EventTag(this, tag);
tags.add(eventTag);
return eventTag;
}

public void validateAlreadyParticipate(final Member member) {
if (isAlreadyParticipate(member)) {
throw new EventException(EventExceptionType.ALREADY_PARTICIPATED);
Expand All @@ -77,7 +84,7 @@ private boolean isAlreadyParticipate(final Member member) {
.anyMatch(participant -> participant.isSameMember(member));
}

public EventStatus calculateEventStatus(LocalDate now) {
public EventStatus calculateEventStatus(final LocalDate now) {
if (now.isBefore(startDate.toLocalDate())) {
return EventStatus.UPCOMING;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ public enum EventExceptionType implements BaseExceptionType {
INVALID_MONTH(
HttpStatus.BAD_REQUEST,
"월은 1에서 12 사이여야 합니다."
),
NOT_FOUND_TAG(
HttpStatus.NOT_FOUND,
"해당하는 태그를 찾을 수 없습니다."
),
START_DATE_TIME_AFTER_END_DATE_TIME(
HttpStatus.BAD_REQUEST,
"행사의 시작 일시가 종료 일시 이후일 수 없습니다."
);

private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.emmsale.tag.application.dto;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;

@Getter
public class TagRequest {

private final String name;

@JsonCreator
public TagRequest(@JsonProperty final String name) {
this.name = name;
}
}
Loading

0 comments on commit 4381081

Please sign in to comment.