예약 관리 시스템 - Spring + Vue

예약 관리 시스템 완성 - 백엔드 (Spring Boot 3 + Mysql)

그랜파 개발자 2025. 5. 29. 11:40

Eclipse에서 Spring Boot 프로젝트 생성

🔹 사전 준비

  • JDK 17 이상
  • Eclipse IDE (최신)
  • Maven 설치 (또는 Eclipse 내장 Maven 사용)
  • MySQL 실행 중

🔹 Step 1: Spring 프로젝트 생성

  1. Eclipse 실행 → File → New → Spring Starter Project
  2. 프로젝트 정보 입력:
    • Name: reservation
    • Type: Maven
    • Packaging: Jar
    • Java Version: 17
    • Language: Java
    • Group: com.example
    • Artifact: j reservation
  3. Dependencies 추가:
    • Spring Web
    • Spring Data JPA
    • Spring Boot DevTools (선택)
    • Spring Security
    • MySQL Driver
    • Lombok
    • Validation
  4. Finish 클릭하면 프로젝트가 생성됩니다.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.0</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>reservation</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>reservation</name>
  <description>Demo project for Spring Boot</description>
  <url/>
  <licenses>
    <license/>
  </licenses>
  <developers>
    <developer/>
  </developers>
  <scm>
    <connection/>
    <developerConnection/>
    <tag/>
    <url/>
  </scm>
  <properties>
    <java.version>17</java.version>
  </properties>
  <dependencies>
    <!-- JPA -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- Security -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- Web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- JWT -->
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-api</artifactId>
      <version>0.11.5</version>
    </dependency>
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-impl</artifactId>
      <version>0.11.5</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-jackson</artifactId>
      <version>0.11.5</version>
      <scope>runtime</scope>
    </dependency>

    <!-- MySQL -->
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- Validation -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <!-- Lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.38</version> <!-- 최신 버전으로 변경 가능 -->
      <scope>provided</scope>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <annotationProcessorPaths>
            <path>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </path>
          </annotationProcessorPaths>
        </configuration>
      </plugin>
      
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <excludes>
            <exclude>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

 

application.yml

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/reservation?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  application:
    name: reservation

jwt:
  secret: my-very-secret-key-for-jwt-token-signing
  expiration: 3600000  # 1 hour

 

ReservationApplication.java

package com.example.reservation;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@SpringBootApplication
@EnableMethodSecurity  // 메서드 보안 활성화
public class ReservationApplication {

	public static void main(String[] args) {
		SpringApplication.run(ReservationApplication.class, args);
	}

}

 

JwtUtil.java

package com.example.reservation.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.List;

@Component
public class JwtUtil {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private long expiration;

    // 토큰 생성 시 역할 포함
    public String generateToken(String email, String role) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + expiration);
        Key key = Keys.hmacShaKeyFor(secret.getBytes());

        return Jwts.builder()
                .setSubject(email)
                .claim("role", role)   // 역할 추가
                .setIssuedAt(now)
                .setExpiration(expiry)
                .signWith(key)
                .compact();
    }

    // 토큰에서 사용자 이메일 추출
    public String extractUsername(String token) {
        Key key = Keys.hmacShaKeyFor(secret.getBytes());
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    // 토큰에서 역할 목록 추출
    @SuppressWarnings("unchecked")
    public List<String> extractRoles(String token) {
        Key key = Keys.hmacShaKeyFor(secret.getBytes());
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();

        Object roles = claims.get("role");
        if (roles instanceof List<?>) {
            return (List<String>) roles;
        }
        return List.of();
    }

    // 토큰 유효성 검사
    public boolean isTokenValid(String token) {
        try {
            Key key = Keys.hmacShaKeyFor(secret.getBytes());
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (JwtException e) {
            return false;
        }
    }
}

 

JwtFilter.java

package com.example.reservation.config;

import com.example.reservation.service.CustomUserDetailsService;
import com.example.reservation.util.JwtUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import lombok.RequiredArgsConstructor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.*;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {

	private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);
    private final JwtUtil jwtUtil;
    private final CustomUserDetailsService userDetailsService;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {

        final String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        try {
	        final String token = header.substring(7);
	        final String username = jwtUtil.extractUsername(token);
	
	        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
	            var userDetails = userDetailsService.loadUserByUsername(username);
	            if (jwtUtil.isTokenValid(token)) {
	                var auth = new UsernamePasswordAuthenticationToken(
	                        userDetails, null, userDetails.getAuthorities()
	                );
	                auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
	                SecurityContextHolder.getContext().setAuthentication(auth);
	            }
	        }
	        chain.doFilter(request, response);
        }
        catch (Exception e) {
        {
        	logger.warn("JWT 인증 중 오류 발생: {}", e.getMessage());
        }
        }
    }
}

 

