diff --git a/README.md b/README.md new file mode 100644 index 00000000..0527c82f --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Spring Core (배포) + +

7단계 - @Configuration

+ +- [x] JWT 관련 로직을 roomescape와 같은 계층의 auth 패키지의 클래스로 분리 +- [x] 불필요한 DB 접근 최소화 + - JWT 토큰에는 사용자 식별 정보와 권한 정보가 들어갑니다. + 만약 이 두 정보만 필요하다면 DB 접근이 필요하지 않습니다. + + +

8단계 - Profile과 Resource

+ +schema.sql 대신 데이터베이스를 초기화 클래스 생성 +- [x] Production용도 DataLoader 생성 + 사용자 정보만 초기화 +- [x] Test용도 TestDataLoader생성 + 테스트에 필요한 사전 값 초기화 +- [x] Environemt 분리 (토큰 비밀키) \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8d52aebc..87e93013 100644 --- a/build.gradle +++ b/build.gradle @@ -15,9 +15,8 @@ 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 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.0.0' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' //jpa변경 implementation 'io.jsonwebtoken:jjwt-api:0.11.2' implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' @@ -29,6 +28,7 @@ dependencies { runtimeOnly 'com.h2database:h2' } + test { useJUnitPlatform() -} +} \ No newline at end of file diff --git a/src/main/java/auth/AuthConfig.java b/src/main/java/auth/AuthConfig.java new file mode 100644 index 00000000..da937124 --- /dev/null +++ b/src/main/java/auth/AuthConfig.java @@ -0,0 +1,12 @@ +package auth; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AuthConfig { + @Bean + public JwtTokenProvider jwtTokenProvider() { + return new JwtTokenProvider(); + } +} \ No newline at end of file diff --git a/src/main/java/auth/JwtTokenProvider.java b/src/main/java/auth/JwtTokenProvider.java new file mode 100644 index 00000000..a1e9bb6c --- /dev/null +++ b/src/main/java/auth/JwtTokenProvider.java @@ -0,0 +1,41 @@ +package auth; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import roomescape.member.MemberResponse; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; + +public class JwtTokenProvider { + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + + @Bean + public SecretKey secretKey() { + return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + public String createToken(MemberResponse member) { + String accessToken = Jwts.builder() + .setSubject(member.getId().toString()) + .claim("name", member.getName()) + .claim("email", member.getEmail()) + .signWith(secretKey()) + .compact(); + + return accessToken; + } + + public Long extractMemberIdFromToken(String token) { + Claims claims = Jwts.parserBuilder() + .setSigningKey(secretKey()) + .build() + .parseClaimsJws(token) + .getBody(); + return Long.valueOf(claims.getSubject()); + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/DataLoader.java b/src/main/java/roomescape/DataLoader.java new file mode 100644 index 00000000..6d7d0c01 --- /dev/null +++ b/src/main/java/roomescape/DataLoader.java @@ -0,0 +1,23 @@ +package roomescape; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import roomescape.member.Member; +import roomescape.member.MemberRepository; + + +@Component +@Profile("Production") +public class DataLoader implements CommandLineRunner { + private final MemberRepository memberRepository; + + public DataLoader(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + @Override + public void run(final String... args) throws Exception { + final Member member1 = memberRepository.save(new Member("어드민", "admin@email.com", "password", "ADMIN")); + final Member member2 = memberRepository.save(new Member("브라운", "brown@email.com", "password", "USER")); + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/PageController.java b/src/main/java/roomescape/PageController.java index ac8ef940..e27ea032 100644 --- a/src/main/java/roomescape/PageController.java +++ b/src/main/java/roomescape/PageController.java @@ -44,4 +44,4 @@ public String login() { public String signup() { return "signup"; } -} +} \ No newline at end of file diff --git a/src/main/java/roomescape/RoomescapeApplication.java b/src/main/java/roomescape/RoomescapeApplication.java index 2ca0f743..51f1fbdb 100644 --- a/src/main/java/roomescape/RoomescapeApplication.java +++ b/src/main/java/roomescape/RoomescapeApplication.java @@ -1,11 +1,14 @@ package roomescape; +import auth.AuthConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; +@Import(AuthConfig.class) @SpringBootApplication public class RoomescapeApplication { public static void main(String[] args) { SpringApplication.run(RoomescapeApplication.class, args); } -} +} \ No newline at end of file diff --git a/src/main/java/roomescape/TestDataLoader.java b/src/main/java/roomescape/TestDataLoader.java new file mode 100644 index 00000000..2b89b9ff --- /dev/null +++ b/src/main/java/roomescape/TestDataLoader.java @@ -0,0 +1,56 @@ +package roomescape; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import roomescape.member.Member; +import roomescape.member.MemberRepository; +import roomescape.reservation.Reservation; +import roomescape.reservation.ReservationRepository; +import roomescape.theme.Theme; +import roomescape.theme.ThemeRepository; +import roomescape.time.Time; +import roomescape.time.TimeRepository; + +@Profile("test") +@Component +public class TestDataLoader implements CommandLineRunner { + private final MemberRepository memberRepository; + private final ThemeRepository themeRepository; + private final TimeRepository timeRepository; + private final ReservationRepository reservationRepository; + + @Autowired + public TestDataLoader(final MemberRepository memberRepository, + final ThemeRepository themeRepository, + final TimeRepository timeRepository, + final ReservationRepository reservationRepository) { + this.memberRepository = memberRepository; + this.themeRepository = themeRepository; + this.timeRepository = timeRepository; + this.reservationRepository = reservationRepository; + } + + @Override + public void run(final String... args) throws Exception { + final Member member1 = memberRepository.save(new Member("어드민", "admin@email.com", "password", "ADMIN")); + final Member member2 = memberRepository.save(new Member("브라운", "brown@email.com", "password", "USER")); + + final Theme theme1 = themeRepository.save(new Theme("테마1", "테마1입니다.")); + final Theme theme2 = themeRepository.save(new Theme("테마2", "테마2입니다.")); + final Theme theme3 = themeRepository.save(new Theme("테마3", "테마3입니다.")); + + final Time time1 = timeRepository.save(new Time("10:00")); + final Time time2 = timeRepository.save(new Time("12:00")); + final Time time3 = timeRepository.save(new Time("14:00")); + final Time time4 = timeRepository.save(new Time("16:00")); + final Time time5 = timeRepository.save(new Time("18:00")); + final Time time6 = timeRepository.save(new Time("20:00")); + + reservationRepository.save(new Reservation("어드민", "2024-03-01", time1, theme1, member1)); + reservationRepository.save(new Reservation("어드민", "2024-03-01", time2, theme2, member1)); + reservationRepository.save(new Reservation("어드민", "2024-03-01", time3, theme3, member1)); + reservationRepository.save(new Reservation("브라운", "2024-03-01", time4, theme1, member2)); + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/member/AdminInterceptor.java b/src/main/java/roomescape/member/AdminInterceptor.java new file mode 100644 index 00000000..3e75303b --- /dev/null +++ b/src/main/java/roomescape/member/AdminInterceptor.java @@ -0,0 +1,26 @@ +package roomescape.member; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class AdminInterceptor implements HandlerInterceptor { + + @Autowired + private MemberService memberService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String token = memberService.extractTokenFromCookie(request.getCookies()); + Member member = memberService.extractMemberFromToken(token); + + if (member == null || !member.getRole().equals("ADMIN")) { + response.setStatus(401); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/member/LoginMember.java b/src/main/java/roomescape/member/LoginMember.java new file mode 100644 index 00000000..d5490819 --- /dev/null +++ b/src/main/java/roomescape/member/LoginMember.java @@ -0,0 +1,61 @@ +package roomescape.member; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity +public class LoginMember { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private String email; + private String role; + + public LoginMember() { + + } + + public LoginMember(Long id, String name, String email, String role) { + this.id = id; + this.name = name; + this.email = email; + this.role = role; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/member/LoginMemberArgumentResolver.java b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java new file mode 100644 index 00000000..9c9b1871 --- /dev/null +++ b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java @@ -0,0 +1,35 @@ +package roomescape.member; + +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; + + public LoginMemberArgumentResolver(MemberService memberService) { + this.memberService = memberService; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(LoginMember.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + String token = webRequest.getHeader("Cookie").split("=")[1]; + + Member member = memberService.extractMemberFromToken(token); + + if (member == null) { + throw new RuntimeException("인증되지 않은 사용자입니다."); + } + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/member/Member.java b/src/main/java/roomescape/member/Member.java index 903aaa9b..0e27fc65 100644 --- a/src/main/java/roomescape/member/Member.java +++ b/src/main/java/roomescape/member/Member.java @@ -1,19 +1,31 @@ package roomescape.member; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@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) { + public Member() { + } + + public Member(Long id, String name, String email, String password, String role) { this.id = id; this.name = name; this.email = email; + this.password = password; this.role = role; } - public Member(String name, String email, String password, String role) { this.name = name; this.email = email; @@ -40,4 +52,5 @@ public String getPassword() { public String getRole() { return role; } -} + +} \ No newline at end of file diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 881ae5e0..1de39ca4 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -3,11 +3,9 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.net.URI; @@ -20,11 +18,34 @@ public MemberController(MemberService memberService) { } @PostMapping("/members") - public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { + public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { MemberResponse member = memberService.createMember(memberRequest); return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member); } + @PostMapping("/login") + public ResponseEntity login(@RequestBody MemberRequest memberRequest, HttpServletResponse response){ + MemberResponse member = memberService.findMember(memberRequest.getEmail(), memberRequest.getPassword()); + if (member == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + String token = memberService.createToken(member); + memberService.createCookie(response, token); + + return ResponseEntity.status(HttpStatus.OK).build(); + } + + @GetMapping("/login/check") + public ResponseEntity checkLogin(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + String token = memberService.extractTokenFromCookie(cookies); + + MemberResponse member = memberService.findMemberById(memberService.extractMemberFromToken(token).getId()); + + MemberResponse memberResponse = new MemberResponse(member.getId(), member.getName(), member.getEmail()); + return ResponseEntity.ok().body(memberResponse); + } + @PostMapping("/logout") public ResponseEntity logout(HttpServletResponse response) { Cookie cookie = new Cookie("token", ""); @@ -34,4 +55,4 @@ public ResponseEntity logout(HttpServletResponse response) { response.addCookie(cookie); return ResponseEntity.ok().build(); } -} +} \ No newline at end of file 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..ea332bd6 --- /dev/null +++ b/src/main/java/roomescape/member/MemberRepository.java @@ -0,0 +1,14 @@ +package roomescape.member; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import roomescape.reservation.Reservation; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface MemberRepository extends JpaRepository { + Member findByEmailAndPassword(String email, String password); + Optional findByName(String name); +} \ No newline at end of file diff --git a/src/main/java/roomescape/member/MemberResponse.java b/src/main/java/roomescape/member/MemberResponse.java index b9fa3b97..2de85dfd 100644 --- a/src/main/java/roomescape/member/MemberResponse.java +++ b/src/main/java/roomescape/member/MemberResponse.java @@ -22,4 +22,4 @@ public String getName() { public String getEmail() { return email; } -} +} \ No newline at end of file diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index ccaa8cba..60e8f126 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -1,17 +1,68 @@ package roomescape.member; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import auth.JwtTokenProvider; + +import java.util.Optional; @Service public class MemberService { - private MemberDao memberDao; + @Autowired + private MemberRepository memberRepository; + @Autowired + private JwtTokenProvider jwtTokenProvider; - public MemberService(MemberDao memberDao) { - this.memberDao = memberDao; + public MemberService(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider) { + this.memberRepository = memberRepository; + this.jwtTokenProvider = jwtTokenProvider; } 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(), "USER")); + return new MemberResponse(member.getId(), member.getName(), member.getEmail()); + } + + public MemberResponse findMember(String email, String password) { + Member member = memberRepository.findByEmailAndPassword(email, password); return new MemberResponse(member.getId(), member.getName(), member.getEmail()); } -} + + public MemberResponse findMemberById(Long memberId) { + Optional optionalMember = memberRepository.findById(memberId); + Member member = optionalMember.orElseThrow(() -> new IllegalArgumentException("Invalid member ID")); + return new MemberResponse(member.getId(), member.getName(), member.getEmail()); + } + + public String createToken(MemberResponse memberResponse) { + String accessToken = jwtTokenProvider.createToken(memberResponse); + return accessToken; + } + + public void createCookie(HttpServletResponse response, String token) { + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + } + + public Member extractMemberFromToken(String token) { + Long memberId = jwtTokenProvider.extractMemberIdFromToken(token); + if (memberId != null) { + Optional optionalMember = memberRepository.findById(memberId); + return optionalMember.orElse(null); + } + return null; + } + + public String extractTokenFromCookie(Cookie[] cookies) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("token")) { + return cookie.getValue(); + } + } + return ""; + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/member/WebMvcConfigure.java b/src/main/java/roomescape/member/WebMvcConfigure.java new file mode 100644 index 00000000..0cb241f5 --- /dev/null +++ b/src/main/java/roomescape/member/WebMvcConfigure.java @@ -0,0 +1,35 @@ +package roomescape.member; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + + +import java.util.List; + +@Configuration +public class WebMvcConfigure implements WebMvcConfigurer { + @Autowired + private final LoginMemberArgumentResolver loginMemberArgumentResolver; + @Autowired + private AdminInterceptor adminInterceptor; + + public WebMvcConfigure(LoginMemberArgumentResolver loginMemberArgumentResolver) { + this.loginMemberArgumentResolver = loginMemberArgumentResolver; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginMemberArgumentResolver); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(adminInterceptor) + .addPathPatterns("/admin/**"); //admin에 적용 + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/reservation/MyReservationResponse.java b/src/main/java/roomescape/reservation/MyReservationResponse.java new file mode 100644 index 00000000..111998a3 --- /dev/null +++ b/src/main/java/roomescape/reservation/MyReservationResponse.java @@ -0,0 +1,43 @@ +package roomescape.reservation; + +public class MyReservationResponse { + private Long reservationId; + private String theme; + private String date; + private String time; + private String status; + private Long rank; + + public MyReservationResponse(Long reservationId, String theme, String date, String time, String status, Long rank) { + this.reservationId = reservationId; + this.theme = theme; + this.date = date; + this.time = time; + this.status = status; + this.rank = rank; + } + + public Long getReservationId() { + return reservationId; + } + + public String getTheme() { + return theme; + } + + public String getDate() { + return date; + } + + public String getTime() { + return time; + } + + public String getStatus() { + return status; + } + + public Long getRank() { + return rank; + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/reservation/Reservation.java b/src/main/java/roomescape/reservation/Reservation.java index 83a7edf1..e5315166 100644 --- a/src/main/java/roomescape/reservation/Reservation.java +++ b/src/main/java/roomescape/reservation/Reservation.java @@ -1,32 +1,44 @@ package roomescape.reservation; +import jakarta.persistence.*; +import roomescape.member.Member; +import roomescape.member.MemberResponse; import roomescape.theme.Theme; import roomescape.time.Time; +@Entity public class Reservation { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String name; private String date; + + @ManyToOne + @JoinColumn(name = "time_id") private Time time; + + @ManyToOne + @JoinColumn(name = "theme_id") private Theme theme; - public Reservation(Long id, String name, String date, Time time, Theme theme) { - this.id = id; - this.name = name; - this.date = date; - this.time = time; - this.theme = theme; + @ManyToOne + @JoinColumn(name = "member_id") + private Member member; + + public Reservation() { + } + + public Reservation(String name, String date, Time time, Theme theme, MemberResponse member) { } - public Reservation(String name, String date, Time time, Theme theme) { + public Reservation(String name, String date, Time time, Theme theme, Member member) { this.name = name; this.date = date; this.time = time; this.theme = theme; - } - - public Reservation() { - + this.member = member; } public Long getId() { @@ -48,4 +60,13 @@ public Time getTime() { public Theme getTheme() { return theme; } -} + + public Member getMember() { + return member; + } + + public void setMember(Member member) { + this.member = member; + } + +} \ No newline at end of file diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index b3bef399..b1775ea5 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -1,23 +1,26 @@ 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.member.LoginMember; +import roomescape.member.MemberRepository; +import roomescape.waiting.WaitingService; +import roomescape.waiting.WaitingResponse; import java.net.URI; import java.util.List; +import java.util.stream.Collectors; @RestController public class ReservationController { - private final ReservationService reservationService; + private final WaitingService waitingService; + private final MemberRepository memberRepository; - public ReservationController(ReservationService reservationService) { + public ReservationController(MemberRepository memberRepository, ReservationService reservationService, WaitingService waitingService) { + this.memberRepository = memberRepository; this.reservationService = reservationService; + this.waitingService = waitingService; } @GetMapping("/reservations") @@ -26,7 +29,12 @@ public List list() { } @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { + public ResponseEntity create(@RequestBody ReservationRequest reservationRequest, LoginMember loginMember) { + + if (loginMember != null && reservationRequest.getName() == null) { // 멤버가 아닌 경우 (참고) + reservationRequest.setName(loginMember.getName()); + } + if (reservationRequest.getName() == null || reservationRequest.getDate() == null || reservationRequest.getTheme() == null @@ -39,8 +47,18 @@ public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) } @DeleteMapping("/reservations/{id}") - public ResponseEntity delete(@PathVariable Long id) { + public ResponseEntity delete(@PathVariable Long id) { reservationService.deleteById(id); return ResponseEntity.noContent().build(); } -} + + @GetMapping("/reservations-mine") + public List mineReservation(LoginMember loginMember) { + List reservations = reservationService.findReservation(loginMember); + List waitings = waitingService.findWaitingsByMember(loginMember); + + reservations.addAll(waitings); + return reservations; + } + +} \ No newline at end of file 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..d4b5e23b --- /dev/null +++ b/src/main/java/roomescape/reservation/ReservationRepository.java @@ -0,0 +1,12 @@ +package roomescape.reservation; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ReservationRepository extends JpaRepository{ + List findByDateAndThemeId(String date, Long themeId); + List findByMemberIdOrName(Long memberId, String name); +} \ No newline at end of file diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java index 19f44124..e6c887b2 100644 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/roomescape/reservation/ReservationRequest.java @@ -6,6 +6,16 @@ public class ReservationRequest { private Long theme; private Long time; + public void setMember(Long member) { + this.member = member; + } + + private Long member; + + public Long getMember() { + return member; + } + public String getName() { return name; } @@ -21,4 +31,21 @@ public Long getTheme() { public Long getTime() { return time; } -} + public void setName(String name) { + this.name = name; + } + + public void setDate(String date) { + this.date = date; + } + + public void setTheme(Long theme) { + this.theme = theme; + } + + public void setTime(Long time) { + this.time = time; + } + + +} \ No newline at end of file diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index bd331332..1b224ad3 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -1,30 +1,80 @@ package roomescape.reservation; import org.springframework.stereotype.Service; +import roomescape.member.*; +import roomescape.theme.Theme; +import roomescape.theme.ThemeRepository; +import roomescape.time.Time; +import roomescape.time.TimeRepository; import java.util.List; +import java.util.stream.Collectors; @Service public class ReservationService { - private ReservationDao reservationDao; + private ReservationRepository reservationRepository; + private ThemeRepository themeRepository; + private TimeRepository timeRepository; + private MemberRepository memberRepository; + private MemberService memberService; - public ReservationService(ReservationDao reservationDao) { - this.reservationDao = reservationDao; + public ReservationService(ReservationRepository reservationRepository, ThemeRepository themeRepository, TimeRepository timeRepository, MemberRepository memberRepository, MemberService memberService) { + this.reservationRepository = reservationRepository; + this.themeRepository = themeRepository; + this.timeRepository = timeRepository; + this.memberRepository = memberRepository; + this.memberService = memberService; } public ReservationResponse save(ReservationRequest reservationRequest) { - Reservation reservation = reservationDao.save(reservationRequest); + Time time = timeRepository.findById(reservationRequest.getTime()) + .orElseThrow(() -> new IllegalArgumentException("Time not found")); - return new ReservationResponse(reservation.getId(), reservationRequest.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); + Theme theme = themeRepository.findById(reservationRequest.getTheme()) + .orElseThrow(() -> new IllegalArgumentException("Theme not found")); + + Member member = null; + if (reservationRequest.getMember() != null) { + member = memberRepository.findById(reservationRequest.getMember()) + .orElseThrow(() -> new IllegalArgumentException("Member not found")); + } + + Reservation reservation = new Reservation( + reservationRequest.getName(), + reservationRequest.getDate(), + time, + theme, + member + ); + + reservation = reservationRepository.save(reservation); + + return new ReservationResponse( + reservation.getId(), + reservation.getName(), + reservation.getTheme().getName(), + reservation.getDate(), + reservation.getTime().getValue() + ); } public void deleteById(Long id) { - reservationDao.deleteById(id); + reservationRepository.deleteById(id); } public List findAll() { - return reservationDao.findAll().stream() + return reservationRepository.findAll().stream() .map(it -> new ReservationResponse(it.getId(), it.getName(), it.getTheme().getName(), it.getDate(), it.getTime().getValue())) .toList(); } -} + + public List findReservation(LoginMember loginMember) { + List reservations = reservationRepository.findByMemberIdOrName(loginMember.getId(), loginMember.getName()).stream() + .map(it -> new MyReservationResponse(it.getId(), + it.getTheme().getName(), + it.getDate(), + it.getTime().getValue(), "예약", null)) // 예약에는 rank가 없으므로 null + .collect(Collectors.toList()); + return reservations; + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/theme/Theme.java b/src/main/java/roomescape/theme/Theme.java index 430a6239..13be5ef4 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; @@ -30,4 +39,4 @@ public String getName() { public String getDescription() { return description; } -} +} \ No newline at end of file diff --git a/src/main/java/roomescape/theme/ThemeController.java b/src/main/java/roomescape/theme/ThemeController.java index 03bca41a..d3dc60b4 100644 --- a/src/main/java/roomescape/theme/ThemeController.java +++ b/src/main/java/roomescape/theme/ThemeController.java @@ -13,26 +13,25 @@ @RestController public class ThemeController { - private ThemeDao themeDao; + private ThemeRepository themeRepository; - public ThemeController(ThemeDao themeDao) { - this.themeDao = themeDao; + public ThemeController(ThemeRepository themeRepository) { + this.themeRepository = themeRepository; } - @PostMapping("/themes") public ResponseEntity createTheme(@RequestBody Theme theme) { - Theme newTheme = themeDao.save(theme); + Theme newTheme = themeRepository.save(theme); return ResponseEntity.created(URI.create("/themes/" + newTheme.getId())).body(newTheme); } @GetMapping("/themes") public ResponseEntity> list() { - return ResponseEntity.ok(themeDao.findAll()); + return ResponseEntity.ok(themeRepository.findAll()); } @DeleteMapping("/themes/{id}") public ResponseEntity deleteTheme(@PathVariable Long id) { - themeDao.deleteById(id); + themeRepository.deleteById(id); return ResponseEntity.noContent().build(); } -} +} \ No newline at end of file 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..6de5f68e --- /dev/null +++ b/src/main/java/roomescape/theme/ThemeRepository.java @@ -0,0 +1,9 @@ +package roomescape.theme; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ThemeRepository extends JpaRepository { + +} \ No newline at end of file diff --git a/src/main/java/roomescape/time/Time.java b/src/main/java/roomescape/time/Time.java index 008ed93c..1eab14b0 100644 --- a/src/main/java/roomescape/time/Time.java +++ b/src/main/java/roomescape/time/Time.java @@ -1,9 +1,20 @@ package 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() { + + } + public Time(Long id, String value) { this.id = id; this.value = value; @@ -13,10 +24,6 @@ public Time(String value) { this.value = value; } - public Time() { - - } - public Long getId() { return id; } @@ -24,4 +31,12 @@ public Long getId() { public String getValue() { return value; } -} + + public void setId(Long id) { + this.id = id; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/time/TimeDao.java b/src/main/java/roomescape/time/TimeDao.java deleted file mode 100644 index f39a9a32..00000000 --- a/src/main/java/roomescape/time/TimeDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package roomescape.time; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; - -import java.sql.PreparedStatement; -import java.util.List; - -@Repository -public class TimeDao { - private final JdbcTemplate jdbcTemplate; - - public TimeDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List