Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 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
55bc1b7
del : 로그 관련 설정
Jan 20, 2026
52a80f2
fix : 로그인 관련 401 상태코드로 변경
nonactress Jan 20, 2026
ba0194b
fix : transactional 적용
nonactress Jan 21, 2026
0e1576d
fix : 메소드 분리
nonactress Jan 21, 2026
faf90f5
fix : 멤버와 관리자 로직 분리 조건
nonactress Jan 21, 2026
137c138
fix : 소프트 딜리트 관련 삭제 방어코드
nonactress Jan 21, 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
10 changes: 6 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ 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'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

실행시켜서 몇개 api들 동작시켜보면 현재 로그가 매우매우매우 많이 뜨는 걸 확인할 수 있을텐데요
요 줄을 없애면 해결된답니다.
로그가 너무 많으면 확인하기 힘드니 적당히 보이는 것이 좋을 것 같아요!

Copy link
Copy Markdown
Author

@nonactress nonactress Jan 20, 2026

Choose a reason for hiding this comment

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

저도 콘솔 창에 너무 많은 로그가 있어 해윤님이 말씀하신 방향으로 정리해보겠습니다!!!

위 코드를 반영해보니 컴파일에러가 발생하여 찾아보니
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' : SQL 대신 자바 객체(Entity)를 조작합니다. 인터페이스만 선언하면 Spring Data JPA가 실행 시점에 적절한 SQL을 자동으로 생성합니다.
와 같다고 하여
리뷰어님이 말씀하신 방향으로 수정하기 위해
#logging.level.org.hibernate.SQL=DEBUG
를 주석 처리해보았습니다!!
저도 덕분에 어떤 의존성이 어떤 역할을 하는지 확실히 알 수 있는 기회가 된 것 같습니다!

반영 커밋 :


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
14 changes: 0 additions & 14 deletions src/main/java/roomescape/ExceptionController.java

This file was deleted.

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

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

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthMember {
}
44 changes: 44 additions & 0 deletions src/main/java/roomescape/auth/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package roomescape.auth;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import roomescape.exception.AuthenticationException;
import roomescape.infrastructure.JwtTokenProvider;
import roomescape.member.Member;
import roomescape.member.MemberService;

@Service
@Transactional(readOnly = true)
public class AuthService {
private final JwtTokenProvider jwtTokenProvider;
private final MemberService memberService;

public AuthService(JwtTokenProvider jwtTokenProvider, MemberService memberService) {
this.jwtTokenProvider = jwtTokenProvider;
this.memberService = memberService;
}

public Member extractMember(HttpServletRequest request) {
String token = extractTokenFromCookie(request);

if (!jwtTokenProvider.validateToken(token)) {
throw new AuthenticationException("인증되지 않은 사용자입니다.");
}

return memberService.findByToken(token);
}

private String extractTokenFromCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("token".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
}
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) {
}
28 changes: 28 additions & 0 deletions src/main/java/roomescape/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package roomescape.exception;

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

@ControllerAdvice
public class 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("오류가 발생했습니다."));
}
}
103 changes: 103 additions & 0 deletions src/main/java/roomescape/infrastructure/DataInitializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package roomescape.infrastructure;

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.Reservation;
import roomescape.reservation.ReservationRepository;
import roomescape.theme.Theme;
import roomescape.theme.ThemeRepository;
import roomescape.time.Time;
import roomescape.time.TimeRepository;

@Component
public class DataInitializer implements CommandLineRunner {

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

public DataInitializer(
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("초기 데이터 로딩이 완료되었습니다.");
}
}
48 changes: 48 additions & 0 deletions src/main/java/roomescape/infrastructure/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package roomescape.infrastructure;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtTokenProvider {
@Value("${security.jwt.token.secret-key}")
private String secretKey;
@Value("${security.jwt.token.expire-length:3600000}")
private long 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 {
Jws<Claims> claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
return !claims.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}

29 changes: 25 additions & 4 deletions src/main/java/roomescape/member/Member.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
package roomescape.member;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "member")
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String name;

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

@Column(nullable = false)
private String password;

@Column(nullable = false)
private String role;

@Column(name = "deleted")
private boolean deleted = false;

protected Member() {
}

public Member(Long id, String name, String email, String role) {
this.id = id;
this.name = name;
Expand All @@ -33,10 +58,6 @@ public String getEmail() {
return email;
}

public String getPassword() {
return password;
}

public String getRole() {
return role;
}
Expand Down
32 changes: 30 additions & 2 deletions src/main/java/roomescape/member/MemberController.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
package roomescape.member;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import roomescape.auth.AuthMember;

import java.net.URI;

@RestController
public class MemberController {
private MemberService memberService;
private final MemberService memberService;

public MemberController(MemberService memberService) {
this.memberService = memberService;
}

@PostMapping("/login")
public ResponseEntity<Void> login(
@RequestBody MemberRequest memberRequest,
HttpServletResponse response) {

String tokenValue = memberService.login(memberRequest);

Cookie cookie = new Cookie("token", tokenValue);
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setMaxAge(3600);
response.addCookie(cookie);

return ResponseEntity.ok()
.header("Keep-Alive", "timeout=60")
.build();
}

@GetMapping("/login/check")
public ResponseEntity<MemberResponse> check(
@AuthMember Member member
) {
return ResponseEntity.ok()
.header("Connection", "keep-alive")
.header("Keep-Alive", "timeout=60")
.body(new MemberResponse(null, member.getName(), null));
}

@PostMapping("/members")
public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) {
MemberResponse member = memberService.createMember(memberRequest);
Expand Down
Loading