SecurityConfig.java

package com.example.reservation.config;

import lombok.RequiredArgsConstructor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.*;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.*;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;

import java.util.List;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

    private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);

    private final JwtFilter jwtFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable())
            .cors(cors -> cors.configurationSource(request -> {
                CorsConfiguration corsConfig = new CorsConfiguration();
                corsConfig.setAllowedOrigins(List.of("http://localhost:5173"));
                corsConfig.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
                corsConfig.setAllowedHeaders(List.of("Authorization", "Content-Type"));
                corsConfig.setAllowCredentials(true);
                return corsConfig;
            }))
            .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
        		.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()  // OPTIONS 요청 허용
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
            );

        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Bean
    public AuthenticationManager authenticationManager(
        AuthenticationConfiguration config
    ) throws Exception {
        return config.getAuthenticationManager();
    }
}

 

ApiResponse.java

package com.example.reservation.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ApiResponse {
    private boolean success;
    private String message;
}

 

AuthRequest.java

package com.example.reservation.dto;

import lombok.Data;

@Data
public class AuthRequest {
    private String email;
    private String password;
}

 

AuthResponse.java

package com.example.reservation.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class AuthResponse {
    private String token;
    private String name;
    private String role;
}

 

RegisterRequest.java

package com.example.reservation.dto;

import lombok.Data;

@Data
public class RegisterRequest {
    private String email;
    private String password;
    private String name;
}

 

ReservationRequest.java

package com.example.reservation.dto;

import lombok.Data;
import java.util.List;

@Data
public class ReservationRequest {
    private String date; // "yyyy-MM-dd"
    private List<String> timeSlots; // e.g., ["10:00~11:00", "11:00~12:00"]
    private String memo;
}

 

ReservationResponse.java

package com.example.reservation.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;

@Data
@AllArgsConstructor
public class ReservationResponse {
    private Long id;
    private String date;
    private List<String> timeSlots;
    private String memo;
    private String status;
    private String createdAt;
}

 

Reservation.java

package com.example.reservation.model;

import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Reservation {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private LocalDate date;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    private String memo;

    @Enumerated(EnumType.STRING)
    private ReservationStatus status;

    private LocalDateTime createdAt;

    @OneToMany(mappedBy = "reservation", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<ReservationTimeSlot> timeSlots = new ArrayList<>();
}

 

ReservationStatus.java

package com.example.reservation.model;

public enum ReservationStatus {
    PENDING, APPROVED, REJECTED, CANCELLED
}

 

ReservationTimeSlot.java

package com.example.reservation.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ReservationTimeSlot {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String timeSlot;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "reservation_id")
    @JsonIgnore
    private Reservation reservation;

    public ReservationTimeSlot(Reservation reservation, String timeSlot) {
        this.reservation = reservation;
        this.timeSlot = timeSlot;
    }
}

 

User.java

package com.example.reservation.model;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "users")
@Getter
@Setter
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String role;
}

 

ReservationRepository.java

package com.example.reservation.repository;

import com.example.reservation.model.Reservation;
import com.example.reservation.model.User;

import java.time.LocalDate;
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ReservationRepository extends JpaRepository<Reservation, Long> {
    // JpaRepository를 상속받으면 save() 등 기본 CRUD 메서드 사용 가능
	// 특정 사용자에 대한 예약 목록 조회
    List<Reservation> findByUser(User user);
    
    // 날짜 기준 예약 목록 조회
    List<Reservation> findByDate(LocalDate date);
}

 

ReservationTimeSlotRepository.java

package com.example.reservation.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import com.example.reservation.model.ReservationTimeSlot;

public interface ReservationTimeSlotRepository extends JpaRepository<ReservationTimeSlot, Long> {}

 

UserRepository.java

package com.example.reservation.repository;

import com.example.reservation.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

 

CustomUserDetailsService.java

package com.example.reservation.service;

