diff --git "a/.run/MissionStepTest.\354\202\274\353\213\250\352\263\204.run.xml" "b/.run/MissionStepTest.\354\202\274\353\213\250\352\263\204.run.xml" new file mode 100644 index 00000000..80b29a46 --- /dev/null +++ "b/.run/MissionStepTest.\354\202\274\353\213\250\352\263\204.run.xml" @@ -0,0 +1,31 @@ + + + + + + + + false + true + false + true + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8d52aebc..f6df4186 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' @@ -26,7 +26,12 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' + implementation 'com.h2database:h2' runtimeOnly 'com.h2database:h2' + + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' + } test { diff --git a/src/main/java/jwt/JwtProperties.java b/src/main/java/jwt/JwtProperties.java new file mode 100644 index 00000000..47b071cb --- /dev/null +++ b/src/main/java/jwt/JwtProperties.java @@ -0,0 +1,23 @@ +package jwt; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "jwt") +public class JwtProperties { + + private final String secret; + private final long EXPIRATION_TIME_MILLIS = 1000 * 60 * 60 * 24; + + public JwtProperties(String secret) { + this.secret = secret; + } + + public String getSecret() { + return secret; + } + + public long getEXPIRATION_TIME_MILLIS() { + return EXPIRATION_TIME_MILLIS; + } +} + diff --git a/src/main/java/jwt/JwtService.java b/src/main/java/jwt/JwtService.java new file mode 100644 index 00000000..ecd2a266 --- /dev/null +++ b/src/main/java/jwt/JwtService.java @@ -0,0 +1,32 @@ +package jwt; + +import io.jsonwebtoken.Claims; +import org.springframework.stereotype.Service; +import roomescape.member.Member; +import roomescape.member.MemberRepository; + +@Service +public class JwtService { + private final JwtUtils jwtUtils; + private final MemberRepository memberRepository; + + public JwtService(JwtUtils jwtUtils, MemberRepository memberRepository) { + this.jwtUtils = jwtUtils; + this.memberRepository = memberRepository; + } + + public Long getUserIdFromToken(String token) { + Claims claims = jwtUtils.getClaimsFromToken(token); + try { + return Long.valueOf(claims.getSubject()); + } catch (Exception e) { + throw new IllegalArgumentException("Claims에서 User ID를 추출할 수 없습니다.", e); + } + } + + public Member getMemberFromToken(String token) { + Long userId = getUserIdFromToken(token); + return memberRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("토큰으로부터 유저를 찾을 수 없습니다.")); + } +} diff --git a/src/main/java/jwt/JwtUtils.java b/src/main/java/jwt/JwtUtils.java new file mode 100644 index 00000000..51b2cf16 --- /dev/null +++ b/src/main/java/jwt/JwtUtils.java @@ -0,0 +1,48 @@ +package jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.stereotype.Component; +import roomescape.member.Role; + +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.util.Date; + +@Component +public class JwtUtils { + private final JwtProperties jwtProperties; + + public JwtUtils(JwtProperties jwtProperties) { + this.jwtProperties = jwtProperties; + } + + private Key generateKey() { + return new SecretKeySpec(jwtProperties.getSecret().getBytes(), SignatureAlgorithm.HS256.getJcaName()); + } + + public String generateToken(Long userId, Role role) { + Key key = generateKey(); + return Jwts.builder() + .setSubject(String.valueOf(userId)) + .claim("role", role.name()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getEXPIRATION_TIME_MILLIS())) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + public Claims getClaimsFromToken(String token) { + try { + Key key = generateKey(); + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } catch (Exception e) { + throw new IllegalArgumentException("유효하지 않은 토큰입니다.", e); + } + } +} diff --git a/src/main/java/roomescape/ExceptionController.java b/src/main/java/roomescape/ExceptionController.java index 4e2450f9..e452ff0c 100644 --- a/src/main/java/roomescape/ExceptionController.java +++ b/src/main/java/roomescape/ExceptionController.java @@ -1,14 +1,33 @@ package roomescape; +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 org.springframework.web.server.ResponseStatusException; + +import java.util.HashMap; +import java.util.Map; @ControllerAdvice public class ExceptionController { + + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity handleResponseStatusException(ResponseStatusException e) { + return ResponseEntity.status(e.getStatusCode()).build(); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleIllegalArgumentException(IllegalArgumentException e) { + Map errorResponse = new HashMap<>(); + errorResponse.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleRuntimeException(Exception e) { e.printStackTrace(); return ResponseEntity.badRequest().build(); } } + diff --git a/src/main/java/roomescape/RoomescapeApplication.java b/src/main/java/roomescape/RoomescapeApplication.java index 2ca0f743..82220c17 100644 --- a/src/main/java/roomescape/RoomescapeApplication.java +++ b/src/main/java/roomescape/RoomescapeApplication.java @@ -1,9 +1,14 @@ package roomescape; +import jwt.JwtProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; @SpringBootApplication +@EnableConfigurationProperties(JwtProperties.class) +@ComponentScan(basePackages = {"roomescape", "jwt"}) public class RoomescapeApplication { public static void main(String[] args) { SpringApplication.run(RoomescapeApplication.class, args); diff --git a/src/main/java/roomescape/SwaggerConfig.java b/src/main/java/roomescape/SwaggerConfig.java new file mode 100644 index 00000000..313f75cb --- /dev/null +++ b/src/main/java/roomescape/SwaggerConfig.java @@ -0,0 +1,51 @@ +package roomescape; + + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@OpenAPIDefinition( + info = @Info( + title = "roomescape API 명세서", + description = "roomescape 서비스 API 명세서", + version = "v1" + ) +) + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .addSecurityItem(new SecurityRequirement().addList("bearerAuth")) + .components(new Components() + .addSecuritySchemes("bearerAuth", + new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"))); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.addAllowedOriginPattern("*"); + configuration.addAllowedMethod("*"); + configuration.addAllowedHeader("*"); + configuration.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} + diff --git a/src/main/java/roomescape/auth/AdminInterceptor.java b/src/main/java/roomescape/auth/AdminInterceptor.java new file mode 100644 index 00000000..1b67592d --- /dev/null +++ b/src/main/java/roomescape/auth/AdminInterceptor.java @@ -0,0 +1,53 @@ +package roomescape.auth; + +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jwt.JwtUtils; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.HandlerInterceptor; +import roomescape.member.Role; + +import java.io.IOException; +import java.util.Arrays; + +@Component +public class AdminInterceptor implements HandlerInterceptor { + private final JwtUtils jwtUtils; + + public AdminInterceptor(JwtUtils jwtUtils) { + this.jwtUtils = jwtUtils; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { + String token = extractTokenFromCookies(request.getCookies()); + if (token == null) { + response.sendRedirect("/login"); + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "토큰을 찾을 수 없습니다."); + } + + Claims claims = jwtUtils.getClaimsFromToken(token); + String role = claims.get("role", String.class); + + if (!Role.ADMIN.name().equals(role)) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "권한이 없습니다."); + } + return true; + } + + private String extractTokenFromCookies(Cookie[] cookies) { + if (cookies == null) { + return null; + } + + return Arrays.stream(cookies) + .filter(cookie -> "token".equals(cookie.getName())) + .map(Cookie::getValue) + .findFirst() + .orElse(null); + } +} diff --git a/src/main/java/roomescape/auth/Authentication.java b/src/main/java/roomescape/auth/Authentication.java new file mode 100644 index 00000000..d68d8fe3 --- /dev/null +++ b/src/main/java/roomescape/auth/Authentication.java @@ -0,0 +1,9 @@ +package roomescape.auth; + +import java.lang.annotation.*; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Authentication { +} diff --git a/src/main/java/roomescape/auth/AuthenticationArgumentResolver.java b/src/main/java/roomescape/auth/AuthenticationArgumentResolver.java new file mode 100644 index 00000000..eb1ecb6d --- /dev/null +++ b/src/main/java/roomescape/auth/AuthenticationArgumentResolver.java @@ -0,0 +1,36 @@ +package roomescape.auth; + +import jwt.JwtService; +import org.springframework.beans.factory.annotation.Autowired; +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.member.Member; + +@Component +public class AuthenticationArgumentResolver implements HandlerMethodArgumentResolver { + + @Autowired + private JwtService jwtService; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(Authentication.class) + && parameter.getParameterType().equals(Member.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) { + + String token = CookieUtil.extractTokenFromHeader(webRequest) + .orElseThrow(() -> new IllegalArgumentException("토큰이 존재하지 않습니다. 로그인이 필요합니다.")); + + return jwtService.getMemberFromToken(token); + } +} diff --git a/src/main/java/roomescape/auth/CookieUtil.java b/src/main/java/roomescape/auth/CookieUtil.java new file mode 100644 index 00000000..2bbf392a --- /dev/null +++ b/src/main/java/roomescape/auth/CookieUtil.java @@ -0,0 +1,32 @@ +package roomescape.auth; + +import jakarta.servlet.http.Cookie; +import org.springframework.web.context.request.NativeWebRequest; + +import java.util.Arrays; +import java.util.Optional; + +public class CookieUtil { + + public static Optional extractTokenFromCookies(Cookie[] cookies) { + if (cookies == null) { + return Optional.empty(); + } + return Arrays.stream(cookies) + .filter(cookie -> "token".equals(cookie.getName())) + .map(Cookie::getValue) + .findFirst(); + } + + public static Optional extractTokenFromHeader(NativeWebRequest webRequest) { + String cookieHeader = webRequest.getHeader("Cookie"); + if (cookieHeader == null) { + return Optional.empty(); + } + return Arrays.stream(cookieHeader.split(";")) + .map(String::trim) + .filter(cookie -> cookie.startsWith("token=")) + .map(cookie -> cookie.substring("token=".length())) + .findFirst(); + } +} diff --git a/src/main/java/roomescape/auth/WebConfig.java b/src/main/java/roomescape/auth/WebConfig.java new file mode 100644 index 00000000..297b0f49 --- /dev/null +++ b/src/main/java/roomescape/auth/WebConfig.java @@ -0,0 +1,32 @@ +package roomescape.auth; + +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 java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + private final AdminInterceptor adminInterceptor; + private final AuthenticationArgumentResolver authenticationArgumentResolver; + + public WebConfig(AdminInterceptor adminInterceptor, AuthenticationArgumentResolver authenticationArgumentResolver) { + this.adminInterceptor = adminInterceptor; + this.authenticationArgumentResolver = authenticationArgumentResolver; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(adminInterceptor) + .addPathPatterns("/admin/**"); + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(authenticationArgumentResolver); + } +} + diff --git a/src/main/java/roomescape/initializer/DataLoader.java b/src/main/java/roomescape/initializer/DataLoader.java new file mode 100644 index 00000000..b61c53c8 --- /dev/null +++ b/src/main/java/roomescape/initializer/DataLoader.java @@ -0,0 +1,29 @@ +package roomescape.initializer; + +import jakarta.persistence.EntityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + + +@Component +@Profile("prod") +public class DataLoader implements ApplicationRunner { + + @Autowired + private EntityManager entityManager; + + @Override + @Transactional + public void run(ApplicationArguments args) throws Exception { + entityManager.createNativeQuery( + "INSERT INTO member (name, email, password, role) " + + "VALUES ('어드민', 'admin@email.com', 'password', 'ADMIN'),\n" + + "('브라운', 'brown@email.com', 'password', 'USER')" + ).executeUpdate(); + } +} + diff --git a/src/main/java/roomescape/login/LoginController.java b/src/main/java/roomescape/login/LoginController.java new file mode 100644 index 00000000..54fd757a --- /dev/null +++ b/src/main/java/roomescape/login/LoginController.java @@ -0,0 +1,41 @@ +package roomescape.login; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; +import jwt.JwtService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import roomescape.member.Member; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/login") +public class LoginController { + private LoginService loginService; + private JwtService jwtService; + + public LoginController(LoginService loginService, JwtService jwtService) { + this.loginService = loginService; + this.jwtService = jwtService; + } + + @PostMapping + public ResponseEntity> login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) { + String token = loginService.authenticate(loginRequestDto.getEmail(), loginRequestDto.getPassword()); + Cookie cookie = loginService.createAuthCookie(token); + response.addCookie(cookie); + Map responseBody = loginService.createLoginResponse(token); + return ResponseEntity.ok(responseBody); + } + + @GetMapping("/check") + public ResponseEntity> checkLogin(@CookieValue(name = "token", required = true) String token) { + Member member = jwtService.getMemberFromToken(token); + + Map response = new HashMap<>(); + response.put("name", member.getName()); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/roomescape/login/LoginRequestDto.java b/src/main/java/roomescape/login/LoginRequestDto.java new file mode 100644 index 00000000..0f30e886 --- /dev/null +++ b/src/main/java/roomescape/login/LoginRequestDto.java @@ -0,0 +1,19 @@ +package roomescape.login; + +public class LoginRequestDto { + private String email; + private String password; + + public LoginRequestDto(String email, String password) { + this.email = email; + this.password = password; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/roomescape/login/LoginService.java b/src/main/java/roomescape/login/LoginService.java new file mode 100644 index 00000000..4f9fb220 --- /dev/null +++ b/src/main/java/roomescape/login/LoginService.java @@ -0,0 +1,44 @@ +package roomescape.login; + +import jakarta.servlet.http.Cookie; +import jwt.JwtUtils; +import org.springframework.stereotype.Service; +import roomescape.member.Member; +import roomescape.member.MemberRepository; + +import java.util.HashMap; +import java.util.Map; + +@Service +public class LoginService { + private final JwtUtils jwtUtils; + private final MemberRepository memberRepository; + + public LoginService(JwtUtils jwtUtils, MemberRepository memberRepository) { + this.jwtUtils = jwtUtils; + this.memberRepository = memberRepository; + } + + public String authenticate(String email, String password) { + Member member = memberRepository.findByEmailAndPassword(email, password).get(); + if (member == null) { + throw new IllegalArgumentException("Invalid email or password"); + } + + return jwtUtils.generateToken(member.getId(), member.getRole()); + } + + public Cookie createAuthCookie(String token) { + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setPath("/"); + return cookie; + } + + public Map createLoginResponse(String token) { + Map responseBody = new HashMap<>(); + responseBody.put("token", token); + responseBody.put("message", "Login successful"); + return responseBody; + } +} diff --git a/src/main/java/roomescape/member/Member.java b/src/main/java/roomescape/member/Member.java index 903aaa9b..b22b0c8f 100644 --- a/src/main/java/roomescape/member/Member.java +++ b/src/main/java/roomescape/member/Member.java @@ -1,20 +1,31 @@ package roomescape.member; +import jakarta.persistence.*; + +@Entity public class Member { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String name; private String email; private String password; - private String role; - public Member(Long id, String name, String email, String role) { + @Enumerated(EnumType.STRING) + private Role role; + + public Member() { + } + + public Member(Long id, String name, String email, Role role) { this.id = id; this.name = name; this.email = email; this.role = role; } - public Member(String name, String email, String password, String role) { + public Member(String name, String email, String password, Role role) { this.name = name; this.email = email; this.password = password; @@ -37,7 +48,7 @@ public String getPassword() { return password; } - public String getRole() { + public Role getRole() { return role; } } diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 881ae5e0..32c83cb6 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -1,13 +1,13 @@ 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 roomescape.member.dto.MemberRequest; +import roomescape.member.dto.MemberResponse; import java.net.URI; diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java deleted file mode 100644 index 81f77f4c..00000000 --- a/src/main/java/roomescape/member/MemberDao.java +++ /dev/null @@ -1,55 +0,0 @@ -package roomescape.member; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; - -@Repository -public class MemberDao { - private JdbcTemplate jdbcTemplate; - - public MemberDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public Member save(Member member) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(connection -> { - var ps = connection.prepareStatement("INSERT INTO member(name, email, password, role) VALUES (?, ?, ?, ?)", new String[]{"id"}); - ps.setString(1, member.getName()); - ps.setString(2, member.getEmail()); - ps.setString(3, member.getPassword()); - ps.setString(4, member.getRole()); - return ps; - }, keyHolder); - - return new Member(keyHolder.getKey().longValue(), member.getName(), member.getEmail(), "USER"); - } - - public Member findByEmailAndPassword(String email, String password) { - return jdbcTemplate.queryForObject( - "SELECT id, name, email, role FROM member WHERE email = ? AND password = ?", - (rs, rowNum) -> new Member( - rs.getLong("id"), - rs.getString("name"), - rs.getString("email"), - rs.getString("role") - ), - email, password - ); - } - - public Member findByName(String name) { - return jdbcTemplate.queryForObject( - "SELECT id, name, email, role FROM member WHERE name = ?", - (rs, rowNum) -> new Member( - rs.getLong("id"), - rs.getString("name"), - rs.getString("email"), - rs.getString("role") - ), - name - ); - } -} diff --git a/src/main/java/roomescape/member/MemberRepository.java b/src/main/java/roomescape/member/MemberRepository.java new file mode 100644 index 00000000..5e5f770e --- /dev/null +++ b/src/main/java/roomescape/member/MemberRepository.java @@ -0,0 +1,13 @@ +package roomescape.member; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface MemberRepository extends JpaRepository { + Optional findByEmailAndPassword(String email, String password); + + Optional findByName(String name); +} diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index ccaa8cba..73f7ae31 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -1,17 +1,19 @@ package roomescape.member; import org.springframework.stereotype.Service; +import roomescape.member.dto.MemberRequest; +import roomescape.member.dto.MemberResponse; @Service public class MemberService { - private MemberDao memberDao; + private final MemberRepository memberRepository; - public MemberService(MemberDao memberDao) { - this.memberDao = memberDao; + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; } public MemberResponse createMember(MemberRequest memberRequest) { - Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER")); + Member member = memberRepository.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), Role.USER)); return new MemberResponse(member.getId(), member.getName(), member.getEmail()); } } diff --git a/src/main/java/roomescape/member/Role.java b/src/main/java/roomescape/member/Role.java new file mode 100644 index 00000000..6a86376a --- /dev/null +++ b/src/main/java/roomescape/member/Role.java @@ -0,0 +1,5 @@ +package roomescape.member; + +public enum Role { + ADMIN, USER; +} diff --git a/src/main/java/roomescape/member/MemberRequest.java b/src/main/java/roomescape/member/dto/MemberRequest.java similarity index 90% rename from src/main/java/roomescape/member/MemberRequest.java rename to src/main/java/roomescape/member/dto/MemberRequest.java index cafb79f1..7f7b0fb6 100644 --- a/src/main/java/roomescape/member/MemberRequest.java +++ b/src/main/java/roomescape/member/dto/MemberRequest.java @@ -1,4 +1,4 @@ -package roomescape.member; +package roomescape.member.dto; public class MemberRequest { private String name; diff --git a/src/main/java/roomescape/member/MemberResponse.java b/src/main/java/roomescape/member/dto/MemberResponse.java similarity index 93% rename from src/main/java/roomescape/member/MemberResponse.java rename to src/main/java/roomescape/member/dto/MemberResponse.java index b9fa3b97..b3235d2c 100644 --- a/src/main/java/roomescape/member/MemberResponse.java +++ b/src/main/java/roomescape/member/dto/MemberResponse.java @@ -1,4 +1,4 @@ -package roomescape.member; +package roomescape.member.dto; public class MemberResponse { private Long id; diff --git a/src/main/java/roomescape/reservation/Reservation.java b/src/main/java/roomescape/reservation/Reservation.java index 83a7edf1..2d1efaba 100644 --- a/src/main/java/roomescape/reservation/Reservation.java +++ b/src/main/java/roomescape/reservation/Reservation.java @@ -1,32 +1,53 @@ package roomescape.reservation; +import jakarta.persistence.*; +import roomescape.member.Member; import roomescape.theme.Theme; import roomescape.time.Time; +import java.time.LocalDate; + + +@Entity +@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"date", "theme_id", "time_id"})}) public class Reservation { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String name; - private String date; + private LocalDate date; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "time_id") private Time time; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "theme_id") private Theme theme; - public Reservation(Long id, String name, String date, Time time, Theme theme) { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + public Reservation(final Long id, final String name, final String date, final Time time, final Theme theme, final Member member) { this.id = id; this.name = name; - this.date = date; + this.date = LocalDate.parse(date); this.time = time; this.theme = theme; + this.member = member; } - public Reservation(String name, String date, Time time, Theme theme) { + public Reservation(final String name, final String date, final Time time, final Theme theme, final Member member) { this.name = name; - this.date = date; + this.date = LocalDate.parse(date); this.time = time; this.theme = theme; + this.member = member; } public Reservation() { - } public Long getId() { @@ -37,8 +58,8 @@ public String getName() { return name; } - public String getDate() { - return date; + public String getDateByString() { + return date.toString(); } public Time getTime() { diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index b3bef399..1ec73980 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -1,19 +1,18 @@ 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 org.springframework.web.bind.annotation.*; +import roomescape.auth.Authentication; +import roomescape.member.Member; +import roomescape.reservation.dto.MyReservationResponse; +import roomescape.reservation.dto.ReservationRequest; +import roomescape.reservation.dto.ReservationResponse; import java.net.URI; import java.util.List; @RestController public class ReservationController { - private final ReservationService reservationService; public ReservationController(ReservationService reservationService) { @@ -25,16 +24,24 @@ public List list() { return reservationService.findAll(); } + @GetMapping("/reservations-mine") + public List myList( + @Authentication Member member) { + return reservationService.findMyReservationsByName(member.getName()); + } + @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { - if (reservationRequest.getName() == null - || reservationRequest.getDate() == null - || reservationRequest.getTheme() == null - || reservationRequest.getTime() == null) { + public ResponseEntity create(@Authentication Member member, + @RequestBody ReservationRequest reservationRequest) { + if (reservationRequest.getDate() == null || reservationRequest.getTheme() == null || reservationRequest.getTime() == null) { return ResponseEntity.badRequest().build(); } - ReservationResponse reservation = reservationService.save(reservationRequest); + if (reservationRequest.getName() == null) { + reservationRequest = new ReservationRequest(member.getName(), reservationRequest.getDate(), reservationRequest.getTheme(), reservationRequest.getTime() + ); + } + ReservationResponse reservation = reservationService.save(reservationRequest); return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); } diff --git a/src/main/java/roomescape/reservation/ReservationDao.java b/src/main/java/roomescape/reservation/ReservationDao.java deleted file mode 100644 index a4972430..00000000 --- a/src/main/java/roomescape/reservation/ReservationDao.java +++ /dev/null @@ -1,127 +0,0 @@ -package roomescape.reservation; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; -import roomescape.theme.Theme; -import roomescape.time.Time; - -import java.sql.PreparedStatement; -import java.util.List; - -@Repository -public class ReservationDao { - - private final JdbcTemplate jdbcTemplate; - - public ReservationDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List findAll() { - return jdbcTemplate.query( - "SELECT r.id AS reservation_id, r.name as reservation_name, r.date as reservation_date, " + - "t.id AS theme_id, t.name AS theme_name, t.description AS theme_description, " + - "ti.id AS time_id, ti.time_value AS time_value " + - "FROM reservation r " + - "JOIN theme t ON r.theme_id = t.id " + - "JOIN time ti ON r.time_id = ti.id", - - (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getString("reservation_date"), - new Time( - rs.getLong("time_id"), - rs.getString("time_value") - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("theme_description") - ))); - } - - public Reservation save(ReservationRequest reservationRequest) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(connection -> { - PreparedStatement ps = connection.prepareStatement("INSERT INTO reservation(date, name, theme_id, time_id) VALUES (?, ?, ?, ?)", new String[]{"id"}); - ps.setString(1, reservationRequest.getDate()); - ps.setString(2, reservationRequest.getName()); - ps.setLong(3, reservationRequest.getTheme()); - ps.setLong(4, reservationRequest.getTime()); - return ps; - }, keyHolder); - - Time time = jdbcTemplate.queryForObject("SELECT * FROM time WHERE id = ?", - (rs, rowNum) -> new Time(rs.getLong("id"), rs.getString("time_value")), - reservationRequest.getTime()); - - Theme theme = jdbcTemplate.queryForObject("SELECT * FROM theme WHERE id = ?", - (rs, rowNum) -> new Theme(rs.getLong("id"), rs.getString("name"), rs.getString("description")), - reservationRequest.getTheme()); - - return new Reservation( - keyHolder.getKey().longValue(), - reservationRequest.getName(), - reservationRequest.getDate(), - time, - theme - ); - } - - public void deleteById(Long id) { - jdbcTemplate.update("DELETE FROM reservation WHERE id = ?", id); - } - - public List findReservationsByDateAndTheme(String date, Long themeId) { - return jdbcTemplate.query( - "SELECT r.id AS reservation_id, r.name as reservation_name, r.date as reservation_date, " + - "t.id AS theme_id, t.name AS theme_name, t.description AS theme_description, " + - "ti.id AS time_id, ti.time_value AS time_value " + - "FROM reservation r " + - "JOIN theme t ON r.theme_id = t.id " + - "JOIN time ti ON r.time_id = ti.id" + - "WHERE r.date = ? AND r.theme_id = ?", - new Object[]{date, themeId}, - (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getString("reservation_date"), - new Time( - rs.getLong("time_id"), - rs.getString("time_value") - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("theme_description") - ))); - } - - public List findByDateAndThemeId(String date, Long themeId) { - return jdbcTemplate.query( - "SELECT r.id AS reservation_id, r.name as reservation_name, r.date as reservation_date, " + - "t.id AS theme_id, t.name AS theme_name, t.description AS theme_description, " + - "ti.id AS time_id, ti.time_value AS time_value " + - "FROM reservation r " + - "JOIN theme t ON r.theme_id = t.id " + - "JOIN time ti ON r.time_id = ti.id " + - "WHERE r.date = ? AND r.theme_id = ?", - new Object[]{date, themeId}, - (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getString("reservation_date"), - new Time( - rs.getLong("time_id"), - rs.getString("time_value") - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("theme_description") - ))); - } -} diff --git a/src/main/java/roomescape/reservation/ReservationRepository.java b/src/main/java/roomescape/reservation/ReservationRepository.java new file mode 100644 index 00000000..5332a6d7 --- /dev/null +++ b/src/main/java/roomescape/reservation/ReservationRepository.java @@ -0,0 +1,19 @@ +package roomescape.reservation; + +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +@Repository +public interface ReservationRepository extends JpaRepository { + Optional findByDateAndThemeIdAndTimeId(LocalDate date, Long themeId, Long timeId); + + List findByDateAndThemeId(LocalDate date, Long themeId); + + @EntityGraph(attributePaths = {"theme", "time", "member"}) + List findByMemberId(Long memberId); +} diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index bd331332..1992903d 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -1,30 +1,89 @@ package roomescape.reservation; import org.springframework.stereotype.Service; +import roomescape.member.Member; +import roomescape.member.MemberRepository; +import roomescape.reservation.dto.MyReservationResponse; +import roomescape.reservation.dto.ReservationRequest; +import roomescape.reservation.dto.ReservationResponse; +import roomescape.theme.Theme; +import roomescape.theme.ThemeRepository; +import roomescape.time.Time; +import roomescape.time.TimeRepository; +import roomescape.waiting.WaitingRepository; +import roomescape.waiting.WaitingWithRank; +import java.time.LocalDate; import java.util.List; @Service public class ReservationService { - private ReservationDao reservationDao; + private final ReservationRepository reservationRepository; + private final ThemeRepository themeRepository; + private final TimeRepository timeRepository; + private final MemberRepository memberRepository; + private final WaitingRepository waitingRepository; - public ReservationService(ReservationDao reservationDao) { - this.reservationDao = reservationDao; + public ReservationService(ReservationRepository reservationRepository, + ThemeRepository themeRepository, + TimeRepository timeRepository, + MemberRepository memberRepository, + WaitingRepository waitingRepository) { + this.reservationRepository = reservationRepository; + this.themeRepository = themeRepository; + this.timeRepository = timeRepository; + this.memberRepository = memberRepository; + this.waitingRepository = waitingRepository; } public ReservationResponse save(ReservationRequest reservationRequest) { - Reservation reservation = reservationDao.save(reservationRequest); + Theme theme = themeRepository.getById(reservationRequest.getTheme()); + Time time = timeRepository.getById(reservationRequest.getTime()); + Member member = memberRepository.findByName(reservationRequest.getName()) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다.")); - return new ReservationResponse(reservation.getId(), reservationRequest.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); + validateExistence(reservationRequest, theme, time); + + Reservation reservation = new Reservation( + reservationRequest.getName(), + reservationRequest.getDate(), + time, + theme, + member); + + reservationRepository.save(reservation); + + return new ReservationResponse(reservation.getId(), + reservationRequest.getName(), + reservation.getTheme().getName(), + reservation.getDateByString(), + reservation.getTime().getValueByString()); + } + + private void validateExistence(final ReservationRequest reservationRequest, final Theme theme, final Time time) { + LocalDate date = LocalDate.parse(reservationRequest.getDate()); + reservationRepository.findByDateAndThemeIdAndTimeId(date, theme.getId(), time.getId()) + .ifPresent(it -> { + throw new IllegalArgumentException("이미 예약된 시간입니다."); + }); } public void deleteById(Long id) { - reservationDao.deleteById(id); + reservationRepository.deleteById(id); } public List findAll() { - return reservationDao.findAll().stream() - .map(it -> new ReservationResponse(it.getId(), it.getName(), it.getTheme().getName(), it.getDate(), it.getTime().getValue())) + return reservationRepository.findAll().stream() + .map(it -> new ReservationResponse(it.getId(), it.getName(), it.getTheme().getName(), it.getDateByString(), it.getTime().getValueByString())) .toList(); } + + public List findMyReservationsByName(String name) { + Member member = memberRepository.findByName(name) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다.")); + + final List reservations = reservationRepository.findByMemberId(member.getId()); + final List waitingWithRanks = waitingRepository.findWaitingsWithRankByMemberId(member.getId()); + return MyReservationResponse.of(reservations, waitingWithRanks); + } } diff --git a/src/main/java/roomescape/reservation/dto/MyReservationResponse.java b/src/main/java/roomescape/reservation/dto/MyReservationResponse.java new file mode 100644 index 00000000..2162c783 --- /dev/null +++ b/src/main/java/roomescape/reservation/dto/MyReservationResponse.java @@ -0,0 +1,65 @@ +package roomescape.reservation.dto; + +import roomescape.reservation.Reservation; +import roomescape.waiting.WaitingWithRank; + +import java.util.List; + +import static java.util.stream.Collectors.toList; + +public class MyReservationResponse { + private Long id; + private String theme; + private String date; + private String time; + private String status; + + public MyReservationResponse(Long id, String theme, String date, String time, String status) { + this.id = id; + this.theme = theme; + this.date = date; + this.time = time; + this.status = status; + } + + public static List of(List reservations, + List waitingWithRanks) { + List myReservationResponses = reservations.stream() + .map(it -> new MyReservationResponse(it.getId(), + it.getTheme().getName(), + it.getDateByString(), + it.getTime().getValueByString(), + "예약")) + .collect(toList()); + + waitingWithRanks.stream() + .map(it -> new MyReservationResponse(it.getWaiting().getId(), + it.getWaiting().getTheme().getName(), + it.getWaiting().getDateByString(), + it.getWaiting().getTime().getValueByString(), + String.format("%d번째 예약대기", it.getRank() + 1))) + .forEach(myReservationResponses::add); + + return myReservationResponses; + } + + public String getTheme() { + return theme; + } + + public String getDate() { + return date; + } + + public String getTime() { + return time; + } + + public Long getId() { + return id; + } + + public String getStatus() { + return status; + } +} diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/dto/ReservationRequest.java similarity index 54% rename from src/main/java/roomescape/reservation/ReservationRequest.java rename to src/main/java/roomescape/reservation/dto/ReservationRequest.java index 19f44124..c43782b2 100644 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/roomescape/reservation/dto/ReservationRequest.java @@ -1,4 +1,4 @@ -package roomescape.reservation; +package roomescape.reservation.dto; public class ReservationRequest { private String name; @@ -6,10 +6,21 @@ public class ReservationRequest { private Long theme; private Long time; + public ReservationRequest(String name, String date, Long theme, Long time) { + this.name = name; + this.date = date; + this.theme = theme; + this.time = time; + } + public String getName() { return name; } + public void setName(String name) { + this.name = name; + } + public String getDate() { return date; } diff --git a/src/main/java/roomescape/reservation/ReservationResponse.java b/src/main/java/roomescape/reservation/dto/ReservationResponse.java similarity index 94% rename from src/main/java/roomescape/reservation/ReservationResponse.java rename to src/main/java/roomescape/reservation/dto/ReservationResponse.java index 41360a36..2c219d01 100644 --- a/src/main/java/roomescape/reservation/ReservationResponse.java +++ b/src/main/java/roomescape/reservation/dto/ReservationResponse.java @@ -1,4 +1,4 @@ -package roomescape.reservation; +package roomescape.reservation.dto; public class ReservationResponse { private Long id; diff --git a/src/main/java/roomescape/theme/Theme.java b/src/main/java/roomescape/theme/Theme.java index 430a6239..6ec29d39 100644 --- a/src/main/java/roomescape/theme/Theme.java +++ b/src/main/java/roomescape/theme/Theme.java @@ -1,7 +1,16 @@ 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; @@ -26,8 +35,4 @@ public Long getId() { public String getName() { return name; } - - public String getDescription() { - return description; - } } diff --git a/src/main/java/roomescape/theme/ThemeController.java b/src/main/java/roomescape/theme/ThemeController.java index 03bca41a..0bb0cbaf 100644 --- a/src/main/java/roomescape/theme/ThemeController.java +++ b/src/main/java/roomescape/theme/ThemeController.java @@ -1,38 +1,34 @@ package roomescape.theme; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.net.URI; import java.util.List; @RestController +@RequestMapping("/themes") public class ThemeController { - private ThemeDao themeDao; + private final ThemeRepository themeRepository; - public ThemeController(ThemeDao themeDao) { - this.themeDao = themeDao; + public ThemeController(ThemeRepository themeRepository) { + this.themeRepository = themeRepository; } - @PostMapping("/themes") + @PostMapping 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") + @GetMapping public ResponseEntity> list() { - return ResponseEntity.ok(themeDao.findAll()); + return ResponseEntity.ok(themeRepository.findAll()); } - @DeleteMapping("/themes/{id}") + @DeleteMapping("/{id}") public ResponseEntity deleteTheme(@PathVariable Long id) { - themeDao.deleteById(id); + themeRepository.deleteById(id); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/roomescape/theme/ThemeDao.java b/src/main/java/roomescape/theme/ThemeDao.java deleted file mode 100644 index 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/ThemeRepository.java b/src/main/java/roomescape/theme/ThemeRepository.java new file mode 100644 index 00000000..304cb0ef --- /dev/null +++ b/src/main/java/roomescape/theme/ThemeRepository.java @@ -0,0 +1,8 @@ +package roomescape.theme; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ThemeRepository extends JpaRepository { +} diff --git a/src/main/java/roomescape/time/AvailableTime.java b/src/main/java/roomescape/time/AvailableTime.java index 33acef7e..83b0fa6e 100644 --- a/src/main/java/roomescape/time/AvailableTime.java +++ b/src/main/java/roomescape/time/AvailableTime.java @@ -11,15 +11,7 @@ public AvailableTime(Long timeId, String time, boolean booked) { this.booked = booked; } - public Long getTimeId() { - return timeId; - } - public String getTime() { return time; } - - public boolean isBooked() { - return booked; - } } diff --git a/src/main/java/roomescape/time/Time.java b/src/main/java/roomescape/time/Time.java index 008ed93c..c75e623b 100644 --- a/src/main/java/roomescape/time/Time.java +++ b/src/main/java/roomescape/time/Time.java @@ -1,27 +1,39 @@ package roomescape.time; +import jakarta.persistence.*; + +import java.time.LocalTime; + +@Entity public class Time { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String value; + + @Column(name = "time_value") + private LocalTime value; + + public Time() { + } public Time(Long id, String value) { this.id = id; - this.value = value; + this.value = LocalTime.parse(value); } public Time(String value) { - this.value = value; - } - - public Time() { - + this.value = LocalTime.parse(value); } public Long getId() { return id; } - public String getValue() { + public LocalTime getValue() { return value; } + + public String getValueByString() { + return value.toString(); + } } diff --git a/src/main/java/roomescape/time/TimeController.java b/src/main/java/roomescape/time/TimeController.java index 2343114d..0247cdb9 100644 --- a/src/main/java/roomescape/time/TimeController.java +++ b/src/main/java/roomescape/time/TimeController.java @@ -1,13 +1,7 @@ package roomescape.time; 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.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.net.URI; import java.util.List; @@ -27,7 +21,7 @@ public List