Skip to content

[Logan] Spring Data JPA 4~6단계 미션 #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 45 commits into
base: logan
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
db45fad
fix: 의존성 충돌 해결
DWL21 Apr 11, 2025
d5566d2
feat: 1단계 - 로그인
yueunfive Apr 15, 2025
cdffb4f
feat: 2단계 - 로그인 리팩터링
yueunfive Apr 16, 2025
11b44d1
feat: 3단계 - 관리자 기능
yueunfive Apr 16, 2025
c6b8613
refactor: resolveArgument 반환 타입 변경
yueunfive Apr 16, 2025
c9d4f78
Merge branch 'logan' of https://github.com/yueunfive/java-sprout into…
yueunfive Apr 16, 2025
96b1a20
refactor: 쿠키 유효기간 설정 제거
yueunfive Apr 18, 2025
1ae7379
refactor: RowMapper 변수 분리
yueunfive Apr 18, 2025
1215822
refactor: GlobalExceptionHandler 호출을 위해 상위 @ControllerAdvice 제거
yueunfive Apr 18, 2025
5de5b63
refactor: 예약 생성 로직 수정
yueunfive Apr 18, 2025
15a68e8
refactor: @ExceptionHandler(Exception.class) 추가
yueunfive Apr 18, 2025
9b8ad45
refactor: 예약 생성 로직 수정
yueunfive Apr 18, 2025
3ba6966
refactor: DTO class to record
yueunfive Apr 18, 2025
3dbf00f
feat: 4단계: JPA 전환
yueunfive Apr 19, 2025
901bd5e
feat: 5단계 - 내 예약 목록 조회
yueunfive Apr 19, 2025
9409618
feat: 6단계 - 예약 대기 기능
yueunfive Apr 20, 2025
da5e586
refactor: 예약 상태 관리 구조 개선
yueunfive Apr 25, 2025
6ee806a
refactor: dao to repository
yueunfive Apr 25, 2025
b058fd2
refactor: merge conflict resolve
yueunfive Apr 25, 2025
bddf51d
refactor: remove .DS_Store
yueunfive Apr 25, 2025
4dc79da
refactor: API 변경사항에 맞게 타임리프 JS 수정
yueunfive Apr 28, 2025
8fc5bc8
refactor: Spring Validation 적용
yueunfive Apr 28, 2025
25e7439
refactor: dto 내 정적 팩토리 메서드 추가(+a)
yueunfive Apr 28, 2025
e25c043
refactor: 엔티티 기본 생성자 - public to protected
yueunfive Apr 28, 2025
44e36f0
refactor: schema.sql - CREATE문 제거
yueunfive Apr 29, 2025
8ab2938
feat: 예약 취소 기능 구현
yueunfive Apr 30, 2025
b487f68
feat: admin 예약 추가 기능 구현
yueunfive May 1, 2025
e65be6d
feat: admin 예약 목록 조회 기능 구현
yueunfive May 1, 2025
d8dd999
refactor: admin 페이지 접근 관련 에외 메시지 처리
yueunfive May 1, 2025
ad56b75
refactor: 예약 삭제 권한 수정
yueunfive May 1, 2025
d0b4b25
refactor: N+1 해결 with fetch join
yueunfive May 1, 2025
05739f8
refactor: 예약 생성시 중복체크
yueunfive May 1, 2025
df7a4d0
refactor: 이메일 중복 방지 및 형식 검증 기능 추가
yueunfive May 3, 2025
335cc99
fix: Time 및 Theme 삭제 시 예외 처리 추가
yueunfive May 3, 2025
e064a8d
refactor: Member 역할 enum 적용 및 isAdmin 메서드 도입
yueunfive May 3, 2025
ea79c7f
fix: SQL 초기 데이터와 충돌하지 않도록 테스트 코드 수정
yueunfive May 3, 2025
4c3e11e
fix: 예약 없는 슬롯(날짜/시간/테마)에 대한 대기 신청 방지 로직 추가
yueunfive May 3, 2025
75b32e6
test: ReservationServiceTest 추가 및 테스트용 DB 분리
yueunfive May 3, 2025
b4c2c18
test: MemberServiceTest 추가
yueunfive May 3, 2025
18dd7a8
test: AuthServiceTest 추가
yueunfive May 4, 2025
f2e6f8b
test: TimeServiceTest 추가
yueunfive May 4, 2025
b1e72b8
fix: 시간 중복 저장 방지 로직 추가
yueunfive May 4, 2025
f3b1434
test: ThemeServiceTest 추가
yueunfive May 4, 2025
ed6767a
fix: 테마 중복 저장 방지 로직 추가
yueunfive May 4, 2025
01ea224
test: 중복 time_value('10:00') 회피를 위해 테스트 데이터 수정
yueunfive May 4, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
.DS_Store

