From 28c556ffd9a8aa94a4d21eec4d8538213b7c171f Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Wed, 24 Jul 2024 20:02:44 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인 응답으로 JWT를 쿠키로 전송합니다. - 로그인 사용자의 정보를 확인하는 기능을 추가합니다. - 패키지 구조를 변경합니다. --- .../roomescape/member/MemberController.java | 37 --------- .../java/roomescape/member/MemberRequest.java | 19 ----- .../roomescape/member/MemberResponse.java | 25 ------- .../java/roomescape/member/MemberService.java | 17 ----- .../member/controller/MemberController.java | 75 +++++++++++++++++++ .../member/controller/dto/MemberRequest.java | 4 + .../member/controller/dto/MemberResponse.java | 4 + .../member/{ => domain}/Member.java | 2 +- .../member/{ => domain}/MemberDao.java | 2 +- .../member/service/MemberService.java | 74 ++++++++++++++++++ src/test/java/roomescape/MissionStepTest.java | 17 ++++- 11 files changed, 173 insertions(+), 103 deletions(-) delete mode 100644 src/main/java/roomescape/member/MemberController.java delete mode 100644 src/main/java/roomescape/member/MemberRequest.java delete mode 100644 src/main/java/roomescape/member/MemberResponse.java delete mode 100644 src/main/java/roomescape/member/MemberService.java create mode 100644 src/main/java/roomescape/member/controller/MemberController.java create mode 100644 src/main/java/roomescape/member/controller/dto/MemberRequest.java create mode 100644 src/main/java/roomescape/member/controller/dto/MemberResponse.java rename src/main/java/roomescape/member/{ => domain}/Member.java (96%) rename src/main/java/roomescape/member/{ => domain}/MemberDao.java (98%) create mode 100644 src/main/java/roomescape/member/service/MemberService.java diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java deleted file mode 100644 index 881ae5e0..00000000 --- a/src/main/java/roomescape/member/MemberController.java +++ /dev/null @@ -1,37 +0,0 @@ -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; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -import java.net.URI; - -@RestController -public class MemberController { - private MemberService memberService; - - public MemberController(MemberService memberService) { - this.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); - } - - @PostMapping("/logout") - public ResponseEntity logout(HttpServletResponse response) { - Cookie cookie = new Cookie("token", ""); - cookie.setHttpOnly(true); - cookie.setPath("/"); - cookie.setMaxAge(0); - response.addCookie(cookie); - return ResponseEntity.ok().build(); - } -} diff --git a/src/main/java/roomescape/member/MemberRequest.java b/src/main/java/roomescape/member/MemberRequest.java deleted file mode 100644 index cafb79f1..00000000 --- a/src/main/java/roomescape/member/MemberRequest.java +++ /dev/null @@ -1,19 +0,0 @@ -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; - } -} diff --git a/src/main/java/roomescape/member/MemberResponse.java b/src/main/java/roomescape/member/MemberResponse.java deleted file mode 100644 index b9fa3b97..00000000 --- a/src/main/java/roomescape/member/MemberResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -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; - } -} diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java deleted file mode 100644 index ccaa8cba..00000000 --- a/src/main/java/roomescape/member/MemberService.java +++ /dev/null @@ -1,17 +0,0 @@ -package roomescape.member; - -import org.springframework.stereotype.Service; - -@Service -public class MemberService { - private MemberDao memberDao; - - public MemberService(MemberDao memberDao) { - this.memberDao = memberDao; - } - - 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()); - } -} diff --git a/src/main/java/roomescape/member/controller/MemberController.java b/src/main/java/roomescape/member/controller/MemberController.java new file mode 100644 index 00000000..7f8b3a5b --- /dev/null +++ b/src/main/java/roomescape/member/controller/MemberController.java @@ -0,0 +1,75 @@ +package roomescape.member.controller; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +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 roomescape.member.controller.dto.MemberRequest; +import roomescape.member.controller.dto.MemberResponse; +import roomescape.member.service.MemberService; +import roomescape.member.domain.Member; + +import java.util.Date; + +@RestController +public class MemberController { + private final MemberService memberService; + + public MemberController(MemberService memberService) { + this.memberService = memberService; + } + + @PostMapping("/members") + public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { + MemberResponse member = memberService.createMember(memberRequest); + return ResponseEntity.status(HttpStatus.CREATED) + .header(HttpHeaders.CONTENT_TYPE, "application/json") + .header(HttpHeaders.LOCATION, "/members/" + member.id()) + .body(member); + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody MemberRequest memberRequest, HttpServletResponse response) { + String token = memberService.login(memberRequest); + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + return ResponseEntity.status(HttpStatus.OK) + .header(HttpHeaders.CONTENT_TYPE, "application/json") + .header(HttpHeaders.SET_COOKIE, "token=" + token + "; HttpOnly; Path=/") + .header("Keep-Alive", "timeout=60") + .build(); + } + + @GetMapping("/login/check") + public ResponseEntity checkLogin(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + Member member = memberService.getMember(cookies); + return ResponseEntity.status(HttpStatus.OK) + .header("Connection", "keep-alive") + .header(HttpHeaders.CONTENT_TYPE, "application/json") + .header("Keep-Alive", "timeout=60") + .header("Transfer-Encoding", "chunked") + .header("Date", new Date().toString()) + .body(new MemberResponse(member.getId(), member.getName(), member.getEmail())); + } + + @PostMapping("/logout") + public ResponseEntity logout(HttpServletResponse response) { + Cookie cookie = new Cookie("token", ""); + cookie.setHttpOnly(true); + cookie.setPath("/"); + cookie.setMaxAge(0); + response.addCookie(cookie); + return ResponseEntity.status(HttpStatus.OK) + .header(HttpHeaders.CONTENT_TYPE, "application/json") + .build(); + } +} diff --git a/src/main/java/roomescape/member/controller/dto/MemberRequest.java b/src/main/java/roomescape/member/controller/dto/MemberRequest.java new file mode 100644 index 00000000..952aec5a --- /dev/null +++ b/src/main/java/roomescape/member/controller/dto/MemberRequest.java @@ -0,0 +1,4 @@ +package roomescape.member.controller.dto; + +public record MemberRequest(String name, String email, String password) { +} diff --git a/src/main/java/roomescape/member/controller/dto/MemberResponse.java b/src/main/java/roomescape/member/controller/dto/MemberResponse.java new file mode 100644 index 00000000..b8cc62cd --- /dev/null +++ b/src/main/java/roomescape/member/controller/dto/MemberResponse.java @@ -0,0 +1,4 @@ +package roomescape.member.controller.dto; + +public record MemberResponse(Long id, String name, String email) { +} diff --git a/src/main/java/roomescape/member/Member.java b/src/main/java/roomescape/member/domain/Member.java similarity index 96% rename from src/main/java/roomescape/member/Member.java rename to src/main/java/roomescape/member/domain/Member.java index 903aaa9b..ae754e15 100644 --- a/src/main/java/roomescape/member/Member.java +++ b/src/main/java/roomescape/member/domain/Member.java @@ -1,4 +1,4 @@ -package roomescape.member; +package roomescape.member.domain; public class Member { private Long id; diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/domain/MemberDao.java similarity index 98% rename from src/main/java/roomescape/member/MemberDao.java rename to src/main/java/roomescape/member/domain/MemberDao.java index 81f77f4c..0441590c 100644 --- a/src/main/java/roomescape/member/MemberDao.java +++ b/src/main/java/roomescape/member/domain/MemberDao.java @@ -1,4 +1,4 @@ -package roomescape.member; +package roomescape.member.domain; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; diff --git a/src/main/java/roomescape/member/service/MemberService.java b/src/main/java/roomescape/member/service/MemberService.java new file mode 100644 index 00000000..f5279cf1 --- /dev/null +++ b/src/main/java/roomescape/member/service/MemberService.java @@ -0,0 +1,74 @@ +package roomescape.member.service; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.Cookie; +import org.springframework.stereotype.Service; +import roomescape.member.controller.dto.MemberRequest; +import roomescape.member.controller.dto.MemberResponse; +import roomescape.member.domain.Member; +import roomescape.member.domain.MemberDao; + +@Service +public class MemberService { + private static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + + private final MemberDao memberDao; + + public MemberService(MemberDao memberDao) { + this.memberDao = memberDao; + } + + public MemberResponse createMember(MemberRequest memberRequest) { + Member member = memberDao.save(requestToMember(memberRequest)); + return memberToResponse(member); + } + + private Member requestToMember(MemberRequest memberRequest) { + return new Member(memberRequest.name(), memberRequest.email(), memberRequest.password(), "USER"); + } + + private MemberResponse memberToResponse(Member member) { + return new MemberResponse(member.getId(), member.getName(), member.getEmail()); + } + + public String login(MemberRequest memberRequest) { + Member member = memberDao.findByEmailAndPassword(memberRequest.email(), memberRequest.password()); + if (member == null) { + throw new RuntimeException("로그인 실패"); + } + + return Jwts.builder() + .setSubject(member.getId().toString()) + .claim("name", member.getName()) + .claim("role", member.getRole()) + .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + .compact(); + } + + public Member getMember(Cookie[] cookies) { + String token = extractTokenFromCookie(cookies); + if (token == null) { + throw new RuntimeException("로그인이 필요합니다."); + } + + String memberName = Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + .build() + .parseClaimsJws(token) + .getBody() + .get("name", String.class); + + return memberDao.findByName(memberName); + } + + private String extractTokenFromCookie(Cookie[] cookies) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("token")) { + return cookie.getValue(); + } + } + + return ""; + } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 6add784b..5caa7624 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -4,6 +4,7 @@ import io.restassured.http.ContentType; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; @@ -18,7 +19,8 @@ public class MissionStepTest { @Test - void 일단계() { + @DisplayName("1단계: 로그인") + void step1_login() { Map params = new HashMap<>(); params.put("email", "admin@email.com"); params.put("password", "password"); @@ -32,7 +34,16 @@ 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 3e324624d36994c16e00b3a5d680c59673a23f5a Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Wed, 24 Jul 2024 21:37:27 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EC=9D=98=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EC=B6=9C=ED=95=B4=EC=A3=BC=EB=8A=94=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인한 사용자의 이름을 추출합니다. - 로그인 상태가 아닌 경우 null을 반환합니다. --- .../roomescape/global/config/WebConfig.java | 22 ++++++++ .../login/LoginMemberArgumentResolver.java | 56 +++++++++++++++++++ .../global/login/LoginMemberName.java | 11 ++++ 3 files changed, 89 insertions(+) create mode 100644 src/main/java/roomescape/global/config/WebConfig.java create mode 100644 src/main/java/roomescape/global/login/LoginMemberArgumentResolver.java create mode 100644 src/main/java/roomescape/global/login/LoginMemberName.java diff --git a/src/main/java/roomescape/global/config/WebConfig.java b/src/main/java/roomescape/global/config/WebConfig.java new file mode 100644 index 00000000..6a0b7b92 --- /dev/null +++ b/src/main/java/roomescape/global/config/WebConfig.java @@ -0,0 +1,22 @@ +package roomescape.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import roomescape.global.login.LoginMemberArgumentResolver; + +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final LoginMemberArgumentResolver loginMemberArgumentResolver; + + public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver) { + this.loginMemberArgumentResolver = loginMemberArgumentResolver; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginMemberArgumentResolver); + } +} diff --git a/src/main/java/roomescape/global/login/LoginMemberArgumentResolver.java b/src/main/java/roomescape/global/login/LoginMemberArgumentResolver.java new file mode 100644 index 00000000..63d3b6e4 --- /dev/null +++ b/src/main/java/roomescape/global/login/LoginMemberArgumentResolver.java @@ -0,0 +1,56 @@ +package roomescape.global.login; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.jetbrains.annotations.NotNull; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +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; + +@Component +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + private static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterAnnotation(LoginMemberName.class) != null; + } + + @Override + public Object resolveArgument(@NotNull MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + Cookie[] cookies = request.getCookies(); + String token = extractTokenFromCookie(cookies); + + System.out.println("token: " + token); + + if (token.isEmpty()) { + return null; + } + + return Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + .build() + .parseClaimsJws(token) + .getBody() + .get("name", String.class); + } + + 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/global/login/LoginMemberName.java b/src/main/java/roomescape/global/login/LoginMemberName.java new file mode 100644 index 00000000..9e4ddc84 --- /dev/null +++ b/src/main/java/roomescape/global/login/LoginMemberName.java @@ -0,0 +1,11 @@ +package roomescape.global.login; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface LoginMemberName { +} From 942076a92cc2f498d22e865995e72cb21f1166d5 Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Wed, 24 Jul 2024 21:38:09 +0900 Subject: [PATCH 03/14] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=EC=9D=98=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C=20=EA=B8=B0=EB=8A=A5=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인 사용자의 정보를 추출하는 기능을 분리하여 코드 중복을 줄입니다. - 패키지 구조를 변경합니다. --- .../{ => global}/ExceptionController.java | 2 +- .../{ => global}/PageController.java | 2 +- .../member/controller/MemberController.java | 13 ++-- .../roomescape/member/domain/MemberDao.java | 10 +-- .../member/service/MemberService.java | 26 ++----- .../reservation/ReservationController.java | 46 ------------- .../reservation/ReservationRequest.java | 24 ------- .../reservation/ReservationResponse.java | 37 ---------- .../reservation/ReservationService.java | 30 --------- .../controller/ReservationController.java | 67 +++++++++++++++++++ .../controller/dto/ReservationRequest.java | 4 ++ .../controller/dto/ReservationResponse.java | 4 ++ .../reservation/{ => domain}/Reservation.java | 2 +- .../{ => domain}/ReservationDao.java | 19 +++--- .../service/ReservationService.java | 42 ++++++++++++ .../java/roomescape/time/TimeService.java | 4 +- src/test/java/roomescape/MissionStepTest.java | 60 ++++++++++++++--- 17 files changed, 200 insertions(+), 192 deletions(-) rename src/main/java/roomescape/{ => global}/ExceptionController.java (94%) rename src/main/java/roomescape/{ => global}/PageController.java (97%) delete mode 100644 src/main/java/roomescape/reservation/ReservationController.java delete mode 100644 src/main/java/roomescape/reservation/ReservationRequest.java delete mode 100644 src/main/java/roomescape/reservation/ReservationResponse.java delete mode 100644 src/main/java/roomescape/reservation/ReservationService.java create mode 100644 src/main/java/roomescape/reservation/controller/ReservationController.java create mode 100644 src/main/java/roomescape/reservation/controller/dto/ReservationRequest.java create mode 100644 src/main/java/roomescape/reservation/controller/dto/ReservationResponse.java rename src/main/java/roomescape/reservation/{ => domain}/Reservation.java (95%) rename src/main/java/roomescape/reservation/{ => domain}/ReservationDao.java (91%) create mode 100644 src/main/java/roomescape/reservation/service/ReservationService.java diff --git a/src/main/java/roomescape/ExceptionController.java b/src/main/java/roomescape/global/ExceptionController.java similarity index 94% rename from src/main/java/roomescape/ExceptionController.java rename to src/main/java/roomescape/global/ExceptionController.java index 4e2450f9..e34c539c 100644 --- a/src/main/java/roomescape/ExceptionController.java +++ b/src/main/java/roomescape/global/ExceptionController.java @@ -1,4 +1,4 @@ -package roomescape; +package roomescape.global; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; diff --git a/src/main/java/roomescape/PageController.java b/src/main/java/roomescape/global/PageController.java similarity index 97% rename from src/main/java/roomescape/PageController.java rename to src/main/java/roomescape/global/PageController.java index ac8ef940..f793c0c1 100644 --- a/src/main/java/roomescape/PageController.java +++ b/src/main/java/roomescape/global/PageController.java @@ -1,4 +1,4 @@ -package roomescape; +package roomescape.global; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/roomescape/member/controller/MemberController.java b/src/main/java/roomescape/member/controller/MemberController.java index 7f8b3a5b..12df4bef 100644 --- a/src/main/java/roomescape/member/controller/MemberController.java +++ b/src/main/java/roomescape/member/controller/MemberController.java @@ -1,8 +1,9 @@ package roomescape.member.controller; import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -10,15 +11,16 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import roomescape.global.login.LoginMemberName; import roomescape.member.controller.dto.MemberRequest; import roomescape.member.controller.dto.MemberResponse; import roomescape.member.service.MemberService; -import roomescape.member.domain.Member; import java.util.Date; @RestController public class MemberController { + private static final Logger log = LoggerFactory.getLogger(MemberController.class); private final MemberService memberService; public MemberController(MemberService memberService) { @@ -49,16 +51,15 @@ public ResponseEntity login(@RequestBody MemberRequest memberRequest, Http } @GetMapping("/login/check") - public ResponseEntity checkLogin(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - Member member = memberService.getMember(cookies); + public ResponseEntity checkLogin(@LoginMemberName String memberName) { + MemberResponse memberResponse = memberService.getMemberByName(memberName); return ResponseEntity.status(HttpStatus.OK) .header("Connection", "keep-alive") .header(HttpHeaders.CONTENT_TYPE, "application/json") .header("Keep-Alive", "timeout=60") .header("Transfer-Encoding", "chunked") .header("Date", new Date().toString()) - .body(new MemberResponse(member.getId(), member.getName(), member.getEmail())); + .body(memberResponse); } @PostMapping("/logout") diff --git a/src/main/java/roomescape/member/domain/MemberDao.java b/src/main/java/roomescape/member/domain/MemberDao.java index 0441590c..5652a7ee 100644 --- a/src/main/java/roomescape/member/domain/MemberDao.java +++ b/src/main/java/roomescape/member/domain/MemberDao.java @@ -5,9 +5,11 @@ import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public class MemberDao { - private JdbcTemplate jdbcTemplate; + private final JdbcTemplate jdbcTemplate; public MemberDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -40,8 +42,8 @@ public Member findByEmailAndPassword(String email, String password) { ); } - public Member findByName(String name) { - return jdbcTemplate.queryForObject( + public Optional findByName(String name) { + return Optional.ofNullable(jdbcTemplate.queryForObject( "SELECT id, name, email, role FROM member WHERE name = ?", (rs, rowNum) -> new Member( rs.getLong("id"), @@ -50,6 +52,6 @@ public Member findByName(String name) { rs.getString("role") ), name - ); + )); } } diff --git a/src/main/java/roomescape/member/service/MemberService.java b/src/main/java/roomescape/member/service/MemberService.java index f5279cf1..9f171590 100644 --- a/src/main/java/roomescape/member/service/MemberService.java +++ b/src/main/java/roomescape/member/service/MemberService.java @@ -46,29 +46,13 @@ public String login(MemberRequest memberRequest) { .compact(); } - public Member getMember(Cookie[] cookies) { - String token = extractTokenFromCookie(cookies); - if (token == null) { + public MemberResponse getMemberByName(String memberName) { + if (memberName == null) { throw new RuntimeException("로그인이 필요합니다."); } - String memberName = Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) - .build() - .parseClaimsJws(token) - .getBody() - .get("name", String.class); - - return memberDao.findByName(memberName); - } - - private String extractTokenFromCookie(Cookie[] cookies) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("token")) { - return cookie.getValue(); - } - } - - return ""; + return memberDao.findByName(memberName) + .map(this::memberToResponse) + .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")); } } diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java deleted file mode 100644 index b3bef399..00000000 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ /dev/null @@ -1,46 +0,0 @@ -package roomescape.reservation; - -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 java.net.URI; -import java.util.List; - -@RestController -public class ReservationController { - - private final ReservationService reservationService; - - public ReservationController(ReservationService reservationService) { - this.reservationService = reservationService; - } - - @GetMapping("/reservations") - public List list() { - return reservationService.findAll(); - } - - @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { - if (reservationRequest.getName() == null - || 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); - } - - @DeleteMapping("/reservations/{id}") - public ResponseEntity delete(@PathVariable Long id) { - reservationService.deleteById(id); - return ResponseEntity.noContent().build(); - } -} diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java deleted file mode 100644 index 19f44124..00000000 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ /dev/null @@ -1,24 +0,0 @@ -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; - } -} diff --git a/src/main/java/roomescape/reservation/ReservationResponse.java b/src/main/java/roomescape/reservation/ReservationResponse.java deleted file mode 100644 index 41360a36..00000000 --- a/src/main/java/roomescape/reservation/ReservationResponse.java +++ /dev/null @@ -1,37 +0,0 @@ -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; - } -} diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java deleted file mode 100644 index bd331332..00000000 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ /dev/null @@ -1,30 +0,0 @@ -package roomescape.reservation; - -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -public class ReservationService { - private ReservationDao reservationDao; - - public ReservationService(ReservationDao reservationDao) { - this.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()); - } - - public void deleteById(Long id) { - reservationDao.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())) - .toList(); - } -} diff --git a/src/main/java/roomescape/reservation/controller/ReservationController.java b/src/main/java/roomescape/reservation/controller/ReservationController.java new file mode 100644 index 00000000..5397f3ad --- /dev/null +++ b/src/main/java/roomescape/reservation/controller/ReservationController.java @@ -0,0 +1,67 @@ +package roomescape.reservation.controller; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +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 roomescape.global.login.LoginMemberName; +import roomescape.reservation.service.ReservationService; +import roomescape.reservation.controller.dto.ReservationRequest; +import roomescape.reservation.controller.dto.ReservationResponse; + +import java.util.List; + +@RestController +public class ReservationController { + + private final ReservationService reservationService; + + public ReservationController(ReservationService reservationService) { + this.reservationService = reservationService; + } + + @GetMapping("/reservations") + public List list() { + return reservationService.findAll(); + } + + @PostMapping("/reservations") + public ResponseEntity create(@LoginMemberName String memberName, + @RequestBody ReservationRequest reservationRequest) { + if (isNamePresentInRequest(reservationRequest)) { + memberName = reservationRequest.name(); + } + if (isNotNullRequestParameters(memberName, reservationRequest)) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + + ReservationResponse reservation = reservationService.save(memberName, reservationRequest); + return ResponseEntity.status(HttpStatus.CREATED) + .header(HttpHeaders.CONTENT_TYPE, "application/json") + .header(HttpHeaders.LOCATION, "/reservations/" + reservation.id()) + .body(reservation); + } + + private boolean isNamePresentInRequest(ReservationRequest reservationRequest) { + return reservationRequest.name() != null; + } + + private boolean isNotNullRequestParameters(String memberName, ReservationRequest reservationRequest) { + return memberName == null + || reservationRequest.date() == null + || reservationRequest.theme() == null + || reservationRequest.time() == null; + } + + @DeleteMapping("/reservations/{id}") + public ResponseEntity delete(@PathVariable Long id) { + reservationService.deleteById(id); + return ResponseEntity.status(HttpStatus.NO_CONTENT) + .build(); + } +} diff --git a/src/main/java/roomescape/reservation/controller/dto/ReservationRequest.java b/src/main/java/roomescape/reservation/controller/dto/ReservationRequest.java new file mode 100644 index 00000000..78a60ad3 --- /dev/null +++ b/src/main/java/roomescape/reservation/controller/dto/ReservationRequest.java @@ -0,0 +1,4 @@ +package roomescape.reservation.controller.dto; + +public record ReservationRequest(String name, String date, Long theme, Long time) { +} diff --git a/src/main/java/roomescape/reservation/controller/dto/ReservationResponse.java b/src/main/java/roomescape/reservation/controller/dto/ReservationResponse.java new file mode 100644 index 00000000..bc8c768a --- /dev/null +++ b/src/main/java/roomescape/reservation/controller/dto/ReservationResponse.java @@ -0,0 +1,4 @@ +package roomescape.reservation.controller.dto; + +public record ReservationResponse(Long id, String name, String theme, String date, String time) { +} diff --git a/src/main/java/roomescape/reservation/Reservation.java b/src/main/java/roomescape/reservation/domain/Reservation.java similarity index 95% rename from src/main/java/roomescape/reservation/Reservation.java rename to src/main/java/roomescape/reservation/domain/Reservation.java index 83a7edf1..a7f282dc 100644 --- a/src/main/java/roomescape/reservation/Reservation.java +++ b/src/main/java/roomescape/reservation/domain/Reservation.java @@ -1,4 +1,4 @@ -package roomescape.reservation; +package roomescape.reservation.domain; import roomescape.theme.Theme; import roomescape.time.Time; diff --git a/src/main/java/roomescape/reservation/ReservationDao.java b/src/main/java/roomescape/reservation/domain/ReservationDao.java similarity index 91% rename from src/main/java/roomescape/reservation/ReservationDao.java rename to src/main/java/roomescape/reservation/domain/ReservationDao.java index a4972430..8e54b34c 100644 --- a/src/main/java/roomescape/reservation/ReservationDao.java +++ b/src/main/java/roomescape/reservation/domain/ReservationDao.java @@ -1,9 +1,10 @@ -package roomescape.reservation; +package roomescape.reservation.domain; 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.reservation.controller.dto.ReservationRequest; import roomescape.theme.Theme; import roomescape.time.Time; @@ -47,25 +48,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/service/ReservationService.java b/src/main/java/roomescape/reservation/service/ReservationService.java new file mode 100644 index 00000000..7dcc0c30 --- /dev/null +++ b/src/main/java/roomescape/reservation/service/ReservationService.java @@ -0,0 +1,42 @@ +package roomescape.reservation.service; + +import org.springframework.stereotype.Service; +import roomescape.reservation.controller.dto.ReservationRequest; +import roomescape.reservation.controller.dto.ReservationResponse; +import roomescape.reservation.domain.Reservation; +import roomescape.reservation.domain.ReservationDao; + +import java.util.List; + +@Service +public class ReservationService { + private final ReservationDao reservationDao; + + public ReservationService(ReservationDao reservationDao) { + this.reservationDao = reservationDao; + } + + public ReservationResponse save(String memberName, + ReservationRequest reservationRequest) { + Reservation reservation = reservationDao.save(requestToReservation(memberName, reservationRequest)); + return reservationToResponse(reservation); + } + + private ReservationResponse reservationToResponse(Reservation reservation) { + return new ReservationResponse(reservation.getId(), reservation.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); + } + + private ReservationRequest requestToReservation(String memberName, ReservationRequest reservationRequest) { + return new ReservationRequest(memberName, reservationRequest.date(), reservationRequest.theme(), reservationRequest.time()); + } + + public void deleteById(Long id) { + reservationDao.deleteById(id); + } + + public List findAll() { + return reservationDao.findAll().stream() + .map(this::reservationToResponse) + .toList(); + } +} diff --git a/src/main/java/roomescape/time/TimeService.java b/src/main/java/roomescape/time/TimeService.java index 7d0cb9b0..5a22563b 100644 --- a/src/main/java/roomescape/time/TimeService.java +++ b/src/main/java/roomescape/time/TimeService.java @@ -1,8 +1,8 @@ package roomescape.time; import org.springframework.stereotype.Service; -import roomescape.reservation.Reservation; -import roomescape.reservation.ReservationDao; +import roomescape.reservation.domain.Reservation; +import roomescape.reservation.domain.ReservationDao; import java.util.List; diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 5caa7624..19635daf 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import roomescape.reservation.controller.dto.ReservationResponse; import java.util.HashMap; import java.util.Map; @@ -21,29 +22,68 @@ public class MissionStepTest { @Test @DisplayName("1단계: 로그인") void step1_login() { + String token = createToken("admin@email.com", "password"); + 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("어드민"); + } + + @Test + @DisplayName("2단계: 로그인 로직 분리") + void step2_login_refactoring() { + String token = createToken("admin@email.com", "password"); + Map params = new HashMap<>(); - params.put("email", "admin@email.com"); - params.put("password", "password"); + 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) - .when().post("/login") + .cookie("token", token) + .contentType(ContentType.JSON) + .post("/reservations") .then().log().all() - .statusCode(200) .extract(); - String token = response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; - assertThat(token).isNotBlank(); + assertThat(adminResponse.statusCode()).isEqualTo(201); + assertThat(adminResponse.as(ReservationResponse.class).name()).isEqualTo("브라운"); + } - ExtractableResponse checkResponse = RestAssured.given().log().all() + 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) - .cookie("token", token) - .when().get("/login/check") + .body(params) + .when().post("/login") .then().log().all() .statusCode(200) .extract(); - assertThat(checkResponse.body().jsonPath().getString("name")).isEqualTo("어드민"); + return response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; } } From a751b311e6160f1a80f9eeaa4194d7931309eade Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Wed, 24 Jul 2024 21:53:29 +0900 Subject: [PATCH 04/14] =?UTF-8?q?fix:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=B6=9C=EB=A0=A5=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/global/login/LoginMemberArgumentResolver.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/roomescape/global/login/LoginMemberArgumentResolver.java b/src/main/java/roomescape/global/login/LoginMemberArgumentResolver.java index 63d3b6e4..56f3a404 100644 --- a/src/main/java/roomescape/global/login/LoginMemberArgumentResolver.java +++ b/src/main/java/roomescape/global/login/LoginMemberArgumentResolver.java @@ -30,8 +30,6 @@ public Object resolveArgument(@NotNull MethodParameter parameter, Cookie[] cookies = request.getCookies(); String token = extractTokenFromCookie(cookies); - System.out.println("token: " + token); - if (token.isEmpty()) { return null; } From 7f1d4dd7fec4e352b49cf435b23609d907910296 Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Wed, 24 Jul 2024 21:57:53 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EC=9D=98=20=EA=B6=8C=ED=95=9C?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EC=B6=9C=ED=95=98=EB=8A=94=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 권한 체크가 필요한 API 메서드마다 사용할 수 있습니다. - 단계별 권한으로 구현하여 기준보다 아래의 권한을 가진 사용자인 경우 403을 반환합니다. --- .../roomescape/global/config/WebConfig.java | 11 +++- .../java/roomescape/global/login/Role.java | 5 ++ .../roomescape/global/login/RoleCheck.java | 12 ++++ .../global/login/RoleCheckInterceptor.java | 64 +++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/main/java/roomescape/global/login/Role.java create mode 100644 src/main/java/roomescape/global/login/RoleCheck.java create mode 100644 src/main/java/roomescape/global/login/RoleCheckInterceptor.java diff --git a/src/main/java/roomescape/global/config/WebConfig.java b/src/main/java/roomescape/global/config/WebConfig.java index 6a0b7b92..b9b160c4 100644 --- a/src/main/java/roomescape/global/config/WebConfig.java +++ b/src/main/java/roomescape/global/config/WebConfig.java @@ -2,21 +2,30 @@ 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.global.login.LoginMemberArgumentResolver; +import roomescape.global.login.RoleCheckInterceptor; import java.util.List; @Configuration public class WebConfig implements WebMvcConfigurer { private final LoginMemberArgumentResolver loginMemberArgumentResolver; + private final RoleCheckInterceptor roleCheckInterceptor; - public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver) { + public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver, RoleCheckInterceptor roleCheckInterceptor) { this.loginMemberArgumentResolver = loginMemberArgumentResolver; + this.roleCheckInterceptor = roleCheckInterceptor; } @Override public void addArgumentResolvers(List resolvers) { resolvers.add(loginMemberArgumentResolver); } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(roleCheckInterceptor); + } } diff --git a/src/main/java/roomescape/global/login/Role.java b/src/main/java/roomescape/global/login/Role.java new file mode 100644 index 00000000..e5ffb076 --- /dev/null +++ b/src/main/java/roomescape/global/login/Role.java @@ -0,0 +1,5 @@ +package roomescape.global.login; + +public enum Role { + USER, ADMIN +} diff --git a/src/main/java/roomescape/global/login/RoleCheck.java b/src/main/java/roomescape/global/login/RoleCheck.java new file mode 100644 index 00000000..83ce4253 --- /dev/null +++ b/src/main/java/roomescape/global/login/RoleCheck.java @@ -0,0 +1,12 @@ +package roomescape.global.login; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RoleCheck { + Role role() default Role.USER; +} diff --git a/src/main/java/roomescape/global/login/RoleCheckInterceptor.java b/src/main/java/roomescape/global/login/RoleCheckInterceptor.java new file mode 100644 index 00000000..801084d6 --- /dev/null +++ b/src/main/java/roomescape/global/login/RoleCheckInterceptor.java @@ -0,0 +1,64 @@ +package roomescape.global.login; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import io.micrometer.common.lang.NonNull; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class RoleCheckInterceptor implements HandlerInterceptor { + private static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + + @Override + public boolean preHandle(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull Object handler) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + RoleCheck roleCheck = handlerMethod.getMethodAnnotation(RoleCheck.class); + + if (roleCheck == null) { + return true; + } + + Cookie[] cookies = request.getCookies(); + String token = extractTokenFromCookie(cookies); + + if (token.isEmpty()) { + response.setStatus(401); + return false; + } + + String role = parseMemberRole(token); + + if (Role.valueOf(role).ordinal() < roleCheck.role().ordinal()) { + response.setStatus(403); + return false; + } + + return true; + } + + private String extractTokenFromCookie(Cookie[] cookies) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("token")) { + return cookie.getValue(); + } + } + + return ""; + } + + private String parseMemberRole(String token) { + return Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + .build() + .parseClaimsJws(token) + .getBody() + .get("role", String.class); + } +} From 301dfd89c3542e2a24f2995b4c09e8adf6041b15 Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Wed, 24 Jul 2024 21:59:46 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 관리자 전용 페이지는 관리자만 접속할 수 있습니다. --- .../roomescape/global/PageController.java | 6 ++++++ src/test/java/roomescape/MissionStepTest.java | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/main/java/roomescape/global/PageController.java b/src/main/java/roomescape/global/PageController.java index f793c0c1..802d22f7 100644 --- a/src/main/java/roomescape/global/PageController.java +++ b/src/main/java/roomescape/global/PageController.java @@ -2,25 +2,31 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import roomescape.global.login.Role; +import roomescape.global.login.RoleCheck; @Controller public class PageController { @GetMapping("/admin") + @RoleCheck(role = Role.ADMIN) public String admin() { return "admin/index"; } @GetMapping("/admin/reservation") + @RoleCheck(role = Role.ADMIN) public String adminReservation() { return "admin/reservation"; } @GetMapping("/admin/theme") + @RoleCheck(role = Role.ADMIN) public String adminTheme() { return "admin/theme"; } @GetMapping("/admin/time") + @RoleCheck(role = Role.ADMIN) public String adminTime() { return "admin/time"; } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 19635daf..e990ef56 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -71,6 +71,26 @@ void step2_login_refactoring() { assertThat(adminResponse.as(ReservationResponse.class).name()).isEqualTo("브라운"); } + @Test + @DisplayName("3단계: 권한 검사") + void step3_authorization() { + String brownToken = createToken("brown@email.com", "password"); + + RestAssured.given().log().all() + .cookie("token", brownToken) + .get("/admin") + .then().log().all() + .statusCode(403); + + String adminToken = createToken("admin@email.com", "password"); + + RestAssured.given().log().all() + .cookie("token", adminToken) + .get("/admin") + .then().log().all() + .statusCode(200); + } + private String createToken(String email, String password) { Map params = new HashMap<>(); params.put("email", email); From 06eeba39fe46f1df25f0ecff7f5f0896d922a0a6 Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Mon, 29 Jul 2024 16:28:02 +0900 Subject: [PATCH 07/14] =?UTF-8?q?fix:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용하지 않는 로깅 관련 코드를 삭제합니다. --- .../java/roomescape/member/controller/MemberController.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/roomescape/member/controller/MemberController.java b/src/main/java/roomescape/member/controller/MemberController.java index 12df4bef..a8c51f85 100644 --- a/src/main/java/roomescape/member/controller/MemberController.java +++ b/src/main/java/roomescape/member/controller/MemberController.java @@ -2,8 +2,6 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -20,7 +18,6 @@ @RestController public class MemberController { - private static final Logger log = LoggerFactory.getLogger(MemberController.class); private final MemberService memberService; public MemberController(MemberService memberService) { From 468474ac82b8de9e1c0bf8ac8bb841118a0f80e6 Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Mon, 29 Jul 2024 17:09:11 +0900 Subject: [PATCH 08/14] =?UTF-8?q?refactor:=20=EC=97=AD=ED=95=A0=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 열거형 타입 순서에 의존하던 방식에서 일치하는 지 확인하는 방식으로 수정합니다. --- .../global/login/RoleCheckInterceptor.java | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/main/java/roomescape/global/login/RoleCheckInterceptor.java b/src/main/java/roomescape/global/login/RoleCheckInterceptor.java index 801084d6..1932c4e1 100644 --- a/src/main/java/roomescape/global/login/RoleCheckInterceptor.java +++ b/src/main/java/roomescape/global/login/RoleCheckInterceptor.java @@ -10,6 +10,9 @@ import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; + @Component public class RoleCheckInterceptor implements HandlerInterceptor { private static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; @@ -18,31 +21,47 @@ public class RoleCheckInterceptor implements HandlerInterceptor { public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { - HandlerMethod handlerMethod = (HandlerMethod) handler; - RoleCheck roleCheck = handlerMethod.getMethodAnnotation(RoleCheck.class); + if (!(handler instanceof HandlerMethod handlerMethod)) { + return true; + } + + RoleCheck roleCheck = handlerMethod.getMethodAnnotation(RoleCheck.class); if (roleCheck == null) { return true; } - Cookie[] cookies = request.getCookies(); + Cookie[] cookies = getCookies(request); String token = extractTokenFromCookie(cookies); if (token.isEmpty()) { - response.setStatus(401); + int statusCode = UNAUTHORIZED.value(); + response.setStatus(statusCode); return false; } - String role = parseMemberRole(token); + Role requiredRole = roleCheck.role(); + Role memberRole = parseMemberRole(token); + if (memberRole == Role.ADMIN) { + return true; + } - if (Role.valueOf(role).ordinal() < roleCheck.role().ordinal()) { - response.setStatus(403); + if (isNotRoleExact(requiredRole, memberRole)) { + response.setStatus(FORBIDDEN.value()); return false; } return true; } + private boolean isNotRoleExact(Role requiredRole, Role memberRole) { + return requiredRole != memberRole; + } + + private Cookie[] getCookies(HttpServletRequest request) { + return request.getCookies(); + } + private String extractTokenFromCookie(Cookie[] cookies) { for (Cookie cookie : cookies) { if (cookie.getName().equals("token")) { @@ -53,12 +72,12 @@ private String extractTokenFromCookie(Cookie[] cookies) { return ""; } - private String parseMemberRole(String token) { - return Jwts.parserBuilder() + private Role parseMemberRole(String token) { + return Role.valueOf(Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) .build() .parseClaimsJws(token) .getBody() - .get("role", String.class); + .get("role", String.class)); } } From bfc40a049cf0398847201550c7cdefbc390443de Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Fri, 9 Aug 2024 00:12:45 +0900 Subject: [PATCH 09/14] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=85=98=20?= =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전: spring-boot-stater-jdbc 이후: spring-boot-starter-data-jpa --- build.gradle | 2 +- src/main/resources/application.properties | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 8d52aebc..9bc129a0 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/resources/application.properties b/src/main/resources/application.properties index a0f33bba..45e49375 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= \ No newline at end of file From 8db55bb18e00b88a2c501c94d85eb8b48541cbe6 Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Fri, 9 Aug 2024 01:01:15 +0900 Subject: [PATCH 10/14] =?UTF-8?q?fix:=20JPA=20ddl-auto=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EC=82=AC=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20sql=20=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테이블 생성 명령어를 삭제합니다. --- .../roomescape/member/domain/MemberDao.java | 57 -------- .../reservation/domain/ReservationDao.java | 128 ------------------ src/main/java/roomescape/theme/ThemeDao.java | 41 ------ .../{ => controller}/ThemeController.java | 12 +- .../roomescape/theme/{ => domain}/Theme.java | 8 ++ src/main/java/roomescape/time/TimeDao.java | 41 ------ .../time/{ => controller}/TimeController.java | 0 .../time/{ => domain}/AvailableTime.java | 0 .../roomescape/time/{ => domain}/Time.java | 12 +- .../time/{ => service}/TimeService.java | 22 +-- src/main/resources/schema.sql | 39 ------ 11 files changed, 36 insertions(+), 324 deletions(-) delete mode 100644 src/main/java/roomescape/member/domain/MemberDao.java delete mode 100644 src/main/java/roomescape/reservation/domain/ReservationDao.java delete mode 100644 src/main/java/roomescape/theme/ThemeDao.java rename src/main/java/roomescape/theme/{ => controller}/ThemeController.java (76%) rename src/main/java/roomescape/theme/{ => domain}/Theme.java (73%) delete mode 100644 src/main/java/roomescape/time/TimeDao.java rename src/main/java/roomescape/time/{ => controller}/TimeController.java (100%) rename src/main/java/roomescape/time/{ => domain}/AvailableTime.java (100%) rename src/main/java/roomescape/time/{ => domain}/Time.java (57%) rename src/main/java/roomescape/time/{ => service}/TimeService.java (54%) diff --git a/src/main/java/roomescape/member/domain/MemberDao.java b/src/main/java/roomescape/member/domain/MemberDao.java deleted file mode 100644 index 5652a7ee..00000000 --- a/src/main/java/roomescape/member/domain/MemberDao.java +++ /dev/null @@ -1,57 +0,0 @@ -package roomescape.member.domain; - -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.Optional; - -@Repository -public class MemberDao { - private final 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 Optional findByName(String name) { - return Optional.ofNullable(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/reservation/domain/ReservationDao.java b/src/main/java/roomescape/reservation/domain/ReservationDao.java deleted file mode 100644 index 8e54b34c..00000000 --- a/src/main/java/roomescape/reservation/domain/ReservationDao.java +++ /dev/null @@ -1,128 +0,0 @@ -package roomescape.reservation.domain; - -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.reservation.controller.dto.ReservationRequest; -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/theme/ThemeDao.java b/src/main/java/roomescape/theme/ThemeDao.java deleted file mode 100644 index 945341d8..00000000 --- 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/ThemeController.java b/src/main/java/roomescape/theme/controller/ThemeController.java similarity index 76% rename from src/main/java/roomescape/theme/ThemeController.java rename to src/main/java/roomescape/theme/controller/ThemeController.java index 03bca41a..f336ffe8 100644 --- a/src/main/java/roomescape/theme/ThemeController.java +++ b/src/main/java/roomescape/theme/controller/ThemeController.java @@ -13,26 +13,26 @@ @RestController public class ThemeController { - private ThemeDao themeDao; + private ThemeRepository themeRepository; - public ThemeController(ThemeDao themeDao) { - this.themeDao = themeDao; + public ThemeController(ThemeRepository themeRepository) { + this.themeRepository = themeRepository; } @PostMapping("/themes") public ResponseEntity createTheme(@RequestBody Theme theme) { - Theme newTheme = themeDao.save(theme); + Theme newTheme = themeRepository.save(theme); return ResponseEntity.created(URI.create("/themes/" + newTheme.getId())).body(newTheme); } @GetMapping("/themes") public ResponseEntity> list() { - return ResponseEntity.ok(themeDao.findAll()); + return ResponseEntity.ok(themeRepository.findAll()); } @DeleteMapping("/themes/{id}") public ResponseEntity deleteTheme(@PathVariable Long id) { - themeDao.deleteById(id); + themeRepository.deleteById(id); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/roomescape/theme/Theme.java b/src/main/java/roomescape/theme/domain/Theme.java similarity index 73% rename from src/main/java/roomescape/theme/Theme.java rename to src/main/java/roomescape/theme/domain/Theme.java index 430a6239..7f7858a3 100644 --- a/src/main/java/roomescape/theme/Theme.java +++ b/src/main/java/roomescape/theme/domain/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/time/TimeDao.java b/src/main/java/roomescape/time/TimeDao.java deleted file mode 100644 index f39a9a32..00000000 --- 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