Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
29a3411
feat : 일단계 테스트 통과
nonactress Dec 24, 2025
76e20ba
feat(dao) : 이메일로 멤버 찾기
nonactress Dec 24, 2025
6884888
feat(MemberController) : 회원 조회
nonactress Dec 26, 2025
b994a63
feat : 2단계 통과~!!!!
nonactress Dec 26, 2025
9234df4
feat : AdminInterceptor 생성
nonactress Dec 26, 2025
103e6bb
feat(webConfig) : admin인터셉터 추가
nonactress Dec 26, 2025
ecd0c7f
fix : 개행정리
nonactress Dec 26, 2025
334327e
fix : 개행정리2
nonactress Dec 26, 2025
eeb4f66
fix : 패키지 정리
nonactress Dec 26, 2025
a75257b
fix : 1단계 login 쿠키 설정 및 tokenResponse dto 제거
nonactress Dec 26, 2025
f95bdc5
feat : time 엔티티로 수정 및 repository 생성
nonactress Dec 31, 2025
c1610e8
fix(Time) : 서비스에서 timerepository 사용 리팩토링
nonactress Dec 31, 2025
aa8870e
fix(Theme) : 컨트롤러에서 DAO -> Repository 사용
nonactress Dec 31, 2025
9f54ac5
fix(All)
nonactress Jan 1, 2026
a674fa5
fix(All) : reservation.save 오버로딩 및 영속성 관리 하여 에러 수정
nonactress Jan 1, 2026
f9f63d1
refactor(Reservaiton) : 예약 조회 기능 생성
nonactress Jan 1, 2026
5ee3e74
feat : waiting 엔티티 생성
nonactress Jan 1, 2026
cc28fde
feat : WaitingRepository 생성
nonactress Jan 1, 2026
b5cca3b
fix : ReservationService 리팩토링
nonactress Jan 1, 2026
9deeb22
feat : waiting dto 생성
nonactress Jan 1, 2026
7742db7
feat : WaitingService,controller 생성
nonactress Jan 1, 2026
0812bd7
개행정리
nonactress Jan 1, 2026
8f084a7
feat : 예외 처리
nonactress Jan 1, 2026
4876179
fix : WaitingRepository 스프링 data jpa 사용
nonactress Jan 7, 2026
0f3aadf
fix : ReservationRepository 스프링 data jpa 사용
nonactress Jan 7, 2026
7a12ebf
fix : MemberRepository 스프링 data jpa 사용
nonactress Jan 7, 2026
b05b545
fix : ThemeRepository 스프링 data jpa 사용
nonactress Jan 8, 2026
e83a263
fix : TimeRepository 스프링 data jpa 사용
nonactress Jan 8, 2026
916af1e
rename : advice 패키지 -> exception 패키지
nonactress Jan 8, 2026
48a6984
fix : theme 생성자
nonactress Jan 8, 2026
659c17b
fix : 예외 처리
nonactress Jan 10, 2026
f4f58e9
fix : 예외 처리2
nonactress Jan 10, 2026
4c51bea
fix : authService 생성 및 상태코드 변경
nonactress Jan 10, 2026
db04811
refactor : 리포맷 적용 && 안쓰는 메소드 삭제
nonactress Jan 13, 2026
1ca9fa1
refactor : isDeleted->delete
nonactress Jan 13, 2026
494fffe
refactor : readOnly = true 적용
nonactress Jan 14, 2026
0f655d2
필드 값 final로 변경
nonactress Jan 14, 2026
5ce1144
refactor(themeService) : 클래스 단위 트랜젝션 적용
nonactress Jan 14, 2026
eecfe3a
refactor(themeService) : 중복된 삭제 로직 정리
nonactress Jan 14, 2026
bd4201c
refactor(themeService) : 중복된 삭제 로직 정리
nonactress Jan 14, 2026
d4fc462
refactor(all) : 안쓰는 import 과 메소드 정리
nonactress Jan 14, 2026
fd5d1a5
refactor(all) : 안쓰는 import 과 메소드 정리
nonactress Jan 14, 2026
7c953c7
refactor(all) : 안쓰는 import 과 메소드 정리2
nonactress Jan 14, 2026
ecf4071
feat : Auth 패키지 설정 및 jwtConfig 생성
nonactress Jan 14, 2026
c7303bc
feat : 7단계 테스트 통과
nonactress Jan 14, 2026
1bfd9e3
feat : TestDataLoader 생성
nonactress Jan 14, 2026
3e72013
feat : application-key.properties 생성 및 숨김 처리
nonactress Jan 14, 2026
62a0229
fix : ProductionDataLoader 와 TestDataLoader 주입 값 수정
nonactress Jan 14, 2026
6de128e
fix : 8단계 통과
nonactress Jan 14, 2026
d488aff
fix : 배포 파일 작성
nonactress Jan 14, 2026
8bd9234
fix : 소프트 딜리트 관련 삭제 방어코드
nonactress Jan 22, 2026
c5bdf60
fix : 토큰 만료 시간 로직 중복
nonactress Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ out/

