diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 00000000..871806c8 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -euo pipefail # 명령어 하나라도 실패하면 즉시 스크립트 중단 +# deploy.sh 파일이 있는 프로젝트 루트로 이동 +REPOSITORY="$(cd "$(dirname "$0")" && pwd)" +cd "$REPOSITORY" || exit 1 +PID_FILE="$REPOSITORY/app.pid" + +echo "[INFO] 프로젝트 빌드" +./gradlew build -x test + +if [ $? -ne 0 ]; then + echo "[ERROR] 빌드에 실패했습니다. 배포를 중단합니다." + exit 1 +fi + +echo "[INFO] 기존에 실행 중인 애플리케이션 PID 종료" +CURRENT_PID=$(pgrep -f "$REPOSITORY/build/libs/.*\.jar" || true) + +if [ -z "$CURRENT_PID" ]; then + echo "[INFO] 종료할 애플리케이션이 없습니다." +else + echo "[INFO] kill -15 $CURRENT_PID" + kill -15 $CURRENT_PID + while kill -0 $CURRENT_PID 2>/dev/null; do + sleep 1 + done +fi + +echo "[INFO] 새 애플리케이션 실행" +JAR_NAME=$(ls $REPOSITORY/build/libs/*.jar | grep -v plain) + +nohup java -jar $JAR_NAME > $REPOSITORY/nohup.log 2>&1 & + +echo $! > $PID_FILE +echo "[INFO] 애플리케이션이 실행되었습니다. PID: $(cat $PID_FILE)" diff --git a/src/main/java/roomescape/DataLoader.java b/src/main/java/roomescape/DataLoader.java new file mode 100644 index 00000000..d30c49af --- /dev/null +++ b/src/main/java/roomescape/DataLoader.java @@ -0,0 +1,47 @@ +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; +import roomescape.theme.Theme; +import roomescape.theme.ThemeRepository; +import roomescape.time.Time; +import roomescape.time.TimeRepository; + +@Profile("!test") //테스트 환경이 아닐때 실행되게 하는 기능 +@Component +public class DataLoader implements CommandLineRunner { + + private final MemberRepository memberRepository; + private final ThemeRepository themeRepository; + private final TimeRepository timeRepository; + + public DataLoader(MemberRepository memberRepository, ThemeRepository themeRepository, TimeRepository timeRepository) { + this.memberRepository = memberRepository; + this.themeRepository = themeRepository; + this.timeRepository = timeRepository; + } + + @Override + public void run(String... args) throws Exception { + // 1. 회원 저장 (ID: 1, 2) + memberRepository.save(new Member("어드민", "admin@email.com", "password", "ADMIN")); + memberRepository.save(new Member("브라운", "brown@email.com", "password", "USER")); + + // 2. 테마 저장 + themeRepository.save(new Theme("테마1", "테마1입니다.")); + themeRepository.save(new Theme("테마2", "테마2입니다.")); + themeRepository.save(new Theme("테마3", "테마3입니다.")); + + // 3. 시간 저장 + timeRepository.save(new Time("10:00")); + timeRepository.save(new Time("12:00")); + timeRepository.save(new Time("14:00")); + timeRepository.save(new Time("16:00")); + timeRepository.save(new Time("18:00")); + timeRepository.save(new Time("20:00")); + } +} diff --git a/src/main/java/roomescape/TestDataLoader.java b/src/main/java/roomescape/TestDataLoader.java new file mode 100644 index 00000000..26f6ca7c --- /dev/null +++ b/src/main/java/roomescape/TestDataLoader.java @@ -0,0 +1,56 @@ +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; +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; + + public TestDataLoader(MemberRepository memberRepository, + ThemeRepository themeRepository, + TimeRepository timeRepository, + ReservationRepository reservationRepository) { + this.memberRepository = memberRepository; + this.themeRepository = themeRepository; + this.timeRepository = timeRepository; + this.reservationRepository = reservationRepository; + } + + @Override + public void run(String... args) throws Exception { + // 1. 회원 저장 (1L: 어드민, 2L: 브라운) + Member admin = memberRepository.save(new Member("어드민", "admin@email.com", "password", "ADMIN")); + Member brown = memberRepository.save(new Member("브라운", "brown@email.com", "password", "USER")); + + // 2. 테마 저장 + Theme theme1 = themeRepository.save(new Theme("테마1", "테마1입니다.")); + Theme theme2 = themeRepository.save(new Theme("테마2", "테마2입니다.")); + Theme theme3 = themeRepository.save(new Theme("테마3", "테마3입니다.")); + + // 3. 시간 저장 + Time time1 = timeRepository.save(new Time("10:00")); + Time time2 = timeRepository.save(new Time("12:00")); + Time time3 = timeRepository.save(new Time("14:00")); + + // 4. 5단계, 6단계 테스트에서 요구하는 예약 데이터 저장 + reservationRepository.save(new Reservation("어드민", "2024-03-01", time1, theme1, admin)); + reservationRepository.save(new Reservation("어드민", "2024-03-01", time1, theme2, admin)); + reservationRepository.save(new Reservation("어드민", "2024-03-01", time3, theme3, admin)); + } +} diff --git a/src/main/java/roomescape/WebMvcConfiguration.java b/src/main/java/roomescape/WebMvcConfiguration.java index 8d5df072..51fe41e3 100644 --- a/src/main/java/roomescape/WebMvcConfiguration.java +++ b/src/main/java/roomescape/WebMvcConfiguration.java @@ -1,12 +1,11 @@ package roomescape; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import roomescape.auth.JwtUtils; import roomescape.member.AdminInterceptor; -import roomescape.member.JwtTokenProvider; import roomescape.member.LoginMemberArgumentResolver; import roomescape.member.MemberRepository; @@ -15,21 +14,21 @@ @Configuration public class WebMvcConfiguration implements WebMvcConfigurer { - private final JwtTokenProvider jwtTokenProvider; - private final MemberRepository memberRepository; + private final JwtUtils jwtUtils; + private final LoginMemberArgumentResolver loginMemberArgumentResolver; - public WebMvcConfiguration(JwtTokenProvider jwtTokenProvider, MemberRepository memberRepository){ - this.jwtTokenProvider = jwtTokenProvider; - this.memberRepository = memberRepository; + public WebMvcConfiguration(JwtUtils jwtUtils, MemberRepository memberRepository, LoginMemberArgumentResolver loginMemberArgumentResolver){ + this.jwtUtils = jwtUtils; + this.loginMemberArgumentResolver = loginMemberArgumentResolver; } @Override public void addArgumentResolvers(List resolvers) { - resolvers.add(new LoginMemberArgumentResolver(jwtTokenProvider, memberRepository)); + resolvers.add(loginMemberArgumentResolver); } @Override public void addInterceptors(InterceptorRegistry registry){ - registry.addInterceptor(new AdminInterceptor(jwtTokenProvider)) + registry.addInterceptor(new AdminInterceptor(jwtUtils)) .addPathPatterns("/admin/**", "/admin"); } } diff --git a/src/main/java/roomescape/auth/AuthConfig.java b/src/main/java/roomescape/auth/AuthConfig.java new file mode 100644 index 00000000..a98ee549 --- /dev/null +++ b/src/main/java/roomescape/auth/AuthConfig.java @@ -0,0 +1,18 @@ +package roomescape.auth; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AuthConfig { + + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + + @Bean + public JwtUtils jwtUtils() { + // 1시간(3600000ms) 만료 기준 + return new JwtUtils(secretKey, 3600000L); + } +} diff --git a/src/main/java/roomescape/auth/JwtUtils.java b/src/main/java/roomescape/auth/JwtUtils.java new file mode 100644 index 00000000..5e9b6859 --- /dev/null +++ b/src/main/java/roomescape/auth/JwtUtils.java @@ -0,0 +1,47 @@ +package roomescape.auth; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Date; + +public class JwtUtils { + + private final String secretKey; + private final long validityInMilliseconds; + + public JwtUtils(String secretKey, long validityInMilliseconds) { + this.secretKey = secretKey; + this.validityInMilliseconds = validityInMilliseconds; + } + + private Key getSigningKey() { + return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + public String createToken(String id, String role) { + Claims claims = Jwts.claims().setSubject(id); + claims.put("role", role); + + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(getSigningKey()) + .compact(); + } + + public Claims parseToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/src/main/java/roomescape/config/JwtConfig.java b/src/main/java/roomescape/config/JwtConfig.java deleted file mode 100644 index dc6917e2..00000000 --- a/src/main/java/roomescape/config/JwtConfig.java +++ /dev/null @@ -1,16 +0,0 @@ -package roomescape.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import roomescape.member.JwtTokenProvider; - -@Configuration -public class JwtConfig { - - @Bean - public JwtTokenProvider jwtTokenProvider(@Value("${jwt.secret}") String secretKey) { - return new JwtTokenProvider(secretKey); - } -} -//스프링이 jwtTokenProvider 객체를 생성해 관리하게 해줌 diff --git a/src/main/java/roomescape/member/AdminInterceptor.java b/src/main/java/roomescape/member/AdminInterceptor.java index 1279f895..21c37ccd 100644 --- a/src/main/java/roomescape/member/AdminInterceptor.java +++ b/src/main/java/roomescape/member/AdminInterceptor.java @@ -9,12 +9,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; +import roomescape.auth.JwtUtils; public class AdminInterceptor implements HandlerInterceptor { - private final JwtTokenProvider jwtTokenProvider; + private final JwtUtils jwtUtils; - public AdminInterceptor(JwtTokenProvider jwtTokenProvider) { - this.jwtTokenProvider = jwtTokenProvider; + public AdminInterceptor(JwtUtils jwtUtils) { + this.jwtUtils = jwtUtils; } @Override @@ -44,7 +45,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons } try { - Claims claims = jwtTokenProvider.parseToken(token); + Claims claims = jwtUtils.parseToken(token); String role = claims.get("role", String.class); diff --git a/src/main/java/roomescape/member/JwtTokenProvider.java b/src/main/java/roomescape/member/JwtTokenProvider.java deleted file mode 100644 index c812a3b0..00000000 --- a/src/main/java/roomescape/member/JwtTokenProvider.java +++ /dev/null @@ -1,35 +0,0 @@ -package roomescape.member; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; - -import java.util.Date; - -public class JwtTokenProvider { - - private final String secretKey; - - public JwtTokenProvider(String secretKey) { - this.secretKey = secretKey; - } - - public String createToken(Member member) { - return Jwts.builder() - .setSubject(member.getId().toString()) - .claim("name", member.getName()) - .claim("role", member.getRole()) - .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 30)) - .signWith(Keys.hmacShaKeyFor(secretKey.getBytes()), SignatureAlgorithm.HS256) - .compact(); - } - - public Claims parseToken(String token) { - return Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) - .build() - .parseClaimsJws(token) - .getBody(); - } -} diff --git a/src/main/java/roomescape/member/LoginMemberArgumentResolver.java b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java index 1f9e1b79..ff55405f 100644 --- a/src/main/java/roomescape/member/LoginMemberArgumentResolver.java +++ b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java @@ -6,18 +6,21 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +import roomescape.auth.JwtUtils; //컨트롤러의 매개변수를 자동으로 만들어주는 클래스 +@Component public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { - private final JwtTokenProvider jwtTokenProvider; + private final JwtUtils jwtUtils; private final MemberRepository memberRepository; - public LoginMemberArgumentResolver(JwtTokenProvider jwtTokenProvider,MemberRepository memberRepository){ - this.jwtTokenProvider = jwtTokenProvider; + public LoginMemberArgumentResolver(JwtUtils jwtUtils, MemberRepository memberRepository){ + this.jwtUtils = jwtUtils; this.memberRepository = memberRepository; } @@ -30,31 +33,29 @@ public boolean supportsParameter(MethodParameter parameter) { public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); //현재 HTTP 요청 가져오기 - Cookie[] cookies = request.getCookies(); //쿠키 가져오기 + String token = extractToken(request); - if (cookies == null) { - throw new IllegalArgumentException("토큰 쿠키가 존재하지 않습니다."); + if (token == null) { + throw new IllegalArgumentException("토큰이 존재하지 않습니다."); } - String token = ""; - for (Cookie cookie : cookies) { - if ("token".equals(cookie.getName())) {//이름이 token인 쿠키 찾기 - token = cookie.getValue(); //JWT 문자열 저장 - } - } - - if(token.isBlank()){ - throw new IllegalArgumentException("토큰 값이 비어있습니다."); - } - - // 토큰 파싱하여 회원 정보 추출 - Claims claims = jwtTokenProvider.parseToken(token); - //정보 꺼내기 + Claims claims = jwtUtils.parseToken(token); Long id = Long.parseLong(claims.getSubject()); - return memberRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다.")); } + + private String extractToken(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("token".equals(cookie.getName())) { + return cookie.getValue(); + } + } + } + return null; + } } //브라우저가 보낸 JWT 쿠키를 읽어서 그 안에 저장된 회원 정보를 Member 객체로 복원한 뒤, 컨트롤러의 Member loginMember 매개변수에 자동으로 //넣어주는 역할 diff --git a/src/main/java/roomescape/member/Member.java b/src/main/java/roomescape/member/Member.java index a4dfa3a9..783d9315 100644 --- a/src/main/java/roomescape/member/Member.java +++ b/src/main/java/roomescape/member/Member.java @@ -32,4 +32,9 @@ public Member(String name, String email, String password, String role) { this.password = password; this.role = role; } + + public Member(Long id, String role) { + this.id = id; + this.role = role; + } } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index b5d09880..c55006aa 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -4,17 +4,18 @@ import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import roomescape.auth.JwtUtils; import java.util.Date; @Service public class MemberService { private MemberRepository memberRepository; - private final JwtTokenProvider jwtTokenProvider; + private final JwtUtils jwtUtils; - public MemberService(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider) { + public MemberService(MemberRepository memberRepository, JwtUtils jwtUtils) { this.memberRepository = memberRepository; - this.jwtTokenProvider = jwtTokenProvider; + this.jwtUtils = jwtUtils; } public MemberResponse createMember(MemberRequest memberRequest) { @@ -29,7 +30,7 @@ public String login(LoginRequest loginRequest) { loginRequest.getPassword() ).orElseThrow(() -> new IllegalArgumentException("이메일 또는 비밀번호가 일치하지 않습니다.")); - return jwtTokenProvider.createToken(member); + return jwtUtils.createToken(String.valueOf(member.getId()), member.getRole()); } diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index f5e779ea..3b0d97fd 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -8,7 +8,9 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import roomescape.member.Member; +import roomescape.member.MemberRepository; import roomescape.theme.Theme; +import roomescape.theme.ThemeRepository; import roomescape.time.Time; import roomescape.time.TimeRepository; import roomescape.waiting.WaitingRepository; @@ -26,25 +28,15 @@ @RestController public class ReservationController { - private final ReservationRepository reservationRepository; - private final roomescape.member.MemberRepository memberRepository; - private final roomescape.theme.ThemeRepository themeRepository; - private final TimeRepository timeRepository; - private final WaitingRepository waitingRepository; + private final ReservationService reservationService; - public ReservationController(ReservationRepository reservationRepository, - roomescape.member.MemberRepository memberRepository, - roomescape.theme.ThemeRepository themeRepository, TimeRepository timeRepository, WaitingRepository waitingRepository) { - this.reservationRepository = reservationRepository; - this.memberRepository = memberRepository; - this.themeRepository = themeRepository; - this.timeRepository = timeRepository; - this.waitingRepository = waitingRepository; + public ReservationController(ReservationService reservationService) { + this.reservationService = reservationService; } @GetMapping("/reservations") //예약 목록 반환 public List list() { - return reservationRepository.findAll(); + return reservationService.findAll(); } @PostMapping("/reservations") @@ -52,58 +44,21 @@ public ResponseEntity create( @RequestBody ReservationRequest reservationRequest, Member loginMember //현재 로그인한 회원 ) { - Member member = memberRepository.findById(loginMember.getId()).orElseThrow(); - - String finalName = (reservationRequest.getName() != null && !reservationRequest.getName().isBlank()) - ? reservationRequest.getName() - : loginMember.getName(); - - Theme theme = themeRepository.findById(reservationRequest.getTheme()).orElseThrow(); - Time time = timeRepository.findById(reservationRequest.getTime()).orElseThrow(); - Reservation reservation = new Reservation(finalName, reservationRequest.getDate(), time, theme, member); - Reservation saved = reservationRepository.save(reservation); - + Reservation saved = reservationService.createReservation(reservationRequest, loginMember); return ResponseEntity.created(URI.create("/reservations/" + saved.getId())).body(saved); } @DeleteMapping("/reservations/{id}") public ResponseEntity delete(@PathVariable Long id) { - reservationRepository.deleteById(id); + reservationService.deleteReservation(id); return ResponseEntity.noContent().build(); } //예약 조회할 때 대기 목록까지 같이 반환하도록 변경 @GetMapping("/reservations-mine") public List listMine(Member loginMember) { - - List result = new ArrayList<>(); - List reservations = reservationRepository.findByMemberId(loginMember.getId()); - - for(Reservation r : reservations){ - result.add(new MyReservationResponse( - r.getId(), - r.getTheme().getName(), - r.getDate(), - r.getTime().getValue(), - "예약" - )); - } - - List waitingsWithRank = waitingRepository.findWaitingsWithRankByMemberId(loginMember.getId()); - for (WaitingWithRank wr : waitingsWithRank) { - Waiting w = wr.getWaiting(); - long displayRank = wr.getRank() + 1; //1번째 대기로 - result.add(new MyReservationResponse( - w.getId(), - w.getTheme().getName(), - w.getDate(), - w.getTime().getValue(), - displayRank + "번째 예약대기" - )); - } - - return result; + return reservationService.findMyReservations(loginMember); } } diff --git a/src/main/java/roomescape/reservation/ReservationRepository.java b/src/main/java/roomescape/reservation/ReservationRepository.java index f50c2dad..c6d6aa3a 100644 --- a/src/main/java/roomescape/reservation/ReservationRepository.java +++ b/src/main/java/roomescape/reservation/ReservationRepository.java @@ -13,13 +13,34 @@ @Repository public interface ReservationRepository extends JpaRepository { - @Query("SELECT r FROM Reservation r JOIN FETCH r.time JOIN FETCH r.theme JOIN FETCH r.member") + @Query(""" + SELECT r + FROM Reservation r + JOIN FETCH r.time + JOIN FETCH r.theme + JOIN FETCH r.member + """) List findAllWithFetchJoin(); - @Query("SELECT r FROM Reservation r JOIN FETCH r.time JOIN FETCH r.theme JOIN FETCH r.member WHERE r.member.id = :memberId") + @Query(""" + SELECT r + FROM Reservation r + JOIN FETCH r.time + JOIN FETCH r.theme + JOIN FETCH r.member + WHERE r.member.id = :memberId + """) + + List findByMemberId(@Param("memberId") Long memberId); //중복 예약 방지 검증하ㅣ기 - @Query("SELECT COUNT(r) > 0 FROM Reservation r WHERE r.date = :date AND r.time.id = :timeId AND r.theme.id = :themeId") + @Query(""" + SELECT COUNT(r) > 0 + FROM Reservation r + WHERE r.date = :date + AND r.time.id = :timeId + AND r.theme.id = :themeId + """) boolean existsByDateAndTimeAndTheme(@Param("date") String date, @Param("timeId") Long timeId, @Param("themeId") Long themeId); } diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java new file mode 100644 index 00000000..0afc9fd1 --- /dev/null +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -0,0 +1,89 @@ +package roomescape.reservation; + + + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import roomescape.member.Member; +import roomescape.member.MemberRepository; +import roomescape.theme.Theme; +import roomescape.theme.ThemeRepository; +import roomescape.time.Time; +import roomescape.time.TimeRepository; +import roomescape.waiting.Waiting; +import roomescape.waiting.WaitingRepository; +import roomescape.waiting.WaitingWithRank; + +import java.util.ArrayList; +import java.util.List; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ReservationService { + + private final ReservationRepository reservationRepository; + private final MemberRepository memberRepository; + private final ThemeRepository themeRepository; + private final TimeRepository timeRepository; + private final WaitingRepository waitingRepository; + + public List findAll() { + return reservationRepository.findAllWithFetchJoin(); + } + + @Transactional + public Reservation createReservation(ReservationRequest reservationRequest, Member loginMember) { + Member member = memberRepository.findById(loginMember.getId()) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다.")); + + String finalName = (reservationRequest.getName() != null && !reservationRequest.getName().isBlank()) + ? reservationRequest.getName() + : loginMember.getName(); + + Theme theme = themeRepository.findById(reservationRequest.getTheme()) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 테마입니다.")); + Time time = timeRepository.findById(reservationRequest.getTime()) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 시간입니다.")); + + Reservation reservation = new Reservation(finalName, reservationRequest.getDate(), time, theme, member); + return reservationRepository.save(reservation); + } + + // 예약 삭제 + @Transactional + public void deleteReservation(Long id) { + reservationRepository.deleteById(id); + } + + public List findMyReservations(Member loginMember) { + List result = new ArrayList<>(); + + List reservations = reservationRepository.findByMemberId(loginMember.getId()); + for (Reservation r : reservations) { + result.add(new MyReservationResponse( + r.getId(), + r.getTheme().getName(), + r.getDate(), + r.getTime().getValue(), + "예약" + )); + } + + List waitingsWithRank = waitingRepository.findWaitingsWithRankByMemberId(loginMember.getId()); + for (WaitingWithRank wr : waitingsWithRank) { + Waiting w = wr.getWaiting(); + long displayRank = wr.getRank() + 1; + result.add(new MyReservationResponse( + w.getId(), + w.getTheme().getName(), + w.getDate(), + w.getTime().getValue(), + displayRank + "번째 예약대기" + )); + } + + return result; + } +} diff --git a/src/main/java/roomescape/theme/ThemeController.java b/src/main/java/roomescape/theme/ThemeController.java index 86cf57cf..193e4b39 100644 --- a/src/main/java/roomescape/theme/ThemeController.java +++ b/src/main/java/roomescape/theme/ThemeController.java @@ -13,28 +13,26 @@ @RestController public class ThemeController { - private final ThemeRepository themeRepository; + private final ThemeService themeService; - public ThemeController(ThemeRepository themeRepository) { - this.themeRepository= themeRepository; + public ThemeController(ThemeService themeService) { + this.themeService = themeService; } @PostMapping("/themes") public ResponseEntity createTheme(@RequestBody Theme theme) { - Theme newTheme = themeRepository.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(themeRepository.findAll()); + return ResponseEntity.ok(themeService.findAll()); } @DeleteMapping("/themes/{id}") public ResponseEntity deleteTheme(@PathVariable Long id) { - Theme theme = themeRepository.findById(id).orElseThrow(); - theme.delete(); - themeRepository.save(theme); + themeService.deleteTheme(id); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/roomescape/theme/ThemeRepository.java b/src/main/java/roomescape/theme/ThemeRepository.java index 3c30d3fe..0907cdf4 100644 --- a/src/main/java/roomescape/theme/ThemeRepository.java +++ b/src/main/java/roomescape/theme/ThemeRepository.java @@ -1,9 +1,6 @@ package roomescape.theme; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.transaction.annotation.Transactional; import org.springframework.stereotype.Repository; import java.util.List; diff --git a/src/main/java/roomescape/theme/ThemeService.java b/src/main/java/roomescape/theme/ThemeService.java new file mode 100644 index 00000000..9e9184fd --- /dev/null +++ b/src/main/java/roomescape/theme/ThemeService.java @@ -0,0 +1,34 @@ +package roomescape.theme; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional(readOnly = true) +public class ThemeService { + + private final ThemeRepository themeRepository; + + public ThemeService(ThemeRepository themeRepository) { + this.themeRepository = themeRepository; + } + + @Transactional + public Theme createTheme(Theme theme) { + return themeRepository.save(theme); + } + + public List findAll() { + return themeRepository.findAll(); + } + + @Transactional + public void deleteTheme(Long id) { + Theme theme = themeRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 테마입니다.")); + theme.delete(); + themeRepository.save(theme); + } +} diff --git a/src/main/java/roomescape/time/TimeController.java b/src/main/java/roomescape/time/TimeController.java index 30257807..5df9efae 100644 --- a/src/main/java/roomescape/time/TimeController.java +++ b/src/main/java/roomescape/time/TimeController.java @@ -9,44 +9,32 @@ import java.util.List; @RestController public class TimeController { - private final TimeRepository timeRepository; - private final ReservationRepository reservationRepository; + private final TimeService timeService; - public TimeController(TimeRepository timeRepository,ReservationRepository reservationRepository) { - this.timeRepository = timeRepository; - this.reservationRepository = reservationRepository; + public TimeController(TimeService timeService) { + this.timeService = timeService; } @GetMapping("/times") public List