From 672941e5ad5a2cdf9f0877da221309ada758c7e7 Mon Sep 17 00:00:00 2001 From: anhye0n Date: Mon, 24 Jun 2024 18:38:43 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=201=EB=8B=A8=EA=B3=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/member/MemberController.java | 39 ++++++++++++--- .../java/roomescape/member/MemberDao.java | 10 ++-- .../java/roomescape/member/MemberService.java | 47 ++++++++++++++++++- .../java/roomescape/token/TokenService.java | 10 ++++ src/test/java/roomescape/MissionStepTest.java | 15 ++++-- 5 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 src/main/java/roomescape/token/TokenService.java diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 881ae5e0d..40b988aa0 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -4,29 +4,54 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.net.URI; +import java.util.HashMap; +import java.util.Map; @RestController public class MemberController { - private MemberService memberService; + private final MemberService memberService; public MemberController(MemberService memberService) { this.memberService = memberService; } @PostMapping("/members") - public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { + public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { MemberResponse member = memberService.createMember(memberRequest); return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member); } + @PostMapping("/login") + public ResponseEntity login(@RequestBody MemberRequest memberRequest, HttpServletResponse response) { + memberService.login(memberRequest, response); + return ResponseEntity.ok().build(); + } + + @GetMapping("/login/check") + public ResponseEntity> checkLogin(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies == null) { + return ResponseEntity.status(401).build(); // Unauthorized + } + + String name; + try { + name = memberService.getNameFromToken(cookies); + } catch (Exception e) { + return ResponseEntity.status(401).build(); // Unauthorized + } + + Map responseBody = new HashMap<>(); + responseBody.put("name", name); + + return ResponseEntity.ok(responseBody); + } + @PostMapping("/logout") - public ResponseEntity logout(HttpServletResponse response) { + public ResponseEntity logout(HttpServletResponse response) { Cookie cookie = new Cookie("token", ""); cookie.setHttpOnly(true); cookie.setPath("/"); diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java index 81f77f4cd..c1f82f44f 100644 --- a/src/main/java/roomescape/member/MemberDao.java +++ b/src/main/java/roomescape/member/MemberDao.java @@ -7,7 +7,7 @@ @Repository public class MemberDao { - private JdbcTemplate jdbcTemplate; + private final JdbcTemplate jdbcTemplate; public MemberDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -24,7 +24,7 @@ public Member save(Member member) { return ps; }, keyHolder); - return new Member(keyHolder.getKey().longValue(), member.getName(), member.getEmail(), "USER"); + return new Member(keyHolder.getKey().longValue(), member.getName(), member.getEmail(), member.getRole()); } public Member findByEmailAndPassword(String email, String password) { @@ -40,16 +40,16 @@ public Member findByEmailAndPassword(String email, String password) { ); } - public Member findByName(String name) { + public Member findById(Long id) { return jdbcTemplate.queryForObject( - "SELECT id, name, email, role FROM member WHERE name = ?", + "SELECT id, name, email, role FROM member WHERE id = ?", (rs, rowNum) -> new Member( rs.getLong("id"), rs.getString("name"), rs.getString("email"), rs.getString("role") ), - name + id ); } } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index ccaa8cba5..fe4b1c1f7 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -1,10 +1,15 @@ package roomescape.member; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Service; @Service public class MemberService { - private MemberDao memberDao; + private final MemberDao memberDao; + private final String secretKey = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; public MemberService(MemberDao memberDao) { this.memberDao = memberDao; @@ -14,4 +19,44 @@ 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 void login(MemberRequest memberRequest, HttpServletResponse response) { + Member member = memberDao.findByEmailAndPassword(memberRequest.getEmail(), memberRequest.getPassword()); + String accessToken = Jwts.builder() + .setSubject(member.getId().toString()) + .claim("name", member.getName()) + .claim("role", member.getRole()) + .signWith(Keys.hmacShaKeyFor(secretKey.getBytes())) + .compact(); + + Cookie cookie = new Cookie("token", accessToken); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + } + + public String getNameFromToken(Cookie[] cookies) { + String token = extractTokenFromCookie(cookies); + if (token.isEmpty()) { + throw new RuntimeException("Token not found"); + } + + Long memberId = Long.valueOf(Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) + .build() + .parseClaimsJws(token) + .getBody().getSubject()); + + Member member = memberDao.findById(memberId); + return member.getName(); + } + + private String extractTokenFromCookie(Cookie[] cookies) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("token")) { + return cookie.getValue(); + } + } + return ""; + } } diff --git a/src/main/java/roomescape/token/TokenService.java b/src/main/java/roomescape/token/TokenService.java new file mode 100644 index 000000000..4c2efdd86 --- /dev/null +++ b/src/main/java/roomescape/token/TokenService.java @@ -0,0 +1,10 @@ +package roomescape.token; + +import jakarta.servlet.http.Cookie; + + +public class TokenService { + + + +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 6add784bd..a5b824b21 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -16,7 +16,6 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { - @Test void 일단계() { Map params = new HashMap<>(); @@ -32,7 +31,17 @@ public class MissionStepTest { .extract(); String token = response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; - assertThat(token).isNotBlank(); + + ExtractableResponse checkResponse = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .cookie("token", token) + .when().get("/login/check") + .then().log().all() + .statusCode(200) + .extract(); + + assertThat(checkResponse.body().jsonPath().getString("name")).isEqualTo("어드민"); } -} \ No newline at end of file +} + From 2626aa719c3f71f8df52e74879739141e94bd5a3 Mon Sep 17 00:00:00 2001 From: anhye0n Date: Mon, 24 Jun 2024 19:43:18 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=202=EB=8B=A8=EA=B3=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/member/LoginMember.java | 34 ++++++++++++ .../member/LoginMemberArgumentResolver.java | 55 +++++++++++++++++++ .../java/roomescape/member/MemberDao.java | 2 +- .../java/roomescape/member/MemberService.java | 35 +++++++----- .../reservation/ReservationController.java | 30 ++++++---- .../reservation/ReservationRequest.java | 26 +++++++-- .../reservation/ReservationService.java | 3 +- src/test/java/roomescape/MissionStepTest.java | 53 ++++++++++++++++++ 8 files changed, 206 insertions(+), 32 deletions(-) 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/member/LoginMember.java b/src/main/java/roomescape/member/LoginMember.java new file mode 100644 index 000000000..184c3b3b9 --- /dev/null +++ b/src/main/java/roomescape/member/LoginMember.java @@ -0,0 +1,34 @@ +package roomescape.member; + +public class LoginMember { + private Long id; + private String name; + private String email; + private String role; + + public LoginMember(Long id, String name, String email, String role) { + this.id = id; + this.name = name; + this.email = email; + this.role = role; + } + + // Getters and setters + + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public String getRole() { + return 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..976740db1 --- /dev/null +++ b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java @@ -0,0 +1,55 @@ +package roomescape.member; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + private final MemberService memberService; + private final String secretKey = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + + 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, org.springframework.web.bind.support.WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + Cookie[] cookies = request.getCookies(); + + String token = null; + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("token".equals(cookie.getName())) { + token = cookie.getValue(); + break; + } + } + } + + if (token == null) { + throw new IllegalArgumentException("No token found in cookies"); + } + + Long memberId = Long.valueOf(Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) + .build() + .parseClaimsJws(token) + .getBody().getSubject()); + + Member member = memberService.findById(memberId); + + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } +} diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java index c1f82f44f..1d68715ad 100644 --- a/src/main/java/roomescape/member/MemberDao.java +++ b/src/main/java/roomescape/member/MemberDao.java @@ -24,7 +24,7 @@ public Member save(Member member) { return ps; }, keyHolder); - return new Member(keyHolder.getKey().longValue(), member.getName(), member.getEmail(), member.getRole()); + return new Member(keyHolder.getKey().longValue(), member.getName(), member.getEmail(), "USER"); } public Member findByEmailAndPassword(String email, String password) { diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index fe4b1c1f7..d6c51b556 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -1,5 +1,6 @@ package roomescape.member; +import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import jakarta.servlet.http.Cookie; @@ -35,28 +36,32 @@ public void login(MemberRequest memberRequest, HttpServletResponse response) { response.addCookie(cookie); } + public Member findById(Long id) { + return memberDao.findById(id); + } + public String getNameFromToken(Cookie[] cookies) { - String token = extractTokenFromCookie(cookies); - if (token.isEmpty()) { - throw new RuntimeException("Token not found"); + String token = extractTokenFromCookies(cookies); + if (token != null) { + Claims claims = Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) + .build() + .parseClaimsJws(token) + .getBody(); + return claims.get("name", String.class); } - - Long memberId = Long.valueOf(Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) - .build() - .parseClaimsJws(token) - .getBody().getSubject()); - - Member member = memberDao.findById(memberId); - return member.getName(); + return null; } - private String extractTokenFromCookie(Cookie[] cookies) { + private String extractTokenFromCookies(Cookie[] cookies) { + if (cookies == null) { + return null; + } for (Cookie cookie : cookies) { - if (cookie.getName().equals("token")) { + if ("token".equals(cookie.getName())) { return cookie.getValue(); } } - return ""; + return null; } } diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index b3bef3990..af6c6467f 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -1,12 +1,10 @@ package roomescape.reservation; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; 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 roomescape.member.MemberService; import java.net.URI; import java.util.List; @@ -15,9 +13,11 @@ public class ReservationController { private final ReservationService reservationService; + private final MemberService memberService; - public ReservationController(ReservationService reservationService) { + public ReservationController(ReservationService reservationService, MemberService memberService) { this.reservationService = reservationService; + this.memberService = memberService; } @GetMapping("/reservations") @@ -26,13 +26,23 @@ public List list() { } @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { - if (reservationRequest.getName() == null - || reservationRequest.getDate() == null + public ResponseEntity create(@RequestBody ReservationRequest reservationRequest, HttpServletRequest request) { + if (reservationRequest.getName() == null) { + Cookie[] cookies = request.getCookies(); + String name = memberService.getNameFromToken(cookies); + if (name != null) { + reservationRequest.setName(name); + } else { + return ResponseEntity.badRequest().build(); // name이 없으면 400 응답 + } + } + + if (reservationRequest.getDate() == null || reservationRequest.getTheme() == null || reservationRequest.getTime() == null) { return ResponseEntity.badRequest().build(); } + ReservationResponse reservation = reservationService.save(reservationRequest); return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java index 19f441246..70c2dd21a 100644 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/roomescape/reservation/ReservationRequest.java @@ -1,24 +1,42 @@ package roomescape.reservation; public class ReservationRequest { - private String name; private String date; private Long theme; private Long time; + private String name; - public String getName() { - return name; - } + // Getters and setters public String getDate() { return date; } + public void setDate(String date) { + this.date = date; + } + public Long getTheme() { return theme; } + public void setTheme(Long theme) { + this.theme = theme; + } + public Long getTime() { return time; } + + public void setTime(Long time) { + this.time = time; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index bd3313328..be4243076 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -6,7 +6,7 @@ @Service public class ReservationService { - private ReservationDao reservationDao; + private final ReservationDao reservationDao; public ReservationService(ReservationDao reservationDao) { this.reservationDao = reservationDao; @@ -14,7 +14,6 @@ public ReservationService(ReservationDao reservationDao) { public ReservationResponse save(ReservationRequest reservationRequest) { Reservation reservation = reservationDao.save(reservationRequest); - return new ReservationResponse(reservation.getId(), reservationRequest.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index a5b824b21..410aee144 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import roomescape.reservation.ReservationResponse; import java.util.HashMap; import java.util.Map; @@ -16,6 +17,23 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { + + private String createToken(String email, String password){ + Map params = new HashMap<>(); + params.put("email", email); + params.put("password", password); + + ExtractableResponse response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/login") + .then().log().all() + .statusCode(200) + .extract(); + + return response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; + } + @Test void 일단계() { Map params = new HashMap<>(); @@ -43,5 +61,40 @@ public class MissionStepTest { assertThat(checkResponse.body().jsonPath().getString("name")).isEqualTo("어드민"); } + + @Test + void 이단계() { + String token = createToken("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).getName()).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).getName()).isEqualTo("브라운"); + } + } From a7a9513972f8bc6cef9f3e6ca791ede550a6eb62 Mon Sep 17 00:00:00 2001 From: anhye0n Date: Tue, 25 Jun 2024 10:54:33 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=203=EB=8B=A8=EA=B3=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/admin/AdminInterceptor.java | 50 +++++++++++++++++++ .../roomescape/config/InterceptorConfig.java | 20 ++++++++ .../roomescape/member/MemberController.java | 8 +-- .../java/roomescape/member/MemberService.java | 15 ++++++ .../reservation/ReservationController.java | 2 +- src/main/resources/static/js/theme.js | 2 +- src/test/java/roomescape/MissionStepTest.java | 21 +++++++- 7 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 src/main/java/roomescape/admin/AdminInterceptor.java create mode 100644 src/main/java/roomescape/config/InterceptorConfig.java diff --git a/src/main/java/roomescape/admin/AdminInterceptor.java b/src/main/java/roomescape/admin/AdminInterceptor.java new file mode 100644 index 000000000..c148e472b --- /dev/null +++ b/src/main/java/roomescape/admin/AdminInterceptor.java @@ -0,0 +1,50 @@ +package roomescape.admin; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +@Component +public class AdminInterceptor implements HandlerInterceptor { + + private static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String token = Arrays.stream(request.getCookies()) + .filter(cookie -> "token".equals(cookie.getName())) + .findFirst() + .map(Cookie::getValue) + .orElse(null); + + if (token == null || !isAdmin(token)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + return true; + } + + private boolean isAdmin(String token) { + System.out.println("Debug 1: " + token); + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) + .build() + .parseClaimsJws(token) + .getBody(); + String role = claims.get("role", String.class); + System.out.println("role test" + role); + return "ADMIN".equals(role); + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/roomescape/config/InterceptorConfig.java b/src/main/java/roomescape/config/InterceptorConfig.java new file mode 100644 index 000000000..8d0a9e286 --- /dev/null +++ b/src/main/java/roomescape/config/InterceptorConfig.java @@ -0,0 +1,20 @@ +package roomescape.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import roomescape.admin.AdminInterceptor; + +@Configuration +public class InterceptorConfig implements WebMvcConfigurer { + + @Autowired + private AdminInterceptor adminInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(adminInterceptor) + .addPathPatterns("/admin/**"); + } +} diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 40b988aa0..518e5924c 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -34,18 +34,20 @@ public ResponseEntity login(@RequestBody MemberRequest memberRequest, Http public ResponseEntity> checkLogin(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies == null) { - return ResponseEntity.status(401).build(); // Unauthorized + return ResponseEntity.status(401).build(); } - String name; + String name, role; try { name = memberService.getNameFromToken(cookies); + role = memberService.getRoleFromToken(cookies); } catch (Exception e) { - return ResponseEntity.status(401).build(); // Unauthorized + return ResponseEntity.status(401).build(); } Map responseBody = new HashMap<>(); responseBody.put("name", name); + responseBody.put("role", role); return ResponseEntity.ok(responseBody); } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index d6c51b556..c4aee638f 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -7,6 +7,8 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Service; +import java.nio.charset.StandardCharsets; + @Service public class MemberService { private final MemberDao memberDao; @@ -52,6 +54,19 @@ public String getNameFromToken(Cookie[] cookies) { } return null; } + public String getRoleFromToken(Cookie[] cookies) { + String token = extractTokenFromCookies(cookies); + if (token != null) { + Claims claims = Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8))) + .build() + .parseClaimsJws(token) + .getBody(); + return claims.get("role", String.class); + } + return null; + } + private String extractTokenFromCookies(Cookie[] cookies) { if (cookies == null) { diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index af6c6467f..99ad4d272 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -33,7 +33,7 @@ public ResponseEntity create(@RequestBody ReservationRequest reservationRequest, if (name != null) { reservationRequest.setName(name); } else { - return ResponseEntity.badRequest().build(); // name이 없으면 400 응답 + return ResponseEntity.badRequest().build(); } } diff --git a/src/main/resources/static/js/theme.js b/src/main/resources/static/js/theme.js index 26f3b19b0..a6550a6e9 100644 --- a/src/main/resources/static/js/theme.js +++ b/src/main/resources/static/js/theme.js @@ -33,7 +33,7 @@ function render(data) { } function addRow() { - if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음 + if (isEditing) return; const tableBody = document.getElementById('table-body'); const row = tableBody.insertRow(); diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 410aee144..0b1fb9a79 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -18,7 +18,7 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { - private String createToken(String email, String password){ + private String createToken(String email, String password) { Map params = new HashMap<>(); params.put("email", email); params.put("password", password); @@ -96,5 +96,24 @@ private String createToken(String email, String password){ assertThat(adminResponse.as(ReservationResponse.class).getName()).isEqualTo("브라운"); } + @Test + void 삼단계() { + String brownToken = createToken("brown@email.com", "password"); + + RestAssured.given().log().all() + .cookie("token", brownToken) + .get("/admin") + .then().log().all() + .statusCode(401); + + String adminToken = createToken("admin@email.com", "password"); + + + RestAssured.given().log().all() + .cookie("token", adminToken) + .get("/admin") + .then().log().all() + .statusCode(200); + } } From 6758db4b8fb58dc140753712c1eb73adbae81eae Mon Sep 17 00:00:00 2001 From: anhye0n Date: Tue, 25 Jun 2024 11:00:31 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/admin/AdminInterceptor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/roomescape/admin/AdminInterceptor.java b/src/main/java/roomescape/admin/AdminInterceptor.java index c148e472b..ea61c4cb7 100644 --- a/src/main/java/roomescape/admin/AdminInterceptor.java +++ b/src/main/java/roomescape/admin/AdminInterceptor.java @@ -33,7 +33,6 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons } private boolean isAdmin(String token) { - System.out.println("Debug 1: " + token); try { Claims claims = Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) @@ -41,7 +40,6 @@ private boolean isAdmin(String token) { .parseClaimsJws(token) .getBody(); String role = claims.get("role", String.class); - System.out.println("role test" + role); return "ADMIN".equals(role); } catch (Exception e) { return false; From 6e5bed7d3aa6dda10af49498ebe4de9cbb8f564f Mon Sep 17 00:00:00 2001 From: anhye0n Date: Wed, 3 Jul 2024 09:28:13 +0900 Subject: [PATCH 5/9] =?UTF-8?q?4=EB=8B=A8=EA=B3=84=20=ED=86=B5=EA=B3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../roomescape/reservation/Reservation.java | 23 +++++++++----- .../reservation/ReservationRepository.java | 6 ++++ src/main/java/roomescape/theme/Theme.java | 14 +++++++-- .../roomescape/theme/ThemeRepository.java | 6 ++++ src/main/java/roomescape/time/Time.java | 24 +++++++++++---- .../java/roomescape/time/TimeRepository.java | 6 ++++ src/main/resources/application.properties | 8 ++--- src/main/resources/schema.sql | 5 ++++ src/test/java/roomescape/JpaTest.java | 30 +++++++++++++++++++ src/test/java/roomescape/MissionStepTest.java | 2 ++ 11 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 src/main/java/roomescape/reservation/ReservationRepository.java create mode 100644 src/main/java/roomescape/theme/ThemeRepository.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 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/reservation/Reservation.java b/src/main/java/roomescape/reservation/Reservation.java index 83a7edf1b..6c6368972 100644 --- a/src/main/java/roomescape/reservation/Reservation.java +++ b/src/main/java/roomescape/reservation/Reservation.java @@ -1,34 +1,43 @@ 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) { - this.id = id; + public Reservation() { + } + + public Reservation(String name, String date, Time time, Theme theme) { this.name = name; this.date = date; this.time = time; this.theme = theme; } - public Reservation(String name, String date, Time time, 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; } - public Reservation() { - - } - public Long getId() { return id; } diff --git a/src/main/java/roomescape/reservation/ReservationRepository.java b/src/main/java/roomescape/reservation/ReservationRepository.java new file mode 100644 index 000000000..f7186959e --- /dev/null +++ b/src/main/java/roomescape/reservation/ReservationRepository.java @@ -0,0 +1,6 @@ +package roomescape.reservation; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReservationRepository extends JpaRepository { +} diff --git a/src/main/java/roomescape/theme/Theme.java b/src/main/java/roomescape/theme/Theme.java index 430a6239c..5e40cc4e0 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; @@ -8,13 +16,13 @@ public class Theme { public Theme() { } - public Theme(Long id, String name, String description) { - this.id = id; + public Theme(String name, String description) { this.name = name; this.description = description; } - public Theme(String name, String description) { + public Theme(Long id, String name, String description) { + this.id = id; this.name = name; this.description = description; } 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/time/Time.java b/src/main/java/roomescape/time/Time.java index 008ed93cf..c9b003c4d 100644 --- a/src/main/java/roomescape/time/Time.java +++ b/src/main/java/roomescape/time/Time.java @@ -1,20 +1,30 @@ package roomescape.time; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Column; + +@Entity public class Time { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(name = "time_value", nullable = false) private String value; - public Time(Long id, String value) { - this.id = id; - this.value = value; + public Time() { } public Time(String value) { this.value = value; } - public Time() { - + public Time(Long id, String value) { + this.id = id; + this.value = value; } public Long getId() { @@ -24,4 +34,8 @@ public Long getId() { public String getValue() { return value; } + + public String getTime() { + return value; + } } diff --git a/src/main/java/roomescape/time/TimeRepository.java b/src/main/java/roomescape/time/TimeRepository.java new file mode 100644 index 000000000..998100641 --- /dev/null +++ b/src/main/java/roomescape/time/TimeRepository.java @@ -0,0 +1,6 @@ +package roomescape.time; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TimeRepository extends JpaRepository { +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a0f33bbab..f1f05167f 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 diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 75c947a5a..a41508811 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,3 +1,8 @@ +DROP TABLE IF EXISTS reservation; +DROP TABLE IF EXISTS member; +DROP TABLE IF EXISTS theme; +DROP TABLE IF EXISTS time; + CREATE TABLE time ( id BIGINT NOT NULL AUTO_INCREMENT, diff --git a/src/test/java/roomescape/JpaTest.java b/src/test/java/roomescape/JpaTest.java new file mode 100644 index 000000000..60cdf7faa --- /dev/null +++ b/src/test/java/roomescape/JpaTest.java @@ -0,0 +1,30 @@ +package roomescape; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import roomescape.time.Time; +import roomescape.time.TimeRepository; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +public class JpaTest { + @Autowired + private TestEntityManager entityManager; + + @Autowired + private TimeRepository timeRepository; + + @Test + void 사단계() { + Time time = new Time("10:00"); + entityManager.persist(time); + entityManager.flush(); + + Time persistTime = timeRepository.findById(time.getId()).orElse(null); + + assertThat(persistTime.getTime()).isEqualTo(time.getTime()); + } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 0b1fb9a79..98e58a198 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -115,5 +115,7 @@ private String createToken(String email, String password) { .then().log().all() .statusCode(200); } + + } From f02f80e9cb1daa07584836aa09c9ca26f5ecd0d6 Mon Sep 17 00:00:00 2001 From: anhye0n Date: Wed, 3 Jul 2024 10:14:41 +0900 Subject: [PATCH 6/9] =?UTF-8?q?5=EB=8B=A8=EA=B3=84=20=ED=86=B5=EA=B3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/member/MemberService.java | 31 +++++----------- .../reservation/MyReservationResponse.java | 36 +++++++++++++++++++ .../roomescape/reservation/Reservation.java | 12 +++++-- .../reservation/ReservationController.java | 5 +++ .../reservation/ReservationDao.java | 35 +++++++++++------- .../reservation/ReservationRequest.java | 8 +++-- .../reservation/ReservationService.java | 18 +++++++++- src/main/java/roomescape/utils/JwtUtil.java | 35 ++++++++++++++++++ src/main/resources/schema.sql | 11 ++++-- src/test/java/roomescape/MissionStepTest.java | 15 ++++++++ 10 files changed, 163 insertions(+), 43 deletions(-) create mode 100644 src/main/java/roomescape/reservation/MyReservationResponse.java create mode 100644 src/main/java/roomescape/utils/JwtUtil.java diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index c4aee638f..58ecf18ee 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -1,21 +1,19 @@ package roomescape.member; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Service; - -import java.nio.charset.StandardCharsets; +import roomescape.JwtUtil; @Service public class MemberService { private final MemberDao memberDao; - private final String secretKey = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + private final JwtUtil jwtUtil; - public MemberService(MemberDao memberDao) { + public MemberService(MemberDao memberDao, JwtUtil jwtUtil) { this.memberDao = memberDao; + this.jwtUtil = jwtUtil; } public MemberResponse createMember(MemberRequest memberRequest) { @@ -25,12 +23,7 @@ public MemberResponse createMember(MemberRequest memberRequest) { public void login(MemberRequest memberRequest, HttpServletResponse response) { Member member = memberDao.findByEmailAndPassword(memberRequest.getEmail(), memberRequest.getPassword()); - String accessToken = Jwts.builder() - .setSubject(member.getId().toString()) - .claim("name", member.getName()) - .claim("role", member.getRole()) - .signWith(Keys.hmacShaKeyFor(secretKey.getBytes())) - .compact(); + String accessToken = jwtUtil.generateToken(member.getId().toString(), member.getName(), member.getRole()); Cookie cookie = new Cookie("token", accessToken); cookie.setHttpOnly(true); @@ -45,29 +38,21 @@ public Member findById(Long id) { public String getNameFromToken(Cookie[] cookies) { String token = extractTokenFromCookies(cookies); if (token != null) { - Claims claims = Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) - .build() - .parseClaimsJws(token) - .getBody(); + Claims claims = jwtUtil.parseToken(token); return claims.get("name", String.class); } return null; } + public String getRoleFromToken(Cookie[] cookies) { String token = extractTokenFromCookies(cookies); if (token != null) { - Claims claims = Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8))) - .build() - .parseClaimsJws(token) - .getBody(); + Claims claims = jwtUtil.parseToken(token); return claims.get("role", String.class); } return null; } - private String extractTokenFromCookies(Cookie[] cookies) { if (cookies == null) { return null; diff --git a/src/main/java/roomescape/reservation/MyReservationResponse.java b/src/main/java/roomescape/reservation/MyReservationResponse.java new file mode 100644 index 000000000..ce9c850f1 --- /dev/null +++ b/src/main/java/roomescape/reservation/MyReservationResponse.java @@ -0,0 +1,36 @@ +package roomescape.reservation; + +public class MyReservationResponse { + private Long reservationId; + private String theme; + private String date; + private String time; + private String status; + + public MyReservationResponse(Long reservationId, String theme, String date, String time, String status) { + this.reservationId = reservationId; + this.theme = theme; + this.date = date; + this.time = time; + this.status = status; + } + public Long getReservationId() { + return reservationId; + } + + public String getTheme() { + return theme; + } + + public String getDate() { + return date; + } + + public String getTime() { + return time; + } + + public String getStatus() { + return status; + } +} diff --git a/src/main/java/roomescape/reservation/Reservation.java b/src/main/java/roomescape/reservation/Reservation.java index 6c6368972..6a135848c 100644 --- a/src/main/java/roomescape/reservation/Reservation.java +++ b/src/main/java/roomescape/reservation/Reservation.java @@ -20,22 +20,26 @@ public class Reservation { @JoinColumn(name = "theme_id") private Theme theme; + private Long memberId; + public Reservation() { } - public Reservation(String name, String date, Time time, Theme theme) { + public Reservation(String name, String date, Time time, Theme theme, Long memberId) { this.name = name; this.date = date; this.time = time; this.theme = theme; + this.memberId = memberId; } - public Reservation(Long id, String name, String date, Time time, Theme theme) { + public Reservation(Long id, String name, String date, Time time, Theme theme, Long memberId) { this.id = id; this.name = name; this.date = date; this.time = time; this.theme = theme; + this.memberId = memberId; } public Long getId() { @@ -57,4 +61,8 @@ public Time getTime() { public Theme getTheme() { return theme; } + + public Long getMemberId() { + return memberId; + } } diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index 99ad4d272..099cd527a 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -53,4 +53,9 @@ public ResponseEntity delete(@PathVariable Long id) { reservationService.deleteById(id); return ResponseEntity.noContent().build(); } + + @GetMapping("/reservations-mine") + public List getMyReservations(@CookieValue("token") String token) { + return reservationService.getMyReservations(token); + } } diff --git a/src/main/java/roomescape/reservation/ReservationDao.java b/src/main/java/roomescape/reservation/ReservationDao.java index a4972430c..133f11bd6 100644 --- a/src/main/java/roomescape/reservation/ReservationDao.java +++ b/src/main/java/roomescape/reservation/ReservationDao.java @@ -23,7 +23,8 @@ 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 " + + "ti.id AS time_id, ti.time_value AS time_value, " + + "r.member_id AS member_id " + "FROM reservation r " + "JOIN theme t ON r.theme_id = t.id " + "JOIN time ti ON r.time_id = ti.id", @@ -40,17 +41,20 @@ public List findAll() { rs.getLong("theme_id"), rs.getString("theme_name"), rs.getString("theme_description") - ))); + ), + rs.getLong("member_id") + )); } 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"}); + PreparedStatement ps = connection.prepareStatement("INSERT INTO reservation(date, name, theme_id, time_id, member_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.setLong(5, reservationRequest.getMemberId()); return ps; }, keyHolder); @@ -67,7 +71,8 @@ public Reservation save(ReservationRequest reservationRequest) { reservationRequest.getName(), reservationRequest.getDate(), time, - theme + theme, + reservationRequest.getMemberId() ); } @@ -75,16 +80,17 @@ public void deleteById(Long id) { jdbcTemplate.update("DELETE FROM reservation WHERE id = ?", id); } - public List findReservationsByDateAndTheme(String date, Long themeId) { + public List findByMemberId(Long memberId) { 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 " + + "ti.id AS time_id, ti.time_value AS time_value, " + + "r.member_id AS member_id " + "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}, + "JOIN time ti ON r.time_id = ti.id " + + "WHERE r.member_id = ?", + new Object[]{memberId}, (rs, rowNum) -> new Reservation( rs.getLong("reservation_id"), rs.getString("reservation_name"), @@ -97,14 +103,17 @@ public List findReservationsByDateAndTheme(String date, Long themeI rs.getLong("theme_id"), rs.getString("theme_name"), rs.getString("theme_description") - ))); + ), + rs.getLong("member_id") + )); } 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 " + + "ti.id AS time_id, ti.time_value AS time_value, " + + "r.member_id AS member_id " + "FROM reservation r " + "JOIN theme t ON r.theme_id = t.id " + "JOIN time ti ON r.time_id = ti.id " + @@ -122,6 +131,8 @@ public List findByDateAndThemeId(String date, Long themeId) { rs.getLong("theme_id"), rs.getString("theme_name"), rs.getString("theme_description") - ))); + ), + rs.getLong("member_id") + )); } } diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java index 70c2dd21a..46e61f620 100644 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/roomescape/reservation/ReservationRequest.java @@ -5,8 +5,7 @@ public class ReservationRequest { private Long theme; private Long time; private String name; - - // Getters and setters + private Long memberId; public String getDate() { return date; @@ -39,4 +38,9 @@ public String getName() { public void setName(String name) { this.name = name; } + + + public Long getMemberId() { + return memberId; + } } diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index be4243076..ed0014c5c 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -1,15 +1,18 @@ package roomescape.reservation; import org.springframework.stereotype.Service; +import roomescape.JwtUtil; import java.util.List; @Service public class ReservationService { private final ReservationDao reservationDao; + private final JwtUtil jwtUtil; - public ReservationService(ReservationDao reservationDao) { + public ReservationService(ReservationDao reservationDao, JwtUtil jwtUtil) { this.reservationDao = reservationDao; + this.jwtUtil = jwtUtil; } public ReservationResponse save(ReservationRequest reservationRequest) { @@ -26,4 +29,17 @@ public List findAll() { .map(it -> new ReservationResponse(it.getId(), it.getName(), it.getTheme().getName(), it.getDate(), it.getTime().getValue())) .toList(); } + + public List getMyReservations(String token) { + Long userId = jwtUtil.getUserIdFromToken(token); + return reservationDao.findByMemberId(userId).stream() + .map(reservation -> new MyReservationResponse( + reservation.getId(), + reservation.getTheme().getName(), + reservation.getDate(), + reservation.getTime().getValue(), + "예약" + )) + .toList(); + } } diff --git a/src/main/java/roomescape/utils/JwtUtil.java b/src/main/java/roomescape/utils/JwtUtil.java new file mode 100644 index 000000000..852900700 --- /dev/null +++ b/src/main/java/roomescape/utils/JwtUtil.java @@ -0,0 +1,35 @@ +package roomescape; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; + +@Component +public class JwtUtil { + private static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + + public String generateToken(String userId, String name, String role) { + return Jwts.builder() + .setSubject(userId) + .claim("name", name) + .claim("role", role) + .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) + .compact(); + } + + public Claims parseToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) + .build() + .parseClaimsJws(token) + .getBody(); + } + + public Long getUserIdFromToken(String token) { + Claims claims = parseToken(token); + return Long.parseLong(claims.getSubject()); + } +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index a41508811..4b70a52d5 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -33,11 +33,13 @@ CREATE TABLE member CREATE TABLE reservation ( id BIGINT NOT NULL AUTO_INCREMENT, + member_id BIGINT, date VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, time_id BIGINT, theme_id BIGINT, PRIMARY KEY (id), + FOREIGN KEY (member_id) REFERENCES member (id), FOREIGN KEY (time_id) REFERENCES time (id), FOREIGN KEY (theme_id) REFERENCES theme (id) ); @@ -59,7 +61,10 @@ VALUES ('10:00'), ('18:00'), ('20:00'); +INSERT INTO reservation (member_id, name, date, time_id, theme_id) +VALUES (1, '', '2024-03-01', 1, 1), + (1, '', '2024-03-01', 2, 2), + (1, '', '2024-03-01', 3, 3); + 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 +VALUES ('브라운', '2024-03-01', 1, 2); diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 98e58a198..74df7c3d0 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -7,9 +7,11 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import roomescape.reservation.MyReservationResponse; import roomescape.reservation.ReservationResponse; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -116,6 +118,19 @@ private String createToken(String email, String password) { .statusCode(200); } + @Test + void 오단계() { + String adminToken = createToken("admin@email.com", "password"); + + List reservations = RestAssured.given().log().all() + .cookie("token", adminToken) + .get("/reservations-mine") + .then().log().all() + .statusCode(200) + .extract().jsonPath().getList(".", MyReservationResponse.class); + + assertThat(reservations).hasSize(3); + } } From 64cf3a36c7dd1ca1fc260a53ecc6dcd6d787b7b9 Mon Sep 17 00:00:00 2001 From: anhye0n Date: Thu, 4 Jul 2024 07:36:51 +0900 Subject: [PATCH 7/9] =?UTF-8?q?6=EB=8B=A8=EA=B3=84=20=ED=86=B5=EA=B3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/member/LoginMember.java | 3 - .../reservation/MyReservationResponse.java | 16 +++-- .../reservation/ReservationService.java | 31 +++++++--- src/main/java/roomescape/utils/JwtUtil.java | 4 +- src/main/java/roomescape/waiting/Waiting.java | 61 +++++++++++++++++++ .../roomescape/waiting/WaitingController.java | 45 ++++++++++++++ .../roomescape/waiting/WaitingRepository.java | 20 ++++++ .../roomescape/waiting/WaitingRequest.java | 38 ++++++++++++ .../roomescape/waiting/WaitingResponse.java | 19 ++++++ .../waiting/WaitingResponseWithRank.java | 57 +++++++++++++++++ .../roomescape/waiting/WaitingService.java | 39 ++++++++++++ .../roomescape/waiting/WaitingWithRank.java | 23 +++++++ src/test/java/roomescape/MissionStepTest.java | 41 +++++++++++++ 13 files changed, 380 insertions(+), 17 deletions(-) create mode 100644 src/main/java/roomescape/waiting/Waiting.java create mode 100644 src/main/java/roomescape/waiting/WaitingController.java create mode 100644 src/main/java/roomescape/waiting/WaitingRepository.java create mode 100644 src/main/java/roomescape/waiting/WaitingRequest.java create mode 100644 src/main/java/roomescape/waiting/WaitingResponse.java create mode 100644 src/main/java/roomescape/waiting/WaitingResponseWithRank.java create mode 100644 src/main/java/roomescape/waiting/WaitingService.java create mode 100644 src/main/java/roomescape/waiting/WaitingWithRank.java diff --git a/src/main/java/roomescape/member/LoginMember.java b/src/main/java/roomescape/member/LoginMember.java index 184c3b3b9..0acfe7a79 100644 --- a/src/main/java/roomescape/member/LoginMember.java +++ b/src/main/java/roomescape/member/LoginMember.java @@ -13,9 +13,6 @@ public LoginMember(Long id, String name, String email, String role) { this.role = role; } - // Getters and setters - - public Long getId() { return id; } diff --git a/src/main/java/roomescape/reservation/MyReservationResponse.java b/src/main/java/roomescape/reservation/MyReservationResponse.java index ce9c850f1..5d6409300 100644 --- a/src/main/java/roomescape/reservation/MyReservationResponse.java +++ b/src/main/java/roomescape/reservation/MyReservationResponse.java @@ -1,21 +1,27 @@ package roomescape.reservation; public class MyReservationResponse { - private Long reservationId; + + private Long id; private String theme; private String date; private String time; private String status; - public MyReservationResponse(Long reservationId, String theme, String date, String time, String status) { - this.reservationId = reservationId; + public MyReservationResponse(Long id, String theme, String date, String time, String status) { + this.id = id; this.theme = theme; this.date = date; this.time = time; this.status = status; } - public Long getReservationId() { - return reservationId; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; } public String getTheme() { diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index ed0014c5c..10551e2be 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -2,17 +2,20 @@ import org.springframework.stereotype.Service; import roomescape.JwtUtil; +import roomescape.waiting.WaitingRepository; +import roomescape.waiting.WaitingWithRank; import java.util.List; +import java.util.stream.Collectors; @Service public class ReservationService { private final ReservationDao reservationDao; - private final JwtUtil jwtUtil; + private final WaitingRepository waitingRepository; - public ReservationService(ReservationDao reservationDao, JwtUtil jwtUtil) { + public ReservationService(ReservationDao reservationDao, WaitingRepository waitingRepository) { this.reservationDao = reservationDao; - this.jwtUtil = jwtUtil; + this.waitingRepository = waitingRepository; } public ReservationResponse save(ReservationRequest reservationRequest) { @@ -27,12 +30,12 @@ public void deleteById(Long id) { public List findAll() { return reservationDao.findAll().stream() .map(it -> new ReservationResponse(it.getId(), it.getName(), it.getTheme().getName(), it.getDate(), it.getTime().getValue())) - .toList(); + .collect(Collectors.toList()); } public List getMyReservations(String token) { - Long userId = jwtUtil.getUserIdFromToken(token); - return reservationDao.findByMemberId(userId).stream() + Long userId = JwtUtil.getUserIdFromToken(token); + List reservations = reservationDao.findByMemberId(userId).stream() .map(reservation -> new MyReservationResponse( reservation.getId(), reservation.getTheme().getName(), @@ -40,6 +43,20 @@ public List getMyReservations(String token) { reservation.getTime().getValue(), "예약" )) - .toList(); + .collect(Collectors.toList()); + + List waitings = waitingRepository.findWaitingsWithRankByMemberId(userId); + List waitingResponses = waitings.stream() + .map(waitingWithRank -> new MyReservationResponse( + waitingWithRank.getWaiting().getId(), + waitingWithRank.getWaiting().getThemeId().toString(), + waitingWithRank.getWaiting().getDate(), + waitingWithRank.getWaiting().getTimeId().toString(), + waitingWithRank.getRank() + "번째 예약대기" + )) + .collect(Collectors.toList()); + + reservations.addAll(waitingResponses); + return reservations; } } diff --git a/src/main/java/roomescape/utils/JwtUtil.java b/src/main/java/roomescape/utils/JwtUtil.java index 852900700..12de36f3a 100644 --- a/src/main/java/roomescape/utils/JwtUtil.java +++ b/src/main/java/roomescape/utils/JwtUtil.java @@ -20,7 +20,7 @@ public String generateToken(String userId, String name, String role) { .compact(); } - public Claims parseToken(String token) { + public static Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) .build() @@ -28,7 +28,7 @@ public Claims parseToken(String token) { .getBody(); } - public Long getUserIdFromToken(String token) { + public static Long getUserIdFromToken(String token) { Claims claims = parseToken(token); return Long.parseLong(claims.getSubject()); } diff --git a/src/main/java/roomescape/waiting/Waiting.java b/src/main/java/roomescape/waiting/Waiting.java new file mode 100644 index 000000000..4a8af303b --- /dev/null +++ b/src/main/java/roomescape/waiting/Waiting.java @@ -0,0 +1,61 @@ +package roomescape.waiting; + +import jakarta.persistence.*; + +@Entity +public class Waiting { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Long memberId; + private String date; + private Long timeId; + private Long themeId; + + public Waiting() { + } + + public Waiting(Long memberId, String date, Long timeId, Long themeId) { + this.memberId = memberId; + this.date = date; + this.timeId = timeId; + this.themeId = themeId; + } + + public Long getId() { + return id; + } + + public void setMemberId(Long memberId) { + this.memberId = memberId; + } + + public Long getMemberId() { + return memberId; + } + + public void setDate(String date) { + this.date = date; + } + + public String getDate() { + return date; + } + + public void setTimeId(Long timeId) { + this.timeId = timeId; + } + + public Long getTimeId() { + return timeId; + } + + public void setThemeId(Long themeId) { + this.themeId = themeId; + } + + public Long getThemeId() { + return themeId; + } +} diff --git a/src/main/java/roomescape/waiting/WaitingController.java b/src/main/java/roomescape/waiting/WaitingController.java new file mode 100644 index 000000000..6724b6e4a --- /dev/null +++ b/src/main/java/roomescape/waiting/WaitingController.java @@ -0,0 +1,45 @@ +package roomescape.waiting; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import roomescape.JwtUtil; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/waitings") +public class WaitingController { + private final WaitingService waitingService; + private final JwtUtil jwtUtil; + + public WaitingController(WaitingService waitingService, JwtUtil jwtUtil) { + this.waitingService = waitingService; + this.jwtUtil = jwtUtil; + } + + @PostMapping + public ResponseEntity createWaiting(@RequestBody WaitingRequest waitingRequest, HttpServletRequest request) { + String token = request.getCookies() != null ? request.getCookies()[0].getValue() : ""; + Long memberId = JwtUtil.getUserIdFromToken(token); + Waiting waiting = waitingService.createWaiting(memberId, waitingRequest.getDate(), waitingRequest.getTimeId(), waitingRequest.getThemeId()); + return new ResponseEntity<>(new WaitingResponse(waiting.getId()), HttpStatus.CREATED); + } + + @DeleteMapping("/{id}") + public void deleteWaiting(@PathVariable Long id) { + waitingService.deleteWaiting(id); + } + + @GetMapping + public List getWaitings(HttpServletRequest request) { + String token = request.getCookies() != null ? request.getCookies()[0].getValue() : ""; + Long memberId = JwtUtil.getUserIdFromToken(token); + List waitingsWithRank = waitingService.getWaitingsWithRankByMemberId(memberId); + return waitingsWithRank.stream() + .map(wr -> new WaitingResponseWithRank(wr.getWaiting().getId(), wr.getWaiting().getThemeId().toString(), wr.getWaiting().getDate(), wr.getWaiting().getTimeId().toString(), wr.getStatus())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/roomescape/waiting/WaitingRepository.java b/src/main/java/roomescape/waiting/WaitingRepository.java new file mode 100644 index 000000000..a04b923a6 --- /dev/null +++ b/src/main/java/roomescape/waiting/WaitingRepository.java @@ -0,0 +1,20 @@ +package roomescape.waiting; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface WaitingRepository extends JpaRepository { + @Query("SELECT new roomescape.waiting.WaitingWithRank(" + + " w, " + + " (SELECT COUNT(w2) " + + " FROM Waiting w2 " + + " WHERE w2.themeId = w.themeId " + + " AND w2.date = w.date " + + " AND w2.timeId = w.timeId " + + " AND w2.id < w.id)) " + + "FROM Waiting w " + + "WHERE w.memberId = :memberId") + List findWaitingsWithRankByMemberId(Long memberId); +} diff --git a/src/main/java/roomescape/waiting/WaitingRequest.java b/src/main/java/roomescape/waiting/WaitingRequest.java new file mode 100644 index 000000000..122ba593b --- /dev/null +++ b/src/main/java/roomescape/waiting/WaitingRequest.java @@ -0,0 +1,38 @@ +package roomescape.waiting; + +import com.fasterxml.jackson.annotation.JsonAlias; + +public class WaitingRequest { + + private String date; + + @JsonAlias("time") + private Long timeId; + + @JsonAlias("theme") + private Long themeId; + + public void setDate(String date) { + this.date = date; + } + + public String getDate() { + return date; + } + + public void setTimeId(Long timeId) { + this.timeId = timeId; + } + + public Long getTimeId() { + return timeId; + } + + public void setThemeId(Long themeId) { + this.themeId = themeId; + } + + public Long getThemeId() { + return themeId; + } +} diff --git a/src/main/java/roomescape/waiting/WaitingResponse.java b/src/main/java/roomescape/waiting/WaitingResponse.java new file mode 100644 index 000000000..7e8c95cd4 --- /dev/null +++ b/src/main/java/roomescape/waiting/WaitingResponse.java @@ -0,0 +1,19 @@ +package roomescape.waiting; + +public class WaitingResponse { + private Long id; + + public WaitingResponse() {} + + public WaitingResponse(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/src/main/java/roomescape/waiting/WaitingResponseWithRank.java b/src/main/java/roomescape/waiting/WaitingResponseWithRank.java new file mode 100644 index 000000000..9cc263c2a --- /dev/null +++ b/src/main/java/roomescape/waiting/WaitingResponseWithRank.java @@ -0,0 +1,57 @@ +package roomescape.waiting; + +public class WaitingResponseWithRank { + private Long id; + private String theme; + private String date; + private String time; + private String status; + + public WaitingResponseWithRank(Long id, String theme, String date, String time, String status) { + this.id = id; + this.theme = theme; + this.date = date; + this.time = time; + this.status = status; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTheme() { + return theme; + } + + public void setTheme(String theme) { + this.theme = theme; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } +} diff --git a/src/main/java/roomescape/waiting/WaitingService.java b/src/main/java/roomescape/waiting/WaitingService.java new file mode 100644 index 000000000..8054033b9 --- /dev/null +++ b/src/main/java/roomescape/waiting/WaitingService.java @@ -0,0 +1,39 @@ +package roomescape.waiting; + +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class WaitingService { + private final WaitingRepository waitingRepository; + + public WaitingService(WaitingRepository waitingRepository) { + this.waitingRepository = waitingRepository; + } + + public Waiting createWaiting(Long memberId, String date, Long timeId, Long themeId) { + if (date == null || timeId == null || themeId == null) { + throw new IllegalArgumentException("Date, time, and theme must be provided"); + } + + if (memberId == null) { + memberId = 0L; + } + + Waiting waiting = new Waiting(memberId, date, timeId, themeId); + return waitingRepository.save(waiting); + } + + public List getWaitingsWithRankByMemberId(Long memberId) { + return waitingRepository.findWaitingsWithRankByMemberId(memberId) + .stream() + .map(wr -> new WaitingWithRank(wr.getWaiting(), wr.getRank())) + .collect(Collectors.toList()); + } + + public void deleteWaiting(Long waitingId) { + waitingRepository.deleteById(waitingId); + } +} diff --git a/src/main/java/roomescape/waiting/WaitingWithRank.java b/src/main/java/roomescape/waiting/WaitingWithRank.java new file mode 100644 index 000000000..d0bb3fd85 --- /dev/null +++ b/src/main/java/roomescape/waiting/WaitingWithRank.java @@ -0,0 +1,23 @@ +package roomescape.waiting; + +public class WaitingWithRank { + private Waiting waiting; + private Long rank; + + public WaitingWithRank(Waiting waiting, Long rank) { + this.waiting = waiting; + this.rank = rank + 1; + } + + public Waiting getWaiting() { + return waiting; + } + + public Long getRank() { + return rank; + } + + public String getStatus() { + return rank + "번째 예약대기"; + } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 74df7c3d0..cebc156b7 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -9,6 +9,7 @@ import org.springframework.test.annotation.DirtiesContext; import roomescape.reservation.MyReservationResponse; import roomescape.reservation.ReservationResponse; +import roomescape.waiting.WaitingResponse; import java.util.HashMap; import java.util.List; @@ -132,5 +133,45 @@ private String createToken(String email, String password) { assertThat(reservations).hasSize(3); } + + @Test + void 육단계() { + String brownToken = createToken("brown@email.com", "password"); + + Map params = new HashMap<>(); + params.put("date", "2024-03-01"); + params.put("time", "1"); + params.put("theme", "1"); + + // 예약 대기 생성 + WaitingResponse waiting = RestAssured.given().log().all() + .body(params) + .cookie("token", brownToken) + .contentType(ContentType.JSON) + .post("/waitings") + .then().log().all() + .statusCode(201) + .extract().as(WaitingResponse.class); + + // 내 예약 목록 조회 + List myReservations = RestAssured.given().log().all() + .body(params) + .cookie("token", brownToken) + .contentType(ContentType.JSON) + .get("/reservations-mine") + .then().log().all() + .statusCode(200) + .extract().jsonPath().getList(".", MyReservationResponse.class); + + // 예약 대기 상태 확인 + String status = myReservations.stream() + .filter(it -> it.getId() == waiting.getId()) + .filter(it -> !it.getStatus().equals("예약")) + .findFirst() + .map(it -> it.getStatus()) + .orElse(null); + + assertThat(status).isEqualTo("1번째 예약대기"); + } } From 898db85ed31b4fecca94ad809b23a2127c0b70b7 Mon Sep 17 00:00:00 2001 From: anhye0n Date: Tue, 9 Jul 2024 10:07:40 +0900 Subject: [PATCH 8/9] =?UTF-8?q?7=EB=8B=A8=EA=B3=84=20=ED=86=B5=EA=B3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/auth/JwtConfig.java | 20 +++++++ src/main/java/roomescape/auth/JwtUtil.java | 53 +++++++++++++++++++ .../java/roomescape/member/MemberService.java | 2 +- .../reservation/ReservationService.java | 2 +- src/main/java/roomescape/utils/JwtUtil.java | 35 ------------ .../roomescape/waiting/WaitingController.java | 6 +-- .../roomescape/waiting/WaitingService.java | 2 +- src/main/resources/application.properties | 4 +- src/test/java/roomescape/MissionStepTest.java | 11 ++++ 9 files changed, 92 insertions(+), 43 deletions(-) create mode 100644 src/main/java/roomescape/auth/JwtConfig.java create mode 100644 src/main/java/roomescape/auth/JwtUtil.java delete mode 100644 src/main/java/roomescape/utils/JwtUtil.java diff --git a/src/main/java/roomescape/auth/JwtConfig.java b/src/main/java/roomescape/auth/JwtConfig.java new file mode 100644 index 000000000..36c19b0d7 --- /dev/null +++ b/src/main/java/roomescape/auth/JwtConfig.java @@ -0,0 +1,20 @@ +package roomescape.auth; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JwtConfig { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.expiration}") + private Long expiration; + + @Bean + public JwtUtil jwtUtil() { + return new JwtUtil(secret, expiration); + } +} diff --git a/src/main/java/roomescape/auth/JwtUtil.java b/src/main/java/roomescape/auth/JwtUtil.java new file mode 100644 index 000000000..822dba925 --- /dev/null +++ b/src/main/java/roomescape/auth/JwtUtil.java @@ -0,0 +1,53 @@ +package roomescape.auth; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class JwtUtil { + + private static SecretKey secretKey; + private final Long expiration; + + public JwtUtil(String secret, Long expiration) { + secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + this.expiration = expiration; + } + + public String generateToken(String userId, String name, String role) { + Claims claims = Jwts.claims().setSubject(userId); + claims.put("name", name); + claims.put("role", role); + return Jwts.builder() + .setClaims(claims) + .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) + .signWith(secretKey, SignatureAlgorithm.HS512) + .compact(); + } + + public static Claims parseToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody(); + } + + public static Long getUserIdFromToken(String token) { + Claims claims = parseToken(token); + return Long.parseLong(claims.getSubject()); + } + + public String getRoleFromToken(String token) { + Claims claims = parseToken(token); + return (String) claims.get("role"); + } +} diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index 58ecf18ee..797517035 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -4,7 +4,7 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Service; -import roomescape.JwtUtil; +import roomescape.auth.JwtUtil; @Service public class MemberService { diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index 10551e2be..40fe65f88 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -1,7 +1,7 @@ package roomescape.reservation; import org.springframework.stereotype.Service; -import roomescape.JwtUtil; +import roomescape.auth.JwtUtil; import roomescape.waiting.WaitingRepository; import roomescape.waiting.WaitingWithRank; diff --git a/src/main/java/roomescape/utils/JwtUtil.java b/src/main/java/roomescape/utils/JwtUtil.java deleted file mode 100644 index 12de36f3a..000000000 --- a/src/main/java/roomescape/utils/JwtUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package roomescape; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import org.springframework.stereotype.Component; - -import java.nio.charset.StandardCharsets; - -@Component -public class JwtUtil { - private static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; - - public String generateToken(String userId, String name, String role) { - return Jwts.builder() - .setSubject(userId) - .claim("name", name) - .claim("role", role) - .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) - .compact(); - } - - public static Claims parseToken(String token) { - return Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) - .build() - .parseClaimsJws(token) - .getBody(); - } - - public static Long getUserIdFromToken(String token) { - Claims claims = parseToken(token); - return Long.parseLong(claims.getSubject()); - } -} diff --git a/src/main/java/roomescape/waiting/WaitingController.java b/src/main/java/roomescape/waiting/WaitingController.java index 6724b6e4a..fd8ba5ba3 100644 --- a/src/main/java/roomescape/waiting/WaitingController.java +++ b/src/main/java/roomescape/waiting/WaitingController.java @@ -4,7 +4,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import roomescape.JwtUtil; +import roomescape.auth.JwtUtil; import java.util.List; import java.util.stream.Collectors; @@ -23,7 +23,7 @@ public WaitingController(WaitingService waitingService, JwtUtil jwtUtil) { @PostMapping public ResponseEntity createWaiting(@RequestBody WaitingRequest waitingRequest, HttpServletRequest request) { String token = request.getCookies() != null ? request.getCookies()[0].getValue() : ""; - Long memberId = JwtUtil.getUserIdFromToken(token); + Long memberId = jwtUtil.getUserIdFromToken(token); Waiting waiting = waitingService.createWaiting(memberId, waitingRequest.getDate(), waitingRequest.getTimeId(), waitingRequest.getThemeId()); return new ResponseEntity<>(new WaitingResponse(waiting.getId()), HttpStatus.CREATED); } @@ -36,7 +36,7 @@ public void deleteWaiting(@PathVariable Long id) { @GetMapping public List getWaitings(HttpServletRequest request) { String token = request.getCookies() != null ? request.getCookies()[0].getValue() : ""; - Long memberId = JwtUtil.getUserIdFromToken(token); + Long memberId = jwtUtil.getUserIdFromToken(token); List waitingsWithRank = waitingService.getWaitingsWithRankByMemberId(memberId); return waitingsWithRank.stream() .map(wr -> new WaitingResponseWithRank(wr.getWaiting().getId(), wr.getWaiting().getThemeId().toString(), wr.getWaiting().getDate(), wr.getWaiting().getTimeId().toString(), wr.getStatus())) diff --git a/src/main/java/roomescape/waiting/WaitingService.java b/src/main/java/roomescape/waiting/WaitingService.java index 8054033b9..6289c89a3 100644 --- a/src/main/java/roomescape/waiting/WaitingService.java +++ b/src/main/java/roomescape/waiting/WaitingService.java @@ -29,7 +29,7 @@ public Waiting createWaiting(Long memberId, String date, Long timeId, Long theme public List getWaitingsWithRankByMemberId(Long memberId) { return waitingRepository.findWaitingsWithRankByMemberId(memberId) .stream() - .map(wr -> new WaitingWithRank(wr.getWaiting(), wr.getRank())) + .map(wr -> new WaitingWithRank(wr.getWaiting(), (long) (wr.getRank().intValue() + 1))) .collect(Collectors.toList()); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f1f05167f..386e47ec1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,5 +7,5 @@ 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 +jwt.secret=Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E +jwt.expiration=3600 \ No newline at end of file diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index cebc156b7..45606bb31 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -5,8 +5,11 @@ 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.boot.test.context.SpringBootTest; +import org.springframework.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; +import roomescape.auth.JwtUtil; import roomescape.reservation.MyReservationResponse; import roomescape.reservation.ReservationResponse; import roomescape.waiting.WaitingResponse; @@ -21,6 +24,9 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { + @Autowired + private JwtUtil jwtUtil; + private String createToken(String email, String password) { Map params = new HashMap<>(); params.put("email", email); @@ -173,5 +179,10 @@ private String createToken(String email, String password) { assertThat(status).isEqualTo("1번째 예약대기"); } + @Test + void 칠단계() { + Component componentAnnotation = JwtUtil.class.getAnnotation(Component.class); + assertThat(componentAnnotation).isNull(); + } } From 957b4ba0837e209f546dfd71ddb00c614b39a093 Mon Sep 17 00:00:00 2001 From: anhye0n Date: Tue, 9 Jul 2024 10:14:47 +0900 Subject: [PATCH 9/9] =?UTF-8?q?8=EB=8B=A8=EA=B3=84=20=ED=86=B5=EA=B3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/auth/JwtConfig.java | 4 +- .../java/roomescape/member/MemberDao.java | 17 ++++++ .../roomescape/reservation/Reservation.java | 2 +- .../java/roomescape/waiting/DataLoader.java | 28 +++++++++ .../roomescape/waiting/TestDataLoader.java | 58 +++++++++++++++++++ src/main/resources/application.properties | 1 + src/test/java/roomescape/JpaTest.java | 9 +++ src/test/java/roomescape/MissionStepTest.java | 9 +++ 8 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 src/main/java/roomescape/waiting/DataLoader.java create mode 100644 src/main/java/roomescape/waiting/TestDataLoader.java diff --git a/src/main/java/roomescape/auth/JwtConfig.java b/src/main/java/roomescape/auth/JwtConfig.java index 36c19b0d7..ed646e716 100644 --- a/src/main/java/roomescape/auth/JwtConfig.java +++ b/src/main/java/roomescape/auth/JwtConfig.java @@ -7,7 +7,7 @@ @Configuration public class JwtConfig { - @Value("${jwt.secret}") + @Value("${roomescape.auth.jwt.secret}") private String secret; @Value("${jwt.expiration}") @@ -17,4 +17,4 @@ public class JwtConfig { public JwtUtil jwtUtil() { return new JwtUtil(secret, expiration); } -} +} \ No newline at end of file diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java index 1d68715ad..15dab800a 100644 --- a/src/main/java/roomescape/member/MemberDao.java +++ b/src/main/java/roomescape/member/MemberDao.java @@ -52,4 +52,21 @@ public Member findById(Long id) { id ); } + + public boolean existsByEmail(String email) { + Integer count = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM member WHERE email = ?", new Object[]{email}, Integer.class); + return count != null && count > 0; + } + + public Member findByEmail(String email) { + return jdbcTemplate.queryForObject( + "SELECT * FROM member WHERE email = ?", new Object[]{email}, + (rs, rowNum) -> new Member( + rs.getString("name"), + rs.getString("email"), + rs.getString("password"), + rs.getString("role") + )); + } } diff --git a/src/main/java/roomescape/reservation/Reservation.java b/src/main/java/roomescape/reservation/Reservation.java index 6a135848c..a8d9243d8 100644 --- a/src/main/java/roomescape/reservation/Reservation.java +++ b/src/main/java/roomescape/reservation/Reservation.java @@ -22,7 +22,7 @@ public class Reservation { private Long memberId; - public Reservation() { + public Reservation(Long id, String date, Long theme1Id, Long time1Id) { } public Reservation(String name, String date, Time time, Theme theme, Long memberId) { diff --git a/src/main/java/roomescape/waiting/DataLoader.java b/src/main/java/roomescape/waiting/DataLoader.java new file mode 100644 index 000000000..31dc21b3b --- /dev/null +++ b/src/main/java/roomescape/waiting/DataLoader.java @@ -0,0 +1,28 @@ +package roomescape.waiting; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import roomescape.member.Member; +import roomescape.member.MemberDao; + +@Component +@Profile("!test") +public class DataLoader implements CommandLineRunner { + + private final MemberDao memberDao; + + public DataLoader(MemberDao memberDao) { + this.memberDao = memberDao; + } + + @Override + public void run(String... args) throws Exception { + if (!memberDao.existsByEmail("admin@email.com")) { + memberDao.save(new Member("어드민", "admin@email.com", "password", "ADMIN")); + } + if (!memberDao.existsByEmail("brown@email.com")) { + memberDao.save(new Member("브라운", "brown@email.com", "password", "USER")); + } + } +} diff --git a/src/main/java/roomescape/waiting/TestDataLoader.java b/src/main/java/roomescape/waiting/TestDataLoader.java new file mode 100644 index 000000000..3235fb90c --- /dev/null +++ b/src/main/java/roomescape/waiting/TestDataLoader.java @@ -0,0 +1,58 @@ +package roomescape.waiting; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import roomescape.member.Member; +import roomescape.member.MemberDao; +import roomescape.reservation.Reservation; +import roomescape.reservation.ReservationRepository; +import roomescape.theme.Theme; +import roomescape.theme.ThemeRepository; +import roomescape.time.Time; +import roomescape.time.TimeRepository; + +@Component +@Profile("test") +public class TestDataLoader implements CommandLineRunner { + + private final MemberDao memberDao; + private final ThemeRepository themeRepository; + private final TimeRepository timeRepository; + private final ReservationRepository reservationRepository; + + public TestDataLoader(MemberDao memberDao, ThemeRepository themeRepository, + TimeRepository timeRepository, ReservationRepository reservationRepository) { + this.memberDao = memberDao; + this.themeRepository = themeRepository; + this.timeRepository = timeRepository; + this.reservationRepository = reservationRepository; + } + + @Override + public void run(String... args) throws Exception { + Member admin = memberDao.existsByEmail("admin@email.com") ? + memberDao.findByEmail("admin@email.com") : + memberDao.save(new Member("어드민", "admin@email.com", "password", "ADMIN")); + + Member brown = memberDao.existsByEmail("brown@email.com") ? + memberDao.findByEmail("brown@email.com") : + memberDao.save(new Member("브라운", "brown@email.com", "password", "USER")); + + Theme theme1 = themeRepository.save(new Theme("테마1", "테마1입니다.")); + Theme theme2 = themeRepository.save(new Theme("테마2", "테마2입니다.")); + Theme theme3 = themeRepository.save(new Theme("테마3", "테마3입니다.")); + + Time time1 = timeRepository.save(new Time("10:00")); + Time time2 = timeRepository.save(new Time("12:00")); + Time time3 = timeRepository.save(new Time("14:00")); + Time time4 = timeRepository.save(new Time("16:00")); + Time time5 = timeRepository.save(new Time("18:00")); + Time time6 = timeRepository.save(new Time("20:00")); + + reservationRepository.save(new Reservation(admin.getId(), "2024-03-01", theme1.getId(), time1.getId())); + reservationRepository.save(new Reservation(admin.getId(), "2024-03-01", theme2.getId(), time2.getId())); + reservationRepository.save(new Reservation(admin.getId(), "2024-03-01", theme3.getId(), time3.getId())); + reservationRepository.save(new Reservation(brown.getId(), "2024-03-01", theme2.getId(), time1.getId())); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 386e47ec1..c290de586 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,4 +8,5 @@ spring.jpa.properties.hibernate.format_sql=true spring.jpa.ddl-auto=create-drop spring.jpa.defer-datasource-initialization=true jwt.secret=Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E +roomescape.auth.jwt.secret=Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= jwt.expiration=3600 \ No newline at end of file diff --git a/src/test/java/roomescape/JpaTest.java b/src/test/java/roomescape/JpaTest.java index 60cdf7faa..ab3626813 100644 --- a/src/test/java/roomescape/JpaTest.java +++ b/src/test/java/roomescape/JpaTest.java @@ -2,6 +2,7 @@ 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.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import roomescape.time.Time; @@ -27,4 +28,12 @@ public class JpaTest { assertThat(persistTime.getTime()).isEqualTo(time.getTime()); } + + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + + @Test + void 팔단계() { + assertThat(secretKey).isNotBlank(); + } } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 45606bb31..c9660130a 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -6,6 +6,7 @@ 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.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; @@ -184,5 +185,13 @@ private String createToken(String email, String password) { Component componentAnnotation = JwtUtil.class.getAnnotation(Component.class); assertThat(componentAnnotation).isNull(); } + + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + + @Test + void 팔단계() { + assertThat(secretKey).isNotBlank(); + } }