### STS ###
.apt_generated
Expand Down
5 changes: 2 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter:3.4.4'
implementation 'org.springframework.boot:spring-boot-starter-web:3.4.4'
implementation 'org.springframework.boot:spring-boot-starter-jdbc:3.4.4'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.4.4'

implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.5.1'
implementation 'org.springframework.boot:spring-boot-starter-validation'

implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.6'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.yourssu.roomescape.auth;

import com.yourssu.roomescape.exception.CustomException;
import com.yourssu.roomescape.exception.ErrorCode;
import com.yourssu.roomescape.jwt.TokenExtractor;
import com.yourssu.roomescape.member.Member;
import jakarta.servlet.http.Cookie;
Expand All @@ -25,9 +23,10 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
String token = TokenExtractor.extractTokenFromCookies(cookies);
Member member = authService.checkLogin(token);

if(!"ADMIN".equals(member.getRole())) {
if(!member.isAdmin()) {
response.setStatus(401);
response.getWriter().write("해당 접근에 대한 권한이 없습니다.");
response.setContentType("application/json; charset=UTF-8");
response.getWriter().write("{\"code\":\"NOT_ADMIN\",\"message\":\"관리자 권한이 필요합니다.\"}");
return false;
}

Expand Down
5 changes: 2 additions & 3 deletions src/main/java/com/yourssu/roomescape/auth/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.yourssu.roomescape.auth;

import com.yourssu.roomescape.auth.dto.CheckLoginResponse;
import com.yourssu.roomescape.auth.dto.LoginRequest;
import com.yourssu.roomescape.jwt.TokenExtractor;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -10,8 +12,6 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;

@RestController
public class AuthController {

Expand All @@ -36,7 +36,6 @@ public ResponseEntity<Void> login(@RequestBody LoginRequest loginRequest, HttpSe
public ResponseEntity<CheckLoginResponse> loginCheck(HttpServletRequest request) {

Cookie[] cookies = request.getCookies();
System.out.println(Arrays.toString(cookies));
String token = TokenExtractor.extractTokenFromCookies(cookies);

String name = authService.checkLogin(token).getName();
Expand Down
17 changes: 11 additions & 6 deletions src/main/java/com/yourssu/roomescape/auth/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
package com.yourssu.roomescape.auth;

import com.yourssu.roomescape.auth.dto.LoginRequest;
import com.yourssu.roomescape.exception.CustomException;
import com.yourssu.roomescape.exception.ErrorCode;
import com.yourssu.roomescape.jwt.TokenProvider;
import com.yourssu.roomescape.member.Member;
import com.yourssu.roomescape.member.MemberDao;
import com.yourssu.roomescape.member.MemberRepository;
import org.springframework.stereotype.Service;

@Service
public class AuthService {

private final MemberDao memberDao;
private final MemberRepository memberRepository;
private final TokenProvider tokenProvider;

public AuthService(TokenProvider tokenProvider, MemberDao memberDao) {
public AuthService(TokenProvider tokenProvider, MemberRepository memberRepository) {
this.tokenProvider = tokenProvider;
this.memberDao = memberDao;
this.memberRepository = memberRepository;
}

public String login(LoginRequest loginRequest) {
Member member = memberDao.findByEmailAndPassword(loginRequest.email(), loginRequest.password());
Member member = memberRepository.findByEmailAndPassword(loginRequest.email(), loginRequest.password())
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
return tokenProvider.createToken(member.getEmail());
Comment on lines +23 to 25
Copy link
Collaborator

Choose a reason for hiding this comment

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

토큰을 만들 때 멤버의 email 정보로 만들고 있는데 , 지금 유은님 코드 상으로는 동일한 이메일로 두 개 이상의 Member가 생길 수 있어요. 이러면 어떻게 될까요?

Copy link
Author

Choose a reason for hiding this comment

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

public Member checkLogin(String token) {
        String payload = tokenProvider.getPayload(token);
        return memberRepository.findByEmail(payload)
                .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
    }

해당 부분에서 token에서 추출한 payload(email)가 DB에 중복으로 존재할 경우,
findByEmail()이 2개 이상의 결과를 반환하면서 IncorrectResultSizeDataAccessException 예외가 발생합니다.
👉 이메일 중복 문제가 발생하지 않도록 Member 엔티티의 email 필드에 @column(unique = true)를 적용하여 DB 레벨에서 중복을 방지하고, 회원가입 로직에서는 existsByEmail을 통해 사전 중복 검사를 수행한 뒤, 중복 시 CustomException을 발생시키도록 보완했습니다.

}

public Member checkLogin(String token) {
String payload = tokenProvider.getPayload(token);
return memberDao.findByEmail(payload);
return memberRepository.findByEmail(payload)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.yourssu.roomescape.auth;
package com.yourssu.roomescape.auth.dto;

public record CheckLoginResponse(String name) {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.yourssu.roomescape.auth;
package com.yourssu.roomescape.auth.dto;

public record LoginRequest(String email, String password) {
}
25 changes: 22 additions & 3 deletions src/main/java/com/yourssu/roomescape/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,31 @@
public enum ErrorCode {

INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버에 문제가 생겼습니다."),

// Auth
COOKIE_NOT_FOUND(HttpStatus.UNAUTHORIZED, "쿠키가 존재하지 않습니다."),
TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "쿠키에 토큰이 존재하지 않습니다."),

// Member
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자의 정보를 찾을 수 없습니다."),
INVALID_RESERVATION_REQUEST(HttpStatus.BAD_REQUEST, "예약 요청 값이 누락되었습니다."),
UNAUTHORIZED_ACCESS(HttpStatus.FORBIDDEN, "해당 접근에 대한 권한이 없습니다."),
DUPLICATE_MEMBER_NAME_EXISTS(HttpStatus.INTERNAL_SERVER_ERROR, "중복된 이름이 존재합니다. 관리자에게 문의하세요.");
NOT_ADMIN(HttpStatus.FORBIDDEN, "관리자 권한이 필요합니다."),
EMAIL_ALREADY_EXISTS(HttpStatus.CONFLICT, "이메일 정보가 이미 존재합니다."),

// Reservation
RESERVATION_NOT_FOUND(HttpStatus.NOT_FOUND, "예약 정보를 찾을 수 없습니다."),
NO_PERMISSION_FOR_RESERVATION(HttpStatus.FORBIDDEN, "해당 예약에 대한 권한이 없습니다."),
RESERVATION_ALREADY_EXISTS(HttpStatus.CONFLICT, "예약 정보가 이미 존재합니다."),
CANNOT_WAIT_WITHOUT_RESERVED(HttpStatus.BAD_REQUEST, "예약이 없는 상태에서는 대기를 신청할 수 없습니다."),

// Time
TIME_NOT_FOUND(HttpStatus.NOT_FOUND, "시간 정보를 찾을 수 없습니다."),
TIME_IN_USE(HttpStatus.BAD_REQUEST, "해당 시간은 예약이 존재하여 삭제할 수 없습니다."),
TIME_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 시간입니다."),

// Theme
THEME_NOT_FOUND(HttpStatus.NOT_FOUND, "테마 정보를 찾을 수 없습니다."),
THEME_IN_USE(HttpStatus.BAD_REQUEST, "해당 테마는 예약이 존재하여 삭제할 수 없습니다."),
THEME_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 테마입니다.");

private final HttpStatus status;
private final String message;
Expand Down
36 changes: 27 additions & 9 deletions src/main/java/com/yourssu/roomescape/member/Member.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
package com.yourssu.roomescape.member;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
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;

@Column(unique = true)
private String email;

private String password;
private String role;

public Member(Long id, String name, String email, String role) {
this.id = id;
this.name = name;
this.email = email;
this.role = role;
}
@Enumerated(EnumType.STRING)
private MemberRole role;

public Member(String name, String email, String password, String role) {
public Member(String name, String email, String password, MemberRole role) {
this.name = name;
this.email = email;
this.password = password;
this.role = role;
}

protected Member() {

}

public Long getId() {
return id;
}
Expand All @@ -37,7 +51,11 @@ public String getPassword() {
return password;
}

public String getRole() {
public MemberRole getRole() {
return role;
}

public boolean isAdmin() {
return this.role == MemberRole.ADMIN;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.yourssu.roomescape.member;

import com.yourssu.roomescape.member.dto.MemberRequest;
import com.yourssu.roomescape.member.dto.MemberResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -18,9 +21,9 @@ public MemberController(MemberService memberService) {
}

@PostMapping("/members")
public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) {
public ResponseEntity createMember(@RequestBody @Valid MemberRequest memberRequest) {
MemberResponse member = memberService.createMember(memberRequest);
return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member);
return ResponseEntity.created(URI.create("/members/" + member.id())).body(member);
}

@PostMapping("/logout")
Expand Down
80 changes: 0 additions & 80 deletions src/main/java/com/yourssu/roomescape/member/MemberDao.java

This file was deleted.

13 changes: 13 additions & 0 deletions src/main/java/com/yourssu/roomescape/member/MemberRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yourssu.roomescape.member;

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

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findByEmailAndPassword(String email, String password);
Optional<Member> findByEmail(String email);
Optional<Member> findByName(String name);
boolean existsByEmail(String email);
}
19 changes: 0 additions & 19 deletions src/main/java/com/yourssu/roomescape/member/MemberRequest.java

This file was deleted.

25 changes: 0 additions & 25 deletions src/main/java/com/yourssu/roomescape/member/MemberResponse.java

This file was deleted.

5 changes: 5 additions & 0 deletions src/main/java/com/yourssu/roomescape/member/MemberRole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.yourssu.roomescape.member;

public enum MemberRole {
ADMIN, USER
}
Loading