### VS Code ###
.vscode/

**/application-key.properties
10 changes: 7 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.0.0'

implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'io.jsonwebtoken:jjwt-gson:0.11.2'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
testImplementation 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'




runtimeOnly 'com.h2database:h2'
}
Expand Down
42 changes: 42 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현진님이 작성해주신 배포스크립트를 활용하면 어떻게 배포할 수 있나요?

즉, 이 배포 스크립트 실행 전에 해야할 사전 작업이 있나요?

echo "=========================================="
echo "배포 시작"
echo "=========================================="

echo ">>> Git Pull"
git pull origin main

if [ $? -ne 0 ]; then
echo "Git Pull 실패"
exit 1
fi

echo ">>> 프로젝트 빌드 시작"
./gradlew clean build

if [ $? -ne 0 ]; then
echo "빌드 실패"
exit 1
fi

echo ">>> 실행 중인 애플리케이션 확인"
CURRENT_PID=$(pgrep -f roomescape)

if [ -z "$CURRENT_PID" ]; then
echo ">>> 실행 중인 애플리케이션이 없습니다."
else
echo ">>> 애플리케이션 종료 (PID: $CURRENT_PID)"
kill -15 $CURRENT_PID
sleep 5
fi

echo ">>> 새 애플리케이션 실행"
nohup java -jar build/libs/roomescape-0.0.1-SNAPSHOT.jar > application.log 2>&1 &

sleep 3

NEW_PID=$(pgrep -f roomescape)
echo ">>> 배포 완료 (PID: $NEW_PID)"

echo "=========================================="
50 changes: 50 additions & 0 deletions src/main/java/auth/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;


public class JwtTokenProvider {
private final String secretKey;
private final long validityInMilliseconds;

public JwtTokenProvider(String secretKey, long validityInMilliseconds) {
this.secretKey = secretKey;
this.validityInMilliseconds = validityInMilliseconds;
}

public String createToken(String payload) {
Claims claims = Jwts.claims().setSubject(payload);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}

public String getPayload(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}

public boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
return true;

} catch (JwtException | IllegalArgumentException e) {

return false;
}
}
}

14 changes: 0 additions & 14 deletions src/main/java/roomescape/ExceptionController.java

This file was deleted.

74 changes: 74 additions & 0 deletions src/main/java/roomescape/Loader/ProductionDataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package roomescape.Loader;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import roomescape.member.Member;
import roomescape.member.MemberRepository;
import roomescape.reservation.ReservationRepository;
import roomescape.theme.Theme;
import roomescape.theme.ThemeRepository;
import roomescape.time.Time;
import roomescape.time.TimeRepository;