import com.example.reservation.config.JwtFilter;
import com.example.reservation.model.User;
import com.example.reservation.repository.UserRepository;
import lombok.RequiredArgsConstructor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.*;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

	private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);
    private final UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email)
            .orElseThrow(() -> new UsernameNotFoundException("User not found"));
        
        logger.info("Loaded user roles: " + user.getRole()); // 여기서 roles 확인
        
        return org.springframework.security.core.userdetails.User
                .withUsername(user.getEmail())
                .password(user.getPassword())
                .roles(user.getRole())
                .build();
    }
}

 

ReservationService.java

package com.example.reservation.service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.example.reservation.dto.ReservationRequest;
import com.example.reservation.dto.ReservationResponse;
import com.example.reservation.model.Reservation;
import com.example.reservation.model.ReservationStatus;
import com.example.reservation.model.ReservationTimeSlot;
import com.example.reservation.model.User;
import com.example.reservation.repository.ReservationRepository;
import com.example.reservation.repository.UserRepository;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ReservationService {

    private final ReservationRepository reservationRepository;
    private final UserRepository userRepository;

    /**
     * 사용자: 예약 생성
     */
    @Transactional
    public ReservationResponse createReservation(String email, ReservationRequest request) {
        User user = userRepository.findByEmail(email).orElseThrow();

        Reservation reservation = new Reservation();
        reservation.setDate(LocalDate.parse(request.getDate()));
        reservation.setUser(user);
        reservation.setMemo(request.getMemo());
        reservation.setStatus(ReservationStatus.PENDING);
        reservation.setCreatedAt(LocalDateTime.now());

        List<ReservationTimeSlot> slotEntities = request.getTimeSlots().stream()
            .map(ts -> {
                ReservationTimeSlot slot = new ReservationTimeSlot();
                slot.setTimeSlot(ts);
                slot.setReservation(reservation);  // 필수
                return slot;
            }).collect(Collectors.toList());

        reservation.setTimeSlots(slotEntities);

        Reservation saved = reservationRepository.save(reservation);

        return new ReservationResponse(
                saved.getId(),
                saved.getDate().toString(),
                saved.getTimeSlots().stream().map(ReservationTimeSlot::getTimeSlot).toList(),
                saved.getMemo(),
                saved.getStatus().name(),
                saved.getCreatedAt().toString()
        );
    }
    
    /**
     * 관리자: 예약 상태 변경 (APPROVED, REJECTED, CANCELLED 등)
     */
    @Transactional
    public Reservation updateReservationStatus(Long reservationId, ReservationStatus status) {
        Reservation reservation = reservationRepository.findById(reservationId)
                .orElseThrow(() -> new RuntimeException("Reservation not found"));

        reservation.setStatus(status);
        return reservationRepository.save(reservation);
    }

    /**
     * 관리자: 전체 예약 목록 조회
     */
    public List<Reservation> getAllReservations() {
        return reservationRepository.findAll();
    }
    
    public List<Reservation> getReservationsByDate(String date) {
        // date 형식에 맞게 파싱 후 DB 쿼리 (예: '2025-05-27')
    	LocalDate localDate = LocalDate.parse(date);  // 문자열 → LocalDate 변환
        return reservationRepository.findByDate(localDate);
    }

    /**
     * 사용자: 내 예약 목록 조회
     */
    public List<Reservation> getReservationsByUser(String email) {
    	User user = userRepository.findByEmail(email).orElseThrow();
        return reservationRepository.findByUser(user);
    }
    
    public void cancelReservation(Long reservationId, String email) {
        Reservation reservation = reservationRepository.findById(reservationId)
                .orElseThrow(() -> new RuntimeException("예약을 찾을 수 없습니다."));

        // 클라이언트에서 로그인 후 예약을 취소하므로 본인의 예약만 취소를 한다.
        // 승인 또는 거부가 된 예약도 취소할 수 있다.
//        if (!reservation.getEmail().equals(email)) {
//            throw new SecurityException("본인의 예약만 취소할 수 있습니다.");
//        }

//        if (reservation.getStatus() != ReservationStatus.PENDING) {
//           throw new IllegalStateException("대기 중인 예약만 취소할 수 있습니다.");
//        }

        reservation.setStatus(ReservationStatus.CANCELLED);
        reservationRepository.save(reservation);
    }
}

 

AuthController.java

package com.example.reservation.controller;

import com.example.reservation.dto.*;
import com.example.reservation.model.User;
import com.example.reservation.repository.UserRepository;
import com.example.reservation.util.JwtUtil;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

