diff --git a/build.gradle b/build.gradle index 8d52aebc6..9bc129a0e 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' - implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.0.0' diff --git a/src/main/java/roomescape/auth/configuration/LoginMemberArgumentResolver.java b/src/main/java/roomescape/auth/configuration/LoginMemberArgumentResolver.java index 18915ca46..21f5c1cb9 100644 --- a/src/main/java/roomescape/auth/configuration/LoginMemberArgumentResolver.java +++ b/src/main/java/roomescape/auth/configuration/LoginMemberArgumentResolver.java @@ -24,7 +24,7 @@ public LoginMemberArgumentResolver(JwtUtil jwtUtil) { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(LoginMember.class) - && parameter.getParameterType().equals(Member.class); + && parameter.getParameterType().equals(MemberInfo.class); } @Override @@ -47,6 +47,7 @@ public Object resolveArgument( JwtPayload jwtPayload = jwtUtil.parseToken(token); return new MemberInfo( + jwtPayload.id(), jwtPayload.name(), jwtPayload.role() ); diff --git a/src/main/java/roomescape/auth/controller/LoginController.java b/src/main/java/roomescape/auth/controller/LoginController.java index f802efe03..32af677fd 100644 --- a/src/main/java/roomescape/auth/controller/LoginController.java +++ b/src/main/java/roomescape/auth/controller/LoginController.java @@ -1,5 +1,6 @@ package roomescape.auth.controller; +import org.springframework.data.crossstore.ChangeSetPersister.NotFoundException; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CookieValue; @@ -26,7 +27,7 @@ public LoginController(LoginService loginService) { @PostMapping public ResponseEntity login( @RequestBody LoginRequest loginRequest - ) { + ) throws NotFoundException { String token = loginService.login(loginRequest.email(), loginRequest.password()); ResponseCookie cookie = ResponseCookie.from("token", token) @@ -42,7 +43,7 @@ public ResponseEntity login( @GetMapping("/check") public ResponseEntity loginCheck( @CookieValue(value = "token", required = false) String token - ) { + ) throws NotFoundException { if (token == null || token.isBlank()) { return ResponseEntity.status(401).build(); } diff --git a/src/main/java/roomescape/auth/service/LoginService.java b/src/main/java/roomescape/auth/service/LoginService.java index 181fac10a..3c039bc12 100644 --- a/src/main/java/roomescape/auth/service/LoginService.java +++ b/src/main/java/roomescape/auth/service/LoginService.java @@ -1,34 +1,39 @@ package roomescape.auth.service; import org.springframework.stereotype.Service; -import roomescape.member.Member; -import roomescape.member.MemberDao; -import roomescape.member.MemberInfo; import roomescape.auth.util.JwtPayload; import roomescape.auth.util.JwtUtil; +import roomescape.member.Member; +import roomescape.member.MemberInfo; +import roomescape.member.MemberRepository; @Service public class LoginService { - private final MemberDao memberDao; + private final MemberRepository memberRepository; private final JwtUtil jwtUtil; - public LoginService(MemberDao memberDao, JwtUtil jwtUtil) { - this.memberDao = memberDao; + public LoginService(MemberRepository memberRepository, JwtUtil jwtUtil) { + this.memberRepository = memberRepository; this.jwtUtil = jwtUtil; } public String login(String email, String password) { - Member member = memberDao.findByEmailAndPassword(email, password); + Member member = memberRepository.findByEmailAndPassword(email, password) + .orElseThrow( + () -> new IllegalArgumentException("not found Member with email: " + email)); return jwtUtil.createToken(member); } public MemberInfo check(String token) { JwtPayload payload = jwtUtil.parseToken(token); - Member member = memberDao.findByName(payload.name()); + Member member = memberRepository.findByName(payload.name()) + .orElseThrow(() -> new IllegalArgumentException( + "not found Member with name: " + payload.name())); return new MemberInfo( + member.getId(), member.getName(), member.getRole() ); diff --git a/src/main/java/roomescape/auth/util/JwtPayload.java b/src/main/java/roomescape/auth/util/JwtPayload.java index 9415968a8..ceb2a4fee 100644 --- a/src/main/java/roomescape/auth/util/JwtPayload.java +++ b/src/main/java/roomescape/auth/util/JwtPayload.java @@ -1,5 +1,5 @@ package roomescape.auth.util; -public record JwtPayload(String name, String role) { +public record JwtPayload(Long id, String name, String role) { } diff --git a/src/main/java/roomescape/auth/util/JwtUtil.java b/src/main/java/roomescape/auth/util/JwtUtil.java index 006a6cd79..1a0d1e921 100644 --- a/src/main/java/roomescape/auth/util/JwtUtil.java +++ b/src/main/java/roomescape/auth/util/JwtUtil.java @@ -36,6 +36,7 @@ public String createToken(Member member) { public JwtPayload parseToken(String token) { Claims claims = jwtParser.parseClaimsJws(token).getBody(); return new JwtPayload( + Long.valueOf(claims.getSubject()), String.valueOf(claims.get("name")), String.valueOf(claims.get("role")) ); diff --git a/src/main/java/roomescape/member/Member.java b/src/main/java/roomescape/member/Member.java index 903aaa9b0..33cee3692 100644 --- a/src/main/java/roomescape/member/Member.java +++ b/src/main/java/roomescape/member/Member.java @@ -1,6 +1,15 @@ package roomescape.member; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity public class Member { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; @@ -21,6 +30,10 @@ public Member(String name, String email, String password, String role) { this.role = role; } + public Member() { + + } + public Long getId() { return id; } diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java deleted file mode 100644 index 81f77f4cd..000000000 --- a/src/main/java/roomescape/member/MemberDao.java +++ /dev/null @@ -1,55 +0,0 @@ -package roomescape.member; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; - -@Repository -public class MemberDao { - private JdbcTemplate jdbcTemplate; - - public MemberDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public Member save(Member member) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(connection -> { - var ps = connection.prepareStatement("INSERT INTO member(name, email, password, role) VALUES (?, ?, ?, ?)", new String[]{"id"}); - ps.setString(1, member.getName()); - ps.setString(2, member.getEmail()); - ps.setString(3, member.getPassword()); - ps.setString(4, member.getRole()); - return ps; - }, keyHolder); - - return new Member(keyHolder.getKey().longValue(), member.getName(), member.getEmail(), "USER"); - } - - public Member findByEmailAndPassword(String email, String password) { - return jdbcTemplate.queryForObject( - "SELECT id, name, email, role FROM member WHERE email = ? AND password = ?", - (rs, rowNum) -> new Member( - rs.getLong("id"), - rs.getString("name"), - rs.getString("email"), - rs.getString("role") - ), - email, password - ); - } - - public Member findByName(String name) { - return jdbcTemplate.queryForObject( - "SELECT id, name, email, role FROM member WHERE name = ?", - (rs, rowNum) -> new Member( - rs.getLong("id"), - rs.getString("name"), - rs.getString("email"), - rs.getString("role") - ), - name - ); - } -} diff --git a/src/main/java/roomescape/member/MemberInfo.java b/src/main/java/roomescape/member/MemberInfo.java index 692869914..b12e4d45e 100644 --- a/src/main/java/roomescape/member/MemberInfo.java +++ b/src/main/java/roomescape/member/MemberInfo.java @@ -1,6 +1,7 @@ package roomescape.member; public record MemberInfo( + Long id, String name, String role ) { diff --git a/src/main/java/roomescape/member/MemberRepository.java b/src/main/java/roomescape/member/MemberRepository.java new file mode 100644 index 000000000..3fc1ab0f4 --- /dev/null +++ b/src/main/java/roomescape/member/MemberRepository.java @@ -0,0 +1,11 @@ +package roomescape.member; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + + Optional findByEmailAndPassword(String email, String password); + + Optional findByName(String name); +} diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index ccaa8cba5..60d30a62e 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -4,14 +4,22 @@ @Service public class MemberService { - private MemberDao memberDao; - public MemberService(MemberDao memberDao) { - this.memberDao = memberDao; + private MemberRepository memberRepository; + + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; } public MemberResponse createMember(MemberRequest memberRequest) { - Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER")); + Member member = new Member( + memberRequest.getName(), + memberRequest.getEmail(), + memberRequest.getPassword(), + "USER" + ); + + memberRepository.save(member); return new MemberResponse(member.getId(), member.getName(), member.getEmail()); } } diff --git a/src/main/java/roomescape/reservation/MyReservationResponse.java b/src/main/java/roomescape/reservation/MyReservationResponse.java new file mode 100644 index 000000000..ef5e1846b --- /dev/null +++ b/src/main/java/roomescape/reservation/MyReservationResponse.java @@ -0,0 +1,27 @@ +package roomescape.reservation; + +public record MyReservationResponse( + Long reservationId, + String theme, + String date, + String time, + String status +) { + + public static MyReservationResponse from(Reservation reservation, Integer rank) { + return new MyReservationResponse( + reservation.getId(), + reservation.getTheme().getName(), + reservation.getDate(), + reservation.getTime().getValue(), + getStatus(reservation, rank) + ); + } + + public static String getStatus(Reservation reservation, Integer rank) { + if (reservation.isWaiting()) { + return rank + "번째 예약대기"; + } + return "예약"; + } +} diff --git a/src/main/java/roomescape/reservation/Reservation.java b/src/main/java/roomescape/reservation/Reservation.java index 83a7edf1b..9c065bd23 100644 --- a/src/main/java/roomescape/reservation/Reservation.java +++ b/src/main/java/roomescape/reservation/Reservation.java @@ -1,34 +1,72 @@ package roomescape.reservation; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import roomescape.member.Member; import roomescape.theme.Theme; import roomescape.time.Time; +@Entity public class Reservation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String date; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "time_id") private Time time; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "theme_id") private Theme theme; - public Reservation(Long id, String name, String date, Time time, Theme theme) { - this.id = id; - this.name = name; - this.date = date; - this.time = time; - this.theme = theme; - } + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @Enumerated(EnumType.STRING) + private ReservationStatus status; - public Reservation(String name, String date, Time time, Theme theme) { + public Reservation(String name, String date, Time time, Theme theme, Member member, + ReservationStatus status) { this.name = name; this.date = date; this.time = time; this.theme = theme; + this.member = member; + this.status = status; } public Reservation() { } + public boolean isOwnedBy(Long memberId) { + return this.member.getId().equals(memberId); + } + + public boolean isReserved() { + return this.status == ReservationStatus.RESERVED; + } + + public boolean isWaiting() { + return this.status == ReservationStatus.WAITING; + } + + public void promoteToReserved() { + this.status = ReservationStatus.RESERVED; + } + public Long getId() { return id; } @@ -48,4 +86,12 @@ public Time getTime() { public Theme getTheme() { return theme; } + + public Member getMember() { + return member; + } + + public ReservationStatus getStatus() { + return status; + } } diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index 1c48ef403..4a23a575c 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import roomescape.auth.configuration.LoginMember; -import roomescape.member.Member; +import roomescape.member.MemberInfo; @RestController public class ReservationController { @@ -29,7 +29,7 @@ public List list() { @PostMapping("/reservations") public ResponseEntity create( @RequestBody ReservationRequest reservationRequest, - @LoginMember Member member + @LoginMember MemberInfo member ) { if (reservationRequest.getDate() == null || reservationRequest.getTheme() == null @@ -38,9 +38,9 @@ public ResponseEntity create( } if (reservationRequest.getName() == null || reservationRequest.getName().isBlank()) { - reservationRequest.setName(member.getName()); + reservationRequest.setName(member.name()); } - ReservationResponse reservation = reservationService.save(reservationRequest); + ReservationResponse reservation = reservationService.save(member.id(), reservationRequest); return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())) .body(reservation); @@ -48,7 +48,14 @@ public ResponseEntity create( @DeleteMapping("/reservations/{id}") public ResponseEntity delete(@PathVariable Long id) { - reservationService.deleteById(id); + reservationService.cancel(id); return ResponseEntity.noContent().build(); } + + @GetMapping("/reservations-mine") + public ResponseEntity> findMyReservations( + @LoginMember MemberInfo member + ) { + return ResponseEntity.ok(reservationService.findMyReservations(member.id())); + } } diff --git a/src/main/java/roomescape/reservation/ReservationDao.java b/src/main/java/roomescape/reservation/ReservationDao.java deleted file mode 100644 index a4972430c..000000000 --- a/src/main/java/roomescape/reservation/ReservationDao.java +++ /dev/null @@ -1,127 +0,0 @@ -package roomescape.reservation; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; -import roomescape.theme.Theme; -import roomescape.time.Time; - -import java.sql.PreparedStatement; -import java.util.List; - -@Repository -public class ReservationDao { - - private final JdbcTemplate jdbcTemplate; - - public ReservationDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List findAll() { - return jdbcTemplate.query( - "SELECT r.id AS reservation_id, r.name as reservation_name, r.date as reservation_date, " + - "t.id AS theme_id, t.name AS theme_name, t.description AS theme_description, " + - "ti.id AS time_id, ti.time_value AS time_value " + - "FROM reservation r " + - "JOIN theme t ON r.theme_id = t.id " + - "JOIN time ti ON r.time_id = ti.id", - - (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getString("reservation_date"), - new Time( - rs.getLong("time_id"), - rs.getString("time_value") - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("theme_description") - ))); - } - - public Reservation save(ReservationRequest reservationRequest) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(connection -> { - PreparedStatement ps = connection.prepareStatement("INSERT INTO reservation(date, name, theme_id, time_id) VALUES (?, ?, ?, ?)", new String[]{"id"}); - ps.setString(1, reservationRequest.getDate()); - ps.setString(2, reservationRequest.getName()); - ps.setLong(3, reservationRequest.getTheme()); - ps.setLong(4, reservationRequest.getTime()); - return ps; - }, keyHolder); - - Time time = jdbcTemplate.queryForObject("SELECT * FROM time WHERE id = ?", - (rs, rowNum) -> new Time(rs.getLong("id"), rs.getString("time_value")), - reservationRequest.getTime()); - - Theme theme = jdbcTemplate.queryForObject("SELECT * FROM theme WHERE id = ?", - (rs, rowNum) -> new Theme(rs.getLong("id"), rs.getString("name"), rs.getString("description")), - reservationRequest.getTheme()); - - return new Reservation( - keyHolder.getKey().longValue(), - reservationRequest.getName(), - reservationRequest.getDate(), - time, - theme - ); - } - - public void deleteById(Long id) { - jdbcTemplate.update("DELETE FROM reservation WHERE id = ?", id); - } - - public List findReservationsByDateAndTheme(String date, Long themeId) { - return jdbcTemplate.query( - "SELECT r.id AS reservation_id, r.name as reservation_name, r.date as reservation_date, " + - "t.id AS theme_id, t.name AS theme_name, t.description AS theme_description, " + - "ti.id AS time_id, ti.time_value AS time_value " + - "FROM reservation r " + - "JOIN theme t ON r.theme_id = t.id " + - "JOIN time ti ON r.time_id = ti.id" + - "WHERE r.date = ? AND r.theme_id = ?", - new Object[]{date, themeId}, - (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getString("reservation_date"), - new Time( - rs.getLong("time_id"), - rs.getString("time_value") - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("theme_description") - ))); - } - - public List findByDateAndThemeId(String date, Long themeId) { - return jdbcTemplate.query( - "SELECT r.id AS reservation_id, r.name as reservation_name, r.date as reservation_date, " + - "t.id AS theme_id, t.name AS theme_name, t.description AS theme_description, " + - "ti.id AS time_id, ti.time_value AS time_value " + - "FROM reservation r " + - "JOIN theme t ON r.theme_id = t.id " + - "JOIN time ti ON r.time_id = ti.id " + - "WHERE r.date = ? AND r.theme_id = ?", - new Object[]{date, themeId}, - (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getString("reservation_date"), - new Time( - rs.getLong("time_id"), - rs.getString("time_value") - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("theme_description") - ))); - } -} diff --git a/src/main/java/roomescape/reservation/ReservationRepository.java b/src/main/java/roomescape/reservation/ReservationRepository.java new file mode 100644 index 000000000..e8f79efec --- /dev/null +++ b/src/main/java/roomescape/reservation/ReservationRepository.java @@ -0,0 +1,29 @@ +package roomescape.reservation; + +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import roomescape.theme.Theme; +import roomescape.time.Time; + +public interface ReservationRepository extends JpaRepository { + + List findByDateAndThemeId(String date, Long themeId); + + @Query("SELECT r FROM Reservation r JOIN FETCH r.theme JOIN FETCH r.time WHERE r.member.id = :memberId") + List findByMemberIdWithThemeAndTime(@Param("memberId") Long memberId); + + boolean existsByMemberIdAndDateAndTimeAndTheme(Long memberId, String date, Time time, + Theme theme); + + long countByDateAndTimeAndThemeAndStatus(String date, Time time, Theme theme, + ReservationStatus reservationStatus); + + Optional findFirstByDateAndTimeAndThemeAndStatus( + String date, Time time, Theme theme, ReservationStatus status); + + List findByDateAndTimeAndThemeAndStatusOrderByIdAsc(String date, Time time, + Theme theme, ReservationStatus reservationStatus); +} diff --git a/src/main/java/roomescape/reservation/ReservationResponse.java b/src/main/java/roomescape/reservation/ReservationResponse.java index 41360a363..a84ab105b 100644 --- a/src/main/java/roomescape/reservation/ReservationResponse.java +++ b/src/main/java/roomescape/reservation/ReservationResponse.java @@ -1,6 +1,7 @@ package roomescape.reservation; public class ReservationResponse { + private Long id; private String name; private String theme; @@ -15,6 +16,16 @@ public ReservationResponse(Long id, String name, String theme, String date, Stri this.time = time; } + public static ReservationResponse from(Reservation reservation) { + return new ReservationResponse( + reservation.getId(), + reservation.getMember().getName(), + reservation.getTheme().getName(), + reservation.getDate(), + reservation.getTime().getValue() + ); + } + public Long getId() { return id; } diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index bd3313328..5493a25e0 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -1,30 +1,139 @@ package roomescape.reservation; -import org.springframework.stereotype.Service; - import java.util.List; +import org.springframework.stereotype.Service; +import roomescape.member.Member; +import roomescape.member.MemberRepository; +import roomescape.theme.Theme; +import roomescape.theme.ThemeRepository; +import roomescape.time.Time; +import roomescape.time.TimeRepository; @Service public class ReservationService { - private ReservationDao reservationDao; - public ReservationService(ReservationDao reservationDao) { - this.reservationDao = reservationDao; + private static final int MAX_RESERVED_COUNT = 1; + + private final ReservationRepository reservationRepository; + private final MemberRepository memberRepository; + private final TimeRepository timeRepository; + private final ThemeRepository themeRepository; + + public ReservationService( + ReservationRepository reservationRepository, + MemberRepository memberRepository, + TimeRepository timeRepository, + ThemeRepository themeRepository + ) { + this.reservationRepository = reservationRepository; + this.memberRepository = memberRepository; + this.timeRepository = timeRepository; + this.themeRepository = themeRepository; } - public ReservationResponse save(ReservationRequest reservationRequest) { - Reservation reservation = reservationDao.save(reservationRequest); + public ReservationResponse save(Long memberId, ReservationRequest reservationRequest) { + Member member = getMember(memberId); + Time time = getTime(reservationRequest.getTime()); + Theme theme = getTheme(reservationRequest.getTheme()); + + validateDuplicateReservation(memberId, reservationRequest.getDate(), time, theme); + + Reservation reservation = new Reservation( + member.getName(), + reservationRequest.getDate(), + time, + theme, + member, + determineReservationStatus(reservationRequest.getDate(), time, theme) + ); - return new ReservationResponse(reservation.getId(), reservationRequest.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); + Reservation savedReservation = reservationRepository.save(reservation); + return ReservationResponse.from(savedReservation); } - public void deleteById(Long id) { - reservationDao.deleteById(id); + public void cancel(Long id) { + Reservation reservation = reservationRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException( + "not found reservation with id:" + id)); + boolean wasReserved = reservation.isReserved(); + + reservationRepository.deleteById(id); + + if (wasReserved) { + promoteWaitingToReserved(reservation); + } } public List findAll() { - return reservationDao.findAll().stream() - .map(it -> new ReservationResponse(it.getId(), it.getName(), it.getTheme().getName(), it.getDate(), it.getTime().getValue())) - .toList(); + return reservationRepository.findAll().stream() + .map(it -> new ReservationResponse( + it.getId(), + it.getName(), + it.getTheme().getName(), + it.getDate(), + it.getTime().getValue() + )).toList(); + } + + public List findMyReservations(Long memberId) { + List myReservations = + reservationRepository.findByMemberIdWithThemeAndTime(memberId); + + return myReservations.stream() + .map(reservation -> { + Integer rank = reservation.isWaiting() ? calculateWaitingRank(reservation) : null; + return MyReservationResponse.from(reservation, rank); + }) + .toList(); + } + + private Member getMember(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow( + () -> new IllegalArgumentException("not found member with id: " + memberId)); + } + + private Time getTime(Long timeId) { + return timeRepository.findById(timeId) + .orElseThrow(() -> new IllegalArgumentException("not found time with id: " + timeId)); + } + + private Theme getTheme(Long themeId) { + return themeRepository.findById(themeId) + .orElseThrow(() -> new IllegalArgumentException("not found theme with id: " + themeId)); + } + + private ReservationStatus determineReservationStatus(String date, Time time, Theme theme) { + long reservedCount = + reservationRepository.countByDateAndTimeAndThemeAndStatus(date, time, theme, + ReservationStatus.RESERVED); + return (reservedCount < MAX_RESERVED_COUNT) ? ReservationStatus.RESERVED + : ReservationStatus.WAITING; + } + + private void validateDuplicateReservation(Long memberId, String date, Time time, Theme theme) { + if (reservationRepository.existsByMemberIdAndDateAndTimeAndTheme(memberId, date, time, + theme)) { + throw new IllegalArgumentException("Already reserved"); + } + } + + private Integer calculateWaitingRank(Reservation reservation) { + List waitings = reservationRepository.findByDateAndTimeAndThemeAndStatusOrderByIdAsc( + reservation.getDate(), + reservation.getTime(), + reservation.getTheme(), + ReservationStatus.WAITING + ); + return waitings.indexOf(reservation) + 1; + } + + private void promoteWaitingToReserved(Reservation cancelledReservation) { + reservationRepository.findFirstByDateAndTimeAndThemeAndStatus( + cancelledReservation.getDate(), + cancelledReservation.getTime(), + cancelledReservation.getTheme(), + ReservationStatus.WAITING + ).ifPresent(Reservation::promoteToReserved); } } diff --git a/src/main/java/roomescape/reservation/ReservationStatus.java b/src/main/java/roomescape/reservation/ReservationStatus.java new file mode 100644 index 000000000..798bb354a --- /dev/null +++ b/src/main/java/roomescape/reservation/ReservationStatus.java @@ -0,0 +1,15 @@ +package roomescape.reservation; + +public enum ReservationStatus { + RESERVED("예약"), WAITING("예약대기"); + + private final String status; + + ReservationStatus(String status) { + this.status = status; + } + + public String getStatus() { + return status; + } +} diff --git a/src/main/java/roomescape/theme/Theme.java b/src/main/java/roomescape/theme/Theme.java index 430a6239c..8be4abd92 100644 --- a/src/main/java/roomescape/theme/Theme.java +++ b/src/main/java/roomescape/theme/Theme.java @@ -1,6 +1,15 @@ package roomescape.theme; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity public class Theme { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String description; diff --git a/src/main/java/roomescape/theme/ThemeController.java b/src/main/java/roomescape/theme/ThemeController.java index 03bca41a6..62c77eb5e 100644 --- a/src/main/java/roomescape/theme/ThemeController.java +++ b/src/main/java/roomescape/theme/ThemeController.java @@ -1,5 +1,7 @@ package roomescape.theme; +import java.net.URI; +import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -8,31 +10,29 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import java.net.URI; -import java.util.List; - @RestController public class ThemeController { - private ThemeDao themeDao; - public ThemeController(ThemeDao themeDao) { - this.themeDao = themeDao; + private ThemeRepository themeRepository; + + public ThemeController(ThemeRepository themeRepository) { + this.themeRepository = themeRepository; } @PostMapping("/themes") public ResponseEntity createTheme(@RequestBody Theme theme) { - Theme newTheme = themeDao.save(theme); + Theme newTheme = themeRepository.save(theme); return ResponseEntity.created(URI.create("/themes/" + newTheme.getId())).body(newTheme); } @GetMapping("/themes") public ResponseEntity> list() { - return ResponseEntity.ok(themeDao.findAll()); + return ResponseEntity.ok(themeRepository.findAll()); } @DeleteMapping("/themes/{id}") public ResponseEntity deleteTheme(@PathVariable Long id) { - themeDao.deleteById(id); + themeRepository.deleteById(id); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/roomescape/theme/ThemeDao.java b/src/main/java/roomescape/theme/ThemeDao.java deleted file mode 100644 index 945341d8d..000000000 --- a/src/main/java/roomescape/theme/ThemeDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package roomescape.theme; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public class ThemeDao { - private JdbcTemplate jdbcTemplate; - - public ThemeDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List findAll() { - return jdbcTemplate.query("SELECT * FROM theme where deleted = false", (rs, rowNum) -> new Theme( - rs.getLong("id"), - rs.getString("name"), - rs.getString("description") - )); - } - - public Theme save(Theme theme) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(connection -> { - var ps = connection.prepareStatement("INSERT INTO theme(name, description) VALUES (?, ?)", new String[]{"id"}); - ps.setString(1, theme.getName()); - ps.setString(2, theme.getDescription()); - return ps; - }, keyHolder); - - return new Theme(keyHolder.getKey().longValue(), theme.getName(), theme.getDescription()); - } - - public void deleteById(Long id) { - jdbcTemplate.update("UPDATE theme SET deleted = true WHERE id = ?", id); - } -} diff --git a/src/main/java/roomescape/theme/ThemeRepository.java b/src/main/java/roomescape/theme/ThemeRepository.java new file mode 100644 index 000000000..0f94b11c2 --- /dev/null +++ b/src/main/java/roomescape/theme/ThemeRepository.java @@ -0,0 +1,8 @@ +package roomescape.theme; + +import org.springframework.data.jpa.repository.JpaRepository; +import roomescape.reservation.Reservation; + +public interface ThemeRepository extends JpaRepository { + +} diff --git a/src/main/java/roomescape/time/Time.java b/src/main/java/roomescape/time/Time.java index 008ed93cf..c6a680c74 100644 --- a/src/main/java/roomescape/time/Time.java +++ b/src/main/java/roomescape/time/Time.java @@ -1,7 +1,19 @@ package roomescape.time; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity public class Time { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(name = "time_value") private String value; public Time(Long id, String value) { diff --git a/src/main/java/roomescape/time/TimeDao.java b/src/main/java/roomescape/time/TimeDao.java deleted file mode 100644 index f39a9a328..000000000 --- a/src/main/java/roomescape/time/TimeDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package roomescape.time; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; - -import java.sql.PreparedStatement; -import java.util.List; - -@Repository -public class TimeDao { - private final JdbcTemplate jdbcTemplate; - - public TimeDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List