Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/docs/asciidoc/demo-day.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,14 @@ include::{snippets}/read-demo-day-list/http-response.adoc[]
==== 인증되지 않은 유저가 활성화된 데모데이 목록을 조회할 경우
include::{snippets}/fail-read-demo-day-with-unauthenticated-user/response-fields.adoc[]
include::{snippets}/fail-read-demo-day-with-unauthenticated-user/http-response.adoc[]

== Demo Day Apply

=== Request
include::{snippets}/apply-demo-day/request-headers.adoc[]
include::{snippets}/apply-demo-day/http-request.adoc[]

=== Response

==== 정상적으로 데모데이를 신청한 경우
include::{snippets}/apply-demo-day/http-response.adoc[]
36 changes: 28 additions & 8 deletions src/main/java/dayone/dayone/demoday/entity/DemoDay.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package dayone.dayone.demoday.entity;

import dayone.dayone.demoday.entity.value.Capacity;
import dayone.dayone.demoday.entity.value.DemoDate;
import dayone.dayone.demoday.entity.value.RegistrationDate;
import dayone.dayone.demoday.entity.value.Status;
import dayone.dayone.demoday.exception.DemoDayErrorCode;
import dayone.dayone.demoday.exception.DemoDayException;
import dayone.dayone.global.entity.BaseEntity;
import dayone.dayone.user.entity.User;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
Expand All @@ -20,6 +22,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.Objects;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
Expand All @@ -36,9 +39,6 @@ public class DemoDay extends BaseEntity {

private String thumbnail;

@Embedded
private Capacity capacity;

@Embedded
private RegistrationDate registrationDate;

Expand All @@ -57,7 +57,6 @@ public DemoDay(
final String title,
final String description,
final String thumbnail,
final Capacity capacity,
final RegistrationDate registrationDate,
final DemoDate demoDate,
final String location,
Expand All @@ -68,7 +67,6 @@ public DemoDay(
this.title = title;
this.description = description;
this.thumbnail = thumbnail;
this.capacity = capacity;
this.registrationDate = registrationDate;
this.demoDate = demoDate;
this.location = location;
Expand All @@ -84,7 +82,6 @@ public static DemoDay forSave(
final String thumbnail,
final LocalDate demoDate,
final LocalTime demoTime,
final int capacity,
final String location,
final Long userId
) {
Expand All @@ -93,7 +90,6 @@ public static DemoDay forSave(
title,
description,
thumbnail,
new Capacity(capacity),
RegistrationDate.of(demoDate, demoTime),
DemoDate.of(demoDate, demoTime),
location,
Expand All @@ -102,6 +98,30 @@ public static DemoDay forSave(
);
}

public DemoDayUser apply(final User user, final Ticket ticket, final DemoDayUsers demoDayUsers) {
validateApply(user, ticket, demoDayUsers);
ticket.sold();
return new DemoDayUser(null, this.id, user.getId());
}

private void validateApply(final User user, final Ticket ticket, final DemoDayUsers demoDayUsers) {
if (this.status == Status.CLOSED) {
throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_IS_CLOSED);
}

if (Objects.equals(user.getId(), this.userId)) {
throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_OWNER_NOT_APPLY_ONESELF);
}

if (ticket.getCapacity() == 0) {
throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_IS_FULL);
}
Comment on lines +108 to +118
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동일한 사람이 같은 demo day에 여러번 신청하더라도 문제가 발생하지 않는 상황이 존재하고 그 부분에 대한 예외처리가 필요


if (demoDayUsers.isAlreadyApply(user.getId())) {
throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_ALREADY_APPLY);
}
}

public LocalDateTime getDemoDate() {
return demoDate.getDemoDate();
}
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dayone.dayone.demoday.entity;

import dayone.dayone.global.entity.BaseEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Objects;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class DemoDayUser extends BaseEntity {

@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;

private Long demoDayId;

private Long userId;

public DemoDayUser(final Long id, final Long demoDayId, final Long userId) {
this.id = id;
this.demoDayId = demoDayId;
this.userId = userId;
this.createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS);
this.updatedAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS);
}
}
17 changes: 17 additions & 0 deletions src/main/java/dayone/dayone/demoday/entity/DemoDayUsers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dayone.dayone.demoday.entity;

