Skip to content

[Telos] week 2 과제 완료 #14

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 37 commits into
base: telos
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
db45fad
fix: 의존성 충돌 해결
DWL21 Apr 11, 2025
98f3c7a
feat: 인큐베이팅 엔초조 1단계 페어프로그래밍 구
kwonyj1022 Apr 11, 2025
debe495
[Feat] level2 login refactoring, reservation
rover0811 Apr 16, 2025
62d75e5
[Feat] level3 implements HandlerInterceptor, admin check
rover0811 Apr 16, 2025
461209e
[Fix] code review 반영
rover0811 Apr 18, 2025
386079f
[Migrate] Member data access from JdbcTemplate to JPA
rover0811 Apr 27, 2025
2fac15f
[Migrate] Time management to use JPA Repository
rover0811 Apr 27, 2025
147efad
[Migrate] to use JPA with ThemeRepository and annotate Theme entity
rover0811 Apr 27, 2025
9d330f1
[Migrate] Reservations to use JPA and add member-related logic
rover0811 Apr 27, 2025
dafa9d6
[Migrate] dependencies, add waiting system, and update schema
rover0811 Apr 27, 2025
1bcdc24
[Add] JPA test for entity persistence and reservation retrieval
rover0811 Apr 27, 2025
09cc8c4
[Add] repositories and response class for reservation handling
rover0811 Apr 27, 2025
53e3a90
[Add] waiting feature to manage and prioritize reservations
rover0811 Apr 27, 2025
181b9df
[Move] 공통 모듈 및 보안 관련 클래스 패키지 이동
rover0811 Apr 29, 2025
a2333e7
[Move] AppConstants 클래스 패키지 이동, 마지막 줄 개행 해결
rover0811 Apr 29, 2025
a6b8411
[Feat] 예약 요청에 대한 유효성 검증 추가
rover0811 Apr 29, 2025
a7232d9
[Refactor] ReservationResponse에서 불필요한 repository 제거 및 코드 간소화
rover0811 Apr 29, 2025
11fa4be
[Refactor] 기존 메서드 제거
rover0811 Apr 29, 2025
0f5b37b
[Refactor] Controller에서 MemberRepository 의존성 제거 및 ID 기반 메서드 추가
rover0811 Apr 29, 2025
b2c3671
[Feat] 사용자 정의 예외 클래스 및 글로벌 예외 처리 로직 추가
rover0811 Apr 29, 2025
c5d7e14
[Refactor] Reservation 엔티티에서 name 필드 제거
rover0811 Apr 29, 2025
07e286c
[Refactor] Theme 비즈니스 로직을 Service 계층으로 분리
rover0811 Apr 29, 2025
ec08edc
[Refactor] @LoginMember 어노테이션 기반으로 회원 인증 리팩토링
rover0811 Apr 30, 2025
27edac2
[Refactor] 기존 클래스들을 Java Record로 변경
rover0811 Apr 30, 2025
e8b60c6
[Refactor] 불필요한 주석 제거 및 코드 가독성 개선
rover0811 Apr 30, 2025
bd90307
[Add] 예약 상태를 나타내는 ReservationStatus enum 생성
rover0811 Apr 30, 2025
ee75cba
[Add] UserInfo 인터페이스 추가
rover0811 Apr 30, 2025
3773b13
[Add] SwaggerConfig 추가 및 OpenAPI 의존성 적용
rover0811 Apr 30, 2025
c143405
[Fix] IllegalStateException 처리 핸들러 추가 및 대기 예외 로직 수정
rover0811 May 3, 2025
eba4f6d
[Refactor] Member 대신 UserInfo를 사용하도록 코드 수정
rover0811 May 3, 2025
3e34dae
[Refactor] 불필요한 Repository 메서드 제거
rover0811 May 3, 2025
2e58a63
[Refactor] existsByMemberIdAndDateAndThemeIdAndTimeId 쿼리 메서드 간소화
rover0811 May 3, 2025
368e4e0
[Fix] Reservation의 getName 메서드 반환 로직 수정
rover0811 May 3, 2025
886c2ef
[Fix] 잘못된 인증 정보 처리 로직 수정
rover0811 May 3, 2025
a32ffd7
[Fix] 중복 이메일 등록 시 예외 처리 추가
rover0811 May 3, 2025
dbe39e8
[Add] 각 서비스에 대한 테스트 코드 추가
rover0811 May 4, 2025
3d6ab49
[Remove] TimeRepository 삭제
rover0811 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
9 changes: 4 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ 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 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
implementation 'ch.qos.logback:logback-access:1.4.14'
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.6'
implementation 'io.jsonwebtoken:jjwt-gson:0.12.6'
Expand All @@ -33,7 +33,6 @@ dependencies {

testImplementation 'org.springframework.boot:spring-boot-starter-test:3.4.4'
testImplementation 'io.rest-assured:rest-assured:5.5.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.1'
}

