diff --git a/build.gradle b/build.gradle index a0aabaf..9688c8d 100644 --- a/build.gradle +++ b/build.gradle @@ -20,11 +20,11 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter:3.4.4' implementation 'org.springframework.boot:spring-boot-starter-web:3.4.4' - implementation 'org.springframework.boot:spring-boot-starter-jdbc:3.4.4' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.4.4' - - implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.5.1' - + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + implementation 'ch.qos.logback:logback-access:1.4.14' implementation 'io.jsonwebtoken:jjwt-api:0.12.6' implementation 'io.jsonwebtoken:jjwt-impl:0.12.6' implementation 'io.jsonwebtoken:jjwt-gson:0.12.6' @@ -33,7 +33,6 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test:3.4.4' testImplementation 'io.rest-assured:rest-assured:5.5.1' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.1' } tasks.named('test') { diff --git a/src/main/java/com/yourssu/roomescape/ExceptionController.java b/src/main/java/com/yourssu/roomescape/ExceptionController.java deleted file mode 100644 index 4bc38a3..0000000 --- a/src/main/java/com/yourssu/roomescape/ExceptionController.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.yourssu.roomescape; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; - - -@ControllerAdvice -public class ExceptionController { - @ExceptionHandler(Exception.class) - public ResponseEntity handleRuntimeException(Exception e) { - e.printStackTrace(); - return ResponseEntity.badRequest().build(); - } -} diff --git a/src/main/java/com/yourssu/roomescape/auth/AdminAuthInterceptor.java b/src/main/java/com/yourssu/roomescape/auth/AdminAuthInterceptor.java new file mode 100644 index 0000000..83c92a5 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/auth/AdminAuthInterceptor.java @@ -0,0 +1,68 @@ +package com.yourssu.roomescape.auth; + +import com.yourssu.roomescape.common.AppConstants; +import com.yourssu.roomescape.common.security.JwtTokenProvider; +import com.yourssu.roomescape.member.Member; +import com.yourssu.roomescape.member.MemberService; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class AdminAuthInterceptor implements HandlerInterceptor { + + private final JwtTokenProvider jwtTokenProvider; + private final MemberService memberService; + + public AdminAuthInterceptor(JwtTokenProvider jwtTokenProvider, MemberService memberService) { + this.jwtTokenProvider = jwtTokenProvider; + this.memberService = memberService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + Cookie[] cookies = request.getCookies(); + if (cookies == null) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Authentication required"); + return false; + } + + String token = ""; + for (Cookie cookie : cookies) { + if (cookie.getName() != null && AppConstants.TOKEN_COOKIE_NAME.equals(cookie.getName())) { + token = cookie.getValue(); + break; + } + } + + if (token.isEmpty()) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Token is missing"); + return false; + } + + try { + String email = jwtTokenProvider.getPayload(token); + Member member = memberService.findByEmail(email); + + if (member == null) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("User not found"); + return false; + } + + if (member.getRole() == null || !AppConstants.ROLE_ADMIN.equals(member.getRole())) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 상태 코드 사용 + response.getWriter().write("Admin privileges required"); + return false; + } + return true; + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Invalid token: " + e.getMessage()); + return false; + } + }} diff --git a/src/main/java/com/yourssu/roomescape/auth/LoginCheckResponse.java b/src/main/java/com/yourssu/roomescape/auth/LoginCheckResponse.java new file mode 100644 index 0000000..a296e51 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/auth/LoginCheckResponse.java @@ -0,0 +1,3 @@ +package com.yourssu.roomescape.auth; + +public record LoginCheckResponse(String name) {} diff --git a/src/main/java/com/yourssu/roomescape/auth/LoginMember.java b/src/main/java/com/yourssu/roomescape/auth/LoginMember.java new file mode 100644 index 0000000..bfc4ef6 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/auth/LoginMember.java @@ -0,0 +1,10 @@ +package com.yourssu.roomescape.auth; +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 LoginMember { +} diff --git a/src/main/java/com/yourssu/roomescape/auth/LoginMemberArgumentResolver.java b/src/main/java/com/yourssu/roomescape/auth/LoginMemberArgumentResolver.java new file mode 100644 index 0000000..cc9cecb --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/auth/LoginMemberArgumentResolver.java @@ -0,0 +1,62 @@ +package com.yourssu.roomescape.auth; + +import com.yourssu.roomescape.common.AppConstants; +import com.yourssu.roomescape.common.exception.UnauthorizedException; +import com.yourssu.roomescape.common.security.JwtTokenProvider; +import com.yourssu.roomescape.member.Member; +import com.yourssu.roomescape.member.MemberService; +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; + +@Component +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + private final MemberService memberService; + private final JwtTokenProvider jwtTokenProvider; + + public LoginMemberArgumentResolver(MemberService memberService, JwtTokenProvider jwtTokenProvider) { + this.memberService = memberService; + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(LoginMember.class) && UserInfo.class.isAssignableFrom(parameter.getParameterType()); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + Cookie[] cookies = request.getCookies(); + + if (cookies == null) { + return null; + } + + String token = ""; + + for (Cookie cookie : cookies) { + if (cookie.getName() != null && AppConstants.TOKEN_COOKIE_NAME.equals(cookie.getName())) { + token = cookie.getValue(); + break; + } + } + + if (token.isEmpty()) { + return null; + } + + try { + String email = jwtTokenProvider.getPayload(token); + return memberService.findByEmail(email); + } catch (Exception e) { + throw new UnauthorizedException("Invalid credentials"); + } + } +} diff --git a/src/main/java/com/yourssu/roomescape/auth/LoginRequest.java b/src/main/java/com/yourssu/roomescape/auth/LoginRequest.java new file mode 100644 index 0000000..0bf31a3 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/auth/LoginRequest.java @@ -0,0 +1,16 @@ +package com.yourssu.roomescape.auth; + +public class LoginRequest { + + private String email; + private String password; + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + +} diff --git a/src/main/java/com/yourssu/roomescape/auth/TokenDto.java b/src/main/java/com/yourssu/roomescape/auth/TokenDto.java new file mode 100644 index 0000000..7639974 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/auth/TokenDto.java @@ -0,0 +1,14 @@ +package com.yourssu.roomescape.auth; + +public class TokenDto { + + private String token; + + public TokenDto(final String token) { + this.token = token; + } + + public String getToken() { + return token; + } +} diff --git a/src/main/java/com/yourssu/roomescape/auth/UserInfo.java b/src/main/java/com/yourssu/roomescape/auth/UserInfo.java new file mode 100644 index 0000000..e7083f3 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/auth/UserInfo.java @@ -0,0 +1,8 @@ +package com.yourssu.roomescape.auth; + +public interface UserInfo { + Long getId(); + String getName(); + String getEmail(); + String getRole(); +} diff --git a/src/main/java/com/yourssu/roomescape/common/AppConstants.java b/src/main/java/com/yourssu/roomescape/common/AppConstants.java new file mode 100644 index 0000000..2a2cf27 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/AppConstants.java @@ -0,0 +1,11 @@ +package com.yourssu.roomescape.common; + +public class AppConstants { + public static final String TOKEN_COOKIE_NAME = "token"; + + public static final String ROLE_ADMIN = "ADMIN"; + + private AppConstants() { + throw new AssertionError("Constants class should not be instantiated"); + } +} diff --git a/src/main/java/com/yourssu/roomescape/common/config/SwaggerConfig.java b/src/main/java/com/yourssu/roomescape/common/config/SwaggerConfig.java new file mode 100644 index 0000000..3d61fd4 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/config/SwaggerConfig.java @@ -0,0 +1,19 @@ +package com.yourssu.roomescape.common.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info() + .title("API 문서") + .description("API 명세서입니다.") + .version("v1.0.0")); + } +} diff --git a/src/main/java/com/yourssu/roomescape/common/config/WebConfig.java b/src/main/java/com/yourssu/roomescape/common/config/WebConfig.java new file mode 100644 index 0000000..e9cd376 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/config/WebConfig.java @@ -0,0 +1,32 @@ +package com.yourssu.roomescape.common.config; + +import com.yourssu.roomescape.auth.AdminAuthInterceptor; +import com.yourssu.roomescape.auth.LoginMemberArgumentResolver; +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 LoginMemberArgumentResolver loginMemberArgumentResolver; + private final AdminAuthInterceptor adminAuthInterceptor; + + public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver, AdminAuthInterceptor adminAuthInterceptor) { + this.loginMemberArgumentResolver = loginMemberArgumentResolver; + this.adminAuthInterceptor = adminAuthInterceptor; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginMemberArgumentResolver); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(adminAuthInterceptor).addPathPatterns("/admin/**"); + } +} diff --git a/src/main/java/com/yourssu/roomescape/PageController.java b/src/main/java/com/yourssu/roomescape/common/controller/PageController.java similarity index 94% rename from src/main/java/com/yourssu/roomescape/PageController.java rename to src/main/java/com/yourssu/roomescape/common/controller/PageController.java index a31f368..214f467 100644 --- a/src/main/java/com/yourssu/roomescape/PageController.java +++ b/src/main/java/com/yourssu/roomescape/common/controller/PageController.java @@ -1,4 +1,4 @@ -package com.yourssu.roomescape; +package com.yourssu.roomescape.common.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/yourssu/roomescape/common/exception/BadRequestException.java b/src/main/java/com/yourssu/roomescape/common/exception/BadRequestException.java new file mode 100644 index 0000000..f1e0370 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/exception/BadRequestException.java @@ -0,0 +1,7 @@ +package com.yourssu.roomescape.common.exception; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/com/yourssu/roomescape/common/exception/ErrorResponse.java b/src/main/java/com/yourssu/roomescape/common/exception/ErrorResponse.java new file mode 100644 index 0000000..c3917ed --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/exception/ErrorResponse.java @@ -0,0 +1,20 @@ +package com.yourssu.roomescape.common.exception; + +public class ErrorResponse { + private String message; + private String details; + + public ErrorResponse(String message, String details) { + this.message = message; + this.details = details; + } + + public String getDetails() { + return details; + } + + public String getMessage() { + return message; + } +} + diff --git a/src/main/java/com/yourssu/roomescape/common/exception/ExceptionController.java b/src/main/java/com/yourssu/roomescape/common/exception/ExceptionController.java new file mode 100644 index 0000000..bbbc4f5 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/exception/ExceptionController.java @@ -0,0 +1,55 @@ +package com.yourssu.roomescape.common.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class ExceptionController { + + @ExceptionHandler(BadRequestException.class) + public ResponseEntity handleBadRequest(BadRequestException e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse("잘못된 요청입니다", e.getMessage())); + } + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleNotFound(ResourceNotFoundException e) { + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(new ErrorResponse("리소스를 찾을 수 없습니다", e.getMessage())); + } + + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity handleUnauthorized(UnauthorizedException e) { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .body(new ErrorResponse("인증이 필요합니다", e.getMessage())); + } + + @ExceptionHandler(ForbiddenException.class) + public ResponseEntity handleForbidden(ForbiddenException e) { + return ResponseEntity + .status(HttpStatus.FORBIDDEN) + .body(new ErrorResponse("접근 권한이 없습니다", e.getMessage())); + } + + @ExceptionHandler(IllegalStateException.class) + public ResponseEntity handleIllegalState(IllegalStateException e) { + return ResponseEntity + .status(HttpStatus.CONFLICT) + .body(new ErrorResponse("요청 처리 불가", e.getMessage())); + } + + // 기타 예외에 대한 폴백 핸들러 + @ExceptionHandler(Exception.class) + public ResponseEntity handleServerError(Exception e) { + // 예기치 않은 서버 오류는 로깅 필요 + e.printStackTrace(); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("서버 오류가 발생했습니다", "시스템 관리자에게 문의하세요")); + } +} diff --git a/src/main/java/com/yourssu/roomescape/common/exception/ForbiddenException.java b/src/main/java/com/yourssu/roomescape/common/exception/ForbiddenException.java new file mode 100644 index 0000000..b4dd3d1 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package com.yourssu.roomescape.common.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/src/main/java/com/yourssu/roomescape/common/exception/ResourceNotFoundException.java b/src/main/java/com/yourssu/roomescape/common/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..4c17a79 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/exception/ResourceNotFoundException.java @@ -0,0 +1,7 @@ +package com.yourssu.roomescape.common.exception; + +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/yourssu/roomescape/common/exception/UnauthorizedException.java b/src/main/java/com/yourssu/roomescape/common/exception/UnauthorizedException.java new file mode 100644 index 0000000..db21bad --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/exception/UnauthorizedException.java @@ -0,0 +1,7 @@ +package com.yourssu.roomescape.common.exception; + +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/yourssu/roomescape/common/security/JwtTokenProvider.java b/src/main/java/com/yourssu/roomescape/common/security/JwtTokenProvider.java new file mode 100644 index 0000000..0da0650 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/common/security/JwtTokenProvider.java @@ -0,0 +1,57 @@ +package com.yourssu.roomescape.common.security; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + + +import java.util.Date; + +@Component +public class JwtTokenProvider { + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + @Value("${security.jwt.token.expire-length}") + private long validityInMilliseconds; + + + public String createToken(String payload) { + Claims claims = Jwts.claims().setSubject(payload).build(); + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(Keys.hmacShaKeyFor(secretKey.getBytes())) + .compact(); + } + + public String getPayload(String token) { + try { + return Jwts.parser() + .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) + .build() + .parseClaimsJws(token) + .getBody().getSubject(); + } catch (ExpiredJwtException e) { + throw new RuntimeException("Token has expired", e); + } catch (UnsupportedJwtException e) { + throw new RuntimeException("Unsupported JWT token", e); + } catch (MalformedJwtException e) { + throw new RuntimeException("Invalid JWT token", e); + } catch (SignatureException e) { + throw new RuntimeException("Invalid JWT signature", e); + } catch (IllegalArgumentException e) { + throw new RuntimeException("JWT claims string is empty", e); + } + } +} diff --git a/src/main/java/com/yourssu/roomescape/member/Member.java b/src/main/java/com/yourssu/roomescape/member/Member.java index 6d655ef..bf957cd 100644 --- a/src/main/java/com/yourssu/roomescape/member/Member.java +++ b/src/main/java/com/yourssu/roomescape/member/Member.java @@ -1,12 +1,35 @@ package com.yourssu.roomescape.member; -public class Member { +import com.yourssu.roomescape.auth.UserInfo; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Column; +import jakarta.persistence.Table; + +@Entity +@Table(name = "member") // 테이블 이름 명시적 지정 +public class Member implements UserInfo { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(nullable = false) private String name; + + @Column(nullable = false, unique = true) private String email; + + @Column(nullable = false) private String password; + + @Column(nullable = false) private String role; + public Member() { + } + public Member(Long id, String name, String email, String role) { this.id = id; this.name = name; @@ -21,23 +44,27 @@ public Member(String name, String email, String password, String role) { this.role = role; } + @Override public Long getId() { return id; } + @Override public String getName() { return name; } + @Override public String getEmail() { return email; } - public String getPassword() { - return password; - } - + @Override public String getRole() { return role; } + + public String getPassword() { + return password; + } } diff --git a/src/main/java/com/yourssu/roomescape/member/MemberController.java b/src/main/java/com/yourssu/roomescape/member/MemberController.java index f90da55..c485aa6 100644 --- a/src/main/java/com/yourssu/roomescape/member/MemberController.java +++ b/src/main/java/com/yourssu/roomescape/member/MemberController.java @@ -1,8 +1,14 @@ package com.yourssu.roomescape.member; +import com.yourssu.roomescape.auth.LoginCheckResponse; +import com.yourssu.roomescape.auth.LoginRequest; +import com.yourssu.roomescape.auth.TokenDto; +import com.yourssu.roomescape.common.AppConstants; 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; @@ -16,16 +22,41 @@ public class MemberController { public MemberController(MemberService memberService) { this.memberService = memberService; } + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpServletResponse httpServletResponse){ + TokenDto tokenDTO = memberService.login(loginRequest); // TODO: 여기 변수명과 반환값 이름에 대해서 생각해보기 - @PostMapping("/members") + Cookie cookie = new Cookie(AppConstants.TOKEN_COOKIE_NAME, tokenDTO.getToken()); // TODO: constant 프로퍼티로 관리 + httpServletResponse.addCookie(cookie); + + return ResponseEntity.ok().build(); + } + + @GetMapping("/login/check") + public ResponseEntity loginCheck(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + + // TODO: 빈문자열 관련 예외처리 + String token = ""; + + // TODO: stream 사용해보기 + for (Cookie cookie : cookies) { + if(cookie.getName().equals(AppConstants.TOKEN_COOKIE_NAME)) { + token = cookie.getValue(); + } + } + String name = memberService.loginCheck(token); + return ResponseEntity.ok().body(new LoginCheckResponse(name)); + } + @PostMapping("/members") public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { MemberResponse member = memberService.createMember(memberRequest); - return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member); + return ResponseEntity.created(URI.create("/members/" + member.id())).body(member); } @PostMapping("/logout") public ResponseEntity logout(HttpServletResponse response) { - Cookie cookie = new Cookie("token", ""); + Cookie cookie = new Cookie(AppConstants.TOKEN_COOKIE_NAME, ""); cookie.setHttpOnly(true); cookie.setPath("/"); cookie.setMaxAge(0); diff --git a/src/main/java/com/yourssu/roomescape/member/MemberDao.java b/src/main/java/com/yourssu/roomescape/member/MemberDao.java deleted file mode 100644 index 7923c15..0000000 --- a/src/main/java/com/yourssu/roomescape/member/MemberDao.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.yourssu.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/com/yourssu/roomescape/member/MemberRepository.java b/src/main/java/com/yourssu/roomescape/member/MemberRepository.java new file mode 100644 index 0000000..5bd2dd5 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/member/MemberRepository.java @@ -0,0 +1,10 @@ +package com.yourssu.roomescape.member; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + Optional findByEmail(String email); + Optional findByName(String name); +} diff --git a/src/main/java/com/yourssu/roomescape/member/MemberRequest.java b/src/main/java/com/yourssu/roomescape/member/MemberRequest.java index 7ee60b6..c130c2c 100644 --- a/src/main/java/com/yourssu/roomescape/member/MemberRequest.java +++ b/src/main/java/com/yourssu/roomescape/member/MemberRequest.java @@ -1,19 +1,4 @@ package com.yourssu.roomescape.member; -public class MemberRequest { - private String name; - private String email; - private String password; - - public String getName() { - return name; - } - - public String getEmail() { - return email; - } - - public String getPassword() { - return password; - } +public record MemberRequest(String name, String email, String password) { } diff --git a/src/main/java/com/yourssu/roomescape/member/MemberResponse.java b/src/main/java/com/yourssu/roomescape/member/MemberResponse.java index fe37b56..b19fae1 100644 --- a/src/main/java/com/yourssu/roomescape/member/MemberResponse.java +++ b/src/main/java/com/yourssu/roomescape/member/MemberResponse.java @@ -1,25 +1,4 @@ package com.yourssu.roomescape.member; -public class MemberResponse { - private Long id; - private String name; - private String email; - - public MemberResponse(Long id, String name, String email) { - this.id = id; - this.name = name; - this.email = email; - } - - public Long getId() { - return id; - } - - public String getName() { - return name; - } - - public String getEmail() { - return email; - } +public record MemberResponse(Long id, String name, String email) { } diff --git a/src/main/java/com/yourssu/roomescape/member/MemberService.java b/src/main/java/com/yourssu/roomescape/member/MemberService.java index ff138c4..bae0fac 100644 --- a/src/main/java/com/yourssu/roomescape/member/MemberService.java +++ b/src/main/java/com/yourssu/roomescape/member/MemberService.java @@ -1,17 +1,67 @@ package com.yourssu.roomescape.member; +import com.yourssu.roomescape.auth.LoginRequest; +import com.yourssu.roomescape.auth.TokenDto; +import com.yourssu.roomescape.common.exception.BadRequestException; +import com.yourssu.roomescape.common.exception.ResourceNotFoundException; +import com.yourssu.roomescape.common.exception.UnauthorizedException; +import com.yourssu.roomescape.common.security.JwtTokenProvider; +import jakarta.transaction.Transactional; import org.springframework.stereotype.Service; @Service public class MemberService { - private MemberDao memberDao; + private MemberRepository memberRepository; + private JwtTokenProvider jwtTokenProvider; - public MemberService(MemberDao memberDao) { - this.memberDao = memberDao; + public MemberService(JwtTokenProvider jwtTokenProvider, MemberRepository memberRepository) { + this.jwtTokenProvider = jwtTokenProvider; + this.memberRepository = memberRepository; } + @Transactional public MemberResponse createMember(MemberRequest memberRequest) { - Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER")); + memberRepository.findByEmail(memberRequest.email()) + .ifPresent(member -> { + throw new BadRequestException("이미 사용 중인 이메일입니다"); + }); + + Member member = memberRepository.save(new Member( + memberRequest.name(), + memberRequest.email(), + memberRequest.password(), + "USER" + )); return new MemberResponse(member.getId(), member.getName(), member.getEmail()); } + + public TokenDto login(LoginRequest loginRequest) { + Member member = memberRepository.findByEmail(loginRequest.getEmail()) + .orElseThrow(() -> new ResourceNotFoundException("User not found")); + + if (!member.getPassword().equals(loginRequest.getPassword())) { + throw new UnauthorizedException("Invalid credentials"); + } + + try { + String token = jwtTokenProvider.createToken(member.getEmail()); + return new TokenDto(token); + } catch (Exception e) { + throw new RuntimeException("인증 토큰 생성에 실패했습니다"); + } + } + public String loginCheck(String token) { + + String email = jwtTokenProvider.getPayload(token); + Member member = memberRepository.findByEmail(email).orElseThrow(() -> new UnauthorizedException("Invalid credentials")); + return member.getName(); + } + + public Member findByEmail(String email) { + return memberRepository.findByEmail(email).orElseThrow(() -> new UnauthorizedException("Invalid credentials")); + } + + public Member findByName(String name) { + return memberRepository.findByName(name).orElseThrow(() -> new UnauthorizedException("Invalid credentials")); + } } diff --git a/src/main/java/com/yourssu/roomescape/reservation/MyReservationResponse.java b/src/main/java/com/yourssu/roomescape/reservation/MyReservationResponse.java new file mode 100644 index 0000000..721ce17 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/reservation/MyReservationResponse.java @@ -0,0 +1,10 @@ +package com.yourssu.roomescape.reservation; + +public record MyReservationResponse( + Long reservationId, + String theme, + String date, + String time, + String status +) { +} diff --git a/src/main/java/com/yourssu/roomescape/reservation/Reservation.java b/src/main/java/com/yourssu/roomescape/reservation/Reservation.java index 66a0376..09eff0a 100644 --- a/src/main/java/com/yourssu/roomescape/reservation/Reservation.java +++ b/src/main/java/com/yourssu/roomescape/reservation/Reservation.java @@ -1,28 +1,50 @@ package com.yourssu.roomescape.reservation; +import com.yourssu.roomescape.member.Member; import com.yourssu.roomescape.theme.Theme; import com.yourssu.roomescape.time.Time; +import jakarta.persistence.*; +@Entity public class Reservation { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String name; + @Column private String 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; + + @Column(nullable = false) + private String name; // name 필드 추가 + + + public Reservation(Long id, String date, Time time, Theme theme) { this.id = id; - this.name = name; this.date = date; this.time = time; this.theme = theme; } - public Reservation(String name, String date, Time time, Theme theme) { - this.name = name; + public Reservation(String date, Time time, Theme theme, Member member) { this.date = date; this.time = time; this.theme = theme; + this.member = member; + + if (member != null) { + this.name = member.getName(); + } } public Reservation() { @@ -33,10 +55,6 @@ public Long getId() { return id; } - public String getName() { - return name; - } - public String getDate() { return date; } @@ -48,4 +66,16 @@ public Time getTime() { public Theme getTheme() { return theme; } + + public Member getMember() { + return member; + } + + public void setMember(Member member) { + this.member = member; + } + + public String getName() { + return name; + } } diff --git a/src/main/java/com/yourssu/roomescape/reservation/ReservationController.java b/src/main/java/com/yourssu/roomescape/reservation/ReservationController.java index 77f108d..a56ff6d 100644 --- a/src/main/java/com/yourssu/roomescape/reservation/ReservationController.java +++ b/src/main/java/com/yourssu/roomescape/reservation/ReservationController.java @@ -1,6 +1,10 @@ package com.yourssu.roomescape.reservation; +import com.yourssu.roomescape.auth.LoginMember; +import com.yourssu.roomescape.auth.UserInfo; +import com.yourssu.roomescape.member.Member; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.net.URI; @@ -20,17 +24,23 @@ public List list() { return reservationService.findAll(); } + @GetMapping("/reservations-mine") + public List myReservations(@LoginMember UserInfo userInfo) { + if (userInfo == null) { + return List.of(); + } + return reservationService.findMyReservationsByMemberId(userInfo.getId()); + } + @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { - if (reservationRequest.getName() == null - || reservationRequest.getDate() == null - || reservationRequest.getTheme() == null - || reservationRequest.getTime() == null) { + public ResponseEntity create(@Validated @RequestBody ReservationRequest reservationRequest, @LoginMember UserInfo userInfo) { + if (reservationRequest.name() == null && userInfo == null) { return ResponseEntity.badRequest().build(); } - ReservationResponse reservation = reservationService.save(reservationRequest); - return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); + ReservationResponse reservation = reservationService.save(reservationRequest, userInfo); + + return ResponseEntity.created(URI.create("/reservations/" + reservation.id())).body(reservation); } @DeleteMapping("/reservations/{id}") diff --git a/src/main/java/com/yourssu/roomescape/reservation/ReservationDao.java b/src/main/java/com/yourssu/roomescape/reservation/ReservationDao.java deleted file mode 100644 index 5b643bb..0000000 --- a/src/main/java/com/yourssu/roomescape/reservation/ReservationDao.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.yourssu.roomescape.reservation; - -import com.yourssu.roomescape.theme.Theme; -import com.yourssu.roomescape.time.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 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/com/yourssu/roomescape/reservation/ReservationRepository.java b/src/main/java/com/yourssu/roomescape/reservation/ReservationRepository.java new file mode 100644 index 0000000..e17305e --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/reservation/ReservationRepository.java @@ -0,0 +1,24 @@ +package com.yourssu.roomescape.reservation; + +import com.yourssu.roomescape.member.Member; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ReservationRepository extends JpaRepository { + List findByDateAndThemeId(String date, Long themeId); + List findByMember(Member member); + @Query("SELECT COUNT(r) > 0 FROM Reservation r " + + "WHERE r.member.id = :memberId " + + "AND r.date = :date " + + "AND r.theme.id = :themeId " + + "AND r.time.id = :timeId") + boolean existsByMemberIdAndDateAndThemeIdAndTimeId( + @Param("memberId") Long memberId, + @Param("date") String date, + @Param("themeId") Long themeId, + @Param("timeId") Long timeId); + +} diff --git a/src/main/java/com/yourssu/roomescape/reservation/ReservationRequest.java b/src/main/java/com/yourssu/roomescape/reservation/ReservationRequest.java index b413312..b1ede53 100644 --- a/src/main/java/com/yourssu/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/com/yourssu/roomescape/reservation/ReservationRequest.java @@ -1,24 +1,16 @@ package com.yourssu.roomescape.reservation; +import jakarta.validation.constraints.NotNull; -public class ReservationRequest { - private String name; - private String date; - private Long theme; - private Long time; +public record ReservationRequest( + String name, - public String getName() { - return name; - } + @NotNull(message="date is required") + String date, - public String getDate() { - return date; - } + @NotNull(message="theme is required") + Long theme, - public Long getTheme() { - return theme; - } - - public Long getTime() { - return time; - } + @NotNull(message="time is required") + Long time +) { } diff --git a/src/main/java/com/yourssu/roomescape/reservation/ReservationResponse.java b/src/main/java/com/yourssu/roomescape/reservation/ReservationResponse.java index c8b83aa..b2f8881 100644 --- a/src/main/java/com/yourssu/roomescape/reservation/ReservationResponse.java +++ b/src/main/java/com/yourssu/roomescape/reservation/ReservationResponse.java @@ -1,37 +1,10 @@ package com.yourssu.roomescape.reservation; -public class ReservationResponse { - private Long id; - private String name; - private String theme; - private String date; - private String time; - - public ReservationResponse(Long id, String name, String theme, String date, String time) { - this.id = id; - this.name = name; - this.theme = theme; - this.date = date; - this.time = time; - } - - public Long getId() { - return id; - } - - public String getName() { - return name; - } - - public String getTheme() { - return theme; - } - - public String getDate() { - return date; - } - - public String getTime() { - return time; - } +public record ReservationResponse( + Long id, + String name, + String theme, + String date, + String time +) { } diff --git a/src/main/java/com/yourssu/roomescape/reservation/ReservationService.java b/src/main/java/com/yourssu/roomescape/reservation/ReservationService.java index 1dab4f6..eb3b771 100644 --- a/src/main/java/com/yourssu/roomescape/reservation/ReservationService.java +++ b/src/main/java/com/yourssu/roomescape/reservation/ReservationService.java @@ -1,30 +1,129 @@ package com.yourssu.roomescape.reservation; +import com.yourssu.roomescape.auth.UserInfo; +import com.yourssu.roomescape.common.exception.ResourceNotFoundException; +import com.yourssu.roomescape.common.exception.UnauthorizedException; +import com.yourssu.roomescape.member.Member; +import com.yourssu.roomescape.member.MemberRepository; +import com.yourssu.roomescape.theme.Theme; +import com.yourssu.roomescape.theme.ThemeRepository; +import com.yourssu.roomescape.time.Time; +import com.yourssu.roomescape.time.TimeRepository; +import com.yourssu.roomescape.waiting.WaitingRepository; +import com.yourssu.roomescape.waiting.WaitingWithRank; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.stream.Collectors; @Service public class ReservationService { - private ReservationDao reservationDao; + private final ReservationRepository reservationRepository; + private final TimeRepository timeRepository; + private final ThemeRepository themeRepository; + private final MemberRepository memberRepository; + private final WaitingRepository waitingRepository; - public ReservationService(ReservationDao reservationDao) { - this.reservationDao = reservationDao; + public ReservationService(ReservationRepository reservationRepository, + TimeRepository timeRepository, + ThemeRepository themeRepository, + MemberRepository memberRepository, + WaitingRepository waitingRepository) { + this.reservationRepository = reservationRepository; + this.timeRepository = timeRepository; + this.themeRepository = themeRepository; + this.memberRepository = memberRepository; + this.waitingRepository = waitingRepository; } - public ReservationResponse save(ReservationRequest reservationRequest) { - Reservation reservation = reservationDao.save(reservationRequest); + @Transactional + public ReservationResponse save(ReservationRequest reservationRequest, UserInfo userInfo) { + String name; + Member member = null; - return new ReservationResponse(reservation.getId(), reservationRequest.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); + if (userInfo != null) { + member = memberRepository.findById(userInfo.getId()) + .orElseThrow(() -> new ResourceNotFoundException("Member not found")); + name = userInfo.getName(); + } else if (reservationRequest.name() != null) { + name = reservationRequest.name(); + } else { + throw new UnauthorizedException("login is required"); + } + + Time time = timeRepository.findById(reservationRequest.time()) + .orElseThrow(() -> new ResourceNotFoundException("Time not found")); + Theme theme = themeRepository.findById(reservationRequest.theme()) + .orElseThrow(() -> new ResourceNotFoundException("Theme not found")); + + Reservation reservation = new Reservation( + reservationRequest.date(), + time, + theme, + member + ); + + Reservation savedReservation = reservationRepository.save(reservation); + + return new ReservationResponse( + savedReservation.getId(), + name, + theme.getName(), + savedReservation.getDate(), + time.getValue() + ); } + @Transactional 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.getDate(), + it.getTime().getValue() + )) + .toList(); + } + + public List findMyReservations(Member member) { + List reservations = reservationRepository.findByMember(member).stream() + .map(it -> new MyReservationResponse( + it.getId(), + it.getTheme().getName(), + it.getDate(), + it.getTime().getValue(), + ReservationStatus.RESERVED.getValue() + )) + .collect(Collectors.toList()); + + List waitings = waitingRepository.findWaitingsWithRankByMemberId(member.getId()); + + List waitingResponses = waitings.stream() + .map(wr -> new MyReservationResponse( + wr.getWaiting().getId(), + wr.getWaiting().getTheme().getName(), + wr.getWaiting().getDate(), + wr.getWaiting().getTime().getValue(), + ReservationStatus.WAITING.getWaitingStatusWithRank(wr.getRank()) + )) .toList(); + + reservations.addAll(waitingResponses); + + return reservations; + } + + public List findMyReservationsByMemberId(Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new ResourceNotFoundException("Member not found")); + + return findMyReservations(member); } } diff --git a/src/main/java/com/yourssu/roomescape/reservation/ReservationStatus.java b/src/main/java/com/yourssu/roomescape/reservation/ReservationStatus.java new file mode 100644 index 0000000..9c37aa7 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/reservation/ReservationStatus.java @@ -0,0 +1,25 @@ +package com.yourssu.roomescape.reservation; + +public enum ReservationStatus { + RESERVED("예약"), + CANCELED("취소"), + COMPLETED("완료"), + WAITING("예약대기"); + + private final String value; + + ReservationStatus(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public String getWaitingStatusWithRank(long rank) { + if (this == WAITING) { + return rank + "번째 " + this.value; + } + return this.value; + } +} diff --git a/src/main/java/com/yourssu/roomescape/theme/Theme.java b/src/main/java/com/yourssu/roomescape/theme/Theme.java index 0fb5d06..f180511 100644 --- a/src/main/java/com/yourssu/roomescape/theme/Theme.java +++ b/src/main/java/com/yourssu/roomescape/theme/Theme.java @@ -1,10 +1,22 @@ package com.yourssu.roomescape.theme; +import jakarta.persistence.*; + +@Entity public class Theme { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column private String name; + + @Column private String description; + @Column + private boolean deleted = false; + public Theme() { } @@ -30,4 +42,12 @@ public String getName() { public String getDescription() { return description; } -} + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } +} \ No newline at end of file diff --git a/src/main/java/com/yourssu/roomescape/theme/ThemeController.java b/src/main/java/com/yourssu/roomescape/theme/ThemeController.java index 485d949..d010457 100644 --- a/src/main/java/com/yourssu/roomescape/theme/ThemeController.java +++ b/src/main/java/com/yourssu/roomescape/theme/ThemeController.java @@ -8,26 +8,26 @@ @RestController public class ThemeController { - private ThemeDao themeDao; + private final ThemeService themeService; - public ThemeController(ThemeDao themeDao) { - this.themeDao = themeDao; + public ThemeController(ThemeService themeService) { + this.themeService = themeService; } @PostMapping("/themes") public ResponseEntity createTheme(@RequestBody Theme theme) { - Theme newTheme = themeDao.save(theme); + Theme newTheme = themeService.createTheme(theme); return ResponseEntity.created(URI.create("/themes/" + newTheme.getId())).body(newTheme); } @GetMapping("/themes") public ResponseEntity> list() { - return ResponseEntity.ok(themeDao.findAll()); + return ResponseEntity.ok(themeService.getAllThemes()); } @DeleteMapping("/themes/{id}") public ResponseEntity deleteTheme(@PathVariable Long id) { - themeDao.deleteById(id); + themeService.deleteTheme(id); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/com/yourssu/roomescape/theme/ThemeDao.java b/src/main/java/com/yourssu/roomescape/theme/ThemeDao.java deleted file mode 100644 index 6fcf67f..0000000 --- a/src/main/java/com/yourssu/roomescape/theme/ThemeDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.yourssu.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/com/yourssu/roomescape/theme/ThemeRepository.java b/src/main/java/com/yourssu/roomescape/theme/ThemeRepository.java new file mode 100644 index 0000000..0c9764f --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/theme/ThemeRepository.java @@ -0,0 +1,9 @@ +package com.yourssu.roomescape.theme; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ThemeRepository extends JpaRepository { + List findByDeletedFalse(); +} diff --git a/src/main/java/com/yourssu/roomescape/theme/ThemeService.java b/src/main/java/com/yourssu/roomescape/theme/ThemeService.java new file mode 100644 index 0000000..a826966 --- /dev/null +++ b/src/main/java/com/yourssu/roomescape/theme/ThemeService.java @@ -0,0 +1,26 @@ +package com.yourssu.roomescape.theme; + +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class ThemeService { + private final ThemeRepository themeRepository; + + public ThemeService(ThemeRepository themeRepository) { + this.themeRepository = themeRepository; + } + + public Theme createTheme(Theme theme) { + return themeRepository.save(theme); + } + + public List getAllThemes() { + return themeRepository.findAll(); + } + + public void deleteTheme(Long id) { + themeRepository.deleteById(id); + } +} diff --git a/src/main/java/com/yourssu/roomescape/time/Time.java b/src/main/java/com/yourssu/roomescape/time/Time.java index 4b298e2..89a9bb8 100644 --- a/src/main/java/com/yourssu/roomescape/time/Time.java +++ b/src/main/java/com/yourssu/roomescape/time/Time.java @@ -1,7 +1,14 @@ package com.yourssu.roomescape.time; +import jakarta.persistence.*; + +@Entity public class Time { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(name = "time_value") private String value; public Time(Long id, String value) { @@ -13,9 +20,7 @@ public Time(String value) { this.value = value; } - public Time() { - - } + public Time() {} public Long getId() { return id; diff --git a/src/main/java/com/yourssu/roomescape/time/TimeDao.java b/src/main/java/com/yourssu/roomescape/time/TimeDao.java deleted file mode 100644 index 7d45644..0000000 --- a/src/main/java/com/yourssu/roomescape/time/TimeDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.yourssu.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