import java.util.List;

public class DemoDayUsers {

private final List<DemoDayUser> demoDayUsers;

public DemoDayUsers(final List<DemoDayUser> demoDayUsers) {
this.demoDayUsers = demoDayUsers;
}

public boolean isAlreadyApply(final Long userId) {
return demoDayUsers.stream()
.anyMatch(demoDayUser -> demoDayUser.getUserId().equals(userId));
}
}
56 changes: 56 additions & 0 deletions src/main/java/dayone/dayone/demoday/entity/Ticket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package dayone.dayone.demoday.entity;

import dayone.dayone.demoday.entity.value.Capacity;
import dayone.dayone.global.entity.BaseEntity;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class Ticket extends BaseEntity {

@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;

@OneToOne
@JoinColumn(name = "demo_day_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private DemoDay demoDay;

@Embedded
private Capacity capacity;

public Ticket(final Long id, final DemoDay demoDay, final Capacity capacity) {
this.id = id;
this.demoDay = demoDay;
this.capacity = capacity;
this.createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS);
this.updatedAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS);
}

public static Ticket forSave(final DemoDay demoDay, final int capacity) {
return new Ticket(null, demoDay, new Capacity(capacity));
}

public void sold() {
this.capacity.minus();
}

public int getCapacity() {
return capacity.getValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
import java.util.List;

public interface DemoDayRepository extends JpaRepository<DemoDay, Long> {
List<DemoDay> findAllByStatus(Status status);
List<DemoDay> findAllByStatus(final Status status);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dayone.dayone.demoday.entity.respository;

import dayone.dayone.demoday.entity.DemoDayUser;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface DemoDayUserRepository extends JpaRepository<DemoDayUser, Long> {
Optional<DemoDayUser> findByDemoDayIdAndUserId(final Long demoDayId, final Long userId);

List<DemoDayUser> findByDemoDayId(final Long demoDayId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dayone.dayone.demoday.entity.respository;

import dayone.dayone.demoday.entity.Ticket;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface TicketRepository extends JpaRepository<Ticket, Long> {

Optional<Ticket> findByDemoDayId(final Long demoDayId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public Capacity(final int value) {
this.value = value;
}

public void minus() {
this.value--;
}

private void validate(final int value) {
if (value < CAPACITY_MIN_VALUE) {
throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_CAPACITY_UNDER_ZERO);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ public enum DemoDayErrorCode implements ErrorCode {

DEMO_DAY_REGISTRATION_PERIOD_ERROR(HttpStatus.BAD_REQUEST, 5001, "신청 마감 시간이 신청 시작 시간보다 빠를 수 없습니다."),
DEMO_DAY_IS_NOT_BEFORE_NOW(HttpStatus.BAD_REQUEST, 5002, "데모데이 날이 지금보다 이전일 수 없습니다."),
DEMO_DAY_CAPACITY_UNDER_ZERO(HttpStatus.BAD_REQUEST, 5003, "데모데이 참여 인원은 0명이거나 음수가 될 수 없습니다.");
DEMO_DAY_CAPACITY_UNDER_ZERO(HttpStatus.BAD_REQUEST, 5003, "데모데이 참여 인원은 0명이거나 음수가 될 수 없습니다."),
NOT_EXIST_DEMO_DAY(HttpStatus.NOT_FOUND, 5004, "데모데이가 존재하지 않습니다."),
DEMO_DAY_IS_CLOSED(HttpStatus.BAD_REQUEST, 5005, "데모데이가 종료되었습니다."),
DEMO_DAY_OWNER_NOT_APPLY_ONESELF(HttpStatus.BAD_REQUEST, 5006, "자신이 등록한 데모데이에는 신청할 수 없습니다."),
DEMO_DAY_IS_FULL(HttpStatus.BAD_REQUEST, 5007, "데모데이가 참여 인원이 만료되었습니다."),
DEMO_DAY_ALREADY_APPLY(HttpStatus.BAD_REQUEST, 5008, "이미 등록된 데모데이에 신청할 수 없습니다.");

private final HttpStatus httpStatus;
private final int code;
Expand Down
26 changes: 25 additions & 1 deletion src/main/java/dayone/dayone/demoday/service/DemoDayService.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package dayone.dayone.demoday.service;

import dayone.dayone.demoday.entity.DemoDay;
import dayone.dayone.demoday.entity.DemoDayUser;
import dayone.dayone.demoday.entity.DemoDayUsers;
import dayone.dayone.demoday.entity.Ticket;
import dayone.dayone.demoday.entity.respository.DemoDayRepository;
import dayone.dayone.demoday.entity.respository.DemoDayUserRepository;
import dayone.dayone.demoday.entity.respository.TicketRepository;
import dayone.dayone.demoday.entity.value.Status;
import dayone.dayone.demoday.exception.DemoDayErrorCode;
import dayone.dayone.demoday.service.dto.DemoDayCreateRequest;
import dayone.dayone.demoday.service.dto.DemoDayListResponse;
import dayone.dayone.user.entity.User;
import dayone.dayone.user.entity.repository.UserRepository;
import dayone.dayone.user.exception.UserErrorCode;
import dayone.dayone.user.exception.UserException;
Expand All @@ -20,6 +27,8 @@
public class DemoDayService {

private final DemoDayRepository demoDayRepository;
private final TicketRepository ticketRepository;
private final DemoDayUserRepository demoDayUserRepository;
private final UserRepository userRepository;

@Transactional
Expand All @@ -32,16 +41,31 @@ public Long create(final Long userId, final DemoDayCreateRequest request) {
request.thumbnail(),
request.demoDate(),
request.demoTime(),
request.capacity(),
request.location(),
userId);

demoDayRepository.save(demoDay);
final Ticket ticket = Ticket.forSave(demoDay, request.capacity());
ticketRepository.save(ticket);
return demoDay.getId();
}

public DemoDayListResponse getDemoDaysWithStatus(final String status) {
final List<DemoDay> demoDays = demoDayRepository.findAllByStatus(Status.valueOf(status));
return DemoDayListResponse.from(demoDays);
}

@Transactional
public void applyDemoDay(final Long userId, final Long demoDayId) {
final User user = userRepository.findById(userId)
.orElseThrow(() -> new UserException(UserErrorCode.NOT_EXIST_USER));
final DemoDay demoDay = demoDayRepository.findById(demoDayId)
.orElseThrow(() -> new UserException(DemoDayErrorCode.NOT_EXIST_DEMO_DAY));
final Ticket ticket = ticketRepository.findByDemoDayId(demoDayId)
.orElseThrow(() -> new UserException(DemoDayErrorCode.NOT_EXIST_DEMO_DAY));
final List<DemoDayUser> demoDayUsers = demoDayUserRepository.findByDemoDayId(demoDayId);

final DemoDayUser apply = demoDay.apply(user, ticket, new DemoDayUsers(demoDayUsers));
demoDayUserRepository.save(apply);
}
}
7 changes: 7 additions & 0 deletions src/main/java/dayone/dayone/demoday/ui/DemoDayController.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.data.repository.query.Param;
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;
Expand All @@ -34,4 +35,10 @@ public CommonResponseDto<DemoDayListResponse> getDemoDays(@Param("status") final
final DemoDayListResponse response = demoDayService.getDemoDaysWithStatus(status);
return CommonResponseDto.forSuccess(1, "데모데이 조회 성공", response);
}

@PostMapping("/{demoDayId}/apply")
public CommonResponseDto<Void> applyDemoDay(@AuthUser final Long userId, @PathVariable("demoDayId") final Long demoDayId) {
demoDayService.applyDemoDay(userId, demoDayId);
return CommonResponseDto.forSuccess(1, "데모데이 신청 성공", null);
}
}
Loading