tasks.named('test') {
Expand Down
15 changes: 0 additions & 15 deletions src/main/java/com/yourssu/roomescape/ExceptionController.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.yourssu.roomescape.auth;

import com.yourssu.roomescape.common.AppConstants;
import com.yourssu.roomescape.common.security.JwtTokenProvider;
import com.yourssu.roomescape.member.Member;
import com.yourssu.roomescape.member.MemberService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class AdminAuthInterceptor implements HandlerInterceptor {

private final JwtTokenProvider jwtTokenProvider;
private final MemberService memberService;

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

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Authentication required");
return false;
}

String token = "";
for (Cookie cookie : cookies) {
if (cookie.getName() != null && AppConstants.TOKEN_COOKIE_NAME.equals(cookie.getName())) {
token = cookie.getValue();
break;
}
}

if (token.isEmpty()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Token is missing");
return false;
}

try {
String email = jwtTokenProvider.getPayload(token);
Member member = memberService.findByEmail(email);

if (member == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("User not found");
return false;
}

if (member.getRole() == null || !AppConstants.ROLE_ADMIN.equals(member.getRole())) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 상태 코드 사용
response.getWriter().write("Admin privileges required");
return false;
}
return true;
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid token: " + e.getMessage());
return false;
}
}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.yourssu.roomescape.auth;

public record LoginCheckResponse(String name) {}
10 changes: 10 additions & 0 deletions src/main/java/com/yourssu/roomescape/auth/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.yourssu.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 LoginMember {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.yourssu.roomescape.auth;

import com.yourssu.roomescape.common.AppConstants;
import com.yourssu.roomescape.common.exception.UnauthorizedException;
import com.yourssu.roomescape.common.security.JwtTokenProvider;
import com.yourssu.roomescape.member.Member;
import com.yourssu.roomescape.member.MemberService;
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;

@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
private final MemberService memberService;
private final JwtTokenProvider jwtTokenProvider;

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

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(LoginMember.class) && UserInfo.class.isAssignableFrom(parameter.getParameterType());
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
Cookie[] cookies = request.getCookies();

if (cookies == null) {
return null;
}

String token = "";

for (Cookie cookie : cookies) {
if (cookie.getName() != null && AppConstants.TOKEN_COOKIE_NAME.equals(cookie.getName())) {
token = cookie.getValue();
break;
}
}

if (token.isEmpty()) {
return null;
}

try {
String email = jwtTokenProvider.getPayload(token);
return memberService.findByEmail(email);
} catch (Exception e) {
throw new UnauthorizedException("Invalid credentials");
}
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/yourssu/roomescape/auth/LoginRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.yourssu.roomescape.auth;

public class LoginRequest {

private String email;
private String password;

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}

}
14 changes: 14 additions & 0 deletions src/main/java/com/yourssu/roomescape/auth/TokenDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yourssu.roomescape.auth;

public class TokenDto {

private String token;

public TokenDto(final String token) {
this.token = token;
}

public String getToken() {
return token;
}
}
8 changes: 8 additions & 0 deletions src/main/java/com/yourssu/roomescape/auth/UserInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.yourssu.roomescape.auth;

public interface UserInfo {
Long getId();
String getName();
String getEmail();
String getRole();
}
11 changes: 11 additions & 0 deletions src/main/java/com/yourssu/roomescape/common/AppConstants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.yourssu.roomescape.common;

public class AppConstants {
public static final String TOKEN_COOKIE_NAME = "token";

public static final String ROLE_ADMIN = "ADMIN";

private AppConstants() {
throw new AssertionError("Constants class should not be instantiated");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.yourssu.roomescape.common.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info()
.title("API 문서")
.description("API 명세서입니다.")
.version("v1.0.0"));
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/yourssu/roomescape/common/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.yourssu.roomescape.common.config;

import com.yourssu.roomescape.auth.AdminAuthInterceptor;
import com.yourssu.roomescape.auth.LoginMemberArgumentResolver;
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 java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

private final LoginMemberArgumentResolver loginMemberArgumentResolver;
private final AdminAuthInterceptor adminAuthInterceptor;

public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver, AdminAuthInterceptor adminAuthInterceptor) {
this.loginMemberArgumentResolver = loginMemberArgumentResolver;
this.adminAuthInterceptor = adminAuthInterceptor;
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginMemberArgumentResolver);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminAuthInterceptor).addPathPatterns("/admin/**");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.yourssu.roomescape;
package com.yourssu.roomescape.common.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.yourssu.roomescape.common.exception;

public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.yourssu.roomescape.common.exception;

public class ErrorResponse {
private String message;
private String details;

public ErrorResponse(String message, String details) {
this.message = message;
this.details = details;
}

public String getDetails() {
return details;
}

public String getMessage() {
return message;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.yourssu.roomescape.common.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 ExceptionController {

@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequest(BadRequestException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse("잘못된 요청입니다", e.getMessage()));
}

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException e) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("리소스를 찾을 수 없습니다", e.getMessage()));
}

@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ErrorResponse> handleUnauthorized(UnauthorizedException e) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(new ErrorResponse("인증이 필요합니다", e.getMessage()));
}

@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<ErrorResponse> handleForbidden(ForbiddenException e) {
return ResponseEntity
.status(HttpStatus.FORBIDDEN)
.body(new ErrorResponse("접근 권한이 없습니다", e.getMessage()));
}

@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<ErrorResponse> handleIllegalState(IllegalStateException e) {
return ResponseEntity
.status(HttpStatus.CONFLICT)
.body(new ErrorResponse("요청 처리 불가", e.getMessage()));
}

// 기타 예외에 대한 폴백 핸들러
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleServerError(Exception e) {
// 예기치 않은 서버 오류는 로깅 필요
e.printStackTrace();
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("서버 오류가 발생했습니다", "시스템 관리자에게 문의하세요"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.yourssu.roomescape.common.exception;

public class ForbiddenException extends RuntimeException {
public ForbiddenException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.yourssu.roomescape.common.exception;

public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Loading