@CrossOrigin(origins = "http://localhost:5173")
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;
    
    @PostMapping("/register")
    public ResponseEntity<ApiResponse> register(@RequestBody @Valid RegisterRequest request) {
        if (userRepository.findByEmail(request.getEmail()).isPresent()) {
            return ResponseEntity.status(HttpStatus.CONFLICT)
                .body(new ApiResponse(false, "Email already registered"));
        }

        User user = new User();
        user.setEmail(request.getEmail());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setName(request.getName());
        user.setRole("USER");

        userRepository.save(user);
        return ResponseEntity.ok(new ApiResponse(true, "User registered successfully"));
    }
    
    @PostMapping("/login")
    public ResponseEntity<AuthResponse> login(@RequestBody AuthRequest request) {
        User user = userRepository.findByEmail(request.getEmail())
                .orElseThrow(() -> new UsernameNotFoundException("Invalid email or password"));

        if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
            throw new BadCredentialsException("Invalid email or password");
        }

        // role 포함해서 토큰 생성
        String token = jwtUtil.generateToken(user.getEmail(), user.getRole());

        AuthResponse response = new AuthResponse(token, user.getName(), user.getRole());
        return ResponseEntity.ok(response);
    }
}

 

ReservationController.java

package com.example.reservation.controller;

import com.example.reservation.dto.ReservationRequest;
import com.example.reservation.dto.ReservationResponse;
import com.example.reservation.model.Reservation;
import com.example.reservation.model.ReservationStatus;
import com.example.reservation.service.ReservationService;
import com.example.reservation.util.JwtUtil;
import lombok.RequiredArgsConstructor;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/")
@CrossOrigin(origins = "http://localhost:5173")
@RequiredArgsConstructor
public class ReservationController {

	private static final Logger logger = LoggerFactory.getLogger(ReservationController.class);

    private final ReservationService reservationService;
    private final JwtUtil jwtUtil;

    @PostMapping("auth/reservation")
    public ReservationResponse create(
            @RequestBody ReservationRequest request,
            @RequestHeader("Authorization") String authHeader
    ) {
        String token = authHeader.replace("Bearer ", "");
        String email = jwtUtil.extractUsername(token);
        return reservationService.createReservation(email, request);
    }
    
    /**
     * ✅ 사용자: 자신의 예약 목록 조회
     */
    @GetMapping("auth/reservations/my")
    @PreAuthorize("hasRole('USER')")
    public List<Reservation> getMyReservations(
    		@RequestHeader("Authorization") String authHeader
    ) {
        String token = authHeader.replace("Bearer ", "");
        String email = jwtUtil.extractUsername(token);
        return reservationService.getReservationsByUser(email);
    }
    
    /**
     * ✅ 사용자: 자신의 예약 취소
     */
    @PatchMapping("auth/reservations/{id}/cancel")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<?> cancelReservation(
            @PathVariable Long id,
            @RequestHeader("Authorization") String authHeader
    ) {
        String token = authHeader.replace("Bearer ", "");
        String email = jwtUtil.extractUsername(token);

        reservationService.cancelReservation(id, email);
        return ResponseEntity.ok("예약이 취소되었습니다.");
    }
    
    /**
     * ✅ 관리자: 전체 예약 목록 조회
     */
    @GetMapping("admin/reservations")
    @PreAuthorize("hasRole('ADMIN')")
    public List<Reservation> getAllReservations(
        @RequestParam(required = false) String date
    ) {
        if (date != null && !date.isEmpty()) {
            return reservationService.getReservationsByDate(date);
        }
        return reservationService.getAllReservations();
    }
    
    
    @PreAuthorize("hasRole('ADMIN')")
    @PostMapping("admin/reservation/{id}/approve")
    public ResponseEntity<?> approveReservation(@PathVariable Long id) {
    	logger.info("==== approveReservation : " + id);
        reservationService.updateReservationStatus(id, ReservationStatus.APPROVED);
        return ResponseEntity.ok("Reservation approved");
    }

    @PreAuthorize("hasRole('ADMIN')")
    @PostMapping("admin/reservation/{id}/reject")
    public ResponseEntity<?> rejectReservation(@PathVariable Long id) {
    	logger.info("==== rejectReservation : " + id);
        reservationService.updateReservationStatus(id, ReservationStatus.REJECTED);
        return ResponseEntity.ok("Reservation rejected");
    }
}