From 06d48f992983d528185bf4251aa4d22c88e48be3 Mon Sep 17 00:00:00 2001 From: seokhwan-an Date: Wed, 30 Apr 2025 19:03:03 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat=20:=20ticket=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dayone/dayone/demoday/entity/DemoDay.java | 8 --- .../dayone/dayone/demoday/entity/Ticket.java | 52 +++++++++++++++++++ .../entity/respository/TicketRepository.java | 11 ++++ .../demoday/service/DemoDayService.java | 6 ++- .../demoday/service/DemoDayServiceTest.java | 8 ++- .../dayone/fixture/TestDemoDayFactory.java | 1 - 6 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 src/main/java/dayone/dayone/demoday/entity/Ticket.java create mode 100644 src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java diff --git a/src/main/java/dayone/dayone/demoday/entity/DemoDay.java b/src/main/java/dayone/dayone/demoday/entity/DemoDay.java index f44a5cd..2150275 100644 --- a/src/main/java/dayone/dayone/demoday/entity/DemoDay.java +++ b/src/main/java/dayone/dayone/demoday/entity/DemoDay.java @@ -1,6 +1,5 @@ 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; @@ -36,9 +35,6 @@ public class DemoDay extends BaseEntity { private String thumbnail; - @Embedded - private Capacity capacity; - @Embedded private RegistrationDate registrationDate; @@ -57,7 +53,6 @@ public DemoDay( final String title, final String description, final String thumbnail, - final Capacity capacity, final RegistrationDate registrationDate, final DemoDate demoDate, final String location, @@ -68,7 +63,6 @@ public DemoDay( this.title = title; this.description = description; this.thumbnail = thumbnail; - this.capacity = capacity; this.registrationDate = registrationDate; this.demoDate = demoDate; this.location = location; @@ -84,7 +78,6 @@ public static DemoDay forSave( final String thumbnail, final LocalDate demoDate, final LocalTime demoTime, - final int capacity, final String location, final Long userId ) { @@ -93,7 +86,6 @@ public static DemoDay forSave( title, description, thumbnail, - new Capacity(capacity), RegistrationDate.of(demoDate, demoTime), DemoDate.of(demoDate, demoTime), location, diff --git a/src/main/java/dayone/dayone/demoday/entity/Ticket.java b/src/main/java/dayone/dayone/demoday/entity/Ticket.java new file mode 100644 index 0000000..1eb3980 --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/Ticket.java @@ -0,0 +1,52 @@ +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 int getCapacity() { + return capacity.getValue(); + } +} diff --git a/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java b/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java new file mode 100644 index 0000000..7a867af --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java @@ -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 { + + Optional findByDemoDayId(Long demoDayId); +} diff --git a/src/main/java/dayone/dayone/demoday/service/DemoDayService.java b/src/main/java/dayone/dayone/demoday/service/DemoDayService.java index 6447b90..4713097 100644 --- a/src/main/java/dayone/dayone/demoday/service/DemoDayService.java +++ b/src/main/java/dayone/dayone/demoday/service/DemoDayService.java @@ -1,7 +1,9 @@ package dayone.dayone.demoday.service; import dayone.dayone.demoday.entity.DemoDay; +import dayone.dayone.demoday.entity.Ticket; import dayone.dayone.demoday.entity.respository.DemoDayRepository; +import dayone.dayone.demoday.entity.respository.TicketRepository; import dayone.dayone.demoday.entity.value.Status; import dayone.dayone.demoday.service.dto.DemoDayCreateRequest; import dayone.dayone.demoday.service.dto.DemoDayListResponse; @@ -20,6 +22,7 @@ public class DemoDayService { private final DemoDayRepository demoDayRepository; + private final TicketRepository ticketRepository; private final UserRepository userRepository; @Transactional @@ -32,11 +35,12 @@ 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(); } diff --git a/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java b/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java index 4e6aeda..efa508d 100644 --- a/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java +++ b/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java @@ -1,7 +1,9 @@ package dayone.dayone.demoday.service; import dayone.dayone.demoday.entity.DemoDay; +import dayone.dayone.demoday.entity.Ticket; import dayone.dayone.demoday.entity.respository.DemoDayRepository; +import dayone.dayone.demoday.entity.respository.TicketRepository; import dayone.dayone.demoday.entity.value.Status; import dayone.dayone.demoday.service.dto.DemoDayCreateRequest; import dayone.dayone.demoday.service.dto.DemoDayListResponse; @@ -35,6 +37,9 @@ class DemoDayServiceTest extends ServiceTest { @Autowired private DemoDayRepository demoDayRepository; + @Autowired + private TicketRepository ticketRepository; + @Autowired private DemoDayService demoDayService; @@ -55,6 +60,7 @@ void createDemoDay() { // then final DemoDay demoDay = demoDayRepository.findById(savedId).get(); + final Ticket ticket = ticketRepository.findByDemoDayId(savedId).get(); SoftAssertions.assertSoftly(softAssertions -> { softAssertions.assertThat(demoDay.getTitle()).isEqualTo(request.title()); @@ -62,10 +68,10 @@ void createDemoDay() { softAssertions.assertThat(demoDay.getThumbnail()).isEqualTo(request.thumbnail()); softAssertions.assertThat(demoDay.getUserId()).isEqualTo(user.getId()); softAssertions.assertThat(demoDay.getLocation()).isEqualTo(request.location()); - softAssertions.assertThat(demoDay.getCapacity().getValue()).isEqualTo(request.capacity()); softAssertions.assertThat(demoDay.getRegistrationDate().getStartRegistrationDate().toLocalDate()).isEqualTo(today); softAssertions.assertThat(demoDay.getRegistrationDate().getEndRegistrationDate().toLocalDate()).isEqualTo(tomorrow); softAssertions.assertThat(demoDay.getDemoDate().toLocalDate()).isEqualTo(tomorrow); + softAssertions.assertThat(ticket.getCapacity()).isEqualTo(request.capacity()); }); } diff --git a/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java b/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java index 612dea4..2c143b6 100644 --- a/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java +++ b/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java @@ -28,7 +28,6 @@ public List createNDemoDaysWithStatus(final int n, final String title, title, description, "이미지", - new Capacity(10), new RegistrationDate(localDateTime, localDateTime.plusDays(1)), new DemoDate(localDateTime.plusDays(1)), "장소", From bbdc9b7aa638b6cd6da811348d582be0ecab3c27 Mon Sep 17 00:00:00 2001 From: seokhwan-an Date: Sat, 3 May 2025 22:22:06 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat=20:=20DemoDayUser=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dayone/demoday/entity/DemoDayUser.java | 35 +++++++++++++++++++ .../respository/DemoDayUserRepository.java | 7 ++++ 2 files changed, 42 insertions(+) create mode 100644 src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java create mode 100644 src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java diff --git a/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java b/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java new file mode 100644 index 0000000..afd784c --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java @@ -0,0 +1,35 @@ +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; + +@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); + } +} diff --git a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java new file mode 100644 index 0000000..e1399ed --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java @@ -0,0 +1,7 @@ +package dayone.dayone.demoday.entity.respository; + +import dayone.dayone.demoday.entity.DemoDayUser; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DemoDayUserRepository extends JpaRepository { +} From cdb839567660a42eb45a742b1a9a629db636c7ce Mon Sep 17 00:00:00 2001 From: seokhwan-an Date: Sat, 3 May 2025 22:54:32 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat=20:=20=EB=8D=B0=EB=AA=A8=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=20=EC=8B=A0=EC=B2=AD=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dayone/dayone/demoday/entity/DemoDay.java | 24 +++++++++++++++++++ .../dayone/dayone/demoday/entity/Ticket.java | 4 ++++ .../dayone/demoday/entity/value/Capacity.java | 4 ++++ .../demoday/exception/DemoDayErrorCode.java | 6 ++++- .../demoday/service/DemoDayService.java | 18 ++++++++++++++ .../dayone/demoday/ui/DemoDayController.java | 7 ++++++ 6 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/main/java/dayone/dayone/demoday/entity/DemoDay.java b/src/main/java/dayone/dayone/demoday/entity/DemoDay.java index 2150275..f21f81a 100644 --- a/src/main/java/dayone/dayone/demoday/entity/DemoDay.java +++ b/src/main/java/dayone/dayone/demoday/entity/DemoDay.java @@ -3,7 +3,10 @@ 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; @@ -19,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 @@ -94,6 +98,26 @@ public static DemoDay forSave( ); } + public DemoDayUser apply(final User user, final Ticket ticket) { + validateApply(user, ticket); + ticket.sold(); + return new DemoDayUser(null, this.id, user.getId()); + } + + private void validateApply(final User user, final Ticket ticket) { + 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); + } + } + public LocalDateTime getDemoDate() { return demoDate.getDemoDate(); } diff --git a/src/main/java/dayone/dayone/demoday/entity/Ticket.java b/src/main/java/dayone/dayone/demoday/entity/Ticket.java index 1eb3980..f5be9b8 100644 --- a/src/main/java/dayone/dayone/demoday/entity/Ticket.java +++ b/src/main/java/dayone/dayone/demoday/entity/Ticket.java @@ -46,6 +46,10 @@ 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(); } diff --git a/src/main/java/dayone/dayone/demoday/entity/value/Capacity.java b/src/main/java/dayone/dayone/demoday/entity/value/Capacity.java index 87e0893..050efcc 100644 --- a/src/main/java/dayone/dayone/demoday/entity/value/Capacity.java +++ b/src/main/java/dayone/dayone/demoday/entity/value/Capacity.java @@ -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); diff --git a/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java b/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java index 9620b8c..aacb0d7 100644 --- a/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java +++ b/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java @@ -7,7 +7,11 @@ 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, 5005, "자신이 등록한 데모데이에는 신청할 수 없습니다."), + DEMO_DAY_IS_FULL(HttpStatus.BAD_REQUEST, 5006, "데모데이가 참여 인원이 만료되었습니다."); private final HttpStatus httpStatus; private final int code; diff --git a/src/main/java/dayone/dayone/demoday/service/DemoDayService.java b/src/main/java/dayone/dayone/demoday/service/DemoDayService.java index 4713097..ecd0ad3 100644 --- a/src/main/java/dayone/dayone/demoday/service/DemoDayService.java +++ b/src/main/java/dayone/dayone/demoday/service/DemoDayService.java @@ -1,12 +1,16 @@ package dayone.dayone.demoday.service; import dayone.dayone.demoday.entity.DemoDay; +import dayone.dayone.demoday.entity.DemoDayUser; 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; @@ -23,6 +27,7 @@ public class DemoDayService { private final DemoDayRepository demoDayRepository; private final TicketRepository ticketRepository; + private final DemoDayUserRepository demoDayUserRepository; private final UserRepository userRepository; @Transactional @@ -48,4 +53,17 @@ public DemoDayListResponse getDemoDaysWithStatus(final String status) { final List 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 DemoDayUser apply = demoDay.apply(user, ticket); + demoDayUserRepository.save(apply); + } } diff --git a/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java b/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java index 60bfb8f..9a35746 100644 --- a/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java +++ b/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java @@ -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; @@ -34,4 +35,10 @@ public CommonResponseDto getDemoDays(@Param("status") final final DemoDayListResponse response = demoDayService.getDemoDaysWithStatus(status); return CommonResponseDto.forSuccess(1, "데모데이 조회 성공", response); } + + @PostMapping("/{demoDayId}/apply") + public CommonResponseDto applyDemoDay(@AuthUser final Long userId, @PathVariable("demoDayId") final Long demoDayId) { + demoDayService.applyDemoDay(userId, demoDayId); + return CommonResponseDto.forSuccess(1, "데모데이 산청 성공", null); + } } From 24305908523f3cff96259aed672eec8371a415bf Mon Sep 17 00:00:00 2001 From: seokhwan-an Date: Mon, 5 May 2025 11:09:46 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat=20:=20=EB=8D=B0=EB=AA=A8=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=20=EC=8B=A0=EC=B2=AD=20api=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/respository/DemoDayRepository.java | 2 +- .../respository/DemoDayUserRepository.java | 3 + .../entity/respository/TicketRepository.java | 2 +- .../dayone/demoday/docs/DemoDayDocsTest.java | 31 +++++ .../dayone/demoday/entity/DemoDayTest.java | 121 ++++++++++++++++++ .../demoday/service/DemoDayServiceTest.java | 35 +++++ .../dayone/fixture/TestDemoDayFactory.java | 18 ++- .../dayone/fixture/TestTicketFactory.java | 19 +++ 8 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java create mode 100644 src/test/java/dayone/dayone/fixture/TestTicketFactory.java diff --git a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayRepository.java b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayRepository.java index 59d186d..2f13842 100644 --- a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayRepository.java +++ b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayRepository.java @@ -7,5 +7,5 @@ import java.util.List; public interface DemoDayRepository extends JpaRepository { - List findAllByStatus(Status status); + List findAllByStatus(final Status status); } diff --git a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java index e1399ed..358cb21 100644 --- a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java +++ b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java @@ -3,5 +3,8 @@ import dayone.dayone.demoday.entity.DemoDayUser; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface DemoDayUserRepository extends JpaRepository { + Optional findByDemoDayIdAndUserId(final Long demoDayId, final Long userId); } diff --git a/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java b/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java index 7a867af..bb2a141 100644 --- a/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java +++ b/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java @@ -7,5 +7,5 @@ public interface TicketRepository extends JpaRepository { - Optional findByDemoDayId(Long demoDayId); + Optional findByDemoDayId(final Long demoDayId); } diff --git a/src/test/java/dayone/dayone/demoday/docs/DemoDayDocsTest.java b/src/test/java/dayone/dayone/demoday/docs/DemoDayDocsTest.java index 8d60e58..0556139 100644 --- a/src/test/java/dayone/dayone/demoday/docs/DemoDayDocsTest.java +++ b/src/test/java/dayone/dayone/demoday/docs/DemoDayDocsTest.java @@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; @@ -31,6 +32,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -185,4 +187,33 @@ void readDemoDayWithUnAuthenticatedUser() throws Exception { )); } } + + @DisplayName("데모데이 신청 요청") + @Nested + class ApplyDemoDay { + @DisplayName("데모데이를 신청한다.") + @Test + void applyDemoDay() throws Exception { + // given + willDoNothing().given(demoDayService).applyDemoDay(anyLong(), anyLong()); + successAuth(); + + // when + final ResultActions result = mockMvc.perform(post("/api/v1/demo-days/{demoDayId}/apply", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer accessToken")); + + // then + result.andExpect(status().isOk()) + .andDo(document("apply-demo-day", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION).description("인증된 사용자의 accessToken") + ), + pathParameters( + parameterWithName("demoDayId").description("신청할 데모데이의 id") + ) + )); + } + } } diff --git a/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java b/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java new file mode 100644 index 0000000..71f3ad2 --- /dev/null +++ b/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java @@ -0,0 +1,121 @@ +package dayone.dayone.demoday.entity; + +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.user.entity.User; +import dayone.dayone.user.entity.value.Role; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DemoDayTest { + + @DisplayName("데모데이를 신청하면 티켓이 하나 감소한다.") + @Test + void applyDemoDay() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayOpen = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + 1L); + final Ticket ticket = Ticket.forSave(demoDayOpen, 1); + final User user = new User(2L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + + // when + final DemoDayUser apply = demoDayOpen.apply(user, ticket); + + // then + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(ticket.getCapacity()).isEqualTo(0); + softly.assertThat(apply.getUserId()).isEqualTo(user.getId()); + softly.assertThat(apply.getDemoDayId()).isEqualTo(demoDayOpen.getId()); + }); + } + + @DisplayName("이미 종료된 데모데이를 신청하면 예외를 발생한다.") + @Test + void applyDemoDayWithClosedDemoDay() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayClosed = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.CLOSED, + 1L); + + final Ticket ticket = Ticket.forSave(demoDayClosed, 1); + final User user = new User(2L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + + // when + // then + assertThatThrownBy(() -> demoDayClosed.apply(user, ticket)) + .isInstanceOf(DemoDayException.class) + .hasMessage(DemoDayErrorCode.DEMO_DAY_IS_CLOSED.getMessage()); + } + + @DisplayName("데모데이 등록자는 자신의 데모데이를 신청할 수 없다.") + @Test + void applyDemoDayWithNotOwner() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayOpen = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + 1L); + final Ticket ticket = Ticket.forSave(demoDayOpen, 1); + final User owner = new User(1L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + + // when + // then + assertThatThrownBy(() -> demoDayOpen.apply(owner, ticket)) + .isInstanceOf(DemoDayException.class) + .hasMessage(DemoDayErrorCode.DEMO_DAY_OWNER_NOT_APPLY_ONESELF.getMessage()); + } + + @DisplayName("참여 인원이 꽉찬 경우 예외를 발생한다.") + @Test + void applyDemoDayWithOverCapacity() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayOpen = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + 1L); + final Ticket ticket = Ticket.forSave(demoDayOpen, 1); + final User user = new User(2L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + ticket.sold(); + + // when + // then + assertThatThrownBy(() -> demoDayOpen.apply(user, ticket)) + .isInstanceOf(DemoDayException.class) + .hasMessage(DemoDayErrorCode.DEMO_DAY_IS_FULL.getMessage()); + } +} diff --git a/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java b/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java index efa508d..d7a7632 100644 --- a/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java +++ b/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java @@ -1,14 +1,17 @@ package dayone.dayone.demoday.service; import dayone.dayone.demoday.entity.DemoDay; +import dayone.dayone.demoday.entity.DemoDayUser; 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.service.dto.DemoDayCreateRequest; import dayone.dayone.demoday.service.dto.DemoDayListResponse; import dayone.dayone.demoday.service.dto.DemoDayResponse; import dayone.dayone.fixture.TestDemoDayFactory; +import dayone.dayone.fixture.TestTicketFactory; import dayone.dayone.fixture.TestUserFactory; import dayone.dayone.support.ServiceTest; import dayone.dayone.user.entity.User; @@ -23,6 +26,7 @@ import java.time.LocalDate; import java.time.LocalTime; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -34,12 +38,18 @@ class DemoDayServiceTest extends ServiceTest { @Autowired private TestDemoDayFactory testDemoDayFactory; + @Autowired + private TestTicketFactory testTicketFactory; + @Autowired private DemoDayRepository demoDayRepository; @Autowired private TicketRepository ticketRepository; + @Autowired + private DemoDayUserRepository demoDayUserRepository; + @Autowired private DemoDayService demoDayService; @@ -116,4 +126,29 @@ void readDemoDayByStatus() { }); } } + + @DisplayName("데모데이 신청") + @Nested + class apply { + @DisplayName("데모데이를 신청하면 티켓이 하나 감소한다.") + @Test + void applyDemoDay() { + // given + final User DemoDayOwner = testUserFactory.createUser("test@test.com", "test", "test", 1); + final User DemoDayUser = testUserFactory.createUser("test2@test.com", "test2", "test2", 1); + final DemoDay demoDayOpen = testDemoDayFactory.createDemoDayOpen("title", "description", DemoDayOwner.getId()); + testTicketFactory.createNTicket(demoDayOpen, 1); + + // when + demoDayService.applyDemoDay(DemoDayUser.getId(), demoDayOpen.getId()); + + // then + final Optional byDemoDayIdAndUserId = demoDayUserRepository.findByDemoDayIdAndUserId(demoDayOpen.getId(), DemoDayUser.getId()); + final Ticket ticket = ticketRepository.findByDemoDayId(demoDayOpen.getId()).get(); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(ticket.getCapacity()).isEqualTo(0); + softly.assertThat(byDemoDayIdAndUserId.isPresent()).isTrue(); + }); + } + } } diff --git a/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java b/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java index 2c143b6..c092f7a 100644 --- a/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java +++ b/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java @@ -2,7 +2,6 @@ import dayone.dayone.demoday.entity.DemoDay; import dayone.dayone.demoday.entity.respository.DemoDayRepository; -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; @@ -19,11 +18,26 @@ public class TestDemoDayFactory { @Autowired private DemoDayRepository demoDayRepository; + public DemoDay createDemoDayOpen(final String title, final String description, final Long userId) { + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDay = new DemoDay(null, + title, + description, + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + userId); + demoDayRepository.save(demoDay); + return demoDay; + } + public List createNDemoDaysWithStatus(final int n, final String title, final String description, final Status status) { List demoDays = new ArrayList<>(); for (int i = 0; i < n; i++) { - LocalDateTime localDateTime = LocalDateTime.now(); + final LocalDateTime localDateTime = LocalDateTime.now(); final DemoDay demoDay = new DemoDay(null, title, description, diff --git a/src/test/java/dayone/dayone/fixture/TestTicketFactory.java b/src/test/java/dayone/dayone/fixture/TestTicketFactory.java new file mode 100644 index 0000000..701f90e --- /dev/null +++ b/src/test/java/dayone/dayone/fixture/TestTicketFactory.java @@ -0,0 +1,19 @@ +package dayone.dayone.fixture; + +import dayone.dayone.demoday.entity.DemoDay; +import dayone.dayone.demoday.entity.Ticket; +import dayone.dayone.demoday.entity.respository.TicketRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class TestTicketFactory { + + @Autowired + private TicketRepository ticketRepository; + + public Ticket createNTicket(final DemoDay demoDay, final int capacity) { + final Ticket ticket = Ticket.forSave(demoDay, capacity); + return ticketRepository.save(ticket); + } +} From 50ad54785cdaec1d12456695a5df2b80b43afb10 Mon Sep 17 00:00:00 2001 From: seokhwan-an Date: Mon, 5 May 2025 11:22:53 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat=20:=20=EB=8D=B0=EB=AA=A8=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=20=EC=8B=A0=EC=B2=AD=20api=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=ED=99=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/demo-day.adoc | 11 +++++++++++ .../dayone/dayone/demoday/ui/DemoDayController.java | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/demo-day.adoc b/src/docs/asciidoc/demo-day.adoc index 31def15..c5a9264 100644 --- a/src/docs/asciidoc/demo-day.adoc +++ b/src/docs/asciidoc/demo-day.adoc @@ -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[] diff --git a/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java b/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java index 9a35746..2b4bbf4 100644 --- a/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java +++ b/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java @@ -39,6 +39,6 @@ public CommonResponseDto getDemoDays(@Param("status") final @PostMapping("/{demoDayId}/apply") public CommonResponseDto applyDemoDay(@AuthUser final Long userId, @PathVariable("demoDayId") final Long demoDayId) { demoDayService.applyDemoDay(userId, demoDayId); - return CommonResponseDto.forSuccess(1, "데모데이 산청 성공", null); + return CommonResponseDto.forSuccess(1, "데모데이 신청 성공", null); } } From 2f2a530b243711aaf55a54b7751ed7bb0831fbd9 Mon Sep 17 00:00:00 2001 From: seokhwan-an Date: Mon, 5 May 2025 13:50:31 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor=20:=20error=20code=20=EB=8F=99?= =?UTF-8?q?=EC=9D=BC=ED=95=9C=20=EA=B2=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dayone/dayone/demoday/exception/DemoDayErrorCode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java b/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java index aacb0d7..a0abb13 100644 --- a/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java +++ b/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java @@ -10,8 +10,8 @@ public enum DemoDayErrorCode implements ErrorCode { 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, 5005, "자신이 등록한 데모데이에는 신청할 수 없습니다."), - DEMO_DAY_IS_FULL(HttpStatus.BAD_REQUEST, 5006, "데모데이가 참여 인원이 만료되었습니다."); + DEMO_DAY_OWNER_NOT_APPLY_ONESELF(HttpStatus.BAD_REQUEST, 5006, "자신이 등록한 데모데이에는 신청할 수 없습니다."), + DEMO_DAY_IS_FULL(HttpStatus.BAD_REQUEST, 5007, "데모데이가 참여 인원이 만료되었습니다."); private final HttpStatus httpStatus; private final int code; From c35a5cb3a42ab561919d564b693e6655ac7f762b Mon Sep 17 00:00:00 2001 From: seokhwan-an Date: Mon, 5 May 2025 14:49:28 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor=20:=20=EC=9D=B4=EB=AF=B8=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=ED=95=9C=20=EB=8D=B0=EB=AA=A8=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=EC=9D=B8=EC=A7=80=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dayone/dayone/demoday/entity/DemoDay.java | 10 ++++-- .../dayone/demoday/entity/DemoDayUser.java | 1 + .../dayone/demoday/entity/DemoDayUsers.java | 17 ++++++++++ .../respository/DemoDayUserRepository.java | 3 ++ .../demoday/exception/DemoDayErrorCode.java | 3 +- .../demoday/service/DemoDayService.java | 4 ++- .../dayone/demoday/entity/DemoDayTest.java | 34 ++++++++++++++++--- 7 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 src/main/java/dayone/dayone/demoday/entity/DemoDayUsers.java diff --git a/src/main/java/dayone/dayone/demoday/entity/DemoDay.java b/src/main/java/dayone/dayone/demoday/entity/DemoDay.java index f21f81a..8910fe7 100644 --- a/src/main/java/dayone/dayone/demoday/entity/DemoDay.java +++ b/src/main/java/dayone/dayone/demoday/entity/DemoDay.java @@ -98,13 +98,13 @@ public static DemoDay forSave( ); } - public DemoDayUser apply(final User user, final Ticket ticket) { - validateApply(user, ticket); + 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) { + 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); } @@ -116,6 +116,10 @@ private void validateApply(final User user, final Ticket ticket) { if (ticket.getCapacity() == 0) { throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_IS_FULL); } + + if (demoDayUsers.isAlreadyApply(user.getId())) { + throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_ALREADY_APPLY); + } } public LocalDateTime getDemoDate() { diff --git a/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java b/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java index afd784c..da35f2e 100644 --- a/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java +++ b/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java @@ -11,6 +11,7 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.Objects; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter diff --git a/src/main/java/dayone/dayone/demoday/entity/DemoDayUsers.java b/src/main/java/dayone/dayone/demoday/entity/DemoDayUsers.java new file mode 100644 index 0000000..ceab002 --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/DemoDayUsers.java @@ -0,0 +1,17 @@ +package dayone.dayone.demoday.entity; + +import java.util.List; + +public class DemoDayUsers { + + private final List demoDayUsers; + + public DemoDayUsers(final List demoDayUsers) { + this.demoDayUsers = demoDayUsers; + } + + public boolean isAlreadyApply(final Long userId) { + return demoDayUsers.stream() + .anyMatch(demoDayUser -> demoDayUser.getUserId().equals(userId)); + } +} diff --git a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java index 358cb21..02a8da9 100644 --- a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java +++ b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java @@ -3,8 +3,11 @@ 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 { Optional findByDemoDayIdAndUserId(final Long demoDayId, final Long userId); + + List findByDemoDayId(final Long demoDayId); } diff --git a/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java b/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java index a0abb13..08a7bf7 100644 --- a/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java +++ b/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java @@ -11,7 +11,8 @@ public enum DemoDayErrorCode implements ErrorCode { 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_IS_FULL(HttpStatus.BAD_REQUEST, 5007, "데모데이가 참여 인원이 만료되었습니다."), + DEMO_DAY_ALREADY_APPLY(HttpStatus.BAD_REQUEST, 5008, "이미 등록된 데모데이에 신청할 수 없습니다."); private final HttpStatus httpStatus; private final int code; diff --git a/src/main/java/dayone/dayone/demoday/service/DemoDayService.java b/src/main/java/dayone/dayone/demoday/service/DemoDayService.java index ecd0ad3..e268eb1 100644 --- a/src/main/java/dayone/dayone/demoday/service/DemoDayService.java +++ b/src/main/java/dayone/dayone/demoday/service/DemoDayService.java @@ -2,6 +2,7 @@ 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; @@ -62,8 +63,9 @@ public void applyDemoDay(final Long userId, final Long 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 demoDayUsers = demoDayUserRepository.findByDemoDayId(demoDayId); - final DemoDayUser apply = demoDay.apply(user, ticket); + final DemoDayUser apply = demoDay.apply(user, ticket, new DemoDayUsers(demoDayUsers)); demoDayUserRepository.save(apply); } } diff --git a/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java b/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java index 71f3ad2..90b357e 100644 --- a/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java +++ b/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import java.time.LocalDateTime; +import java.util.List; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -35,7 +36,7 @@ void applyDemoDay() { final User user = new User(2L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); // when - final DemoDayUser apply = demoDayOpen.apply(user, ticket); + final DemoDayUser apply = demoDayOpen.apply(user, ticket, new DemoDayUsers(List.of())); // then SoftAssertions.assertSoftly(softly -> { @@ -65,7 +66,7 @@ void applyDemoDayWithClosedDemoDay() { // when // then - assertThatThrownBy(() -> demoDayClosed.apply(user, ticket)) + assertThatThrownBy(() -> demoDayClosed.apply(user, ticket, new DemoDayUsers(List.of()))) .isInstanceOf(DemoDayException.class) .hasMessage(DemoDayErrorCode.DEMO_DAY_IS_CLOSED.getMessage()); } @@ -89,7 +90,7 @@ void applyDemoDayWithNotOwner() { // when // then - assertThatThrownBy(() -> demoDayOpen.apply(owner, ticket)) + assertThatThrownBy(() -> demoDayOpen.apply(owner, ticket, new DemoDayUsers(List.of()))) .isInstanceOf(DemoDayException.class) .hasMessage(DemoDayErrorCode.DEMO_DAY_OWNER_NOT_APPLY_ONESELF.getMessage()); } @@ -114,8 +115,33 @@ void applyDemoDayWithOverCapacity() { // when // then - assertThatThrownBy(() -> demoDayOpen.apply(user, ticket)) + assertThatThrownBy(() -> demoDayOpen.apply(user, ticket, new DemoDayUsers(List.of()))) .isInstanceOf(DemoDayException.class) .hasMessage(DemoDayErrorCode.DEMO_DAY_IS_FULL.getMessage()); } + + @DisplayName("이미 신청한 데모데이에 신청할 경우 예외를 발생한다.") + @Test + void applyDemoDayAlreadyApply() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayOpen = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + 1L); + final Ticket ticket = Ticket.forSave(demoDayOpen, 1); + final User user = new User(2L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + final DemoDayUsers demoDayUsers = new DemoDayUsers(List.of(new DemoDayUser(null, 1L, user.getId()))); + + // when + // then + assertThatThrownBy(() -> demoDayOpen.apply(user, ticket, demoDayUsers)) + .isInstanceOf(DemoDayException.class) + .hasMessage(DemoDayErrorCode.DEMO_DAY_ALREADY_APPLY.getMessage()); + } }