Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions src/main/java/org/example/expert/client/WeatherClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ public String getTodayWeather() {
WeatherDto[] weatherArray = responseEntity.getBody();
if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode());
} else {
if (weatherArray == null || weatherArray.length == 0) {
}

// 리얼 else문 제거하기
if (weatherArray == null || weatherArray.length == 0) {
throw new ServerException("날씨 데이터가 없습니다.");
}

}

String today = getCurrentDate();
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/org/example/expert/config/AdminCheckInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.example.expert.config;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.example.expert.domain.common.exception.InvalidRequestException;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.time.LocalDateTime;

@Slf4j
@Component
public class AdminCheckInterceptor implements HandlerInterceptor {

@Override

// 역할 가져오기
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


Object Role = request.getAttribute("userRole");

// 역할이랑 일치하는지 확인
if (Role == null || !"Admin".equals(Role.toString())) {
throw new InvalidRequestException(" 어드민 권한이 필요합니다.");
}


//로깅 인포 추가!
log.info("인증된 관리자 접근, Time:{}, URL:{}",
LocalDateTime.now(), request.getRequestURL());

// 특이사항 없으면 true
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
import org.example.expert.domain.user.enums.UserRole;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
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 AuthUserArgumentResolver implements HandlerMethodArgumentResolver {

@Override
Expand Down Expand Up @@ -44,3 +47,11 @@ public Object resolveArgument(
return new AuthUser(userId, email, userRole);
}
}

/**
* AuthUserArgumentResolver의 역할:
* * 1. 전달자 역할: JwtFilter에서 HttpServletRequest에 저장한 유저 정보(ID, 이메일, 권한)를 꺼내옵니다.
* 2. 객체 변환: 꺼낸 정보들을 컨트롤러가 바로 사용할 수 있도록 AuthUser 객체로 변환(포장)합니다.
* 3. 자동 주입: 컨트롤러 메서드의 파라미터에 @Auth 어노테이션이 있으면, 생성된 AuthUser 객체를 자동으로 넘겨줍니다.
* * 결과적으로 컨트롤러에서 복잡한 로직 없이 유저 정보를 '전달'받아 바로 쓸 수 있게 해주는 고마운 배달부입니다!
*/
4 changes: 3 additions & 1 deletion src/main/java/org/example/expert/config/JwtFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha

httpRequest.setAttribute("userId", Long.parseLong(claims.getSubject()));
httpRequest.setAttribute("email", claims.get("email"));
httpRequest.setAttribute("userRole", claims.get("userRole"));
httpRequest.setAttribute("userRole", userRole.name());

// userRole.name()으로 수정 ; Resolver와 jwtfilter 의 형변환 타입 일치

if (url.startsWith("/admin") && !UserRole.ADMIN.equals(userRole)) {
log.warn("권한 부족: userId={}, role={}, URI={}", claims.getSubject(), userRole, url);
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/org/example/expert/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.example.expert.config;

import lombok.RequiredArgsConstructor;
import org.example.expert.domain.common.annotation.Auth;
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
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

//HttpServletRequest -> AuthUser -> @Auth AuthUser authUser로 전달하는 객체 생성

private final AuthUserArgumentResolver authUserArgumentResolver;
private final AdminCheckInterceptor adminCheckInterceptor;

/*
* 4. 스프링이 컨트롤러의 파라미터를 처리할 때 사용할 '정보 리스트'에 커스텀 Resolver를 추가하는 작업
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(authUserArgumentResolver);
}


//인터셉터 추가 /스프링이 제공하는 레지스트리 및 작성한 adminCheckInterceptor(admin 관련 주소 전부 가져오기)를 활용
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(adminCheckInterceptor).addPathPatterns("/admin/**");


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ public class AuthService {
@Transactional
public SignupResponse signup(SignupRequest signupRequest) {

// 인코딩 전에 검증 로직을 가장먼저 시행할 수 있게 순서를 맨위로 지정한다. lv2 - 1
if (userRepository.existsByEmail(signupRequest.getEmail())) {
throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}


String encodedPassword = passwordEncoder.encode(signupRequest.getPassword());

UserRole userRole = UserRole.of(signupRequest.getUserRole());

if (userRepository.existsByEmail(signupRequest.getEmail())) {
throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}

User newUser = new User(
signupRequest.getEmail(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,22 @@ public class ManagerService {
private final UserRepository userRepository;
private final TodoRepository todoRepository;


// todo에 user가 null인 경우는 보이지 않음... 조건문을 한번 추가해보겠음.
@Transactional
public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
// 일정을 만든 유저
User user = User.fromAuthUser(authUser);
Todo todo = todoRepository.findById(todoId)
.orElseThrow(() -> new InvalidRequestException("Todo not found"));

if (!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {

//user가 null인 경우를 작성했으나 하단 메시지와 동일해서 OR문으로 재작성
// if(todo.getUser()==null) {
// throw new InvalidRequestException("일정을 생성한 유저만 담당자를 지정할 수 있습니다.");
// }

if (todo.getUser()==null||!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
throw new InvalidRequestException("일정을 생성한 유저만 담당자를 지정할 수 있습니다.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.example.expert.domain.todo.entity.Todo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -11,11 +12,23 @@

public interface TodoRepository extends JpaRepository<Todo, Long> {

@Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC")

//목록조회
// todo entity 불러오기 // todo와 관련있는 유저정보 불러오고 수정시간 내림차순으로 정렬하기

// "LEFT JOIN FETCH t.user u" fetch join문을 entityGraph로 변경 : user table 조회 후 Mapping
@EntityGraph(attributePaths = {"user"})
@Query("SELECT t FROM Todo t ORDER BY t.modifiedAt DESC")
// page 형태로
Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);


// 상세조회
// todo entity 불러오기 // todo와 관련있는 유저정보 불러오는데 특정id에 관한것만 불러오기

// "LEFT JOIN FETCH t.user " fetch join문을 entityGraph로 변경 : user table 조회 후 id Mapping
@EntityGraph(attributePaths = {"user"})
@Query("SELECT t FROM Todo t " +
"LEFT JOIN FETCH t.user " +
"WHERE t.id = :todoId")
Optional<Todo> findByIdWithUser(@Param("todoId") Long todoId);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package org.example.expert.domain.user.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.example.expert.domain.common.exception.InvalidRequestException;

@Getter
@NoArgsConstructor
Expand All @@ -12,6 +15,17 @@ public class UserChangePasswordRequest {

@NotBlank
private String oldPassword;


//service에 불필요한 if문 제거 후 size와 pattern 어노테이션 사용 후 제약조건 설정\
@Size(min = 8, message = "새 비밀번호는 8자 이상이어야 합니다.")
//if (userChangePasswordRequest.getNewPassword().length() < 8 ||
@Pattern(regexp = ".*\\d.*", message = "숫자를 포함해야 합니다.")
//!userChangePasswordRequest.getNewPassword().matches(".*\\d.*") ||
@Pattern(regexp = ".*[A-Z].*", message = "대문자를 포함해야 합니다.")
//!userChangePasswordRequest.getNewPassword().matches(".*[A-Z].*"))
@NotBlank
private String newPassword;
}


19 changes: 19 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306/work
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 12345678

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

jwt:
secret:
key: sodlfqodnazoavmtmvmfld3rlghkdlxlddlqslekdighghghghghnavercom

Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ class PasswordEncoderTest {
String rawPassword = "testPassword";
String encodedPassword = passwordEncoder.encode(rawPassword);


// 먼저 입력 비밀번호부터 작성하고 인코딩 패스워드 순으로 작성해야함 ; 강의 & 이전 프로젝트 참고함
// when
boolean matches = passwordEncoder.matches(encodedPassword, rawPassword);
boolean matches = passwordEncoder.matches(rawPassword, encodedPassword);

// then
assertTrue(matches);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.example.expert.domain.comment.entity.Comment;
import org.example.expert.domain.comment.repository.CommentRepository;
import org.example.expert.domain.common.dto.AuthUser;
import org.example.expert.domain.common.exception.InvalidRequestException;
import org.example.expert.domain.common.exception.ServerException;
import org.example.expert.domain.todo.entity.Todo;
import org.example.expert.domain.todo.repository.TodoRepository;
Expand Down Expand Up @@ -36,14 +37,19 @@ class CommentServiceTest {
@Test
public void comment_등록_중_할일을_찾지_못해_에러가_발생한다() {
// given
// 댓글 저장
// 댓글내용을 저장하고 권한유저(아이디, 이메일, 권한(역할) 확인
// todo_id가 비어있는 상황 -- 테스트 코드만 수정해야함.
long todoId = 1;
CommentSaveRequest request = new CommentSaveRequest("contents");
AuthUser authUser = new AuthUser(1L, "email", UserRole.USER);

given(todoRepository.findById(anyLong())).willReturn(Optional.empty());

// when
ServerException exception = assertThrows(ServerException.class, () -> {
// 유저, 아이디, 내용 저장
// todo를 찾지 못하는 건 서버에러가 아닌 InvalidRequestException으로 넘어가야함
InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> {
commentService.saveComment(authUser, todoId, request);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,23 @@ class ManagerServiceTest {
private ManagerService managerService;

@Test
public void manager_목록_조회_시_Todo가_없다면_NPE_에러를_던진다() {

// todo가 없다면 NullPointException Error 던지기;
public void manager_목록_조회_시_Todo가_없다면_InvalidRequestException을_던진다() {

// given
// id 가 1임
long todoId = 1L;
// 근데 empty인 상태임
given(todoRepository.findById(todoId)).willReturn(Optional.empty());

// when & then
// 이 부분에서는 NPE 에러를 던지는게 아니라 Manager not found를 던진다.
// 그렇다면 바꿀점 :
// 메서드명 : NPE 에러를 InvalidRequestException으로 바꿔준다.
// 로직 : todo가 없는거라 Manager not found가 아니라 todo not found로 바꿔야할듯
InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> managerService.getManagers(todoId));
assertEquals("Manager not found", exception.getMessage());
assertEquals("Todo not found", exception.getMessage());
}

@Test
Expand Down