From 0c5989c16c65ce4451bece43052b38095c4f7bf3 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Sat, 22 Jun 2024 20:12:16 +0900 Subject: [PATCH 01/15] =?UTF-8?q?feat:=201=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/member/LoginRequest.java | 14 +++++++ .../roomescape/member/MemberController.java | 39 ++++++++++++++++++- .../java/roomescape/member/MemberService.java | 36 +++++++++++++++++ src/main/resources/application.properties | 10 ++--- 4 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 src/main/java/roomescape/member/LoginRequest.java diff --git a/src/main/java/roomescape/member/LoginRequest.java b/src/main/java/roomescape/member/LoginRequest.java new file mode 100644 index 000000000..c91e6326e --- /dev/null +++ b/src/main/java/roomescape/member/LoginRequest.java @@ -0,0 +1,14 @@ +package roomescape.member; + +public class LoginRequest { + private String email; + private String password; + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 881ae5e0d..511b40d18 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -13,7 +13,7 @@ @RestController public class MemberController { - private MemberService memberService; + private final MemberService memberService; public MemberController(MemberService memberService) { this.memberService = memberService; @@ -25,6 +25,32 @@ public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member); } + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpServletResponse response) { + Member member = memberService.login(loginRequest.getEmail(), loginRequest.getPassword()); + if (member != null) { + String token = memberService.createToken(member); + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + return ResponseEntity.ok().build(); + } + return ResponseEntity.badRequest().build(); + } + + @GetMapping("/login/check") + public ResponseEntity checkLogin(HttpServletRequest request) { + String token = extractTokenFromCookie(request.getCookies()); + if (token != null && !token.isEmpty()) { + Member member = memberService.findMemberByToken(token); + if (member != null) { + return ResponseEntity.ok(new MemberResponse(member.getId(), member.getName(), member.getEmail())); + } + } + return ResponseEntity.badRequest().build(); + } + @PostMapping("/logout") public ResponseEntity logout(HttpServletResponse response) { Cookie cookie = new Cookie("token", ""); @@ -34,4 +60,15 @@ public ResponseEntity logout(HttpServletResponse response) { response.addCookie(cookie); return ResponseEntity.ok().build(); } + + private String extractTokenFromCookie(Cookie[] cookies) { + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("token")) { + return cookie.getValue(); + } + } + } + return null; + } } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index ccaa8cba5..6a479618d 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -1,11 +1,18 @@ package roomescape.member; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service public class MemberService { private MemberDao memberDao; + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + public MemberService(MemberDao memberDao) { this.memberDao = memberDao; } @@ -14,4 +21,33 @@ public MemberResponse createMember(MemberRequest memberRequest) { Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER")); return new MemberResponse(member.getId(), member.getName(), member.getEmail()); } + + public Member login(String email, String password) { + return memberDao.findByEmailAndPassword(email, password); + } + + public String createToken(Member member) { + return Jwts.builder() + .setSubject(member.getId().toString()) + .claim("name", member.getName()) + .claim("role", member.getRole()) + .signWith(Keys.hmacShaKeyFor(secretKey.getBytes())) + .compact(); + } + + public Member findMemberByToken(String token) { + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) + .build() + .parseClaimsJws(token) + .getBody(); + + Long memberId = Long.valueOf(claims.getSubject()); + String name = claims.get("name", String.class); + return memberDao.findByName(name); + } catch (Exception e) { + return null; + } + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a0f33bbab..59c46dc37 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,9 +3,9 @@ spring.h2.console.enabled=true spring.h2.console.path=/h2-console spring.datasource.url=jdbc:h2:mem:database -#spring.jpa.show-sql=true -#spring.jpa.properties.hibernate.format_sql=true -#spring.jpa.ddl-auto=create-drop -#spring.jpa.defer-datasource-initialization=true +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.ddl-auto=create-drop +spring.jpa.defer-datasource-initialization=true -#roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= \ No newline at end of file +roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= From 66c70ce3ef4d60b163ad54a70a03cf0d227fd468 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Sat, 22 Jun 2024 20:50:53 +0900 Subject: [PATCH 02/15] =?UTF-8?q?feat:=202=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/config/WebConfig.java | 24 +++++++++ .../java/roomescape/member/LoginMember.java | 4 ++ .../member/LoginMemberArgumentResolver.java | 46 ++++++++++++++++ .../java/roomescape/member/LoginRequest.java | 12 +---- .../roomescape/member/MemberController.java | 26 ++------- .../java/roomescape/member/MemberRequest.java | 17 +----- .../roomescape/member/MemberResponse.java | 23 +------- .../java/roomescape/member/MemberService.java | 2 +- .../reservation/ReservationController.java | 20 ++++--- .../reservation/ReservationDao.java | 16 +++--- .../reservation/ReservationRequest.java | 22 +------- .../reservation/ReservationResponse.java | 35 +----------- .../reservation/ReservationService.java | 17 ++++-- src/test/java/roomescape/MissionStepTest.java | 54 ++++++++++++++++++- 14 files changed, 173 insertions(+), 145 deletions(-) create mode 100644 src/main/java/roomescape/config/WebConfig.java create mode 100644 src/main/java/roomescape/member/LoginMember.java create mode 100644 src/main/java/roomescape/member/LoginMemberArgumentResolver.java diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java new file mode 100644 index 000000000..ff843f651 --- /dev/null +++ b/src/main/java/roomescape/config/WebConfig.java @@ -0,0 +1,24 @@ +package roomescape.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import roomescape.member.LoginMemberArgumentResolver; +import roomescape.member.MemberService; + +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + private final MemberService memberService; + + public WebConfig(MemberService memberService) { + this.memberService = memberService; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new LoginMemberArgumentResolver(memberService)); + } +} diff --git a/src/main/java/roomescape/member/LoginMember.java b/src/main/java/roomescape/member/LoginMember.java new file mode 100644 index 000000000..3305ed956 --- /dev/null +++ b/src/main/java/roomescape/member/LoginMember.java @@ -0,0 +1,4 @@ +package roomescape.member; + +public record LoginMember(Long id, String name, String email, String role) { +} diff --git a/src/main/java/roomescape/member/LoginMemberArgumentResolver.java b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java new file mode 100644 index 000000000..20d6b86f4 --- /dev/null +++ b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java @@ -0,0 +1,46 @@ +package roomescape.member; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + private MemberService memberService; + + public LoginMemberArgumentResolver(MemberService memberService) { + this.memberService = memberService; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(LoginMember.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + String token = extractTokenFromCookie(request.getCookies()); + if (token != null && !token.isEmpty()) { + Member member = memberService.findMemberByToken(token); + if (member != null) { + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } + } + return null; + } + + private String extractTokenFromCookie(Cookie[] cookies) { + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("token")) { + return cookie.getValue(); + } + } + } + return null; + } +} diff --git a/src/main/java/roomescape/member/LoginRequest.java b/src/main/java/roomescape/member/LoginRequest.java index c91e6326e..66d1bc33c 100644 --- a/src/main/java/roomescape/member/LoginRequest.java +++ b/src/main/java/roomescape/member/LoginRequest.java @@ -1,14 +1,4 @@ package roomescape.member; -public class LoginRequest { - private String email; - private String password; - - public String getEmail() { - return email; - } - - public String getPassword() { - return password; - } +public record LoginRequest(String email, String password) { } diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 511b40d18..98b403075 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -1,7 +1,6 @@ package roomescape.member; import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -22,12 +21,12 @@ public MemberController(MemberService memberService) { @PostMapping("/members") public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { MemberResponse member = memberService.createMember(memberRequest); - return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member); + return ResponseEntity.created(URI.create("/members/" + member.id())).body(member); } @PostMapping("/login") public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpServletResponse response) { - Member member = memberService.login(loginRequest.getEmail(), loginRequest.getPassword()); + Member member = memberService.login(loginRequest.email(), loginRequest.password()); if (member != null) { String token = memberService.createToken(member); Cookie cookie = new Cookie("token", token); @@ -40,13 +39,9 @@ public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpSe } @GetMapping("/login/check") - public ResponseEntity checkLogin(HttpServletRequest request) { - String token = extractTokenFromCookie(request.getCookies()); - if (token != null && !token.isEmpty()) { - Member member = memberService.findMemberByToken(token); - if (member != null) { - return ResponseEntity.ok(new MemberResponse(member.getId(), member.getName(), member.getEmail())); - } + public ResponseEntity checkLogin(LoginMember loginMember) { + if (loginMember != null) { + return ResponseEntity.ok(new MemberResponse(loginMember.id(), loginMember.name(), loginMember.email())); } return ResponseEntity.badRequest().build(); } @@ -60,15 +55,4 @@ public ResponseEntity logout(HttpServletResponse response) { response.addCookie(cookie); return ResponseEntity.ok().build(); } - - private String extractTokenFromCookie(Cookie[] cookies) { - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("token")) { - return cookie.getValue(); - } - } - } - return null; - } } diff --git a/src/main/java/roomescape/member/MemberRequest.java b/src/main/java/roomescape/member/MemberRequest.java index cafb79f14..50285d59f 100644 --- a/src/main/java/roomescape/member/MemberRequest.java +++ b/src/main/java/roomescape/member/MemberRequest.java @@ -1,19 +1,4 @@ package roomescape.member; -public class MemberRequest { - private String name; - private String email; - private String password; - - public String getName() { - return name; - } - - public String getEmail() { - return email; - } - - public String getPassword() { - return password; - } +public record MemberRequest(String name, String email, String password) { } diff --git a/src/main/java/roomescape/member/MemberResponse.java b/src/main/java/roomescape/member/MemberResponse.java index b9fa3b97a..c1babc0c9 100644 --- a/src/main/java/roomescape/member/MemberResponse.java +++ b/src/main/java/roomescape/member/MemberResponse.java @@ -1,25 +1,4 @@ package roomescape.member; -public class MemberResponse { - private Long id; - private String name; - private String email; - - public MemberResponse(Long id, String name, String email) { - this.id = id; - this.name = name; - this.email = email; - } - - public Long getId() { - return id; - } - - public String getName() { - return name; - } - - public String getEmail() { - return email; - } +public record MemberResponse(Long id, String name, String email) { } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index 6a479618d..46c509e74 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -18,7 +18,7 @@ public MemberService(MemberDao memberDao) { } public MemberResponse createMember(MemberRequest memberRequest) { - Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER")); + Member member = memberDao.save(new Member(memberRequest.name(), memberRequest.email(), memberRequest.password(), "USER")); return new MemberResponse(member.getId(), member.getName(), member.getEmail()); } diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index b3bef3990..4dbb811cd 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import roomescape.member.LoginMember; import java.net.URI; import java.util.List; @@ -26,16 +27,21 @@ public List list() { } @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { - if (reservationRequest.getName() == null - || reservationRequest.getDate() == null - || reservationRequest.getTheme() == null - || reservationRequest.getTime() == null) { + public ResponseEntity create(@RequestBody ReservationRequest reservationRequest, LoginMember loginMember) { + if (reservationRequest.date() == null + || reservationRequest.theme() == null + || reservationRequest.time() == null) { return ResponseEntity.badRequest().build(); } - ReservationResponse reservation = reservationService.save(reservationRequest); - return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); + String name = reservationRequest.name(); + if (name == null && loginMember != null) { + name = loginMember.name(); + } + + ReservationResponse reservation = reservationService.save(reservationRequest, name); + + return ResponseEntity.created(URI.create("/reservations/" + reservation.id())).body(reservation); } @DeleteMapping("/reservations/{id}") diff --git a/src/main/java/roomescape/reservation/ReservationDao.java b/src/main/java/roomescape/reservation/ReservationDao.java index a4972430c..d75201e31 100644 --- a/src/main/java/roomescape/reservation/ReservationDao.java +++ b/src/main/java/roomescape/reservation/ReservationDao.java @@ -47,25 +47,25 @@ 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()); + ps.setString(1, reservationRequest.date()); + ps.setString(2, reservationRequest.name()); + ps.setLong(3, reservationRequest.theme()); + ps.setLong(4, reservationRequest.time()); 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()); + reservationRequest.time()); Theme theme = jdbcTemplate.queryForObject("SELECT * FROM theme WHERE id = ?", (rs, rowNum) -> new Theme(rs.getLong("id"), rs.getString("name"), rs.getString("description")), - reservationRequest.getTheme()); + reservationRequest.theme()); return new Reservation( keyHolder.getKey().longValue(), - reservationRequest.getName(), - reservationRequest.getDate(), + reservationRequest.name(), + reservationRequest.date(), time, theme ); diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java index 19f441246..42f008b57 100644 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/roomescape/reservation/ReservationRequest.java @@ -1,24 +1,4 @@ package roomescape.reservation; -public class ReservationRequest { - private String name; - private String date; - private Long theme; - private Long time; - - public String getName() { - return name; - } - - public String getDate() { - return date; - } - - public Long getTheme() { - return theme; - } - - public Long getTime() { - return time; - } +public record ReservationRequest(String name, String date, Long theme, Long time) { } diff --git a/src/main/java/roomescape/reservation/ReservationResponse.java b/src/main/java/roomescape/reservation/ReservationResponse.java index 41360a363..8460cf82a 100644 --- a/src/main/java/roomescape/reservation/ReservationResponse.java +++ b/src/main/java/roomescape/reservation/ReservationResponse.java @@ -1,37 +1,4 @@ package roomescape.reservation; -public class ReservationResponse { - private Long id; - private String name; - private String theme; - private String date; - private String time; - - public ReservationResponse(Long id, String name, String theme, String date, String time) { - this.id = id; - this.name = name; - this.theme = theme; - this.date = date; - this.time = time; - } - - public Long getId() { - return id; - } - - public String getName() { - return name; - } - - public String getTheme() { - return theme; - } - - public String getDate() { - return date; - } - - public String getTime() { - return time; - } +public record ReservationResponse(Long id, String name, String theme, String date, String time) { } diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index bd3313328..d38143b8b 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -12,10 +12,21 @@ public ReservationService(ReservationDao reservationDao) { this.reservationDao = reservationDao; } - public ReservationResponse save(ReservationRequest reservationRequest) { - Reservation reservation = reservationDao.save(reservationRequest); + public ReservationResponse save(ReservationRequest reservationRequest, String name) { + if (name == null) { + throw new IllegalArgumentException("Name is required for reservation"); + } - return new ReservationResponse(reservation.getId(), reservationRequest.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); + ReservationRequest updatedRequest = new ReservationRequest( + name, + reservationRequest.date(), + reservationRequest.theme(), + reservationRequest.time() + ); + + Reservation reservation = reservationDao.save(updatedRequest); + + return new ReservationResponse(reservation.getId(), name, reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); } public void deleteById(Long id) { diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 6add784bd..e9e94be90 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -5,8 +5,13 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import roomescape.member.Member; +import roomescape.member.MemberService; +import roomescape.reservation.ReservationResponse; import java.util.HashMap; import java.util.Map; @@ -16,6 +21,11 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { + @Autowired + private MemberService memberService; + + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; @Test void 일단계() { @@ -35,4 +45,46 @@ public class MissionStepTest { assertThat(token).isNotBlank(); } -} \ No newline at end of file + + @Test + void 이단계() { + String token = createTokenForTest("admin@email.com", "password"); // 일단계에서 토큰을 추출하는 로직을 메서드로 따로 만들어서 활용하세요. + + Map params = new HashMap<>(); + params.put("date", "2024-03-01"); + params.put("time", "1"); + params.put("theme", "1"); + + ExtractableResponse response = RestAssured.given().log().all() + .body(params) + .cookie("token", token) + .contentType(ContentType.JSON) + .post("/reservations") + .then().log().all() + .extract(); + + assertThat(response.statusCode()).isEqualTo(201); + assertThat(response.as(ReservationResponse.class).name()).isEqualTo("어드민"); + + params.put("name", "브라운"); + + ExtractableResponse adminResponse = RestAssured.given().log().all() + .body(params) + .cookie("token", token) + .contentType(ContentType.JSON) + .post("/reservations") + .then().log().all() + .extract(); + + assertThat(adminResponse.statusCode()).isEqualTo(201); + assertThat(adminResponse.as(ReservationResponse.class).name()).isEqualTo("브라운"); + } + + private String createTokenForTest(String email, String password) { + Member member = memberService.login(email, password); + if (member == null) { + throw new IllegalArgumentException("Invalid email or password"); + } + return memberService.createToken(member); + } +} From c4e2c38188ef32404dd4dc521bddefb601259e45 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Sat, 22 Jun 2024 21:04:56 +0900 Subject: [PATCH 03/15] =?UTF-8?q?feat:=203=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/config/AdminInterceptor.java | 50 +++++++++++++++++++ .../java/roomescape/config/WebConfig.java | 7 +++ .../exception/ForbiddenException.java | 7 +++ .../exception/UnauthorizedException.java | 7 +++ .../member/LoginMemberArgumentResolver.java | 15 +----- .../java/roomescape/member/MemberService.java | 1 - src/main/java/roomescape/util/CookieUtil.java | 16 ++++++ src/test/java/roomescape/MissionStepTest.java | 19 +++++++ 8 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 src/main/java/roomescape/config/AdminInterceptor.java create mode 100644 src/main/java/roomescape/exception/ForbiddenException.java create mode 100644 src/main/java/roomescape/exception/UnauthorizedException.java create mode 100644 src/main/java/roomescape/util/CookieUtil.java diff --git a/src/main/java/roomescape/config/AdminInterceptor.java b/src/main/java/roomescape/config/AdminInterceptor.java new file mode 100644 index 000000000..0f826a25b --- /dev/null +++ b/src/main/java/roomescape/config/AdminInterceptor.java @@ -0,0 +1,50 @@ +package roomescape.config; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.servlet.HandlerInterceptor; +import roomescape.exception.ForbiddenException; +import roomescape.exception.UnauthorizedException; +import roomescape.member.Member; +import roomescape.member.MemberService; +import roomescape.util.CookieUtil; + +public class AdminInterceptor implements HandlerInterceptor { + + private final MemberService memberService; + + public AdminInterceptor(MemberService memberService) { + this.memberService = memberService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + try { + String token = CookieUtil.extractTokenFromCookie(request.getCookies()); + + if (token == null) { + throw new UnauthorizedException("No token found in the request"); + } + + Member member = memberService.findMemberByToken(token); + + if (member == null) { + throw new UnauthorizedException("Invalid token"); + } + + if (!"ADMIN".equals(member.getRole())) { + throw new ForbiddenException("User does not have admin privileges"); + } + + return true; + } catch (UnauthorizedException | ForbiddenException e) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write(e.getMessage()); + return false; + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("An unexpected error occurred"); + return false; + } + } +} diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java index ff843f651..dccd684a3 100644 --- a/src/main/java/roomescape/config/WebConfig.java +++ b/src/main/java/roomescape/config/WebConfig.java @@ -2,6 +2,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import roomescape.member.LoginMemberArgumentResolver; import roomescape.member.MemberService; @@ -21,4 +22,10 @@ public WebConfig(MemberService memberService) { public void addArgumentResolvers(List resolvers) { resolvers.add(new LoginMemberArgumentResolver(memberService)); } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new AdminInterceptor(memberService)) + .addPathPatterns("/admin/**"); + } } diff --git a/src/main/java/roomescape/exception/ForbiddenException.java b/src/main/java/roomescape/exception/ForbiddenException.java new file mode 100644 index 000000000..49b538041 --- /dev/null +++ b/src/main/java/roomescape/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package roomescape.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/src/main/java/roomescape/exception/UnauthorizedException.java b/src/main/java/roomescape/exception/UnauthorizedException.java new file mode 100644 index 000000000..0aad343f0 --- /dev/null +++ b/src/main/java/roomescape/exception/UnauthorizedException.java @@ -0,0 +1,7 @@ +package roomescape.exception; + +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/src/main/java/roomescape/member/LoginMemberArgumentResolver.java b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java index 20d6b86f4..803d1702f 100644 --- a/src/main/java/roomescape/member/LoginMemberArgumentResolver.java +++ b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java @@ -1,12 +1,12 @@ package roomescape.member; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +import roomescape.util.CookieUtil; public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { private MemberService memberService; @@ -23,7 +23,7 @@ public boolean supportsParameter(MethodParameter parameter) { @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - String token = extractTokenFromCookie(request.getCookies()); + String token = CookieUtil.extractTokenFromCookie(request.getCookies()); if (token != null && !token.isEmpty()) { Member member = memberService.findMemberByToken(token); if (member != null) { @@ -32,15 +32,4 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m } return null; } - - private String extractTokenFromCookie(Cookie[] cookies) { - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("token")) { - return cookie.getValue(); - } - } - } - return null; - } } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index 46c509e74..5336145aa 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -43,7 +43,6 @@ public Member findMemberByToken(String token) { .parseClaimsJws(token) .getBody(); - Long memberId = Long.valueOf(claims.getSubject()); String name = claims.get("name", String.class); return memberDao.findByName(name); } catch (Exception e) { diff --git a/src/main/java/roomescape/util/CookieUtil.java b/src/main/java/roomescape/util/CookieUtil.java new file mode 100644 index 000000000..835d3d618 --- /dev/null +++ b/src/main/java/roomescape/util/CookieUtil.java @@ -0,0 +1,16 @@ +package roomescape.util; + +import jakarta.servlet.http.Cookie; + +public class CookieUtil { + public static String extractTokenFromCookie(Cookie[] cookies) { + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("token".equals(cookie.getName())) { + return cookie.getValue(); + } + } + } + return null; + } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index e9e94be90..937770d61 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -80,6 +80,25 @@ public class MissionStepTest { assertThat(adminResponse.as(ReservationResponse.class).name()).isEqualTo("브라운"); } + @Test + void 삼단계() { + String brownToken = createTokenForTest("brown@email.com", "password"); + + RestAssured.given().log().all() + .cookie("token", brownToken) + .get("/admin") + .then().log().all() + .statusCode(401); + + String adminToken = createTokenForTest("admin@email.com", "password"); + + RestAssured.given().log().all() + .cookie("token", adminToken) + .get("/admin") + .then().log().all() + .statusCode(200); + } + private String createTokenForTest(String email, String password) { Member member = memberService.login(email, password); if (member == null) { From dd28be6e01c69e990edff75567dc6d8cc1e885fe Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:43:53 +0900 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20=EC=84=A4=EC=A0=95=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=B4=ED=98=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=EB=AA=A8=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++ .gitmodules | 3 + build.gradle | 8 +++ spring-basic-roomescape-playground-submodule | 1 + src/main/resources/application.properties | 11 ---- src/main/resources/schema.sql | 60 -------------------- 6 files changed, 16 insertions(+), 71 deletions(-) create mode 100644 .gitmodules create mode 160000 spring-basic-roomescape-playground-submodule delete mode 100644 src/main/resources/application.properties delete mode 100644 src/main/resources/schema.sql diff --git a/.gitignore b/.gitignore index c2065bc26..e886013be 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ out/ ### VS Code ### .vscode/ + +### Secrets ### +application*.properties +schema.sql diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..0b5f74f4d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "spring-basic-roomescape-playground-submodule"] + path = spring-basic-roomescape-playground-submodule + url = https://github.com/zhy2on/spring-basic-roomescape-playground-submodule.git diff --git a/build.gradle b/build.gradle index 8d52aebc6..7617aefd7 100644 --- a/build.gradle +++ b/build.gradle @@ -32,3 +32,11 @@ dependencies { test { useJUnitPlatform() } + +processResources.dependsOn('copySecret') + +tasks.register('copySecret', Copy) { + from './spring-basic-roomescape-playground-submodule' + include 'application*.properties', 'schema.sql' + into './src/main/resources' +} diff --git a/spring-basic-roomescape-playground-submodule b/spring-basic-roomescape-playground-submodule new file mode 160000 index 000000000..3b55067b4 --- /dev/null +++ b/spring-basic-roomescape-playground-submodule @@ -0,0 +1 @@ +Subproject commit 3b55067b408ca08dddca7c7897f0869c81037dee diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 59c46dc37..000000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,11 +0,0 @@ -spring.sql.init.encoding=utf-8 -spring.h2.console.enabled=true -spring.h2.console.path=/h2-console -spring.datasource.url=jdbc:h2:mem:database - -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true -spring.jpa.ddl-auto=create-drop -spring.jpa.defer-datasource-initialization=true - -roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql deleted file mode 100644 index 75c947a5a..000000000 --- a/src/main/resources/schema.sql +++ /dev/null @@ -1,60 +0,0 @@ -CREATE TABLE time -( - id BIGINT NOT NULL AUTO_INCREMENT, - time_value VARCHAR(20) NOT NULL, - deleted BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (id) -); - -CREATE TABLE theme -( - id BIGINT NOT NULL AUTO_INCREMENT, - name VARCHAR(255) NOT NULL, - description VARCHAR(255) NOT NULL, - deleted BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (id) -); - -CREATE TABLE member -( - id BIGINT NOT NULL AUTO_INCREMENT, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - role VARCHAR(255) NOT NULL, - PRIMARY KEY (id) -); - -CREATE TABLE reservation -( - id BIGINT NOT NULL AUTO_INCREMENT, - date VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL, - time_id BIGINT, - theme_id BIGINT, - PRIMARY KEY (id), - FOREIGN KEY (time_id) REFERENCES time (id), - FOREIGN KEY (theme_id) REFERENCES theme (id) -); - -INSERT INTO member (name, email, password, role) -VALUES ('어드민', 'admin@email.com', 'password', 'ADMIN'), - ('브라운', 'brown@email.com', 'password', 'USER'); - -INSERT INTO theme (name, description) -VALUES ('테마1', '테마1입니다.'), - ('테마2', '테마2입니다.'), - ('테마3', '테마3입니다.'); - -INSERT INTO time (time_value) -VALUES ('10:00'), - ('12:00'), - ('14:00'), - ('16:00'), - ('18:00'), - ('20:00'); - -INSERT INTO reservation (name, date, time_id, theme_id) -VALUES ('어드민', '2024-03-01', 1, 1), - ('어드민', '2024-03-01', 2, 2), - ('어드민', '2024-03-01', 3, 3); \ No newline at end of file From 5c389574c3f486ef05b13052d641678e62dcc195 Mon Sep 17 00:00:00 2001 From: zhy2on <52701529+zhy2on@users.noreply.github.com> Date: Mon, 1 Jul 2024 20:27:07 +0900 Subject: [PATCH 05/15] =?UTF-8?q?feat:=204=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- spring-basic-roomescape-playground-submodule | 2 +- src/main/java/roomescape/member/Member.java | 11 ++ .../java/roomescape/member/MemberDao.java | 55 -------- .../roomescape/member/MemberRepository.java | 9 ++ .../java/roomescape/member/MemberService.java | 21 ++- .../roomescape/reservation/Reservation.java | 11 +- .../reservation/ReservationDao.java | 127 ------------------ .../reservation/ReservationRepository.java | 18 +++ .../reservation/ReservationService.java | 50 ++++--- src/main/java/roomescape/theme/Theme.java | 8 ++ .../roomescape/theme/ThemeController.java | 20 +-- src/main/java/roomescape/theme/ThemeDao.java | 41 ------ .../roomescape/theme/ThemeRepository.java | 6 + .../java/roomescape/theme/ThemeService.java | 25 ++++ .../java/roomescape/time/AvailableTime.java | 11 ++ src/main/java/roomescape/time/Time.java | 17 ++- src/main/java/roomescape/time/TimeDao.java | 41 ------ .../java/roomescape/time/TimeRepository.java | 6 + .../java/roomescape/time/TimeService.java | 22 +-- src/test/java/roomescape/JpaTest.java | 30 +++++ src/test/java/roomescape/MissionStepTest.java | 5 + 22 files changed, 216 insertions(+), 322 deletions(-) delete mode 100644 src/main/java/roomescape/member/MemberDao.java create mode 100644 src/main/java/roomescape/member/MemberRepository.java delete mode 100644 src/main/java/roomescape/reservation/ReservationDao.java create mode 100644 src/main/java/roomescape/reservation/ReservationRepository.java delete mode 100644 src/main/java/roomescape/theme/ThemeDao.java create mode 100644 src/main/java/roomescape/theme/ThemeRepository.java create mode 100644 src/main/java/roomescape/theme/ThemeService.java delete mode 100644 src/main/java/roomescape/time/TimeDao.java create mode 100644 src/main/java/roomescape/time/TimeRepository.java create mode 100644 src/test/java/roomescape/JpaTest.java diff --git a/build.gradle b/build.gradle index 7617aefd7..15eeb8a18 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/spring-basic-roomescape-playground-submodule b/spring-basic-roomescape-playground-submodule index 3b55067b4..7ee845cb6 160000 --- a/spring-basic-roomescape-playground-submodule +++ b/spring-basic-roomescape-playground-submodule @@ -1 +1 @@ -Subproject commit 3b55067b408ca08dddca7c7897f0869c81037dee +Subproject commit 7ee845cb60f7e4034cd330496ad567824a0b2cfa diff --git a/src/main/java/roomescape/member/Member.java b/src/main/java/roomescape/member/Member.java index 903aaa9b0..dd8346cbc 100644 --- a/src/main/java/roomescape/member/Member.java +++ b/src/main/java/roomescape/member/Member.java @@ -1,6 +1,14 @@ 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 +29,9 @@ 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/MemberRepository.java b/src/main/java/roomescape/member/MemberRepository.java new file mode 100644 index 000000000..142b1320e --- /dev/null +++ b/src/main/java/roomescape/member/MemberRepository.java @@ -0,0 +1,9 @@ +package roomescape.member; + +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + +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 5336145aa..560b30485 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -5,25 +5,30 @@ import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class MemberService { - private MemberDao memberDao; + private final MemberRepository memberRepository; @Value("${roomescape.auth.jwt.secret}") private String secretKey; - public MemberService(MemberDao memberDao) { - this.memberDao = memberDao; + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; } + @Transactional public MemberResponse createMember(MemberRequest memberRequest) { - Member member = memberDao.save(new Member(memberRequest.name(), memberRequest.email(), memberRequest.password(), "USER")); - return new MemberResponse(member.getId(), member.getName(), member.getEmail()); + Member member = new Member(memberRequest.name(), memberRequest.email(), memberRequest.password(), "USER"); + Member savedMember = memberRepository.save(member); + return new MemberResponse(savedMember.getId(), savedMember.getName(), savedMember.getEmail()); } + @Transactional(readOnly = true) public Member login(String email, String password) { - return memberDao.findByEmailAndPassword(email, password); + return memberRepository.findByEmailAndPassword(email, password) + .orElseThrow(() -> new IllegalArgumentException("Invalid email or password")); } public String createToken(Member member) { @@ -35,6 +40,7 @@ public String createToken(Member member) { .compact(); } + @Transactional(readOnly = true) public Member findMemberByToken(String token) { try { Claims claims = Jwts.parserBuilder() @@ -44,7 +50,8 @@ public Member findMemberByToken(String token) { .getBody(); String name = claims.get("name", String.class); - return memberDao.findByName(name); + return memberRepository.findByName(name) + .orElseThrow(() -> new IllegalArgumentException("Member not found")); } catch (Exception e) { return null; } diff --git a/src/main/java/roomescape/reservation/Reservation.java b/src/main/java/roomescape/reservation/Reservation.java index 83a7edf1b..ac0b13fc2 100644 --- a/src/main/java/roomescape/reservation/Reservation.java +++ b/src/main/java/roomescape/reservation/Reservation.java @@ -1,13 +1,23 @@ package roomescape.reservation; +import jakarta.persistence.*; 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 + @JoinColumn(name = "time_id") private Time time; + + @ManyToOne + @JoinColumn(name = "theme_id") private Theme theme; public Reservation(Long id, String name, String date, Time time, Theme theme) { @@ -26,7 +36,6 @@ public Reservation(String name, String date, Time time, Theme theme) { } public Reservation() { - } public Long getId() { diff --git a/src/main/java/roomescape/reservation/ReservationDao.java b/src/main/java/roomescape/reservation/ReservationDao.java deleted file mode 100644 index d75201e31..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.date()); - ps.setString(2, reservationRequest.name()); - ps.setLong(3, reservationRequest.theme()); - ps.setLong(4, reservationRequest.time()); - return ps; - }, keyHolder); - - Time time = jdbcTemplate.queryForObject("SELECT * FROM time WHERE id = ?", - (rs, rowNum) -> new Time(rs.getLong("id"), rs.getString("time_value")), - reservationRequest.time()); - - Theme theme = jdbcTemplate.queryForObject("SELECT * FROM theme WHERE id = ?", - (rs, rowNum) -> new Theme(rs.getLong("id"), rs.getString("name"), rs.getString("description")), - reservationRequest.theme()); - - return new Reservation( - keyHolder.getKey().longValue(), - reservationRequest.name(), - reservationRequest.date(), - 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..ea908dee0 --- /dev/null +++ b/src/main/java/roomescape/reservation/ReservationRepository.java @@ -0,0 +1,18 @@ +package roomescape.reservation; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ReservationRepository extends JpaRepository { + + @Query("SELECT r FROM Reservation r JOIN FETCH r.theme t JOIN FETCH r.time ti") + List findAllWithThemeAndTime(); + + List findByDateAndTheme_Id(String date, Long themeId); + + @Query("SELECT r FROM Reservation r JOIN FETCH r.theme t JOIN FETCH r.time ti WHERE r.date = :date AND r.theme.id = :themeId") + List findByDateAndThemeIdWithThemeAndTime(@Param("date") String date, @Param("themeId") Long themeId); +} diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index d38143b8b..eb84e80a9 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -1,41 +1,53 @@ package roomescape.reservation; import org.springframework.stereotype.Service; +import roomescape.theme.Theme; +import roomescape.theme.ThemeRepository; +import roomescape.time.Time; +import roomescape.time.TimeRepository; import java.util.List; @Service public class ReservationService { - private ReservationDao reservationDao; - - public ReservationService(ReservationDao reservationDao) { - this.reservationDao = reservationDao; + private final ReservationRepository reservationRepository; + private final ThemeRepository themeRepository; + private final TimeRepository timeRepository; + + public ReservationService(ReservationRepository reservationRepository, + ThemeRepository themeRepository, + TimeRepository timeRepository) { + this.reservationRepository = reservationRepository; + this.themeRepository = themeRepository; + this.timeRepository = timeRepository; } public ReservationResponse save(ReservationRequest reservationRequest, String name) { - if (name == null) { - throw new IllegalArgumentException("Name is required for reservation"); - } - - ReservationRequest updatedRequest = new ReservationRequest( - name, - reservationRequest.date(), - reservationRequest.theme(), - reservationRequest.time() - ); + Theme theme = themeRepository.findById(reservationRequest.theme()) + .orElseThrow(() -> new RuntimeException("Theme not found")); + Time time = timeRepository.findById(reservationRequest.time()) + .orElseThrow(() -> new RuntimeException("Time not found")); - Reservation reservation = reservationDao.save(updatedRequest); + Reservation reservation = new Reservation(name, reservationRequest.date(), time, theme); + Reservation savedReservation = reservationRepository.save(reservation); - return new ReservationResponse(reservation.getId(), name, reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); + return new ReservationResponse(savedReservation.getId(), name, theme.getName(), + reservationRequest.date(), time.getValue()); } public void deleteById(Long id) { - reservationDao.deleteById(id); + reservationRepository.deleteById(id); } public List findAll() { - return reservationDao.findAll().stream() - .map(it -> new ReservationResponse(it.getId(), it.getName(), it.getTheme().getName(), it.getDate(), it.getTime().getValue())) + return reservationRepository.findAllWithThemeAndTime().stream() + .map(it -> new ReservationResponse( + it.getId(), + it.getName(), + it.getTheme().getName(), + it.getDate(), + it.getTime().getValue() + )) .toList(); } } diff --git a/src/main/java/roomescape/theme/Theme.java b/src/main/java/roomescape/theme/Theme.java index 430a6239c..1063c86de 100644 --- a/src/main/java/roomescape/theme/Theme.java +++ b/src/main/java/roomescape/theme/Theme.java @@ -1,6 +1,14 @@ 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..94b643c04 100644 --- a/src/main/java/roomescape/theme/ThemeController.java +++ b/src/main/java/roomescape/theme/ThemeController.java @@ -1,38 +1,32 @@ package roomescape.theme; import org.springframework.http.ResponseEntity; -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.RestController; - +import org.springframework.web.bind.annotation.*; import java.net.URI; import java.util.List; @RestController public class ThemeController { - private ThemeDao themeDao; + private final ThemeService themeService; - public ThemeController(ThemeDao themeDao) { - this.themeDao = themeDao; + public ThemeController(ThemeService themeService) { + this.themeService = themeService; } @PostMapping("/themes") public ResponseEntity createTheme(@RequestBody Theme theme) { - Theme newTheme = themeDao.save(theme); + Theme newTheme = themeService.createTheme(theme); return ResponseEntity.created(URI.create("/themes/" + newTheme.getId())).body(newTheme); } @GetMapping("/themes") public ResponseEntity> list() { - return ResponseEntity.ok(themeDao.findAll()); + return ResponseEntity.ok(themeService.findAll()); } @DeleteMapping("/themes/{id}") public ResponseEntity deleteTheme(@PathVariable Long id) { - themeDao.deleteById(id); + themeService.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..cbdb21a3d --- /dev/null +++ b/src/main/java/roomescape/theme/ThemeRepository.java @@ -0,0 +1,6 @@ +package roomescape.theme; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ThemeRepository extends JpaRepository { +} diff --git a/src/main/java/roomescape/theme/ThemeService.java b/src/main/java/roomescape/theme/ThemeService.java new file mode 100644 index 000000000..3c3d345fa --- /dev/null +++ b/src/main/java/roomescape/theme/ThemeService.java @@ -0,0 +1,25 @@ +package roomescape.theme; + +import org.springframework.stereotype.Service; +import java.util.List; + +@Service +public class ThemeService { + private final ThemeRepository themeRepository; + + public ThemeService(ThemeRepository themeRepository) { + this.themeRepository = themeRepository; + } + + public Theme createTheme(Theme theme) { + return themeRepository.save(theme); + } + + public List findAll() { + return themeRepository.findAll(); + } + + public void deleteById(Long id) { + themeRepository.deleteById(id); + } +} diff --git a/src/main/java/roomescape/time/AvailableTime.java b/src/main/java/roomescape/time/AvailableTime.java index 33acef7e3..91afb0d3f 100644 --- a/src/main/java/roomescape/time/AvailableTime.java +++ b/src/main/java/roomescape/time/AvailableTime.java @@ -1,6 +1,14 @@ package roomescape.time; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity public class AvailableTime { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long timeId; private String time; private boolean booked; @@ -11,6 +19,9 @@ public AvailableTime(Long timeId, String time, boolean booked) { this.booked = booked; } + public AvailableTime() { + } + public Long getTimeId() { return timeId; } diff --git a/src/main/java/roomescape/time/Time.java b/src/main/java/roomescape/time/Time.java index 008ed93cf..8a4825c8f 100644 --- a/src/main/java/roomescape/time/Time.java +++ b/src/main/java/roomescape/time/Time.java @@ -1,20 +1,27 @@ package roomescape.time; +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; - private String value; + private String time_value; public Time(Long id, String value) { this.id = id; - this.value = value; + this.time_value = value; } public Time(String value) { - this.value = value; + this.time_value = value; } public Time() { - } public Long getId() { @@ -22,6 +29,6 @@ public Long getId() { } public String getValue() { - return value; + return time_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