diff --git a/src/main/java/roomescape/RoomescapeApplication.java b/src/main/java/roomescape/RoomescapeApplication.java index 702706791..f080804d8 100644 --- a/src/main/java/roomescape/RoomescapeApplication.java +++ b/src/main/java/roomescape/RoomescapeApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class RoomescapeApplication { public static void main(String[] args) { SpringApplication.run(RoomescapeApplication.class, args); diff --git a/src/main/java/roomescape/auth/AdminAuthHandlerInterceptor.java b/src/main/java/roomescape/auth/AdminAuthHandlerInterceptor.java index d393ee5b8..a45131d39 100644 --- a/src/main/java/roomescape/auth/AdminAuthHandlerInterceptor.java +++ b/src/main/java/roomescape/auth/AdminAuthHandlerInterceptor.java @@ -17,7 +17,8 @@ public class AdminAuthHandlerInterceptor implements HandlerInterceptor { private final AuthService authService; private final TokenProvider tokenProvider; - public AdminAuthHandlerInterceptor(AuthService authService, TokenProvider tokenProvider) { + public AdminAuthHandlerInterceptor(AuthService authService, + TokenProvider tokenProvider) { this.authService = authService; this.tokenProvider = tokenProvider; } diff --git a/src/main/java/roomescape/auth/AuthenticatedMemberArgumentResolver.java b/src/main/java/roomescape/auth/AuthenticatedMemberArgumentResolver.java index 65829952a..1dadcaa56 100644 --- a/src/main/java/roomescape/auth/AuthenticatedMemberArgumentResolver.java +++ b/src/main/java/roomescape/auth/AuthenticatedMemberArgumentResolver.java @@ -19,7 +19,8 @@ public class AuthenticatedMemberArgumentResolver implements HandlerMethodArgumen private final AuthService authService; private final TokenProvider tokenProvider; - public AuthenticatedMemberArgumentResolver(AuthService authService, TokenProvider tokenProvider) { + public AuthenticatedMemberArgumentResolver(AuthService authService, + TokenProvider tokenProvider) { this.authService = authService; this.tokenProvider = tokenProvider; } @@ -27,7 +28,7 @@ public AuthenticatedMemberArgumentResolver(AuthService authService, TokenProvide @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(AuthenticatedMember.class) - && Member.class.isAssignableFrom(parameter.getParameterType()); + && Member.class.isAssignableFrom(parameter.getParameterType()); } @Override diff --git a/src/main/java/roomescape/config/AuthWebConfig.java b/src/main/java/roomescape/config/AuthWebConfig.java index b6fe867a0..200b7ea05 100644 --- a/src/main/java/roomescape/config/AuthWebConfig.java +++ b/src/main/java/roomescape/config/AuthWebConfig.java @@ -27,6 +27,6 @@ public void addArgumentResolvers(List resolvers) @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(adminAuthHandlerInterceptor).addPathPatterns("/admin/**"); + registry.addInterceptor(adminAuthHandlerInterceptor).addPathPatterns("/admin/**", "/api/admin/**"); } } diff --git a/src/main/java/roomescape/controller/api/AuthApiController.java b/src/main/java/roomescape/controller/api/AuthApiController.java index 1372a9b69..94554b6a8 100644 --- a/src/main/java/roomescape/controller/api/AuthApiController.java +++ b/src/main/java/roomescape/controller/api/AuthApiController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping; 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.RestController; import roomescape.auth.AuthenticatedMember; import roomescape.domain.Member; @@ -14,6 +15,7 @@ import roomescape.service.dto.request.LoginRequest; import roomescape.service.dto.response.MemberIdAndNameResponse; +@RequestMapping("/api") @RestController public class AuthApiController { @@ -29,7 +31,9 @@ public ResponseEntity getMemberLoginInfo(@Authenticated } @PostMapping("/login") - public ResponseEntity login(@RequestBody @Valid LoginRequest request, HttpServletResponse response) { + public ResponseEntity login(@RequestBody @Valid + LoginRequest request, + HttpServletResponse response) { String accessToken = authService.login(request); Cookie cookie = new Cookie("token", accessToken); cookie.setHttpOnly(true); diff --git a/src/main/java/roomescape/controller/api/MemberApiController.java b/src/main/java/roomescape/controller/api/MemberApiController.java index c46f7801c..fc0ab1079 100644 --- a/src/main/java/roomescape/controller/api/MemberApiController.java +++ b/src/main/java/roomescape/controller/api/MemberApiController.java @@ -21,10 +21,11 @@ public MemberApiController(MemberService memberService) { this.memberService = memberService; } - @PostMapping("/members") - public ResponseEntity signup(@RequestBody @Valid SignupRequest request) { + @PostMapping("/api/members") + public ResponseEntity signup(@RequestBody @Valid + SignupRequest request) { Member member = memberService.signUp(request); - return ResponseEntity.created(URI.create("/members/" + member.getId())) + return ResponseEntity.created(URI.create("/api/members/" + member.getId())) .body(new MemberIdAndNameResponse(member.getId(), member.getName())); } } diff --git a/src/main/java/roomescape/controller/api/ReservationApiController.java b/src/main/java/roomescape/controller/api/ReservationApiController.java index ac0132d80..943da042d 100644 --- a/src/main/java/roomescape/controller/api/ReservationApiController.java +++ b/src/main/java/roomescape/controller/api/ReservationApiController.java @@ -9,39 +9,35 @@ 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.RestController; import roomescape.auth.AuthenticatedMember; import roomescape.domain.Member; import roomescape.domain.Reservation; +import roomescape.domain.ReservationStatus; +import roomescape.domain.ReservationWaitingWithRank; import roomescape.service.dto.request.ReservationSaveRequest; +import roomescape.service.dto.response.MemberReservationResponse; import roomescape.service.dto.response.ReservationResponse; -import roomescape.service.dto.response.UserReservationResponse; -import roomescape.service.reservation.ReservationCreateService; -import roomescape.service.reservation.ReservationDeleteService; -import roomescape.service.reservation.ReservationFindService; +import roomescape.service.reservation.ReservationService; import java.net.URI; import java.util.List; @Validated +@RequestMapping("/api") @RestController public class ReservationApiController { - private final ReservationCreateService reservationCreateService; - private final ReservationFindService reservationFindService; - private final ReservationDeleteService reservationDeleteService; + private final ReservationService reservationService; - public ReservationApiController(ReservationCreateService reservationCreateService, - ReservationFindService reservationFindService, - ReservationDeleteService reservationDeleteService) { - this.reservationCreateService = reservationCreateService; - this.reservationFindService = reservationFindService; - this.reservationDeleteService = reservationDeleteService; + public ReservationApiController(ReservationService reservationService) { + this.reservationService = reservationService; } @GetMapping("/reservations") public ResponseEntity> getReservations() { - List reservations = reservationFindService.findReservations(); + List reservations = reservationService.findReservations(); return ResponseEntity.ok( reservations.stream() .map(ReservationResponse::new) @@ -50,27 +46,34 @@ public ResponseEntity> getReservations() { } @GetMapping("/reservations-mine") - public ResponseEntity> getUserReservations(@AuthenticatedMember Member member) { - List userReservations = reservationFindService.findUserReservations(member.getId()); + public ResponseEntity> getMemberReservations(@AuthenticatedMember Member member) { + List reservationWaitingWithRanks = + reservationService.findMemberReservations(member.getId()); return ResponseEntity.ok( - userReservations.stream() - .map(UserReservationResponse::new) + reservationWaitingWithRanks.stream() + .map(MemberReservationResponse::new) .toList() ); } @PostMapping("/reservations") - public ResponseEntity addReservationByUser(@RequestBody @Valid ReservationSaveRequest request, - @AuthenticatedMember Member member) { - Reservation newReservation = reservationCreateService.createReservation(request, member); - return ResponseEntity.created(URI.create("/reservations/" + newReservation.getId())) + public ResponseEntity addReservationByMember(@RequestBody @Valid + ReservationSaveRequest request, + @AuthenticatedMember Member member) { + Reservation newReservation = reservationService.createReservation( + request, + member + ); + return ResponseEntity.created(URI.create("/api/reservations/" + newReservation.getId())) .body(new ReservationResponse(newReservation)); } @DeleteMapping("/reservations/{id}") - public ResponseEntity deleteReservation(@PathVariable - @Positive(message = "1 이상의 값만 입력해주세요.") long id) { - reservationDeleteService.deleteReservation(id); + public ResponseEntity deleteWaiting(@AuthenticatedMember Member member, + @PathVariable + @Positive(message = "1 이상의 값만 입력해주세요.") + long id) { + reservationService.deleteReservation(id, member); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/roomescape/controller/api/ReservationTimeApiController.java b/src/main/java/roomescape/controller/api/ReservationTimeApiController.java index f5e776119..8c08bbe01 100644 --- a/src/main/java/roomescape/controller/api/ReservationTimeApiController.java +++ b/src/main/java/roomescape/controller/api/ReservationTimeApiController.java @@ -1,48 +1,35 @@ package roomescape.controller.api; -import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; 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.RestController; -import roomescape.domain.ReservationStatus; +import roomescape.domain.BookingStatus; import roomescape.domain.ReservationTime; -import roomescape.service.dto.request.ReservationTimeSaveRequest; -import roomescape.service.dto.response.ReservationStatusResponse; +import roomescape.service.dto.response.BookingStatusResponse; import roomescape.service.dto.response.ReservationTimeResponse; -import roomescape.service.reservationtime.ReservationTimeCreateService; -import roomescape.service.reservationtime.ReservationTimeDeleteService; -import roomescape.service.reservationtime.ReservationTimeFindService; +import roomescape.service.reservationtime.ReservationTimeService; -import java.net.URI; import java.time.LocalDate; import java.util.List; @Validated +@RequestMapping("/api/times") @RestController public class ReservationTimeApiController { - private final ReservationTimeCreateService reservationTimeCreateService; - private final ReservationTimeFindService reservationTimeFindService; - private final ReservationTimeDeleteService reservationTimeDeleteService; + private final ReservationTimeService reservationTimeService; - public ReservationTimeApiController(ReservationTimeCreateService reservationTimeCreateService, - ReservationTimeFindService reservationTimeFindService, - ReservationTimeDeleteService reservationTimeDeleteService) { - this.reservationTimeCreateService = reservationTimeCreateService; - this.reservationTimeFindService = reservationTimeFindService; - this.reservationTimeDeleteService = reservationTimeDeleteService; + public ReservationTimeApiController(ReservationTimeService reservationTimeService) { + this.reservationTimeService = reservationTimeService; } - @GetMapping("/times") + @GetMapping public ResponseEntity> getReservationTimes() { - List reservationTimes = reservationTimeFindService.findReservationTimes(); + List reservationTimes = reservationTimeService.findReservationTimes(); return ResponseEntity.ok( reservationTimes.stream() .map(ReservationTimeResponse::new) @@ -50,35 +37,21 @@ public ResponseEntity> getReservationTimes() { ); } - @GetMapping("/times/available") - public ResponseEntity> getReservationTimesIsBooked( - @RequestParam LocalDate date, - @RequestParam @Positive(message = "1 이상의 값만 입력해주세요.") long themeId) { - ReservationStatus reservationStatus = reservationTimeFindService.findIsBooked(date, themeId); + @GetMapping("/available") + public ResponseEntity> getReservationTimesIsBooked(@RequestParam LocalDate date, + @RequestParam + @Positive(message = "1 이상의 값만 입력해주세요.") + long themeId) { + BookingStatus bookingStatus = reservationTimeService.findTimeSlotsBookingStatus(date, themeId); return ResponseEntity.ok( - reservationStatus.getReservationStatus() + bookingStatus.getReservationStatus() .entrySet() .stream() - .map(status -> new ReservationStatusResponse( + .map(status -> new BookingStatusResponse( status.getKey(), status.getValue() ) ).toList() ); } - - @PostMapping("/times") - public ResponseEntity addReservationTime( - @RequestBody @Valid ReservationTimeSaveRequest request) { - ReservationTime reservationTime = reservationTimeCreateService.createReservationTime(request); - return ResponseEntity.created(URI.create("times/" + reservationTime.getId())) - .body(new ReservationTimeResponse(reservationTime)); - } - - @DeleteMapping("/times/{id}") - public ResponseEntity deleteReservationTime(@PathVariable - @Positive(message = "1 이상의 값만 입력해주세요.") long id) { - reservationTimeDeleteService.deleteReservationTime(id); - return ResponseEntity.noContent().build(); - } } diff --git a/src/main/java/roomescape/controller/api/ThemeApiController.java b/src/main/java/roomescape/controller/api/ThemeApiController.java index b61872bb6..0aba7931a 100644 --- a/src/main/java/roomescape/controller/api/ThemeApiController.java +++ b/src/main/java/roomescape/controller/api/ThemeApiController.java @@ -1,43 +1,30 @@ package roomescape.controller.api; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; -import java.net.URI; -import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; 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.RestController; import roomescape.domain.Theme; -import roomescape.service.dto.request.ThemeSaveRequest; import roomescape.service.dto.response.ThemeResponse; -import roomescape.service.theme.ThemeCreateService; -import roomescape.service.theme.ThemeDeleteService; -import roomescape.service.theme.ThemeFindService; +import roomescape.service.theme.ThemeService; + +import java.util.List; @Validated +@RequestMapping("/api/themes") @RestController public class ThemeApiController { - private final ThemeCreateService themeCreateService; - private final ThemeFindService themeFindService; - private final ThemeDeleteService themeDeleteService; + private final ThemeService themeService; - public ThemeApiController(ThemeCreateService themeCreateService, - ThemeFindService themeFindService, - ThemeDeleteService themeDeleteService) { - this.themeCreateService = themeCreateService; - this.themeFindService = themeFindService; - this.themeDeleteService = themeDeleteService; + public ThemeApiController(ThemeService themeService) { + this.themeService = themeService; } - @GetMapping("/themes") + @GetMapping public ResponseEntity> getThemes() { - List themes = themeFindService.findThemes(); + List themes = themeService.findThemes(); return ResponseEntity.ok( themes.stream() .map(ThemeResponse::new) @@ -45,27 +32,13 @@ public ResponseEntity> getThemes() { ); } - @GetMapping("/themes/ranks") + @GetMapping("/ranks") public ResponseEntity> getThemeRanks() { - List themes = themeFindService.findThemeRanks(); + List themes = themeService.findThemeRanks(); return ResponseEntity.ok( themes.stream() .map(ThemeResponse::new) .toList() ); } - - @PostMapping("/themes") - public ResponseEntity addTheme(@RequestBody @Valid ThemeSaveRequest request) { - Theme theme = themeCreateService.createTheme(request); - return ResponseEntity.created(URI.create("/themes/" + theme.getId())) - .body(new ThemeResponse(theme)); - } - - @DeleteMapping("/themes/{id}") - public ResponseEntity deleteTheme(@PathVariable - @Positive(message = "1 이상의 값만 입력해주세요.") long id) { - themeDeleteService.deleteTheme(id); - return ResponseEntity.noContent().build(); - } } diff --git a/src/main/java/roomescape/controller/api/admin/AdminMemberApiController.java b/src/main/java/roomescape/controller/api/admin/AdminMemberApiController.java index b74361074..8c9e0a028 100644 --- a/src/main/java/roomescape/controller/api/admin/AdminMemberApiController.java +++ b/src/main/java/roomescape/controller/api/admin/AdminMemberApiController.java @@ -18,7 +18,7 @@ public AdminMemberApiController(MemberService memberService) { this.memberService = memberService; } - @GetMapping("/admin/members") + @GetMapping("/api/admin/members") public ResponseEntity> getMembers() { List members = memberService.findMembers(); return ResponseEntity.ok( diff --git a/src/main/java/roomescape/controller/api/admin/AdminReservationApiController.java b/src/main/java/roomescape/controller/api/admin/AdminReservationApiController.java index 2dff39072..77077281e 100644 --- a/src/main/java/roomescape/controller/api/admin/AdminReservationApiController.java +++ b/src/main/java/roomescape/controller/api/admin/AdminReservationApiController.java @@ -5,37 +5,42 @@ import org.springframework.web.bind.annotation.GetMapping; 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.RestController; +import roomescape.auth.AuthenticatedMember; +import roomescape.domain.Member; import roomescape.domain.Reservation; +import roomescape.domain.ReservationStatus; import roomescape.service.dto.request.ReservationAdminSaveRequest; import roomescape.service.dto.response.ReservationResponse; -import roomescape.service.reservation.AdminReservationCreateService; -import roomescape.service.reservation.ReservationFindService; +import roomescape.service.dto.response.WaitingResponse; +import roomescape.service.reservation.AdminReservationService; +import roomescape.service.reservation.ReservationService; import java.net.URI; import java.time.LocalDate; import java.util.List; +@RequestMapping("/api/admin/reservations") @RestController public class AdminReservationApiController { - private final AdminReservationCreateService adminReservationCreateService; - private final ReservationFindService reservationFindService; + private final AdminReservationService adminReservationService; + private final ReservationService reservationService; - public AdminReservationApiController(AdminReservationCreateService adminReservationCreateService, - ReservationFindService reservationFindService) { - this.adminReservationCreateService = adminReservationCreateService; - this.reservationFindService = reservationFindService; + public AdminReservationApiController(AdminReservationService adminReservationService, + ReservationService reservationService) { + this.adminReservationService = adminReservationService; + this.reservationService = reservationService; } - @GetMapping("/admin/reservations/search") + @GetMapping("/search") public ResponseEntity> getSearchingReservations(@RequestParam long memberId, @RequestParam long themeId, @RequestParam LocalDate dateFrom, - @RequestParam LocalDate dateTo - ) { - List reservations = reservationFindService.searchReservations(memberId, themeId, dateFrom, dateTo); + @RequestParam LocalDate dateTo) { + List reservations = reservationService.searchReservations(memberId, themeId, dateFrom, dateTo); return ResponseEntity.ok( reservations.stream() .map(ReservationResponse::new) @@ -43,10 +48,21 @@ public ResponseEntity> getSearchingReservations(@Reque ); } - @PostMapping("/admin/reservations") - public ResponseEntity addReservationByAdmin(@RequestBody @Valid ReservationAdminSaveRequest request) { - Reservation newReservation = adminReservationCreateService.createReservation(request); - return ResponseEntity.created(URI.create("/admin/reservations/" + newReservation.getId())) + @GetMapping("/waiting-list") + public ResponseEntity> getWaiting(@AuthenticatedMember Member member) { + List waitings = reservationService.findByReservationStatus(ReservationStatus.WAITING); + return ResponseEntity.ok( + waitings.stream() + .map(WaitingResponse::new) + .toList() + ); + } + + @PostMapping + public ResponseEntity addReservationByAdmin(@RequestBody @Valid + ReservationAdminSaveRequest request) { + Reservation newReservation = adminReservationService.createReservation(request); + return ResponseEntity.created(URI.create("/api/admin/reservations/" + newReservation.getId())) .body(new ReservationResponse(newReservation)); } } diff --git a/src/main/java/roomescape/controller/api/admin/AdminReservationTimeApiController.java b/src/main/java/roomescape/controller/api/admin/AdminReservationTimeApiController.java new file mode 100644 index 000000000..55d2b89b5 --- /dev/null +++ b/src/main/java/roomescape/controller/api/admin/AdminReservationTimeApiController.java @@ -0,0 +1,44 @@ +package roomescape.controller.api.admin; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RestController; +import roomescape.domain.ReservationTime; +import roomescape.service.dto.request.ReservationTimeSaveRequest; +import roomescape.service.dto.response.ReservationTimeResponse; +import roomescape.service.reservationtime.ReservationTimeService; + +import java.net.URI; + +@RequestMapping("/api/admin/times") +@RestController +public class AdminReservationTimeApiController { + + private final ReservationTimeService reservationTimeService; + + public AdminReservationTimeApiController(ReservationTimeService reservationTimeService) { + this.reservationTimeService = reservationTimeService; + } + + @PostMapping + public ResponseEntity addReservationTime(@RequestBody @Valid + ReservationTimeSaveRequest request) { + ReservationTime reservationTime = reservationTimeService.createReservationTime(request); + return ResponseEntity.created(URI.create("api/times/" + reservationTime.getId())) + .body(new ReservationTimeResponse(reservationTime)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteReservationTime(@PathVariable + @Positive(message = "1 이상의 값만 입력해주세요.") + long id) { + reservationTimeService.deleteReservationTime(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/roomescape/controller/api/admin/AdminThemeApiController.java b/src/main/java/roomescape/controller/api/admin/AdminThemeApiController.java new file mode 100644 index 000000000..39e6b2cf5 --- /dev/null +++ b/src/main/java/roomescape/controller/api/admin/AdminThemeApiController.java @@ -0,0 +1,44 @@ +package roomescape.controller.api.admin; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RestController; +import roomescape.domain.Theme; +import roomescape.service.dto.request.ThemeSaveRequest; +import roomescape.service.dto.response.ThemeResponse; +import roomescape.service.theme.ThemeService; + +import java.net.URI; + +@RequestMapping("/api/admin/themes") +@RestController +public class AdminThemeApiController { + + private final ThemeService themeService; + + public AdminThemeApiController(ThemeService themeService) { + this.themeService = themeService; + } + + @PostMapping + public ResponseEntity addTheme(@RequestBody @Valid + ThemeSaveRequest request) { + Theme theme = themeService.createTheme(request); + return ResponseEntity.created(URI.create("/api/themes/" + theme.getId())) + .body(new ThemeResponse(theme)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteTheme(@PathVariable + @Positive(message = "1 이상의 값만 입력해주세요.") + long id) { + themeService.deleteTheme(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/roomescape/controller/web/AdminController.java b/src/main/java/roomescape/controller/web/AdminController.java index 226c546d2..a4e48892a 100644 --- a/src/main/java/roomescape/controller/web/AdminController.java +++ b/src/main/java/roomescape/controller/web/AdminController.java @@ -11,18 +11,23 @@ public String adminPage() { return "admin/index"; } - @GetMapping("/admin/time") + @GetMapping("/admin/times") public String reservationTimeAdminPage() { return "admin/time"; } - @GetMapping("/admin/reservation") + @GetMapping("/admin/reservations") public String reservationAdminPage() { return "admin/reservation-new"; } - @GetMapping("/admin/theme") + @GetMapping("/admin/themes") public String themeAdminPage() { return "admin/theme"; } + + @GetMapping("/admin/waitings") + public String waitingAdminPage() { + return "admin/waiting"; + } } diff --git a/src/main/java/roomescape/controller/web/UserController.java b/src/main/java/roomescape/controller/web/MemberController.java similarity index 71% rename from src/main/java/roomescape/controller/web/UserController.java rename to src/main/java/roomescape/controller/web/MemberController.java index 07af6bae1..9c294bbf3 100644 --- a/src/main/java/roomescape/controller/web/UserController.java +++ b/src/main/java/roomescape/controller/web/MemberController.java @@ -4,15 +4,15 @@ import org.springframework.web.bind.annotation.GetMapping; @Controller -public class UserController { +public class MemberController { - @GetMapping("/reservation") - public String reservationUserPage() { + @GetMapping("/reservations") + public String reservationMemberPage() { return "reservation"; } @GetMapping - public String mainUserPage() { + public String mainMemberPage() { return "index"; } diff --git a/src/main/java/roomescape/domain/BaseTime.java b/src/main/java/roomescape/domain/BaseTime.java new file mode 100644 index 000000000..ab36aa24d --- /dev/null +++ b/src/main/java/roomescape/domain/BaseTime.java @@ -0,0 +1,16 @@ +package roomescape.domain; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseTime { + + @CreatedDate + private LocalDateTime createdAt; +} diff --git a/src/main/java/roomescape/domain/BookingStatus.java b/src/main/java/roomescape/domain/BookingStatus.java new file mode 100644 index 000000000..6dbd3a7e2 --- /dev/null +++ b/src/main/java/roomescape/domain/BookingStatus.java @@ -0,0 +1,50 @@ +package roomescape.domain; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class BookingStatus { + + private final Map reservationStatus; + + private BookingStatus(Map reservationStatus) { + this.reservationStatus = reservationStatus; + } + + public static BookingStatus of(List reservedTimes, + List reservationTimes) { + Map reservationStatus = new HashMap<>(); + for (ReservationTime reservationTime : reservationTimes) { + reservationStatus.put(reservationTime, isReserved(reservedTimes, reservationTime)); + } + return new BookingStatus(reservationStatus); + } + + private static boolean isReserved(List reservedTimes, + ReservationTime reservationTime) { + return reservedTimes.contains(reservationTime); + } + + public Map getReservationStatus() { + return reservationStatus; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + BookingStatus that = (BookingStatus) object; + return Objects.equals(reservationStatus, that.reservationStatus); + } + + @Override + public int hashCode() { + return Objects.hash(reservationStatus); + } +} diff --git a/src/main/java/roomescape/domain/Email.java b/src/main/java/roomescape/domain/Email.java index ff9be38be..b8e486eaf 100644 --- a/src/main/java/roomescape/domain/Email.java +++ b/src/main/java/roomescape/domain/Email.java @@ -8,7 +8,8 @@ @Embeddable public class Email { - private static final Pattern EMAIL_REGEX = Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"); + private static final Pattern EMAIL_REGEX = + Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"); private String email; diff --git a/src/main/java/roomescape/domain/Member.java b/src/main/java/roomescape/domain/Member.java index deec2851a..b9a0ff30f 100644 --- a/src/main/java/roomescape/domain/Member.java +++ b/src/main/java/roomescape/domain/Member.java @@ -11,7 +11,7 @@ import java.util.Objects; @Entity -public class Member { +public class Member extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -23,7 +23,12 @@ public class Member { @Enumerated(value = EnumType.STRING) private Role role; - public Member(Long id, String name, String email, String password, Role role) { + public Member(Long id, + String name, + String email, + String password, + Role role + ) { validate(name, email, password, role); this.id = id; this.name = name; @@ -32,14 +37,20 @@ public Member(Long id, String name, String email, String password, Role role) { this.role = role; } - public Member(String name, String email, String password, Role role) { + public Member(String name, + String email, + String password, + Role role) { this(null, name, email, password, role); } protected Member() { } - private void validate(String name, String email, String password, Role role) { + private void validate(String name, + String email, + String password, + Role role) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("사용자의 이름을 입력해주세요."); } diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index 08318a809..46956389e 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -1,6 +1,8 @@ package roomescape.domain; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -11,7 +13,7 @@ import java.util.Objects; @Entity -public class Reservation { +public class Reservation extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -28,23 +30,40 @@ public class Reservation { @ManyToOne(fetch = FetchType.LAZY) private Theme theme; - public Reservation(Long id, Member member, LocalDate date, ReservationTime reservationTime, Theme theme) { - validate(member, date, reservationTime, theme); + @Enumerated(EnumType.STRING) + private ReservationStatus reservationStatus; + + public Reservation(Long id, + Member member, + LocalDate date, + ReservationTime reservationTime, + Theme theme, + ReservationStatus reservationStatus) { + validate(member, date, reservationTime, theme, reservationStatus); this.id = id; this.member = member; this.date = date; this.reservationTime = reservationTime; this.theme = theme; + this.reservationStatus = reservationStatus; } - public Reservation(Member member, LocalDate date, ReservationTime reservationTime, Theme theme) { - this(null, member, date, reservationTime, theme); + public Reservation(Member member, + LocalDate date, + ReservationTime reservationTime, + Theme theme, + ReservationStatus reservationStatus) { + this(null, member, date, reservationTime, theme, reservationStatus); } protected Reservation() { } - private void validate(Member member, LocalDate date, ReservationTime reservationTime, Theme theme) { + private void validate(Member member, + LocalDate date, + ReservationTime reservationTime, + Theme theme, + ReservationStatus reservationStatus) { if (member == null) { throw new IllegalArgumentException("예약하려는 사용자를 선택해주세요."); } @@ -57,6 +76,21 @@ private void validate(Member member, LocalDate date, ReservationTime reservation if (theme == null) { throw new IllegalArgumentException("예약하려는 테마를 선택해주세요."); } + if (reservationStatus == null) { + throw new IllegalArgumentException("예약상태가 지정되지 않았습니다."); + } + } + + public boolean isReserved() { + return reservationStatus.isReserved(); + } + + public boolean isNotOwnedBy(Member member) { + return !this.member.equals(member); + } + + public void changeReservationStatus(ReservationStatus reservationStatus) { + this.reservationStatus = reservationStatus; } public Long getId() { @@ -79,6 +113,10 @@ public Theme getTheme() { return theme; } + public ReservationStatus getReservationStatus() { + return reservationStatus; + } + @Override public boolean equals(Object object) { if (this == object) { diff --git a/src/main/java/roomescape/domain/ReservationStatus.java b/src/main/java/roomescape/domain/ReservationStatus.java index 52893064e..9bf4ab1cb 100644 --- a/src/main/java/roomescape/domain/ReservationStatus.java +++ b/src/main/java/roomescape/domain/ReservationStatus.java @@ -1,49 +1,21 @@ package roomescape.domain; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +public enum ReservationStatus { -public class ReservationStatus { + RESERVED("예약"), + WAITING("예약대기"); - private final Map reservationStatus; + private final String status; - private ReservationStatus(Map reservationStatus) { - this.reservationStatus = reservationStatus; + ReservationStatus(String status) { + this.status = status; } - public static ReservationStatus of(List reservedTimes, List reservationTimes) { - Map reservationStatus = new HashMap<>(); - for (ReservationTime reservationTime : reservationTimes) { - reservationStatus.put(reservationTime, isReserved(reservedTimes, reservationTime)); - } - return new ReservationStatus(reservationStatus); + public boolean isReserved() { + return this == RESERVED; } - private static boolean isReserved(List reservedTimes, ReservationTime reservationTime) { - return reservedTimes.stream() - .anyMatch(reservedTime -> reservedTime.equals(reservationTime)); - } - - public Map getReservationStatus() { - return reservationStatus; - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object == null || getClass() != object.getClass()) { - return false; - } - ReservationStatus that = (ReservationStatus) object; - return Objects.equals(reservationStatus, that.reservationStatus); - } - - @Override - public int hashCode() { - return Objects.hash(reservationStatus); + public String getStatus() { + return status; } } diff --git a/src/main/java/roomescape/domain/ReservationTime.java b/src/main/java/roomescape/domain/ReservationTime.java index 64857f30c..c4e4795dc 100644 --- a/src/main/java/roomescape/domain/ReservationTime.java +++ b/src/main/java/roomescape/domain/ReservationTime.java @@ -9,7 +9,7 @@ import java.util.Objects; @Entity -public class ReservationTime { +public class ReservationTime extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -20,7 +20,8 @@ public ReservationTime(LocalTime startAt) { this.startAt = startAt; } - public ReservationTime(Long id, LocalTime startAt) { + public ReservationTime(Long id, + LocalTime startAt) { validate(startAt); this.id = id; this.startAt = startAt; diff --git a/src/main/java/roomescape/domain/ReservationWaitingWithRank.java b/src/main/java/roomescape/domain/ReservationWaitingWithRank.java new file mode 100644 index 000000000..dde192d55 --- /dev/null +++ b/src/main/java/roomescape/domain/ReservationWaitingWithRank.java @@ -0,0 +1,21 @@ +package roomescape.domain; + +public class ReservationWaitingWithRank { + + private final Reservation reservation; + private final Long rank; + + public ReservationWaitingWithRank(Reservation reservation, + Long rank) { + this.reservation = reservation; + this.rank = rank; + } + + public Reservation getReservation() { + return reservation; + } + + public Long getRank() { + return rank; + } +} diff --git a/src/main/java/roomescape/domain/Role.java b/src/main/java/roomescape/domain/Role.java index ff3c4de21..1725a6110 100644 --- a/src/main/java/roomescape/domain/Role.java +++ b/src/main/java/roomescape/domain/Role.java @@ -3,5 +3,5 @@ public enum Role { ADMIN, - USER + MEMBER } diff --git a/src/main/java/roomescape/domain/Theme.java b/src/main/java/roomescape/domain/Theme.java index 66effaff1..c7d84c6c8 100644 --- a/src/main/java/roomescape/domain/Theme.java +++ b/src/main/java/roomescape/domain/Theme.java @@ -8,7 +8,7 @@ import java.util.Objects; @Entity -public class Theme { +public class Theme extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -17,21 +17,28 @@ public class Theme { private String description; private String thumbnail; - public Theme(Long id, String name, String description, String thumbnail) { + public Theme(Long id, + String name, + String description, + String thumbnail) { this.id = id; this.name = name; this.description = description; this.thumbnail = thumbnail; } - public Theme(String name, String description, String thumbnail) { + public Theme(String name, + String description, + String thumbnail) { this(null, name, description, thumbnail); } protected Theme() { } - private void validate(String name, String description, String thumbnail) { + private void validate(String name, + String description, + String thumbnail) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("테마 이름을 선택해주세요"); } diff --git a/src/main/java/roomescape/repository/MemberRepository.java b/src/main/java/roomescape/repository/MemberRepository.java index ee990f8a4..baaf2aa30 100644 --- a/src/main/java/roomescape/repository/MemberRepository.java +++ b/src/main/java/roomescape/repository/MemberRepository.java @@ -10,7 +10,8 @@ @Repository public interface MemberRepository extends JpaRepository { - Optional findByEmailAndPassword(Email email, String password); + Optional findByEmailAndPassword(Email email, + String password); boolean existsByEmail(Email email); } diff --git a/src/main/java/roomescape/repository/ReservationRepository.java b/src/main/java/roomescape/repository/ReservationRepository.java index c72a047e4..c136ead51 100644 --- a/src/main/java/roomescape/repository/ReservationRepository.java +++ b/src/main/java/roomescape/repository/ReservationRepository.java @@ -1,21 +1,82 @@ package roomescape.repository; -import java.time.LocalDate; -import java.util.List; +import org.springframework.data.domain.Limit; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import roomescape.domain.Member; import roomescape.domain.Reservation; +import roomescape.domain.ReservationStatus; +import roomescape.domain.ReservationTime; +import roomescape.domain.ReservationWaitingWithRank; +import roomescape.domain.Theme; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; @Repository public interface ReservationRepository extends JpaRepository { boolean existsByThemeId(long themeId); - List findByMemberIdAndThemeIdAndDateBetween(long memberId, long themeId, LocalDate dateFrom, + List findByMemberIdAndThemeIdAndDateBetween(long memberId, + long themeId, + LocalDate dateFrom, LocalDate dateTo); - List findByMemberId(long memberId); - - boolean existsByDateAndReservationTimeIdAndThemeId(LocalDate date, long timeId, long themeId); + boolean existsByDateAndReservationTimeIdAndThemeId(LocalDate date, + long timeId, + long themeId); boolean existsByReservationTimeId(long timeId); + + @Query(""" + SELECT + CASE + WHEN COUNT(r) > 0 THEN true + ELSE false + END + FROM Reservation r + WHERE r.member = :member + AND r.theme = :theme + AND r.reservationTime = :reservationTime + AND r.date = :date + """) + boolean hasBookedReservation(@Param("member") Member member, + @Param("theme") Theme theme, + @Param("reservationTime") ReservationTime reservationTime, + @Param("date") LocalDate date); + + @Query(""" + SELECT new roomescape.domain.ReservationWaitingWithRank( + r, (SELECT COUNT(r2) + 1 + FROM Reservation r2 + WHERE r2.theme = r.theme + AND r2.date = r.date + AND r2.reservationTime = r.reservationTime + AND r2.reservationStatus = r.reservationStatus + AND r2.createdAt < r.createdAt) + ) + FROM Reservation r + WHERE r.member.id = :memberId + """) + List findReservationWaitingWithRankByMemberId(@Param("memberId") long memberId); + + List findByReservationStatus(ReservationStatus reservationStatus); + + @Query(""" + SELECT r + FROM Reservation r + WHERE r.theme = :theme + AND r.reservationTime = :reservationTime + AND r.date = :date + AND r.reservationStatus = 'WAITING' + ORDER BY r.createdAt + """) + Optional findNextWaiting(@Param("theme") Theme theme, + @Param("reservationTime") ReservationTime reservationTime, + @Param("date") LocalDate date, + Limit limit); } + diff --git a/src/main/java/roomescape/service/auth/AuthService.java b/src/main/java/roomescape/service/auth/AuthService.java index 767a57cea..a686fcfab 100644 --- a/src/main/java/roomescape/service/auth/AuthService.java +++ b/src/main/java/roomescape/service/auth/AuthService.java @@ -13,7 +13,8 @@ public class AuthService { private final MemberRepository memberRepository; private final TokenProvider tokenProvider; - public AuthService(MemberRepository memberRepository, TokenProvider tokenProvider) { + public AuthService(MemberRepository memberRepository, + TokenProvider tokenProvider) { this.memberRepository = memberRepository; this.tokenProvider = tokenProvider; } diff --git a/src/main/java/roomescape/service/dto/request/ReservationAdminSaveRequest.java b/src/main/java/roomescape/service/dto/request/ReservationAdminSaveRequest.java index 803198ed9..abc24535b 100644 --- a/src/main/java/roomescape/service/dto/request/ReservationAdminSaveRequest.java +++ b/src/main/java/roomescape/service/dto/request/ReservationAdminSaveRequest.java @@ -1,19 +1,29 @@ package roomescape.service.dto.request; import jakarta.validation.constraints.NotNull; -import java.time.LocalDate; import roomescape.domain.Member; import roomescape.domain.Reservation; +import roomescape.domain.ReservationStatus; import roomescape.domain.ReservationTime; import roomescape.domain.Theme; +import java.time.LocalDate; + public record ReservationAdminSaveRequest(@NotNull(message = "멤버를 입력해주세요") Long memberId, @NotNull(message = "예약 날짜를 입력해주세요.") LocalDate date, @NotNull(message = "예약 시간을 입력해주세요.") Long timeId, @NotNull(message = "예약 테마를 입력해주세요.") Long themeId) { - public Reservation toEntity(ReservationAdminSaveRequest request, ReservationTime reservationTime, - Theme theme, Member member) { - return new Reservation(member, request.date(), reservationTime, theme); + public Reservation toEntity(ReservationAdminSaveRequest request, + ReservationTime reservationTime, + Theme theme, + Member member) { + return new Reservation( + member, + request.date(), + reservationTime, + theme, + ReservationStatus.RESERVED + ); } } diff --git a/src/main/java/roomescape/service/dto/request/ReservationSaveRequest.java b/src/main/java/roomescape/service/dto/request/ReservationSaveRequest.java index 6e816959b..261e86ba7 100644 --- a/src/main/java/roomescape/service/dto/request/ReservationSaveRequest.java +++ b/src/main/java/roomescape/service/dto/request/ReservationSaveRequest.java @@ -1,18 +1,28 @@ package roomescape.service.dto.request; import jakarta.validation.constraints.NotNull; -import java.time.LocalDate; import roomescape.domain.Member; import roomescape.domain.Reservation; +import roomescape.domain.ReservationStatus; import roomescape.domain.ReservationTime; import roomescape.domain.Theme; +import java.time.LocalDate; + public record ReservationSaveRequest(@NotNull(message = "예약 날짜를 입력해주세요.") LocalDate date, @NotNull(message = "예약 시간을 입력해주세요.") Long timeId, - @NotNull(message = "예약 테마를 입력해주세요.") Long themeId) { + @NotNull(message = "예약 테마를 입력해주세요.") Long themeId, + @NotNull(message = "예약 상태를 입력해주세요.") ReservationStatus reservationStatus) { - public Reservation toEntity(ReservationSaveRequest request, ReservationTime reservationTime, - Theme theme, Member member) { - return new Reservation(member, request.date(), reservationTime, theme); + public Reservation toEntity(ReservationTime reservationTime, + Theme theme, + Member member) { + return new Reservation( + member, + date, + reservationTime, + theme, + reservationStatus + ); } } diff --git a/src/main/java/roomescape/service/dto/request/SignupRequest.java b/src/main/java/roomescape/service/dto/request/SignupRequest.java index 825e433d5..a4aefe87a 100644 --- a/src/main/java/roomescape/service/dto/request/SignupRequest.java +++ b/src/main/java/roomescape/service/dto/request/SignupRequest.java @@ -12,6 +12,11 @@ public record SignupRequest(@Email(message = "잘못된 이메일 형식입니 @NotBlank(message = "이름을 입력해주세요.") String name) { public Member toEntity(SignupRequest request) { - return new Member(request.name(), request.email(), request.password(), Role.USER); + return new Member( + request.name(), + request.email(), + request.password(), + Role.MEMBER + ); } } diff --git a/src/main/java/roomescape/service/dto/request/ThemeSaveRequest.java b/src/main/java/roomescape/service/dto/request/ThemeSaveRequest.java index f714c3a23..21d44f5be 100644 --- a/src/main/java/roomescape/service/dto/request/ThemeSaveRequest.java +++ b/src/main/java/roomescape/service/dto/request/ThemeSaveRequest.java @@ -9,6 +9,10 @@ public record ThemeSaveRequest(@NotBlank(message = "테마 이름을 입력해 @NotNull(message = "테마 썸네일을 입력해주세요.") String thumbnail) { public Theme toEntity(ThemeSaveRequest request) { - return new Theme(request.name(), request.description(), request.thumbnail()); + return new Theme( + request.name(), + request.description(), + request.thumbnail() + ); } } diff --git a/src/main/java/roomescape/service/dto/response/BookingStatusResponse.java b/src/main/java/roomescape/service/dto/response/BookingStatusResponse.java new file mode 100644 index 000000000..8fe1fc09f --- /dev/null +++ b/src/main/java/roomescape/service/dto/response/BookingStatusResponse.java @@ -0,0 +1,20 @@ +package roomescape.service.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import roomescape.domain.ReservationTime; + +import java.time.LocalTime; + +public record BookingStatusResponse(@JsonFormat(pattern = "HH:mm") LocalTime startAt, + Long timeId, + boolean alreadyBooked) { + + public BookingStatusResponse(ReservationTime reservationTime, + boolean alreadyBooked) { + this( + reservationTime.getStartAt(), + reservationTime.getId(), + alreadyBooked + ); + } +} diff --git a/src/main/java/roomescape/service/dto/response/MemberIdAndNameResponse.java b/src/main/java/roomescape/service/dto/response/MemberIdAndNameResponse.java index a9f3db2e2..aff33cf3f 100644 --- a/src/main/java/roomescape/service/dto/response/MemberIdAndNameResponse.java +++ b/src/main/java/roomescape/service/dto/response/MemberIdAndNameResponse.java @@ -1,4 +1,5 @@ package roomescape.service.dto.response; -public record MemberIdAndNameResponse(Long id, String name) { +public record MemberIdAndNameResponse(Long id, + String name) { } diff --git a/src/main/java/roomescape/service/dto/response/MemberReservationResponse.java b/src/main/java/roomescape/service/dto/response/MemberReservationResponse.java new file mode 100644 index 000000000..caea2a8d6 --- /dev/null +++ b/src/main/java/roomescape/service/dto/response/MemberReservationResponse.java @@ -0,0 +1,33 @@ +package roomescape.service.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import roomescape.domain.ReservationStatus; +import roomescape.domain.ReservationWaitingWithRank; + +import java.time.LocalDate; +import java.time.LocalTime; + +public record MemberReservationResponse(Long reservationId, + String theme, + @JsonFormat(pattern = "YYYY-MM-dd") LocalDate date, + @JsonFormat(pattern = "HH:mm") LocalTime time, + String status) { + + public MemberReservationResponse(ReservationWaitingWithRank reservationWaitingWithRank) { + this( + reservationWaitingWithRank.getReservation().getId(), + reservationWaitingWithRank.getReservation().getTheme().getName(), + reservationWaitingWithRank.getReservation().getDate(), + reservationWaitingWithRank.getReservation().getReservationTime().getStartAt(), + reservationStatusToString(reservationWaitingWithRank) + ); + } + + private static String reservationStatusToString(ReservationWaitingWithRank reservationWaitingWithRank) { + ReservationStatus reservationStatus = reservationWaitingWithRank.getReservation().getReservationStatus(); + if (reservationStatus.isReserved()) { + return reservationStatus.getStatus(); + } + return reservationWaitingWithRank.getRank() + "번째 " + reservationStatus.getStatus(); + } +} diff --git a/src/main/java/roomescape/service/dto/response/ReservationResponse.java b/src/main/java/roomescape/service/dto/response/ReservationResponse.java index 75579677b..971f0351f 100644 --- a/src/main/java/roomescape/service/dto/response/ReservationResponse.java +++ b/src/main/java/roomescape/service/dto/response/ReservationResponse.java @@ -1,8 +1,9 @@ package roomescape.service.dto.response; -import java.time.LocalDate; import roomescape.domain.Reservation; +import java.time.LocalDate; + public record ReservationResponse(Long id, MemberIdAndNameResponse member, LocalDate date, @@ -10,11 +11,20 @@ public record ReservationResponse(Long id, ThemeResponse theme) { public ReservationResponse(Reservation reservation) { - this(reservation.getId(), - new MemberIdAndNameResponse(reservation.getMember().getId(), reservation.getMember().getName()), + this( + reservation.getId(), + new MemberIdAndNameResponse( + reservation.getMember().getId(), + reservation.getMember().getName() + ), reservation.getDate(), - new ReservationTimeResponse(reservation.getReservationTime()), - new ThemeResponse(reservation.getTheme())); + new ReservationTimeResponse( + reservation.getReservationTime() + ), + new ThemeResponse( + reservation.getTheme() + ) + ); } } diff --git a/src/main/java/roomescape/service/dto/response/ReservationStatusResponse.java b/src/main/java/roomescape/service/dto/response/ReservationStatusResponse.java deleted file mode 100644 index ea27957db..000000000 --- a/src/main/java/roomescape/service/dto/response/ReservationStatusResponse.java +++ /dev/null @@ -1,11 +0,0 @@ -package roomescape.service.dto.response; - -import java.time.LocalTime; -import roomescape.domain.ReservationTime; - -public record ReservationStatusResponse(LocalTime startAt, Long timeId, boolean alreadyBooked) { - - public ReservationStatusResponse(ReservationTime reservationTime, boolean alreadyBooked) { - this(reservationTime.getStartAt(), reservationTime.getId(), alreadyBooked); - } -} diff --git a/src/main/java/roomescape/service/dto/response/ReservationTimeResponse.java b/src/main/java/roomescape/service/dto/response/ReservationTimeResponse.java index 12c773703..3987ba4a6 100644 --- a/src/main/java/roomescape/service/dto/response/ReservationTimeResponse.java +++ b/src/main/java/roomescape/service/dto/response/ReservationTimeResponse.java @@ -1,10 +1,13 @@ package roomescape.service.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; + import java.time.LocalTime; + import roomescape.domain.ReservationTime; -public record ReservationTimeResponse(Long id, @JsonFormat(pattern = "HH:mm") LocalTime startAt) { +public record ReservationTimeResponse(Long id, + @JsonFormat(pattern = "HH:mm") LocalTime startAt) { public ReservationTimeResponse(ReservationTime reservationTime) { this(reservationTime.getId(), reservationTime.getStartAt()); diff --git a/src/main/java/roomescape/service/dto/response/ThemeResponse.java b/src/main/java/roomescape/service/dto/response/ThemeResponse.java index 35a7c2d9c..20a7c1333 100644 --- a/src/main/java/roomescape/service/dto/response/ThemeResponse.java +++ b/src/main/java/roomescape/service/dto/response/ThemeResponse.java @@ -2,9 +2,17 @@ import roomescape.domain.Theme; -public record ThemeResponse(Long id, String name, String description, String thumbnail) { +public record ThemeResponse(Long id, + String name, + String description, + String thumbnail) { public ThemeResponse(Theme theme) { - this(theme.getId(), theme.getName(), theme.getDescription(), theme.getThumbnail()); + this( + theme.getId(), + theme.getName(), + theme.getDescription(), + theme.getThumbnail() + ); } } diff --git a/src/main/java/roomescape/service/dto/response/UserReservationResponse.java b/src/main/java/roomescape/service/dto/response/UserReservationResponse.java deleted file mode 100644 index 137918170..000000000 --- a/src/main/java/roomescape/service/dto/response/UserReservationResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package roomescape.service.dto.response; - -import com.fasterxml.jackson.annotation.JsonFormat; -import java.time.LocalDate; -import java.time.LocalTime; -import roomescape.domain.Reservation; - -public record UserReservationResponse(Long reservationId, - String theme, - @JsonFormat(pattern = "YYYY-MM-dd") LocalDate date, - @JsonFormat(pattern = "HH:mm") LocalTime time, - String status) { - - public UserReservationResponse(Reservation reservation) { - this(reservation.getId(), - reservation.getTheme().getName(), - reservation.getDate(), - reservation.getReservationTime().getStartAt(), - "예약"); - } -} diff --git a/src/main/java/roomescape/service/dto/response/WaitingResponse.java b/src/main/java/roomescape/service/dto/response/WaitingResponse.java new file mode 100644 index 000000000..f4c05959c --- /dev/null +++ b/src/main/java/roomescape/service/dto/response/WaitingResponse.java @@ -0,0 +1,24 @@ +package roomescape.service.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import roomescape.domain.Reservation; + +import java.time.LocalDate; +import java.time.LocalTime; + +public record WaitingResponse(Long id, + String name, + String theme, + @JsonFormat(pattern = "YYYY-MM-dd") LocalDate date, + @JsonFormat(pattern = "HH:mm") LocalTime startAt) { + + public WaitingResponse(Reservation reservation) { + this( + reservation.getId(), + reservation.getMember().getName(), + reservation.getTheme().getName(), + reservation.getDate(), + reservation.getReservationTime().getStartAt() + ); + } +} diff --git a/src/main/java/roomescape/service/reservation/AdminReservationCreateService.java b/src/main/java/roomescape/service/reservation/AdminReservationService.java similarity index 58% rename from src/main/java/roomescape/service/reservation/AdminReservationCreateService.java rename to src/main/java/roomescape/service/reservation/AdminReservationService.java index 835b3e72a..8dbb28f9c 100644 --- a/src/main/java/roomescape/service/reservation/AdminReservationCreateService.java +++ b/src/main/java/roomescape/service/reservation/AdminReservationService.java @@ -3,18 +3,20 @@ import org.springframework.stereotype.Service; import roomescape.domain.Member; import roomescape.domain.Reservation; +import roomescape.domain.ReservationStatus; import roomescape.domain.ReservationTime; import roomescape.domain.Theme; import roomescape.repository.ReservationRepository; import roomescape.service.dto.request.ReservationAdminSaveRequest; @Service -public class AdminReservationCreateService { +public class AdminReservationService { private final ReservationRepository reservationRepository; private final ReservationCreateValidator reservationCreateValidator; - public AdminReservationCreateService(ReservationRepository reservationRepository, ReservationCreateValidator reservationCreateValidator) { + public AdminReservationService(ReservationRepository reservationRepository, + ReservationCreateValidator reservationCreateValidator) { this.reservationRepository = reservationRepository; this.reservationCreateValidator = reservationCreateValidator; } @@ -23,10 +25,25 @@ public Reservation createReservation(ReservationAdminSaveRequest request) { ReservationTime reservationTime = reservationCreateValidator.getValidReservationTime(request.timeId()); reservationCreateValidator.validateDateIsFuture(request.date(), reservationTime); Theme theme = reservationCreateValidator.getValidTheme(request.themeId()); - reservationCreateValidator.validateAlreadyBooked(request.date(), request.timeId(), request.themeId()); + reservationCreateValidator.validateAlreadyBooked( + request.date(), + request.timeId(), + request.themeId(), + ReservationStatus.RESERVED + ); Member member = reservationCreateValidator.getValidMember(request.memberId()); - - Reservation reservation = request.toEntity(request, reservationTime, theme, member); + reservationCreateValidator.validateOwnReservationExist( + member, + theme, + reservationTime, + request.date() + ); + Reservation reservation = request.toEntity( + request, + reservationTime, + theme, + member + ); return reservationRepository.save(reservation); } } diff --git a/src/main/java/roomescape/service/reservation/ReservationCreateService.java b/src/main/java/roomescape/service/reservation/ReservationCreateService.java deleted file mode 100644 index 7aaec14ef..000000000 --- a/src/main/java/roomescape/service/reservation/ReservationCreateService.java +++ /dev/null @@ -1,32 +0,0 @@ -package roomescape.service.reservation; - -import org.springframework.stereotype.Service; -import roomescape.domain.Member; -import roomescape.domain.Reservation; -import roomescape.domain.ReservationTime; -import roomescape.domain.Theme; -import roomescape.repository.ReservationRepository; -import roomescape.service.dto.request.ReservationSaveRequest; - -@Service -public class ReservationCreateService { - - private final ReservationCreateValidator reservationCreateValidator; - private final ReservationRepository reservationRepository; - - public ReservationCreateService(ReservationCreateValidator reservationCreateValidator, - ReservationRepository reservationRepository) { - this.reservationCreateValidator = reservationCreateValidator; - this.reservationRepository = reservationRepository; - } - - public Reservation createReservation(ReservationSaveRequest request, Member member) { - ReservationTime reservationTime = reservationCreateValidator.getValidReservationTime(request.timeId()); - reservationCreateValidator.validateDateIsFuture(request.date(), reservationTime); - Theme theme = reservationCreateValidator.getValidTheme(request.themeId()); - reservationCreateValidator.validateAlreadyBooked(request.date(), request.timeId(), request.themeId()); - - Reservation reservation = request.toEntity(request, reservationTime, theme, member); - return reservationRepository.save(reservation); - } -} diff --git a/src/main/java/roomescape/service/reservation/ReservationCreateValidator.java b/src/main/java/roomescape/service/reservation/ReservationCreateValidator.java index 668e7e2c9..30ddb3289 100644 --- a/src/main/java/roomescape/service/reservation/ReservationCreateValidator.java +++ b/src/main/java/roomescape/service/reservation/ReservationCreateValidator.java @@ -1,9 +1,8 @@ package roomescape.service.reservation; -import java.time.LocalDate; -import java.time.LocalDateTime; import org.springframework.stereotype.Component; import roomescape.domain.Member; +import roomescape.domain.ReservationStatus; import roomescape.domain.ReservationTime; import roomescape.domain.Theme; import roomescape.repository.MemberRepository; @@ -11,6 +10,9 @@ import roomescape.repository.ReservationTimeRepository; import roomescape.repository.ThemeRepository; +import java.time.LocalDate; +import java.time.LocalDateTime; + @Component public class ReservationCreateValidator { @@ -40,20 +42,35 @@ public Theme getValidTheme(long themeId) { .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 테마 입니다.")); } - public void validateAlreadyBooked(LocalDate date, long timeId, long themeId) { - if (reservationRepository.existsByDateAndReservationTimeIdAndThemeId(date, timeId, themeId)) { + public void validateAlreadyBooked(LocalDate date, + long timeId, + long themeId, + ReservationStatus reservationStatus) { + if (reservationStatus.isReserved() + && reservationRepository.existsByDateAndReservationTimeIdAndThemeId(date, timeId, themeId)) { throw new IllegalArgumentException("해당 시간에 이미 예약된 테마입니다."); } } - public void validateDateIsFuture(LocalDate date, ReservationTime reservationTime) { + public void validateOwnReservationExist(Member member, + Theme theme, + ReservationTime reservationTime, + LocalDate date) { + if (reservationRepository.hasBookedReservation(member, theme, reservationTime, date)) { + throw new IllegalArgumentException("요청하신 예약 또는 예약 대기가 이미 존재합니다."); + } + } + + public void validateDateIsFuture(LocalDate date, + ReservationTime reservationTime) { LocalDateTime localDateTime = toLocalDateTime(date, reservationTime); if (localDateTime.isBefore(LocalDateTime.now())) { throw new IllegalArgumentException("지나간 날짜와 시간에 대한 예약 생성은 불가능합니다."); } } - private LocalDateTime toLocalDateTime(LocalDate date, ReservationTime reservationTime) { + private LocalDateTime toLocalDateTime(LocalDate date, + ReservationTime reservationTime) { return LocalDateTime.of(date, reservationTime.getStartAt()); } diff --git a/src/main/java/roomescape/service/reservation/ReservationDeleteService.java b/src/main/java/roomescape/service/reservation/ReservationDeleteService.java deleted file mode 100644 index ffbf79b5c..000000000 --- a/src/main/java/roomescape/service/reservation/ReservationDeleteService.java +++ /dev/null @@ -1,20 +0,0 @@ -package roomescape.service.reservation; - -import org.springframework.stereotype.Service; -import roomescape.repository.ReservationRepository; - -@Service -public class ReservationDeleteService { - - private final ReservationRepository reservationRepository; - - public ReservationDeleteService(ReservationRepository reservationRepository) { - this.reservationRepository = reservationRepository; - } - - public void deleteReservation(long id) { - reservationRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 예약 아이디 입니다.")); - reservationRepository.deleteById(id); - } -} diff --git a/src/main/java/roomescape/service/reservation/ReservationFindService.java b/src/main/java/roomescape/service/reservation/ReservationFindService.java deleted file mode 100644 index 2be99bb55..000000000 --- a/src/main/java/roomescape/service/reservation/ReservationFindService.java +++ /dev/null @@ -1,30 +0,0 @@ -package roomescape.service.reservation; - -import java.time.LocalDate; -import java.util.List; -import org.springframework.stereotype.Service; -import roomescape.domain.Reservation; -import roomescape.repository.ReservationRepository; - -@Service -public class ReservationFindService { - - private final ReservationRepository reservationRepository; - - public ReservationFindService(ReservationRepository reservationRepository) { - this.reservationRepository = reservationRepository; - } - - public List findReservations() { - return reservationRepository.findAll(); - } - - public List searchReservations(long memberId, long themeId, - LocalDate dateFrom, LocalDate dateTo) { - return reservationRepository.findByMemberIdAndThemeIdAndDateBetween(memberId, themeId, dateFrom, dateTo); - } - - public List findUserReservations(long memberId) { - return reservationRepository.findByMemberId(memberId); - } -} diff --git a/src/main/java/roomescape/service/reservation/ReservationService.java b/src/main/java/roomescape/service/reservation/ReservationService.java new file mode 100644 index 000000000..ce602bdef --- /dev/null +++ b/src/main/java/roomescape/service/reservation/ReservationService.java @@ -0,0 +1,111 @@ +package roomescape.service.reservation; + +import jakarta.transaction.Transactional; +import org.springframework.data.domain.Limit; +import org.springframework.stereotype.Service; +import roomescape.domain.Member; +import roomescape.domain.Reservation; +import roomescape.domain.ReservationStatus; +import roomescape.domain.ReservationTime; +import roomescape.domain.ReservationWaitingWithRank; +import roomescape.domain.Role; +import roomescape.domain.Theme; +import roomescape.exception.AuthenticationException; +import roomescape.repository.ReservationRepository; +import roomescape.service.dto.request.ReservationSaveRequest; + +import java.time.LocalDate; +import java.util.List; + +@Service +public class ReservationService { + + private final ReservationCreateValidator reservationCreateValidator; + private final ReservationRepository reservationRepository; + + public ReservationService(ReservationCreateValidator reservationCreateValidator, + ReservationRepository reservationRepository) { + this.reservationCreateValidator = reservationCreateValidator; + this.reservationRepository = reservationRepository; + } + + public Reservation createReservation(ReservationSaveRequest request, + Member member) { + ReservationTime reservationTime = + reservationCreateValidator.getValidReservationTime(request.timeId()); + reservationCreateValidator.validateDateIsFuture(request.date(), reservationTime); + Theme theme = reservationCreateValidator.getValidTheme(request.themeId()); + reservationCreateValidator.validateAlreadyBooked( + request.date(), + request.timeId(), + request.themeId(), + request.reservationStatus() + ); + reservationCreateValidator.validateOwnReservationExist( + member, + theme, + reservationTime, + request.date() + ); + + Reservation reservation = request.toEntity( + reservationTime, + theme, + member + ); + return reservationRepository.save(reservation); + } + + public List findReservations() { + return reservationRepository.findAll(); + } + + public List searchReservations(long memberId, + long themeId, + LocalDate dateFrom, + LocalDate dateTo) { + return reservationRepository.findByMemberIdAndThemeIdAndDateBetween( + memberId, + themeId, + dateFrom, + dateTo + ); + } + + public List findMemberReservations(long memberId) { + return reservationRepository.findReservationWaitingWithRankByMemberId(memberId); + } + + public List findByReservationStatus(ReservationStatus reservationStatus) { + return reservationRepository.findByReservationStatus(reservationStatus); + } + + @Transactional + public void deleteReservation(long id, Member member) { + Reservation deleteReservation = reservationRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 예약 아이디 입니다.")); + if (member.getRole() == Role.MEMBER) { + validateIsOwnReservation(member, deleteReservation); + validateIsWaiting(deleteReservation); + } + reservationRepository.deleteById(id); + + if (deleteReservation.isReserved()) { + reservationRepository.findNextWaiting(deleteReservation.getTheme(), + deleteReservation.getReservationTime(), deleteReservation.getDate(), Limit.of(1)) + .ifPresent(reservation -> reservation.changeReservationStatus(ReservationStatus.RESERVED)); + } + } + + private void validateIsOwnReservation(Member member, Reservation deleteReservation) { + if (deleteReservation.isNotOwnedBy(member)) { + throw new AuthenticationException("본인의 예약만 삭제할 수 있습니다."); + } + } + + private void validateIsWaiting(Reservation reservation) { + if(reservation.isReserved()) { + throw new AuthenticationException("예약 취소는 관리자에게 문의해주세요."); + } + } +} diff --git a/src/main/java/roomescape/service/reservationtime/ReservationTimeCreateService.java b/src/main/java/roomescape/service/reservationtime/ReservationTimeCreateService.java deleted file mode 100644 index 7335f3868..000000000 --- a/src/main/java/roomescape/service/reservationtime/ReservationTimeCreateService.java +++ /dev/null @@ -1,25 +0,0 @@ -package roomescape.service.reservationtime; - -import org.springframework.stereotype.Service; -import roomescape.domain.ReservationTime; -import roomescape.repository.ReservationTimeRepository; -import roomescape.service.dto.request.ReservationTimeSaveRequest; - -@Service -public class ReservationTimeCreateService { - - private final ReservationTimeRepository reservationTimeRepository; - - public ReservationTimeCreateService(ReservationTimeRepository reservationTimeRepository) { - this.reservationTimeRepository = reservationTimeRepository; - } - - public ReservationTime createReservationTime(ReservationTimeSaveRequest request) { - if (reservationTimeRepository.findByStartAt(request.startAt()).isPresent()) { - throw new IllegalArgumentException("이미 존재하는 예약 시간입니다."); - } - - ReservationTime newReservationTime = request.toEntity(request); - return reservationTimeRepository.save(newReservationTime); - } -} diff --git a/src/main/java/roomescape/service/reservationtime/ReservationTimeDeleteService.java b/src/main/java/roomescape/service/reservationtime/ReservationTimeDeleteService.java deleted file mode 100644 index 229162098..000000000 --- a/src/main/java/roomescape/service/reservationtime/ReservationTimeDeleteService.java +++ /dev/null @@ -1,28 +0,0 @@ -package roomescape.service.reservationtime; - -import org.springframework.stereotype.Service; -import roomescape.repository.ReservationRepository; -import roomescape.repository.ReservationTimeRepository; - -@Service -public class ReservationTimeDeleteService { - - private final ReservationTimeRepository reservationTimeRepository; - private final ReservationRepository reservationRepository; - - public ReservationTimeDeleteService(ReservationTimeRepository reservationTimeRepository, - ReservationRepository reservationRepository) { - this.reservationTimeRepository = reservationTimeRepository; - this.reservationRepository = reservationRepository; - } - - public void deleteReservationTime(long id) { - reservationTimeRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 예약 시간 아이디 입니다.")); - - if (reservationRepository.existsByReservationTimeId(id)) { - throw new IllegalArgumentException("이미 예약중인 시간은 삭제할 수 없습니다."); - } - reservationTimeRepository.deleteById(id); - } -} diff --git a/src/main/java/roomescape/service/reservationtime/ReservationTimeFindService.java b/src/main/java/roomescape/service/reservationtime/ReservationTimeFindService.java deleted file mode 100644 index bc434b6ba..000000000 --- a/src/main/java/roomescape/service/reservationtime/ReservationTimeFindService.java +++ /dev/null @@ -1,28 +0,0 @@ -package roomescape.service.reservationtime; - -import java.time.LocalDate; -import java.util.List; -import org.springframework.stereotype.Service; -import roomescape.domain.ReservationStatus; -import roomescape.domain.ReservationTime; -import roomescape.repository.ReservationTimeRepository; - -@Service -public class ReservationTimeFindService { - - private final ReservationTimeRepository reservationTimeRepository; - - public ReservationTimeFindService(ReservationTimeRepository reservationTimeRepository) { - this.reservationTimeRepository = reservationTimeRepository; - } - - public List findReservationTimes() { - return reservationTimeRepository.findAll(); - } - - public ReservationStatus findIsBooked(LocalDate date, long themeId) { - List reservedTimes = reservationTimeRepository.findReservationByThemeIdAndDate(date, themeId); - List reservationTimes = reservationTimeRepository.findAll(); - return ReservationStatus.of(reservedTimes, reservationTimes); - } -} diff --git a/src/main/java/roomescape/service/reservationtime/ReservationTimeService.java b/src/main/java/roomescape/service/reservationtime/ReservationTimeService.java new file mode 100644 index 000000000..ba59ee216 --- /dev/null +++ b/src/main/java/roomescape/service/reservationtime/ReservationTimeService.java @@ -0,0 +1,55 @@ +package roomescape.service.reservationtime; + +import org.springframework.stereotype.Service; +import roomescape.domain.BookingStatus; +import roomescape.domain.ReservationTime; +import roomescape.repository.ReservationRepository; +import roomescape.repository.ReservationTimeRepository; +import roomescape.service.dto.request.ReservationTimeSaveRequest; + +import java.time.LocalDate; +import java.util.List; + +@Service +public class ReservationTimeService { + + private final ReservationTimeRepository reservationTimeRepository; + private final ReservationRepository reservationRepository; + + public ReservationTimeService(ReservationTimeRepository reservationTimeRepository, + ReservationRepository reservationRepository) { + this.reservationTimeRepository = reservationTimeRepository; + this.reservationRepository = reservationRepository; + } + + public ReservationTime createReservationTime(ReservationTimeSaveRequest request) { + if (reservationTimeRepository.findByStartAt(request.startAt()).isPresent()) { + throw new IllegalArgumentException("이미 존재하는 예약 시간입니다."); + } + + ReservationTime newReservationTime = request.toEntity(request); + return reservationTimeRepository.save(newReservationTime); + } + + public List findReservationTimes() { + return reservationTimeRepository.findAll(); + } + + public BookingStatus findTimeSlotsBookingStatus(LocalDate date, + long themeId) { + List reservedTimes = + reservationTimeRepository.findReservationByThemeIdAndDate(date, themeId); + List reservationTimes = reservationTimeRepository.findAll(); + return BookingStatus.of(reservedTimes, reservationTimes); + } + + public void deleteReservationTime(long id) { + reservationTimeRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 예약 시간 아이디 입니다.")); + + if (reservationRepository.existsByReservationTimeId(id)) { + throw new IllegalArgumentException("이미 예약중인 시간은 삭제할 수 없습니다."); + } + reservationTimeRepository.deleteById(id); + } +} diff --git a/src/main/java/roomescape/service/theme/ThemeCreateService.java b/src/main/java/roomescape/service/theme/ThemeCreateService.java deleted file mode 100644 index abe30291f..000000000 --- a/src/main/java/roomescape/service/theme/ThemeCreateService.java +++ /dev/null @@ -1,21 +0,0 @@ -package roomescape.service.theme; - -import org.springframework.stereotype.Service; -import roomescape.domain.Theme; -import roomescape.repository.ThemeRepository; -import roomescape.service.dto.request.ThemeSaveRequest; - -@Service -public class ThemeCreateService { - - private final ThemeRepository themeRepository; - - public ThemeCreateService(ThemeRepository themeRepository) { - this.themeRepository = themeRepository; - } - - public Theme createTheme(ThemeSaveRequest request) { - Theme theme = request.toEntity(request); - return themeRepository.save(theme); - } -} diff --git a/src/main/java/roomescape/service/theme/ThemeDeleteService.java b/src/main/java/roomescape/service/theme/ThemeDeleteService.java deleted file mode 100644 index 7760886d8..000000000 --- a/src/main/java/roomescape/service/theme/ThemeDeleteService.java +++ /dev/null @@ -1,29 +0,0 @@ -package roomescape.service.theme; - -import org.springframework.stereotype.Service; -import roomescape.repository.ReservationRepository; -import roomescape.repository.ThemeRepository; - -@Service -public class ThemeDeleteService { - - private final ThemeRepository themeRepository; - private final ReservationRepository reservationRepository; - - public ThemeDeleteService(ThemeRepository themeRepository, - ReservationRepository reservationRepository) { - this.themeRepository = themeRepository; - this.reservationRepository = reservationRepository; - } - - public void deleteTheme(long id) { - themeRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 테마 아이디 입니다.")); - - if (reservationRepository.existsByThemeId(id)) { - throw new IllegalArgumentException("이미 예약중인 테마는 삭제할 수 없습니다."); - } - - themeRepository.deleteById(id); - } -} diff --git a/src/main/java/roomescape/service/theme/ThemeFindService.java b/src/main/java/roomescape/service/theme/ThemeFindService.java deleted file mode 100644 index 1e406a2fc..000000000 --- a/src/main/java/roomescape/service/theme/ThemeFindService.java +++ /dev/null @@ -1,35 +0,0 @@ -package roomescape.service.theme; - -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; -import roomescape.domain.Theme; -import roomescape.repository.ThemeRepository; - -import java.time.LocalDate; -import java.util.List; - -@Service -public class ThemeFindService { - - private static final int START_DAYS_SUBTRACT = 7; - private static final int END_DAYS_SUBTRACT = 1; - private static final int RANK_COUNT = 7; - - private final ThemeRepository themeRepository; - - public ThemeFindService(ThemeRepository themeRepository) { - this.themeRepository = themeRepository; - } - - public List findThemes() { - return themeRepository.findAll(); - } - - public List findThemeRanks() { - return themeRepository.findPopularThemes( - LocalDate.now().minusDays(START_DAYS_SUBTRACT), - LocalDate.now().minusDays(END_DAYS_SUBTRACT), - PageRequest.of(0, RANK_COUNT) - ); - } -} diff --git a/src/main/java/roomescape/service/theme/ThemeService.java b/src/main/java/roomescape/service/theme/ThemeService.java new file mode 100644 index 000000000..266eb5941 --- /dev/null +++ b/src/main/java/roomescape/service/theme/ThemeService.java @@ -0,0 +1,56 @@ +package roomescape.service.theme; + +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import roomescape.domain.Theme; +import roomescape.repository.ReservationRepository; +import roomescape.repository.ThemeRepository; +import roomescape.service.dto.request.ThemeSaveRequest; + +import java.time.LocalDate; +import java.util.List; + +@Service +public class ThemeService { + + private static final int START_DAYS_SUBTRACT = 7; + private static final int END_DAYS_SUBTRACT = 1; + private static final int RANK_COUNT = 7; + + private final ThemeRepository themeRepository; + private final ReservationRepository reservationRepository; + + public ThemeService(ThemeRepository themeRepository, + ReservationRepository reservationRepository) { + this.themeRepository = themeRepository; + this.reservationRepository = reservationRepository; + } + + public Theme createTheme(ThemeSaveRequest request) { + Theme theme = request.toEntity(request); + return themeRepository.save(theme); + } + + public List findThemes() { + return themeRepository.findAll(); + } + + public List findThemeRanks() { + return themeRepository.findPopularThemes( + LocalDate.now().minusDays(START_DAYS_SUBTRACT), + LocalDate.now().minusDays(END_DAYS_SUBTRACT), + PageRequest.of(0, RANK_COUNT) + ); + } + + public void deleteTheme(long id) { + themeRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 테마 아이디 입니다.")); + + if (reservationRepository.existsByThemeId(id)) { + throw new IllegalArgumentException("이미 예약중인 테마는 삭제할 수 없습니다."); + } + + themeRepository.deleteById(id); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 59a3c8c19..530fd6eaa 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,8 +12,6 @@ spring: format_sql: true hibernate: ddl-auto: validate - defer-datasource-initialization: true - generate-ddl: false security: jwt: diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 5ec88f51a..526420ebc 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,16 +1,33 @@ -INSERT INTO theme (name, description, thumbnail) -VALUES ('theme1', 'description1', 'thumbnail1'); -INSERT INTO theme (name, description, thumbnail) -VALUES ('theme2', 'description2', 'thumbnail2'); +INSERT INTO theme (name, description, thumbnail, created_at) +VALUES ('theme1', 'description1', 'thumbnail1', current_timestamp); +INSERT INTO theme (name, description, thumbnail, created_at) +VALUES ('theme2', 'description2', 'thumbnail2', current_timestamp); +INSERT INTO theme (name, description, thumbnail, created_at) +VALUES ('theme3', 'description3', 'thumbnail3', current_timestamp); -INSERT INTO reservation_time (start_at) -VALUES ('10:00'); -INSERT INTO reservation_time (start_at) -VALUES ('11:00'); +INSERT INTO reservation_time (start_at, created_at) +VALUES ('10:00', current_timestamp); +INSERT INTO reservation_time (start_at, created_at) +VALUES ('11:00', current_timestamp); +INSERT INTO reservation_time (start_at, created_at) +VALUES ('12:00', current_timestamp); -INSERT INTO member (name, email, password, `role`) -VALUES ('testUser', 'user@naver.com', '1234', 'USER'); -INSERT INTO member (name, email, password, `role`) -VALUES ('testAdmin', 'admin@naver.com', '1234', 'ADMIN'); -INSERT INTO reservation (member_id, date, reservation_time_id, theme_id) -VALUES (1, CURRENT_DATE + INTERVAL '1' DAY, 1, 1); +INSERT INTO member (name, email, password, `role`, created_at) +VALUES ('testUser', 'user@naver.com', '1234', 'MEMBER', current_timestamp); +INSERT INTO member (name, email, password, `role`, created_at) +VALUES ('testAdmin', 'admin@naver.com', '1234', 'ADMIN', current_timestamp); +INSERT INTO member (name, email, password, `role`, created_at) +VALUES ('testUser2', 'user2@naver.com', '1234', 'MEMBER', current_timestamp); + +INSERT INTO reservation (member_id, date, reservation_time_id, theme_id, reservation_status, created_at) +VALUES (1, CURRENT_DATE + INTERVAL '1' DAY, 1, 1, 'RESERVED', current_timestamp); +INSERT INTO reservation (member_id, date, reservation_time_id, theme_id, reservation_status, created_at) +VALUES (2, CURRENT_DATE + INTERVAL '1' DAY, 1, 1, 'WAITING', current_timestamp + INTERVAL '1' MINUTE); +INSERT INTO reservation (member_id, date, reservation_time_id, theme_id, reservation_status, created_at) +VALUES (3, CURRENT_DATE + INTERVAL '1' DAY, 1, 1, 'WAITING', current_timestamp + INTERVAL '2' MINUTE); +INSERT INTO reservation (member_id, date, reservation_time_id, theme_id, reservation_status, created_at) +VALUES (1, CURRENT_DATE + INTERVAL '2' DAY, 2, 2, 'RESERVED', current_timestamp); +INSERT INTO reservation (member_id, date, reservation_time_id, theme_id, reservation_status, created_at) +VALUES (2, CURRENT_DATE + INTERVAL '2' DAY, 2, 2, 'WAITING', current_timestamp + INTERVAL '1' MINUTE); +INSERT INTO reservation (member_id, date, reservation_time_id, theme_id, reservation_status, created_at) +VALUES (3, CURRENT_DATE + INTERVAL '2' DAY, 2, 2, 'WAITING', current_timestamp + INTERVAL '2' MINUTE); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 7d12ace49..b012e2f37 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,17 +1,18 @@ create table member ( - id bigint generated by default as identity, - email varchar(255) UNIQUE NOT NULL, - name varchar(255) NOT NULL, - password varchar(255) NOT NULL, - role varchar(255) check (role in ('ADMIN', 'USER')) NOT NULL , + id bigint generated by default as identity, + email varchar(255) UNIQUE NOT NULL, + name varchar(255) NOT NULL, + password varchar(255) NOT NULL, + role varchar(255) check (role in ('ADMIN', 'MEMBER')) NOT NULL, + created_at timestamp NOT NULL, primary key (id) -) +); CREATE TABLE reservation_time ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY, - start_at TIME(6) NOT NULL, - end_at TIME(6) NOT NULL, + id BIGINT GENERATED BY DEFAULT AS IDENTITY, + start_at TIME(6) NOT NULL, + created_at timestamp NOT NULL, PRIMARY KEY (id) ); @@ -21,18 +22,22 @@ CREATE TABLE theme name VARCHAR(255) NOT NULL, description VARCHAR(1000), thumbnail VARCHAR(255), + created_at timestamp NOT NULL, PRIMARY KEY (id) ); CREATE TABLE reservation ( id BIGINT GENERATED BY DEFAULT AS IDENTITY, - date DATE NOT NULL, - member_id BIGINT NOT NULL, - reservation_time_id BIGINT NOT NULL, - theme_id BIGINT NOT NULL, + date DATE NOT NULL, + member_id BIGINT NOT NULL, + reservation_time_id BIGINT NOT NULL, + theme_id BIGINT NOT NULL, + reservation_status varchar(255) check (reservation_status in ('RESERVED', 'WAITING')) NOT NULL, + created_at timestamp NOT NULL, + UNIQUE (date, member_id, reservation_time_id, theme_id), PRIMARY KEY (id), - CONSTRAINT fk_member FOREIGN KEY (member_id) REFERENCES member(id), - CONSTRAINT fk_reservation_time FOREIGN KEY (reservation_time_id) REFERENCES reservation_time(id), - CONSTRAINT fk_theme FOREIGN KEY (theme_id) REFERENCES theme(id) + CONSTRAINT fk_member FOREIGN KEY (member_id) REFERENCES member (id), + CONSTRAINT fk_reservation_time FOREIGN KEY (reservation_time_id) REFERENCES reservation_time (id), + CONSTRAINT fk_theme FOREIGN KEY (theme_id) REFERENCES theme (id) ); diff --git a/src/main/resources/static/js/ranking.js b/src/main/resources/static/js/ranking.js index ceb763e15..b6390266d 100644 --- a/src/main/resources/static/js/ranking.js +++ b/src/main/resources/static/js/ranking.js @@ -1,5 +1,5 @@ document.addEventListener('DOMContentLoaded', () => { - requestRead('/themes/ranks') // 인기 테마 목록 조회 API endpoint + requestRead('/api/themes/ranks') // 인기 테마 목록 조회 API endpoint .then(render) .catch(error => console.error('Error fetching times:', error)); }); diff --git a/src/main/resources/static/js/reservation-mine.js b/src/main/resources/static/js/reservation-mine.js index 96be821db..c06c78f4e 100644 --- a/src/main/resources/static/js/reservation-mine.js +++ b/src/main/resources/static/js/reservation-mine.js @@ -1,57 +1,51 @@ document.addEventListener('DOMContentLoaded', () => { - fetch('/reservations-mine') // 내 예약 목록 조회 API 호출 - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }) - .then(render) - .catch(error => console.error('Error fetching reservations:', error)); + fetch('/api/reservations-mine') // 내 예약 목록 조회 API 호출 + .then(response => { + if (response.status === 200) return response.json(); + throw new Error('Read failed'); + }) + .then(render) + .catch(error => console.error('Error fetching reservations:', error)); }); function render(data) { - const tableBody = document.getElementById('table-body'); - tableBody.innerHTML = ''; + const tableBody = document.getElementById('table-body'); + tableBody.innerHTML = ''; - data.forEach(item => { - const row = tableBody.insertRow(); + data.forEach(item => { + const row = tableBody.insertRow(); - const theme = item.theme; - const date = item.date; - const time = item.time; - const status = item.status; + const theme = item.theme; + const date = item.date; + const time = item.time; + const status = item.status; - row.insertCell(0).textContent = theme; - row.insertCell(1).textContent = date; - row.insertCell(2).textContent = time; - row.insertCell(3).textContent = status; + row.insertCell(0).textContent = theme; + row.insertCell(1).textContent = date; + row.insertCell(2).textContent = time; + row.insertCell(3).textContent = status; - /* - TODO: [3단계] 예약 대기 기능 - 예약 대기 취소 기능 구현 후 활성화 - */ - if (status !== '예약') { // 예약 대기 상태일 때 예약 대기 취소 버튼 추가하는 코드, 상태 값은 변경 가능 - const cancelCell = row.insertCell(4); - const cancelButton = document.createElement('button'); - cancelButton.textContent = '취소'; - cancelButton.className = 'btn btn-danger'; - cancelButton.onclick = function () { - requestDeleteWaiting(item.id).then(() => window.location.reload()); - }; - cancelCell.appendChild(cancelButton); - } else { // 예약 완료 상태일 때 - row.insertCell(4).textContent = ''; - } - }); + if (status !== '예약') { // 예약 대기 상태일 때 예약 대기 취소 버튼 추가하는 코드, 상태 값은 변경 가능 + const cancelCell = row.insertCell(4); + const cancelButton = document.createElement('button'); + cancelButton.textContent = '취소'; + cancelButton.className = 'btn btn-danger'; + cancelButton.onclick = function () { + requestDeleteWaiting(item.reservationId).then(() => window.location.reload()); + }; + cancelCell.appendChild(cancelButton); + } else { // 예약 완료 상태일 때 + row.insertCell(4).textContent = ''; + } + }); } function requestDeleteWaiting(id) { - /* - TODO: [3단계] 예약 대기 기능 - 예약 대기 취소 API 호출 - */ - const endpoint = ''; - return fetch(endpoint, { - method: 'DELETE' - }).then(response => { - if (response.status === 204) return; - throw new Error('Delete failed'); - }); + const endpoint = `/api/reservations/${id}`; + return fetch(endpoint, { + method: 'DELETE' + }).then(response => { + if (response.status === 204) return; + throw new Error('Delete failed'); + }); } diff --git a/src/main/resources/static/js/reservation-new.js b/src/main/resources/static/js/reservation-new.js index 7fec66226..41c7559cf 100644 --- a/src/main/resources/static/js/reservation-new.js +++ b/src/main/resources/static/js/reservation-new.js @@ -1,7 +1,7 @@ let isEditing = false; -const RESERVATION_API_ENDPOINT = '/reservations'; -const TIME_API_ENDPOINT = '/times'; -const THEME_API_ENDPOINT = '/themes'; +const RESERVATION_API_ENDPOINT = '/api/reservations'; +const TIME_API_ENDPOINT = '/api/times'; +const THEME_API_ENDPOINT = '/api/themes'; const timesOptions = []; const themesOptions = []; @@ -179,7 +179,7 @@ function requestDelete(id) { method: 'DELETE', }; - return fetch(`${RESERVATION_API_ENDPOINT}/${id}`, requestOptions) + return fetch(`/api/reservations/${id}`, requestOptions) .then(response => { if (response.status !== 204) throw new Error('Delete failed'); }); diff --git a/src/main/resources/static/js/reservation-with-member.js b/src/main/resources/static/js/reservation-with-member.js index 2960f1914..f94c25d2e 100644 --- a/src/main/resources/static/js/reservation-with-member.js +++ b/src/main/resources/static/js/reservation-with-member.js @@ -1,8 +1,8 @@ let isEditing = false; -const RESERVATION_API_ENDPOINT = '/reservations'; -const TIME_API_ENDPOINT = '/times'; -const THEME_API_ENDPOINT = '/themes'; -const MEMBER_API_ENDPOINT = '/admin/members'; +const RESERVATION_API_ENDPOINT = '/api/reservations'; +const TIME_API_ENDPOINT = '/api/times'; +const THEME_API_ENDPOINT = '/api/themes'; +const MEMBER_API_ENDPOINT = '/api/admin/members'; const timesOptions = []; const themesOptions = []; const membersOptions = []; @@ -192,7 +192,7 @@ function applyFilter(event) { const dateFrom = document.getElementById('date-from').value; const dateTo = document.getElementById('date-to').value; - fetch(`/admin/reservations/search?memberId=${memberId}&themeId=${themeId}&dateFrom=${dateFrom}&dateTo=${dateTo}`, { + fetch(`/api/admin/reservations/search?memberId=${memberId}&themeId=${themeId}&dateFrom=${dateFrom}&dateTo=${dateTo}`, { method: 'GET', headers: { 'Content-Type': 'application/json' @@ -211,7 +211,7 @@ function requestCreate(reservation) { body: JSON.stringify(reservation) }; - return fetch('/admin/reservations', requestOptions) + return fetch('/api/admin/reservations', requestOptions) .then(response => { if (response.status === 201) return response.json(); throw new Error('Create failed'); @@ -223,7 +223,7 @@ function requestDelete(id) { method: 'DELETE', }; - return fetch(`${RESERVATION_API_ENDPOINT}/${id}`, requestOptions) + return fetch(`/api/reservations/${id}`, requestOptions) .then(response => { if (response.status !== 204) throw new Error('Delete failed'); }); diff --git a/src/main/resources/static/js/reservation.js b/src/main/resources/static/js/reservation.js index 909d517ac..06856d1de 100644 --- a/src/main/resources/static/js/reservation.js +++ b/src/main/resources/static/js/reservation.js @@ -1,6 +1,6 @@ let isEditing = false; -const RESERVATION_API_ENDPOINT = '/reservations'; -const TIME_API_ENDPOINT = '/times'; +const RESERVATION_API_ENDPOINT = '/api/reservations'; +const TIME_API_ENDPOINT = '/api/times'; const timesOptions = []; document.addEventListener('DOMContentLoaded', () => { @@ -164,7 +164,7 @@ function requestDelete(id) { method: 'DELETE', }; - return fetch(`${RESERVATION_API_ENDPOINT}/${id}`, requestOptions) + return fetch(`/api/reservations/${id}`, requestOptions) .then(response => { if (response.status !== 204) throw new Error('Delete failed'); }); diff --git a/src/main/resources/static/js/theme.js b/src/main/resources/static/js/theme.js index a2ddee91e..0eb37d233 100644 --- a/src/main/resources/static/js/theme.js +++ b/src/main/resources/static/js/theme.js @@ -1,5 +1,5 @@ let isEditing = false; -const API_ENDPOINT = '/themes'; +const API_ENDPOINT = '/api/themes'; const cellFields = ['id', 'name', 'description', 'thumbnail']; const createCellFields = ['', createInput(), createInput(), createInput()]; @@ -109,7 +109,7 @@ function requestCreate(data) { body: JSON.stringify(data) }; - return fetch(API_ENDPOINT, requestOptions) + return fetch(`/api/admin/times`, requestOptions) .then(response => { if (response.status === 201) return response.json(); throw new Error('Create failed'); @@ -129,7 +129,7 @@ function requestDelete(id) { method: 'DELETE', }; - return fetch(`${API_ENDPOINT}/${id}`, requestOptions) + return fetch(`/api/admin/themes/${id}`, requestOptions) .then(response => { if (response.status !== 204) throw new Error('Delete failed'); }); diff --git a/src/main/resources/static/js/time.js b/src/main/resources/static/js/time.js index 641023a6d..6ef7b6ca1 100644 --- a/src/main/resources/static/js/time.js +++ b/src/main/resources/static/js/time.js @@ -1,5 +1,5 @@ let isEditing = false; -const API_ENDPOINT = '/times'; +const API_ENDPOINT = '/api/times'; const cellFields = ['id', 'startAt']; const createCellFields = ['', createInput()]; @@ -108,7 +108,7 @@ function requestCreate(data) { body: JSON.stringify(data) }; - return fetch(API_ENDPOINT, requestOptions) + return fetch(`/api/admin/times`, requestOptions) .then(response => { if (response.status === 201) return response.json(); throw new Error('Create failed'); @@ -128,7 +128,7 @@ function requestDelete(id) { method: 'DELETE', }; - return fetch(`${API_ENDPOINT}/${id}`, requestOptions) + return fetch(`/api/admin/times/${id}`, requestOptions) .then(response => { if (response.status !== 204) throw new Error('Delete failed'); }); diff --git a/src/main/resources/static/js/user-reservation.js b/src/main/resources/static/js/user-reservation.js index 1c324a2b8..89d11816f 100644 --- a/src/main/resources/static/js/user-reservation.js +++ b/src/main/resources/static/js/user-reservation.js @@ -1,4 +1,4 @@ -const THEME_API_ENDPOINT = '/themes'; +const THEME_API_ENDPOINT = 'api/themes'; document.addEventListener('DOMContentLoaded', () => { requestRead(THEME_API_ENDPOINT) @@ -30,6 +30,7 @@ document.addEventListener('DOMContentLoaded', () => { }); document.getElementById('reserve-button').addEventListener('click', onReservationButtonClick); + document.getElementById('wait-button').addEventListener('click', onWaitButtonClick); }); function renderTheme(themes) { @@ -49,9 +50,6 @@ function createSlot(type, text, id, booked) { div.setAttribute('data-' + type + '-id', id); if (type === 'time') { div.setAttribute('data-time-booked', booked); - if (booked) { - div.classList.add('disabled'); - } } return div; } @@ -82,7 +80,7 @@ function checkDateAndTheme() { } function fetchAvailableTimes(date, themeId) { - fetch(`/times/available?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint + fetch(`/api/times/available?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint method: 'GET', headers: { 'Content-Type': 'application/json', @@ -121,18 +119,22 @@ function checkDateAndThemeAndTime() { const selectedThemeElement = document.querySelector('.theme-slot.active'); const selectedTimeElement = document.querySelector('.time-slot.active'); const reserveButton = document.getElementById("reserve-button"); + const waitButton = document.getElementById("wait-button"); if (selectedDate && selectedThemeElement && selectedTimeElement) { if (selectedTimeElement.getAttribute('data-time-booked') === 'true') { // 선택된 시간이 이미 예약된 경우 reserveButton.classList.add("disabled"); + waitButton.classList.remove("disabled"); // 예약 대기 버튼 활성화 } else { // 선택된 시간이 예약 가능한 경우 reserveButton.classList.remove("disabled"); + waitButton.classList.add("disabled"); // 예약 대기 버튼 비활성화 } } else { // 날짜, 테마, 시간 중 하나라도 선택되지 않은 경우 reserveButton.classList.add("disabled"); + waitButton.classList.add("disabled"); } } @@ -147,9 +149,10 @@ function onReservationButtonClick() { date: selectedDate, themeId: selectedThemeId, timeId: selectedTimeId, + reservationStatus: "RESERVED" }; - fetch('/reservations', { + fetch('/api/reservations', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -173,6 +176,43 @@ function onReservationButtonClick() { } } +function onWaitButtonClick() { + const selectedDate = document.getElementById("datepicker").value; + const selectedThemeId = document.querySelector('.theme-slot.active')?.getAttribute('data-theme-id'); + const selectedTimeId = document.querySelector('.time-slot.active')?.getAttribute('data-time-id'); + + if (selectedDate && selectedThemeId && selectedTimeId) { + const reservationData = { + date: selectedDate, + themeId: selectedThemeId, + timeId: selectedTimeId, + reservationStatus: "WAITING" + }; + + fetch('/api/reservations', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(reservationData) + }) + .then(response => { + if (!response.ok) throw new Error('Reservation waiting failed'); + return response.json(); + }) + .then(data => { + alert('Reservation waiting successful!'); + window.location.href = "/"; + }) + .catch(error => { + alert("An error occurred while making the reservation waiting."); + console.error(error); + }); + } else { + alert("Please select a date, theme, and time before making a reservation waiting."); + } +} + function requestRead(endpoint) { return fetch(endpoint) .then(response => { diff --git a/src/main/resources/static/js/user-scripts.js b/src/main/resources/static/js/user-scripts.js index fb9d15d61..626925edb 100644 --- a/src/main/resources/static/js/user-scripts.js +++ b/src/main/resources/static/js/user-scripts.js @@ -4,7 +4,7 @@ document.addEventListener('DOMContentLoaded', function () { document.getElementById('logout-btn').addEventListener('click', function (event) { event.preventDefault(); - fetch('/logout', { + fetch('/api/logout', { method: 'POST', // 또는 서버 설정에 따라 GET 일 수도 있음 credentials: 'include' // 쿠키를 포함시키기 위해 필요 }) @@ -26,7 +26,7 @@ const registerBtn = document.getElementById('register-btn'); if (registerBtn !== null) registerBtn.addEventListener('click', register); function updateUIBasedOnLogin() { - fetch('/login/check') // 로그인 상태 확인 API 호출 + fetch('/api/login/check') // 로그인 상태 확인 API 호출 .then(response => { if (!response.ok) { // 요청이 실패하거나 로그인 상태가 아닌 경우 throw new Error('Not logged in or other error'); @@ -66,7 +66,7 @@ function login() { return; // 필수 입력 필드가 비어있으면 여기서 함수 실행을 중단 } - fetch('/login', { + fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -117,7 +117,7 @@ function register(event) { }; // AJAX 요청 생성 및 전송 - fetch('/members', { + fetch('/api/members', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/src/main/resources/static/js/waiting.js b/src/main/resources/static/js/waiting.js new file mode 100644 index 000000000..2c0bf2b93 --- /dev/null +++ b/src/main/resources/static/js/waiting.js @@ -0,0 +1,74 @@ +document.addEventListener('DOMContentLoaded', () => { + + fetch('/api/admin/reservations/waiting-list') // 내 예약 목록 조회 API 호출 + .then(response => { + if (response.status === 200) return response.json(); + throw new Error('Read failed'); + }) + .then(render) + .catch(error => console.error('Error fetching reservations:', error)); +}); + +function render(data) { + const tableBody = document.getElementById('table-body'); + tableBody.innerHTML = ''; + + data.forEach(item => { + const row = tableBody.insertRow(); + + const id = item.id; + const name = item.name; + const theme = item.theme; + const date = item.date; + const startAt = item.startAt; + + row.insertCell(0).textContent = id; // 예약 대기 id + row.insertCell(1).textContent = name; // 예약자명 + row.insertCell(2).textContent = theme; // 테마명 + row.insertCell(3).textContent = date; // 예약 날짜 + row.insertCell(4).textContent = startAt; // 시작 시간 + + const actionCell = row.insertCell(row.cells.length); + + // actionCell.appendChild(createActionButton('승인', 'btn-primary', approve)); + actionCell.appendChild(createActionButton('거절', 'btn-danger', deny)); + }); +} + +function approve(event) { + const row = event.target.closest('tr'); + const id = row.cells[0].textContent; + + /* + TODO: [4단계] 예약 대기 목록 관리 기능 + 예약 대기 승인 API 호출 + */ + const endpoint = '' + id; + return fetch(endpoint, { + method: '' + }).then(response => { + if (response.status === 200) return; + throw new Error('Delete failed'); + }).then(() => location.reload()); +} + +function deny(event) { + const row = event.target.closest('tr'); + const id = row.cells[0].textContent; + + const endpoint = '/api/reservations/' + id; + return fetch(endpoint, { + method: 'DELETE' + }).then(response => { + if (response.status === 204) return; + throw new Error('Delete failed'); + }).then(() => location.reload()); +} + +function createActionButton(label, className, eventListener) { + const button = document.createElement('button'); + button.textContent = label; + button.classList.add('btn', className, 'mr-2'); + button.addEventListener('click', eventListener); + return button; +} diff --git a/src/main/resources/templates/admin/index.html b/src/main/resources/templates/admin/index.html index ae67b26f7..4f99df6ac 100644 --- a/src/main/resources/templates/admin/index.html +++ b/src/main/resources/templates/admin/index.html @@ -22,13 +22,16 @@