@Component
public class ProductionDataLoader implements CommandLineRunner {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.sql 파일을 활용하는 대신 CommandLineRunner를 활용했을 때 어떤 차이가 있었나요?

장단점은 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sql


장점 - 퀴리 문으로 작동해서 매우 빠르다
단점 - 퀴리 문법을 알아야 한다....

CommandLineRunner


장점 - 자바 로직으로 데이터를 만들 수 있다, 복잡한 엔티티 연관관계 보다 쉽게 만들 수 있다!
단점 - 속도가 느리다....

차이는 속도적인 차이가 존재하고 sql 엔티티 연관관계 구조를 매번 적용 시켜줘야 한다는 단점이 있는 거 같습니다!


private final MemberRepository memberRepository;
private final ThemeRepository themeRepository;
private final TimeRepository timeRepository;
private final ReservationRepository reservationRepository;

public ProductionDataLoader(
MemberRepository memberRepository,
ThemeRepository themeRepository,
TimeRepository timeRepository,
ReservationRepository reservationRepository
) {
this.memberRepository = memberRepository;
this.themeRepository = themeRepository;
this.timeRepository = timeRepository;
this.reservationRepository = reservationRepository;
}

@Override
@Transactional
public void run(String... args) throws Exception {
// 1. 관리자 계정 생성
if (memberRepository.findByEmail("admin").isEmpty()) {
Member admin = new Member("admin", "admin", "admin", "ADMIN");
memberRepository.save(admin);
System.out.println("관리자 계정이 생성되었습니다.");
}

// 2. 테마 데이터 생성
if (themeRepository.count() == 0) {
Theme theme1 = new Theme("테마1", "테마1입니다.");
Theme theme2 = new Theme("테마2", "테마2입니다.");
Theme theme3 = new Theme("테마3", "테마3입니다.");

themeRepository.save(theme1);
themeRepository.save(theme2);
themeRepository.save(theme3);
System.out.println("테마 데이터가 생성되었습니다.");
}

// 3. 시간 데이터 생성
if (timeRepository.count() == 0) {
Time time1 = new Time("10:00");
Time time2 = new Time("12:00");
Time time3 = new Time("14:00");
Time time4 = new Time("16:00");
Time time5 = new Time("18:00");
Time time6 = new Time("20:00");

timeRepository.save(time1);
timeRepository.save(time2);
timeRepository.save(time3);
timeRepository.save(time4);
timeRepository.save(time5);
timeRepository.save(time6);
System.out.println("시간 데이터가 생성되었습니다.");
}
}
}
107 changes: 107 additions & 0 deletions src/main/java/roomescape/Loader/TestDataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package roomescape.Loader;

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
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
@Transactional
public void run(String... args) throws Exception {
// 1. 관리자 계정 생성
if (memberRepository.findByEmail("admin").isEmpty()) {
Member admin = new Member("admin", "admin", "admin", "ADMIN");
memberRepository.save(admin);
System.out.println("관리자 계정이 생성되었습니다.");
}

// 2. 테마 데이터 생성
if (themeRepository.count() == 0) {
Theme theme1 = new Theme("테마1", "테마1입니다.");
Theme theme2 = new Theme("테마2", "테마2입니다.");
Theme theme3 = new Theme("테마3", "테마3입니다.");

themeRepository.save(theme1);
themeRepository.save(theme2);
themeRepository.save(theme3);
System.out.println("테마 데이터가 생성되었습니다.");
}

// 3. 시간 데이터 생성
if (timeRepository.count() == 0) {
Time time1 = new Time("10:00");
Time time2 = new Time("12:00");
Time time3 = new Time("14:00");
Time time4 = new Time("16:00");
Time time5 = new Time("18:00");
Time time6 = new Time("20:00");

timeRepository.save(time1);
timeRepository.save(time2);
timeRepository.save(time3);
timeRepository.save(time4);
timeRepository.save(time5);
timeRepository.save(time6);
System.out.println("시간 데이터가 생성되었습니다.");
}

if (reservationRepository.count() == 0) {
Member admin = memberRepository.findByEmail("admin")
.orElseThrow(() -> new RuntimeException("Admin not found"));

Time time1 = timeRepository.findById(1L).orElseThrow();
Time time2 = timeRepository.findById(2L).orElseThrow();
Time time3 = timeRepository.findById(3L).orElseThrow();

Theme theme1 = themeRepository.findById(1L).orElseThrow();
Theme theme2 = themeRepository.findById(2L).orElseThrow();
Theme theme3 = themeRepository.findById(3L).orElseThrow();

Reservation reservation1 = new Reservation("", "2024-03-01", time1, theme1, admin);
Reservation reservation2 = new Reservation("", "2024-03-01", time2, theme2, admin);
Reservation reservation3 = new Reservation("", "2024-03-01", time3, theme3, admin);

reservationRepository.save(reservation1);
reservationRepository.save(reservation2);
reservationRepository.save(reservation3);

Reservation reservation4 = new Reservation("브라운", "2024-03-01", time1, theme2);

reservationRepository.save(reservation4);
System.out.println("예약 데이터가 생성되었습니다.");
}

System.out.println("초기 데이터 로딩이 완료되었습니다.");
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package roomescape.exception;

public class AuthenticationException extends RuntimeException {

public AuthenticationException(String message) {
super(message);
}
}
4 changes: 4 additions & 0 deletions src/main/java/roomescape/exception/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package roomescape.exception;

public record ErrorResponse(String message) {
}
36 changes: 36 additions & 0 deletions src/main/java/roomescape/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package roomescape.exception;

import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(new ErrorResponse(e.getMessage()));
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(e.getMessage());
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
return ResponseEntity.internalServerError()
.body(new ErrorResponse("오류가 발생했습니다."));
}

@ExceptionHandler(ExpiredJwtException.class)
public ResponseEntity<ErrorResponse> handleExpiredJwtException(ExpiredJwtException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new ErrorResponse("토큰 만료 시간 초과"));
}
}

11 changes: 11 additions & 0 deletions src/main/java/roomescape/infrastructure/AuthMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package roomescape.infrastructure;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Retention 어노테이션은 무엇인가요?

public @interface AuthMember {
}
Loading