diff --git a/README.md b/README.md new file mode 100644 index 000000000..130319fac --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# πŸ”“ λ°©νƒˆμΆœ μ˜ˆμ•½ μ„œλΉ„μŠ€ + +
+image +image +
+ +## πŸš€ μ£Όμš” κΈ°λŠ₯ + +1. **둜그인**: κ°€μž…ν•œ 이메일과 λΉ„λ°€λ²ˆν˜Έλ‘œ λ‘œκ·ΈμΈν•  수 μžˆμŠ΅λ‹ˆλ‹€. + +2. **μ˜ˆμ•½**: 'μ˜ˆμ•½' νŽ˜μ΄μ§€μ—μ„œ μ›ν•˜λŠ” λ‚ μ§œ, ν…Œλ§ˆ, μ‹œκ°„μ„ μ„ νƒν•˜κ³  'μ˜ˆμ•½ν•˜κΈ°' λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ μ˜ˆμ•½μ΄ μ™„λ£Œλ©λ‹ˆλ‹€. + +3. **μ˜ˆμ•½ 확인 및 μ·¨μ†Œ**: 'λ‚΄ μ˜ˆμ•½' νŽ˜μ΄μ§€μ—μ„œ μ˜ˆμ•½ 내역을 ν™•μΈν•˜κ±°λ‚˜, μ›μΉ˜ μ•ŠλŠ” μ˜ˆμ•½μ„ μ·¨μ†Œν•  수 μžˆμŠ΅λ‹ˆλ‹€. + +4. **μ˜ˆμ•½ λŒ€κΈ° μ‹ μ²­**: μ˜ˆμ•½ λŒ€κΈ°λ₯Ό μ‹ μ²­ν•˜λ©΄, μ·¨μ†Œ μžλ¦¬κ°€ 생겼을 λ•Œ μš°μ„ μ μœΌλ‘œ μ˜ˆμ•½ν•  기회λ₯Ό λ“œλ¦½λ‹ˆλ‹€. diff --git a/src/main/java/auth/JwtUtils.java b/src/main/java/auth/JwtUtils.java new file mode 100644 index 000000000..45ed6d471 --- /dev/null +++ b/src/main/java/auth/JwtUtils.java @@ -0,0 +1,69 @@ +package auth; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; + +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.util.Arrays; +import java.util.Date; + +public class JwtUtils { + + private final Key key; + + public JwtUtils(String secretKey) { + this.key = new SecretKeySpec(secretKey.getBytes(), SignatureAlgorithm.HS256.getJcaName()); + } + + public String createToken(String id, String role) { + Claims claims = Jwts.claims().setSubject(id); + claims.put("role", role); + Date now = new Date(); + Date validity = new Date(now.getTime() + 3600000); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + private Claims getClaims(String token) { + try { + Jws claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + return claims.getBody(); + } catch (JwtException | IllegalArgumentException e) { + throw new IllegalArgumentException("μœ νš¨ν•˜μ§€ μ•Šμ€ ν† ν°μž…λ‹ˆλ‹€."); + } + } + + public String getSubject(String token) { + return getClaims(token).getSubject(); + } + + public String getRole(String token) { + return getClaims(token).get("role", String.class); + } + + public String getTokenFromCookie(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies == null) { + return null; + } + return Arrays.stream(cookies) + .filter(cookie -> "token".equals(cookie.getName())) + .findFirst() + .map(Cookie::getValue) + .orElse(null); + } +} diff --git a/src/main/java/auth/annotation/Login.java b/src/main/java/auth/annotation/Login.java new file mode 100644 index 000000000..d6ee3111d --- /dev/null +++ b/src/main/java/auth/annotation/Login.java @@ -0,0 +1,11 @@ +package auth.annotation; + +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 Login { +} diff --git a/src/main/java/roomescape/ExceptionController.java b/src/main/java/roomescape/ExceptionController.java index 4e2450f9e..8c0a692d3 100644 --- a/src/main/java/roomescape/ExceptionController.java +++ b/src/main/java/roomescape/ExceptionController.java @@ -1,14 +1,33 @@ package roomescape; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import roomescape.auth.exception.UnauthenticatedException; @ControllerAdvice public class ExceptionController { + + private static final Logger log = LoggerFactory.getLogger(ExceptionController.class); + @ExceptionHandler(Exception.class) - public ResponseEntity handleRuntimeException(Exception e) { - e.printStackTrace(); - return ResponseEntity.badRequest().build(); + public ResponseEntity handleRuntimeException(Exception e) { + log.error("μ˜ˆμƒμΉ˜ λͺ»ν•œ μ˜ˆμ™Έκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", e); + return ResponseEntity.internalServerError().body("μ„œλ²„ λ‚΄λΆ€ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."); + } + + @ExceptionHandler(UnauthenticatedException.class) + public ResponseEntity handleUnauthenticatedException(UnauthenticatedException e) { + log.error("μΈμ¦λ˜μ§€ μ•Šμ€ μš”μ²­μž…λ‹ˆλ‹€.", e); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage()); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + log.error("잘λͺ»λœ μš”μ²­: {}", e.getMessage(), e); + return ResponseEntity.badRequest().body(e.getMessage()); } } diff --git a/src/main/java/roomescape/RoomescapeApplication.java b/src/main/java/roomescape/RoomescapeApplication.java index 2ca0f743f..f63d4ca0f 100644 --- a/src/main/java/roomescape/RoomescapeApplication.java +++ b/src/main/java/roomescape/RoomescapeApplication.java @@ -3,7 +3,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -@SpringBootApplication +@SpringBootApplication(scanBasePackages = {"roomescape", "auth"}) public class RoomescapeApplication { public static void main(String[] args) { SpringApplication.run(RoomescapeApplication.class, args); diff --git a/src/main/java/roomescape/auth/AdminInterceptor.java b/src/main/java/roomescape/auth/AdminInterceptor.java index 289db20c5..5c64339d2 100644 --- a/src/main/java/roomescape/auth/AdminInterceptor.java +++ b/src/main/java/roomescape/auth/AdminInterceptor.java @@ -1,35 +1,33 @@ package roomescape.auth; -import jakarta.servlet.http.Cookie; +import auth.JwtUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; -import java.util.Arrays; -import java.util.Optional; - @Component public class AdminInterceptor implements HandlerInterceptor { - private final JwtTokenProvider jwtTokenProvider; + private final JwtUtils jwtUtils; - public AdminInterceptor(JwtTokenProvider jwtTokenProvider) { - this.jwtTokenProvider = jwtTokenProvider; + public AdminInterceptor(JwtUtils jwtUtils) { + this.jwtUtils = jwtUtils; } @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - Optional tokenCookie = findTokenCookie(request.getCookies()); + public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception { + String token = jwtUtils.getTokenFromCookie(request); - if (tokenCookie.isEmpty()) { + if (token == null) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } try { - String token = tokenCookie.get().getValue(); - String role = jwtTokenProvider.getRole(token); + String role = jwtUtils.getRole(token); + if (!"ADMIN".equals(role)) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; @@ -41,13 +39,4 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons return true; } - - private Optional findTokenCookie(Cookie[] cookies) { - if (cookies == null) { - return Optional.empty(); - } - return Arrays.stream(cookies) - .filter(cookie -> "token".equals(cookie.getName())) - .findFirst(); - } } diff --git a/src/main/java/roomescape/auth/JwtTokenProvider.java b/src/main/java/roomescape/auth/JwtTokenProvider.java deleted file mode 100644 index 58ec0a070..000000000 --- a/src/main/java/roomescape/auth/JwtTokenProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -package roomescape.auth; - -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import roomescape.member.Member; - -import javax.crypto.SecretKey; - -@Component -public class JwtTokenProvider { - - private SecretKey secretKey; - - @Value("${roomescape.auth.jwt.secret}") - private String secret; - - @PostConstruct - public void init() { - this.secretKey = Keys.hmacShaKeyFor(secret.getBytes()); - } - - public String createToken(Member member) { - return Jwts.builder() - .setSubject(member.getId().toString()) - .claim("name", member.getName()) - .claim("role", member.getRole()) - .signWith(secretKey) - .compact(); - } - - public String getSubject(String token) { - return Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody() - .getSubject(); - } - - public String getRole(String token) { - return Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody() - .get("role", String.class); - } -} diff --git a/src/main/java/roomescape/auth/LoginController.java b/src/main/java/roomescape/auth/LoginController.java index c3c67fc3b..b726bb082 100644 --- a/src/main/java/roomescape/auth/LoginController.java +++ b/src/main/java/roomescape/auth/LoginController.java @@ -1,5 +1,8 @@ package roomescape.auth; +import auth.JwtUtils; +import auth.annotation.Login; +import roomescape.auth.dto.LoginMember; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.ResponseEntity; @@ -8,22 +11,26 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import roomescape.auth.dto.LoginCheckResponse; -import roomescape.auth.dto.LoginMember; import roomescape.auth.dto.LoginRequest; +import roomescape.member.Member; import roomescape.member.MemberService; @RestController public class LoginController { private final MemberService memberService; + private final JwtUtils jwtUtils; - public LoginController(MemberService memberService) { + public LoginController(MemberService memberService, JwtUtils jwtUtils) { this.memberService = memberService; + this.jwtUtils = jwtUtils; } @PostMapping("/login") public ResponseEntity login(@RequestBody LoginRequest request, HttpServletResponse response) { - String token = memberService.login(request); + Member member = memberService.login(request); + + String token = jwtUtils.createToken(String.valueOf(member.getId()), member.getRole()); Cookie cookie = new Cookie("token", token); cookie.setHttpOnly(true); @@ -45,10 +52,7 @@ public ResponseEntity logout(HttpServletResponse response) { } @GetMapping("/login/check") - public ResponseEntity checkLogin(LoginMember loginMember) { - if (loginMember == null) { - return ResponseEntity.status(401).build(); - } + public ResponseEntity checkLogin(@Login LoginMember loginMember) { return ResponseEntity.ok(new LoginCheckResponse(loginMember.getName())); } } diff --git a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java deleted file mode 100644 index adefee7c2..000000000 --- a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java +++ /dev/null @@ -1,67 +0,0 @@ -package roomescape.auth; - -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -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; -import roomescape.auth.dto.LoginMember; -import roomescape.auth.exception.UnauthenticatedException; -import roomescape.member.Member; -import roomescape.member.MemberService; - -import java.util.Arrays; - -@Component -public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { - - private final JwtTokenProvider jwtTokenProvider; - private final MemberService memberService; - - public LoginMemberArgumentResolver(JwtTokenProvider jwtTokenProvider, MemberService memberService) { - this.jwtTokenProvider = jwtTokenProvider; - this.memberService = memberService; - } - - @Override - public boolean supportsParameter(MethodParameter parameter) { - // 컨트둀러 λ©”μ„œλ“œμ˜ νŒŒλΌλ―Έν„°κ°€ LoginMember νƒ€μž…μΌ λ•Œ 이 리쑸버λ₯Ό μ‚¬μš©ν•˜λ„λ‘ μ„€μ • - return parameter.getParameterType().equals(LoginMember.class); - } - - @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); - if (request == null) { - throw new UnauthenticatedException("μš”μ²­ 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."); - } - - Cookie[] cookies = request.getCookies(); - if (cookies == null) { - return null; - } - - String token = Arrays.stream(cookies) - .filter(cookie -> cookie.getName().equals("token")) - .map(Cookie::getValue) - .findFirst() - .orElse(null); - - if (token == null) { - return null; // 토큰이 μ—†μœΌλ©΄ null λ°˜ν™˜ - } - - try { - Long memberId = Long.valueOf(jwtTokenProvider.getSubject(token)); - Member member = memberService.findById(memberId); - return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); - } catch (Exception e) { - // 토큰이 μœ νš¨ν•˜μ§€ μ•Šμ€ 경우 λ“± - throw new UnauthenticatedException("μœ νš¨ν•˜μ§€ μ•Šμ€ ν† ν°μž…λ‹ˆλ‹€.", e); - } - } -} diff --git a/src/main/java/roomescape/auth/LoginUserArgumentResolver.java b/src/main/java/roomescape/auth/LoginUserArgumentResolver.java new file mode 100644 index 000000000..aa488dc36 --- /dev/null +++ b/src/main/java/roomescape/auth/LoginUserArgumentResolver.java @@ -0,0 +1,56 @@ +package roomescape.auth; + +import auth.JwtUtils; +import auth.annotation.Login; +import roomescape.auth.dto.LoginMember; +import io.jsonwebtoken.JwtException; +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; +import roomescape.auth.exception.UnauthenticatedException; +import roomescape.member.Member; +import roomescape.member.MemberService; + +@Component +public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver { + + private final JwtUtils jwtUtils; + private final MemberService memberService; + + public LoginUserArgumentResolver(JwtUtils jwtUtils, MemberService memberService) { + this.jwtUtils = jwtUtils; + this.memberService = memberService; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(Login.class) + && parameter.getParameterType().equals(LoginMember.class); + } + + @Override + public Object resolveArgument(@NotNull MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + + String token = jwtUtils.getTokenFromCookie(request); + + if (token == null) { + throw new UnauthenticatedException("둜그인 토큰이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."); + } + + try { + Long memberId = Long.parseLong(jwtUtils.getSubject(token)); + Member member = memberService.findById(memberId); + + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } catch (JwtException| IllegalArgumentException e) { + throw new UnauthenticatedException("μœ νš¨ν•˜μ§€ μ•Šμ€ 둜그인 ν† ν°μž…λ‹ˆλ‹€.", e); + } + } +} diff --git a/src/main/java/roomescape/auth/dto/LoginMember.java b/src/main/java/roomescape/auth/dto/LoginMember.java index 2b4b10eab..28c838fa1 100644 --- a/src/main/java/roomescape/auth/dto/LoginMember.java +++ b/src/main/java/roomescape/auth/dto/LoginMember.java @@ -5,7 +5,7 @@ @Getter @AllArgsConstructor -public class LoginMember { // λ‘œκ·ΈμΈν•œ μ‚¬μš©μž 정보λ₯Ό λ‹΄λŠ” DTO +public class LoginMember { private Long id; private String name; diff --git a/src/main/java/roomescape/config/AuthConfig.java b/src/main/java/roomescape/config/AuthConfig.java new file mode 100644 index 000000000..09abb6b06 --- /dev/null +++ b/src/main/java/roomescape/config/AuthConfig.java @@ -0,0 +1,18 @@ +package roomescape.config; + +import auth.JwtUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AuthConfig { + + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + + @Bean + public JwtUtils jwtUtils() { + return new JwtUtils(secretKey); + } +} diff --git a/src/main/java/roomescape/config/DataLoader.java b/src/main/java/roomescape/config/DataLoader.java new file mode 100644 index 000000000..c817df913 --- /dev/null +++ b/src/main/java/roomescape/config/DataLoader.java @@ -0,0 +1,30 @@ +package roomescape.config; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Profile("!test") +@Component +public class DataLoader implements CommandLineRunner { + + private final JdbcTemplate jdbcTemplate; + + public DataLoader(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void run(String... args) throws Exception { + jdbcTemplate.update("INSERT INTO member (name, email, password, role) VALUES ('μ–΄λ“œλ―Ό', 'admin@email.com', 'password', 'ADMIN')"); + + jdbcTemplate.update("INSERT INTO theme (name, description, thumbnail) VALUES ('곡포', '맀우 λ¬΄μ„œμš΄ ν…Œλ§ˆ', 'https://i.imgur.com/1.jpg')"); + jdbcTemplate.update("INSERT INTO theme (name, description, thumbnail) VALUES ('μ½”λ―Ή', '맀우 웃긴 ν…Œλ§ˆ', 'https://i.imgur.com/2.jpg')"); + jdbcTemplate.update("INSERT INTO theme (name, description, thumbnail) VALUES ('μ–΄λ“œλ²€μ²˜', 'μ‹ λ‚˜λŠ” λͺ¨ν—˜ ν…Œλ§ˆ', 'https://i.imgur.com/3.jpg')"); + + jdbcTemplate.update("INSERT INTO time (time) VALUES ('10:00')"); + jdbcTemplate.update("INSERT INTO time (time) VALUES ('13:00')"); + jdbcTemplate.update("INSERT INTO time (time) VALUES ('15:00')"); + } +} diff --git a/src/main/java/roomescape/config/TestDataLoader.java b/src/main/java/roomescape/config/TestDataLoader.java new file mode 100644 index 000000000..34a2121e6 --- /dev/null +++ b/src/main/java/roomescape/config/TestDataLoader.java @@ -0,0 +1,40 @@ +package roomescape.config; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Profile("test") +@Component +public class TestDataLoader implements CommandLineRunner { + + private final JdbcTemplate jdbcTemplate; + + public TestDataLoader(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void run(String... args) throws Exception { + // Member (ID: 1=μ–΄λ“œλ―Ό, 2=브라운, 3=클둜이) + jdbcTemplate.update("INSERT INTO member (name, email, password, role) VALUES ('μ–΄λ“œλ―Ό', 'admin@email.com', 'password', 'ADMIN')"); + jdbcTemplate.update("INSERT INTO member (name, email, password, role) VALUES ('브라운', 'brown@email.com', 'password', 'USER')"); + jdbcTemplate.update("INSERT INTO member (name, email, password, role) VALUES ('클둜이', 'chloe@email.com', 'password', 'USER')"); + + // Theme + jdbcTemplate.update("INSERT INTO theme (name, description, thumbnail) VALUES ('곡포', '맀우 λ¬΄μ„œμš΄ ν…Œλ§ˆ', 'https://i.imgur.com/1.jpg')"); + jdbcTemplate.update("INSERT INTO theme (name, description, thumbnail) VALUES ('μ½”λ―Ή', '맀우 웃긴 ν…Œλ§ˆ', 'https://i.imgur.com/2.jpg')"); + jdbcTemplate.update("INSERT INTO theme (name, description, thumbnail) VALUES ('μ–΄λ“œλ²€μ²˜', 'μ‹ λ‚˜λŠ” λͺ¨ν—˜ ν…Œλ§ˆ', 'https://i.imgur.com/3.jpg')"); + + // Time + jdbcTemplate.update("INSERT INTO time (time) VALUES ('10:00')"); + jdbcTemplate.update("INSERT INTO time (time) VALUES ('13:00')"); + jdbcTemplate.update("INSERT INTO time (time) VALUES ('15:00')"); + + // Reservation + jdbcTemplate.update("INSERT INTO reservation (date, member_id, theme_id, time_id) VALUES ('2024-03-01', 1, 1, 1)"); + jdbcTemplate.update("INSERT INTO reservation (date, member_id, theme_id, time_id) VALUES ('2024-03-01', 1, 1, 1)"); + jdbcTemplate.update("INSERT INTO reservation (date, member_id, theme_id, time_id) VALUES ('2024-03-01', 1, 1, 1)"); + } +} diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java index 91c824905..7949d0afb 100644 --- a/src/main/java/roomescape/config/WebConfig.java +++ b/src/main/java/roomescape/config/WebConfig.java @@ -1,34 +1,33 @@ package roomescape.config; +import roomescape.auth.LoginUserArgumentResolver; 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.auth.AdminInterceptor; -import roomescape.auth.LoginMemberArgumentResolver; import java.util.List; @Configuration public class WebConfig implements WebMvcConfigurer { - private final LoginMemberArgumentResolver loginMemberArgumentResolver; + private final LoginUserArgumentResolver loginUserArgumentResolver; private final AdminInterceptor adminInterceptor; - public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver, - AdminInterceptor adminInterceptor) { - this.loginMemberArgumentResolver = loginMemberArgumentResolver; + public WebConfig(LoginUserArgumentResolver loginUserArgumentResolver, AdminInterceptor adminInterceptor) { + this.loginUserArgumentResolver = loginUserArgumentResolver; this.adminInterceptor = adminInterceptor; } @Override public void addArgumentResolvers(List resolvers) { - resolvers.add(loginMemberArgumentResolver); + resolvers.add(loginUserArgumentResolver); } @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(adminInterceptor) // 등둝할 인터셉터 μ§€μ • - .addPathPatterns("/admin/**"); // 인터셉터λ₯Ό μ μš©ν•  URL νŒ¨ν„΄ μ§€μ • + registry.addInterceptor(adminInterceptor) + .addPathPatterns("/admin/**"); } } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index 9244e5ac1..1a45f3e71 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -2,7 +2,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import roomescape.auth.JwtTokenProvider; import roomescape.auth.dto.LoginRequest; import roomescape.member.dto.MemberRequest; import roomescape.member.dto.MemberResponse; @@ -11,11 +10,9 @@ public class MemberService { private final MemberRepository memberRepository; - private final JwtTokenProvider jwtTokenProvider; - public MemberService(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider) { + public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; - this.jwtTokenProvider = jwtTokenProvider; } @Transactional @@ -27,7 +24,7 @@ public MemberResponse createMember(MemberRequest memberRequest) { } @Transactional(readOnly = true) - public String login(LoginRequest request) { + public Member login(LoginRequest request) { // λ°˜ν™˜ νƒ€μž…μ„ String(토큰)μ—μ„œ Member둜 λ³€κ²½ Member member = memberRepository.findByEmail(request.getEmail()) .orElseThrow(() -> new IllegalArgumentException("이메일 λ˜λŠ” λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")); @@ -35,7 +32,7 @@ public String login(LoginRequest request) { throw new IllegalArgumentException("이메일 λ˜λŠ” λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."); } - return jwtTokenProvider.createToken(member); + return member; } public Member findById(Long id) { diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index 9fa4cf0c6..9ce38ffce 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -1,5 +1,7 @@ package roomescape.reservation; +import auth.annotation.Login; +import roomescape.auth.dto.LoginMember; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -7,7 +9,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import roomescape.auth.dto.LoginMember; import roomescape.reservation.dto.MyReservationResponse; import roomescape.reservation.dto.ReservationRequest; import roomescape.reservation.dto.ReservationResponse; @@ -30,7 +31,7 @@ public ResponseEntity> list() { } @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest request, LoginMember loginMember) { + public ResponseEntity create(@RequestBody ReservationRequest request, @Login LoginMember loginMember) { ReservationResponse reservation = reservationService.create(request, loginMember); return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); } @@ -48,8 +49,8 @@ public ResponseEntity delete(@PathVariable Long id) { } @GetMapping("/reservations-mine") - public ResponseEntity> findMyReservations(LoginMember loginMember) { - List myReservations = reservationService.findMyReservations(loginMember); + public ResponseEntity> findMyReservations(@Login LoginMember loginMember) { + List myReservations = reservationService.findMyReservations(loginMember.getId()); return ResponseEntity.ok(myReservations); } } diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index 4aedc852c..7d15c6256 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -1,8 +1,8 @@ package roomescape.reservation; +import roomescape.auth.dto.LoginMember; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import roomescape.auth.dto.LoginMember; import roomescape.member.Member; import roomescape.member.MemberRepository; import roomescape.reservation.dto.MyReservationResponse; @@ -80,9 +80,9 @@ private Member determineReservationHolder(ReservationRequest request, Member log return loggedInUser; } - public List findMyReservations(LoginMember loginMember) { - List reservations = reservationRepository.findWithDetailsByMemberId(loginMember.getId()); - List waitings = waitingRepository.findWaitingsWithRankByMemberId(loginMember.getId()); + public List findMyReservations(Long memberId) { + List reservations = reservationRepository.findWithDetailsByMemberId(memberId); + List waitings = waitingRepository.findWaitingsWithRankByMemberId(memberId); Stream reservationResponses = reservations.stream() .map(r -> MyReservationResponse.from(r, r.getTheme(), r.getTime())); diff --git a/src/main/java/roomescape/waiting/WaitingController.java b/src/main/java/roomescape/waiting/WaitingController.java index d03329722..f5373f61e 100644 --- a/src/main/java/roomescape/waiting/WaitingController.java +++ b/src/main/java/roomescape/waiting/WaitingController.java @@ -1,5 +1,7 @@ package roomescape.waiting; +import auth.annotation.Login; +import roomescape.auth.dto.LoginMember; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -7,7 +9,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import roomescape.auth.dto.LoginMember; import roomescape.waiting.dto.WaitingRequest; import roomescape.waiting.dto.WaitingResponse; @@ -24,7 +25,7 @@ public WaitingController(WaitingService waitingService) { } @PostMapping - public ResponseEntity createWaiting(@RequestBody WaitingRequest request, LoginMember loginMember) { + public ResponseEntity createWaiting(@RequestBody WaitingRequest request, @Login LoginMember loginMember) { WaitingResponse response = waitingService.create(request, loginMember); return ResponseEntity.created(URI.create("/waitings/" + response.getId())).body(response); } diff --git a/src/main/java/roomescape/waiting/WaitingService.java b/src/main/java/roomescape/waiting/WaitingService.java index cefaf5fa7..8e91d74cd 100644 --- a/src/main/java/roomescape/waiting/WaitingService.java +++ b/src/main/java/roomescape/waiting/WaitingService.java @@ -1,8 +1,9 @@ package roomescape.waiting; +import auth.annotation.Login; +import roomescape.auth.dto.LoginMember; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import roomescape.auth.dto.LoginMember; import roomescape.auth.exception.UnauthenticatedException; import roomescape.member.Member; import roomescape.member.MemberRepository; @@ -34,7 +35,7 @@ public WaitingService(WaitingRepository waitingRepository, ReservationRepository } @Transactional - public WaitingResponse create(WaitingRequest request, LoginMember loginMember) { + public WaitingResponse create(WaitingRequest request, @Login LoginMember loginMember) { Member member = memberRepository.findById(loginMember.getId()) .orElseThrow(() -> new IllegalArgumentException("μ‚¬μš©μž 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")); diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql deleted file mode 100644 index de8f21cf9..000000000 --- a/src/main/resources/data.sql +++ /dev/null @@ -1,15 +0,0 @@ -INSERT INTO member (name, email, password, role) VALUES ('μ–΄λ“œλ―Ό', 'admin@email.com', 'password', 'ADMIN'); -INSERT INTO member (name, email, password, role) VALUES ('클둜이', 'chloe@email.com', 'password', 'USER'); -INSERT INTO member (name, email, password, role) VALUES ('브라운', 'brown@email.com', 'password', 'USER'); - -INSERT INTO theme (name, description, thumbnail) VALUES ('곡포', '맀우 λ¬΄μ„œμš΄ ν…Œλ§ˆ', 'https://i.imgur.com/1.jpg'); -INSERT INTO theme (name, description, thumbnail) VALUES ('μ½”λ―Ή', '맀우 웃긴 ν…Œλ§ˆ', 'https://i.imgur.com/2.jpg'); -INSERT INTO theme (name, description, thumbnail) VALUES ('μ–΄λ“œλ²€μ²˜', 'μ‹ λ‚˜λŠ” λͺ¨ν—˜ ν…Œλ§ˆ', 'https://i.imgur.com/3.jpg'); - -INSERT INTO time (time) VALUES ('10:00'); -INSERT INTO time (time) VALUES ('13:00'); -INSERT INTO time (time) VALUES ('15:00'); - -INSERT INTO reservation (member_id, date, time_id, theme_id) VALUES (1, '2024-03-01', 1, 1); -INSERT INTO reservation (member_id, date, time_id, theme_id) VALUES (1, '2024-03-01', 2, 2); -INSERT INTO reservation (member_id, date, time_id, theme_id) VALUES (1, '2024-03-01', 3, 3); diff --git a/src/main/resources/static/js/reservation.js b/src/main/resources/static/js/reservation.js index 8eaf9e47e..0f73eebef 100644 --- a/src/main/resources/static/js/reservation.js +++ b/src/main/resources/static/js/reservation.js @@ -131,9 +131,9 @@ function saveRow(event) { const row = event.target.parentNode.parentNode; const nameInput = row.querySelector('input[type="text"]'); - const themeSelect = row.querySelector('select'); + const themeSelect = row.querySelector('#theme-select'); const dateInput = row.querySelector('input[type="date"]'); - const timeSelect = row.querySelector('select'); + const timeSelect = row.querySelector('#time-select'); const reservation = { name: nameInput.value, diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 8011fe97b..898b640e8 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -1,12 +1,17 @@ package roomescape; +import auth.JwtUtils; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import org.assertj.core.api.AssertionsForClassTypes; import org.junit.jupiter.api.Test; +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; +import org.springframework.test.context.ActiveProfiles; import roomescape.reservation.dto.MyReservationResponse; import roomescape.reservation.dto.ReservationResponse; import roomescape.waiting.dto.WaitingResponse; @@ -17,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@ActiveProfiles("test") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { @@ -155,4 +161,18 @@ private String createToken(String email, String password) { assertThat(status).isEqualTo("1번째 μ˜ˆμ•½λŒ€κΈ°"); } + + @Test + void 칠단계() { + Component componentAnnotation = JwtUtils.class.getAnnotation(Component.class); + assertThat(componentAnnotation).isNull(); + } + + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + + @Test + void νŒ”λ‹¨κ³„() { + AssertionsForClassTypes.assertThat(secretKey).isNotBlank(); + } }