diff --git a/src/main/java/com/mtvs/devlinkbackend/config/SecurityConfig.java b/src/main/java/com/mtvs/devlinkbackend/config/SecurityConfig.java index de37e30..5481a22 100644 --- a/src/main/java/com/mtvs/devlinkbackend/config/SecurityConfig.java +++ b/src/main/java/com/mtvs/devlinkbackend/config/SecurityConfig.java @@ -51,8 +51,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ) .authorizeHttpRequests(authorizeRequests -> authorizeRequests .requestMatchers( - "/api/auth/epicgames/callback", // 토큰 호출 부분 - "/login", + "/**", "/v3/api-docs/**", // Swagger API Docs 경로 "/swagger-ui/**", // Swagger UI 정적 리소스 경로 "/swagger-ui.html", // Swagger UI 페이지 경로 @@ -64,23 +63,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ).permitAll() // Swagger 관련 경로 허용 .anyRequest().authenticated() ) - - // oauth2Login 설정이 다른 경로에서만 작동하도록 설정 - .oauth2Login(oauth2Login -> oauth2Login - .loginPage("/login") - .defaultSuccessUrl("/", true) - .failureUrl("/login?error=true") - .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint - .userService(oauth2UserService()) - ) - .successHandler(oauth2AuthenticationSuccessHandler()) - ) -// .logout(logout -> logout -// .logoutUrl("/logout") -// .addLogoutHandler(logoutHandler()) -// .logoutSuccessUrl("/") -// ) - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // JWT 필터 추가 return http.build(); } @@ -90,52 +73,4 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // "index.html"로 서블릿 포워딩 되면서 시큐리티 필터를 다시 거치게 되는데 권한 없음으로 403이 내려오는 것이다. // 요청 한번에 권한 인증이 여러번 되는 이유는 직접 구현한 토큰 인증 필터와 같은 경우 OncePerRequestFilter를 확장해서 // 요청 당 한번만 거치도록 설정하지만 기본적으로 필터는 서블릿에 대한 매요청마다 거치는 것이 기본 전략임 - - @Bean - public OAuth2UserService oauth2UserService() { - return new DefaultOAuth2UserService(); - } - - @Bean - public AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler() { - return (HttpServletRequest request, HttpServletResponse response, Authentication authentication) -> { - System.out.println("여기 오냐"); - OAuth2User oauthUser = (OAuth2User) authentication.getPrincipal(); - - // OAuth2AuthorizedClient를 사용하여 액세스 토큰과 리프레시 토큰 가져오기 - String clientRegistrationId = ((OAuth2UserRequest) authentication.getDetails()).getClientRegistration().getRegistrationId(); - OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(clientRegistrationId, oauthUser.getName()); - - String accessToken = authorizedClient.getAccessToken().getTokenValue(); - String refreshToken = authorizedClient.getRefreshToken() != null ? authorizedClient.getRefreshToken().getTokenValue() : null; - - User foundUser = userService.findUserByAccessToken(accessToken); - if(foundUser == null) { // 가입 했던 적이 있는지 확인 - foundUser = userService.registUserByAccessToken(accessToken); - foundUser.setRefreshToken(refreshToken); - response.sendRedirect("/user/info"); // 추가 정보 입력 페이지로 이동 - } else { - foundUser.setRefreshToken(refreshToken); - response.sendRedirect("/"); // 가입했던 적이 있다면 홈으로 redirect - } - }; - } - -// @Bean -// public LogoutHandler logoutHandler() { -// return (HttpServletRequest request, HttpServletResponse response, Authentication authentication) -> { -// // 쿠키 삭제 -// deleteCookie(response, "access_token"); -// deleteCookie(response, "refresh_token"); -// }; -// } -// -// private void deleteCookie(HttpServletResponse response, String cookieName) { -// Cookie cookie = new Cookie(cookieName, null); -// cookie.setPath("/"); -// cookie.setMaxAge(0); // 쿠키 즉시 삭제 -// cookie.setHttpOnly(true); -// cookie.setSecure(true); -// response.addCookie(cookie); -// } } \ No newline at end of file diff --git a/src/main/java/com/mtvs/devlinkbackend/ether/controller/EtherController.java b/src/main/java/com/mtvs/devlinkbackend/ether/controller/EtherController.java index 3aec6a7..52009bd 100644 --- a/src/main/java/com/mtvs/devlinkbackend/ether/controller/EtherController.java +++ b/src/main/java/com/mtvs/devlinkbackend/ether/controller/EtherController.java @@ -14,7 +14,7 @@ import java.util.List; @RestController -@RequestMapping("/ether") +@RequestMapping("/api/ether") public class EtherController { private final EtherService etherService; private final JwtUtil jwtUtil; diff --git a/src/main/java/com/mtvs/devlinkbackend/reply/controller/ReplyController.java b/src/main/java/com/mtvs/devlinkbackend/reply/controller/ReplyController.java index c26ed24..d1ec63f 100644 --- a/src/main/java/com/mtvs/devlinkbackend/reply/controller/ReplyController.java +++ b/src/main/java/com/mtvs/devlinkbackend/reply/controller/ReplyController.java @@ -14,7 +14,7 @@ import java.util.List; @RestController -@RequestMapping("/reply") +@RequestMapping("/api/reply") public class ReplyController { private final ReplyService replyService; diff --git a/src/main/java/com/mtvs/devlinkbackend/request/controller/RequestController.java b/src/main/java/com/mtvs/devlinkbackend/request/controller/RequestController.java new file mode 100644 index 0000000..0a1f791 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/request/controller/RequestController.java @@ -0,0 +1,111 @@ +package com.mtvs.devlinkbackend.request.controller; + +import com.mtvs.devlinkbackend.config.JwtUtil; +import com.mtvs.devlinkbackend.request.dto.RequestRegistRequestDTO; +import com.mtvs.devlinkbackend.request.dto.RequestUpdateRequestDTO; +import com.mtvs.devlinkbackend.request.entity.Request; +import com.mtvs.devlinkbackend.request.service.RequestService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("/api/request") +public class RequestController { + + private final RequestService requestService; + private final JwtUtil jwtUtil; + + public RequestController(RequestService requestService, JwtUtil jwtUtil) { + this.requestService = requestService; + this.jwtUtil = jwtUtil; + } + + @Operation(summary = "새로운 의뢰 등록", description = "새로운 의뢰를 등록합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "의뢰가 성공적으로 등록됨"), + @ApiResponse(responseCode = "400", description = "잘못된 파라미터") + }) + @PostMapping + public ResponseEntity registerRequest( + @RequestBody RequestRegistRequestDTO requestDTO, + @RequestHeader(name = "Authorization") String authorizationHeader) throws Exception { + + String accountId = jwtUtil.getSubjectFromTokenWithoutAuth(authorizationHeader); + Request newRequest = requestService.registRequest(requestDTO, accountId); + return new ResponseEntity<>(newRequest, HttpStatus.CREATED); + } + + @Operation(summary = "특정 의뢰 조회", description = "ID로 특정 의뢰를 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "의뢰가 성공적으로 조회됨"), + @ApiResponse(responseCode = "404", description = "의뢰를 찾을 수 없음") + }) + @GetMapping("/{requestId}") + public ResponseEntity getRequestById(@PathVariable Long requestId) { + Request request = requestService.findRequestByRequestId(requestId); + if (request != null) { + return ResponseEntity.ok(request); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + } + + @Operation(summary = "계정별 의뢰 목록 조회", description = "특정 계정에 대한 모든 의뢰를 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "의뢰 목록이 성공적으로 조회됨") + }) + @GetMapping("/account") + public ResponseEntity> getRequestsByAccountId( + @RequestHeader(name = "Authorization") String authorizationHeader) throws Exception { + + String accountId = jwtUtil.getSubjectFromTokenWithoutAuth(authorizationHeader); + List requests = requestService.findRequestsByAccountId(accountId); + return ResponseEntity.ok(requests); + } + + @Operation(summary = "특정 기간 내의 의뢰 목록 조회", description = "시작과 끝 날짜 사이의 모든 의뢰를 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "의뢰 목록이 성공적으로 조회됨") + }) + @GetMapping("/date-range") + public ResponseEntity> getRequestsBetweenDates(@RequestParam LocalDateTime startDateTime, @RequestParam LocalDateTime endDateTime) { + List requests = requestService.findAllRequestsBetweenStarDateTimeAndEndDateTime(startDateTime, endDateTime); + return ResponseEntity.ok(requests); + } + + @Operation(summary = "의뢰 업데이트", description = "기존 의뢰를 업데이트합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "의뢰가 성공적으로 업데이트됨"), + @ApiResponse(responseCode = "400", description = "잘못된 파라미터 또는 권한 없음") + }) + @PatchMapping + public ResponseEntity updateRequest( + @RequestBody RequestUpdateRequestDTO requestDTO, + @RequestHeader(name = "Authorization") String authorizationHeader) { + try { + String accountId = jwtUtil.getSubjectFromTokenWithoutAuth(authorizationHeader); + Request updatedRequest = requestService.updateRequest(requestDTO, accountId); + return ResponseEntity.ok(updatedRequest); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); + } + } + + @Operation(summary = "의뢰 삭제", description = "ID로 특정 의뢰를 삭제합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "204", description = "의뢰가 성공적으로 삭제됨"), + @ApiResponse(responseCode = "404", description = "의뢰를 찾을 수 없음") + }) + @DeleteMapping("/{requestId}") + public ResponseEntity deleteRequest(@PathVariable Long requestId) { + requestService.deleteRequest(requestId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/mtvs/devlinkbackend/request/dto/RequestRegistRequestDTO.java b/src/main/java/com/mtvs/devlinkbackend/request/dto/RequestRegistRequestDTO.java new file mode 100644 index 0000000..0b9b469 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/request/dto/RequestRegistRequestDTO.java @@ -0,0 +1,16 @@ +package com.mtvs.devlinkbackend.request.dto; + +import lombok.*; + +import java.time.LocalDateTime; + +@Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class RequestRegistRequestDTO { + private String title; + private String content; + private LocalDateTime startDateTime; + private LocalDateTime endDateTime; +} diff --git a/src/main/java/com/mtvs/devlinkbackend/request/dto/RequestUpdateRequestDTO.java b/src/main/java/com/mtvs/devlinkbackend/request/dto/RequestUpdateRequestDTO.java new file mode 100644 index 0000000..44d91ce --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/request/dto/RequestUpdateRequestDTO.java @@ -0,0 +1,18 @@ +package com.mtvs.devlinkbackend.request.dto; + +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class RequestUpdateRequestDTO { + private Long requestId; + private String title; + private String content; + private LocalDateTime startDateTime; + private LocalDateTime endDateTime; +} \ No newline at end of file diff --git a/src/main/java/com/mtvs/devlinkbackend/request/entity/Request.java b/src/main/java/com/mtvs/devlinkbackend/request/entity/Request.java new file mode 100644 index 0000000..ad04f8c --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/request/entity/Request.java @@ -0,0 +1,69 @@ +package com.mtvs.devlinkbackend.request.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Table(name = "REQUEST") +@Entity(name = "REQUEST") +@NoArgsConstructor +@ToString +@Getter +public class Request { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "REQUEST_ID") + private Long requestId; + + @Column(name = "TITLE", nullable = false) + private String title; + + @Column(name = "CONTENT", nullable = false) + private String content; + + @Column(name = "START_DATETIME") + private LocalDateTime startDateTime; + + @Column(name = "END_DATETIME") + private LocalDateTime endDateTime; + + @Column(name = "ACCOUNT_ID", nullable = false) + private String accountId; + + @CreationTimestamp + @Column(name = "CREATED_AT") + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "MODIFIED_AT") + private LocalDateTime modifiedAt; + + public Request(String title, String content, LocalDateTime startDateTime, LocalDateTime endDateTime, String accountId) { + this.title = title; + this.content = content; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.accountId = accountId; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setContent(String content) { + this.content = content; + } + + public void setStartDateTime(LocalDateTime startDateTime) { + this.startDateTime = startDateTime; + } + + public void setEndDateTime(LocalDateTime endDateTime) { + this.endDateTime = endDateTime; + } +} diff --git a/src/main/java/com/mtvs/devlinkbackend/request/repository/RequestRepository.java b/src/main/java/com/mtvs/devlinkbackend/request/repository/RequestRepository.java new file mode 100644 index 0000000..1bb3c2f --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/request/repository/RequestRepository.java @@ -0,0 +1,21 @@ +package com.mtvs.devlinkbackend.request.repository; + +import com.mtvs.devlinkbackend.request.entity.Request; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface RequestRepository extends JpaRepository { + List findRequestsByAccountId(String accountId); + + @Query("SELECT r FROM REQUEST r WHERE r.startDateTime BETWEEN :startDateTime AND :endDateTime " + + "AND r.endDateTime BETWEEN :startDateTime AND :endDateTime") + List findRequestsWithinDateRange( + @Param("startDateTime") LocalDateTime startDateTime, + @Param("endDateTime") LocalDateTime endDateTime); +} diff --git a/src/main/java/com/mtvs/devlinkbackend/request/service/RequestService.java b/src/main/java/com/mtvs/devlinkbackend/request/service/RequestService.java new file mode 100644 index 0000000..eaa46d6 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/request/service/RequestService.java @@ -0,0 +1,67 @@ +package com.mtvs.devlinkbackend.request.service; + +import com.mtvs.devlinkbackend.request.dto.RequestRegistRequestDTO; +import com.mtvs.devlinkbackend.request.dto.RequestUpdateRequestDTO; +import com.mtvs.devlinkbackend.request.entity.Request; +import com.mtvs.devlinkbackend.request.repository.RequestRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +public class RequestService { + private final RequestRepository requestRepository; + + public RequestService(RequestRepository requestRepository) { + this.requestRepository = requestRepository; + } + + @Transactional + public Request registRequest(RequestRegistRequestDTO requestRegistRequestDTO, String accountId) { + return requestRepository.save(new Request( + requestRegistRequestDTO.getTitle(), + requestRegistRequestDTO.getContent(), + requestRegistRequestDTO.getStartDateTime(), + requestRegistRequestDTO.getEndDateTime(), + accountId + )); + } + + public Request findRequestByRequestId(Long requestId) { + return requestRepository.findById(requestId).orElse(null); + } + + public List findRequestsByAccountId(String accountId) { + return requestRepository.findRequestsByAccountId(accountId); + } + + public List findAllRequestsBetweenStarDateTimeAndEndDateTime(LocalDateTime starDateTime, LocalDateTime endDateTime) { + return requestRepository.findRequestsWithinDateRange(starDateTime, endDateTime); + } + + @Transactional + public Request updateRequest(RequestUpdateRequestDTO requestUpdateRequestDTO, String accountId) { + Optional request = requestRepository.findById(requestUpdateRequestDTO.getRequestId()); + if (request.isPresent()) { + Request foundRequest = request.get(); + if(foundRequest.getAccountId().equals(accountId)) { + foundRequest.setTitle(requestUpdateRequestDTO.getTitle()); + foundRequest.setContent(requestUpdateRequestDTO.getContent()); + foundRequest.setStartDateTime(requestUpdateRequestDTO.getStartDateTime()); + foundRequest.setEndDateTime(requestUpdateRequestDTO.getEndDateTime()); + return foundRequest; + } + else throw new IllegalArgumentException("잘못된 accountId로 Request ID : " + + requestUpdateRequestDTO.getRequestId() + "를 수정 시도"); + } + else throw new IllegalArgumentException("잘못된 requestId로 수정 시도"); + } + + public void deleteRequest(Long requestId) { + requestRepository.deleteById(requestId); + } +} + diff --git a/src/test/java/com/mtvs/devlinkbackend/request/RequestCRUDTest.java b/src/test/java/com/mtvs/devlinkbackend/request/RequestCRUDTest.java new file mode 100644 index 0000000..61a24f8 --- /dev/null +++ b/src/test/java/com/mtvs/devlinkbackend/request/RequestCRUDTest.java @@ -0,0 +1,83 @@ +package com.mtvs.devlinkbackend.request; + +import com.mtvs.devlinkbackend.request.dto.RequestRegistRequestDTO; +import com.mtvs.devlinkbackend.request.dto.RequestUpdateRequestDTO; +import com.mtvs.devlinkbackend.request.service.RequestService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.stream.Stream; + +@SpringBootTest +@Transactional +public class RequestCRUDTest { + @Autowired + private RequestService requestService; + + private static Stream newRequest() { + return Stream.of( + Arguments.of(new RequestRegistRequestDTO("의뢰0", "내용0", LocalDateTime.now(), LocalDateTime.now()), "계정0"), + Arguments.of(new RequestRegistRequestDTO("의뢰00", "내용00", LocalDateTime.now(), LocalDateTime.now()), "계정00") + ); + } + + private static Stream modifiedRequest() { + return Stream.of( + Arguments.of(new RequestUpdateRequestDTO(1L,"의뢰0", "내용0", LocalDateTime.now(), LocalDateTime.now()), "계정1"), + Arguments.of(new RequestUpdateRequestDTO(2L,"의뢰00" , "내용00", LocalDateTime.now(), LocalDateTime.now()), "계정1") + ); + } + + @DisplayName("의뢰 추가 테스트") + @ParameterizedTest + @MethodSource("newRequest") + @Order(0) + public void testCreateQuestion(RequestRegistRequestDTO requestRegistRequestDTO, String accountId) { + Assertions.assertDoesNotThrow(() -> requestService.registRequest(requestRegistRequestDTO, accountId)); + } + + @DisplayName("PK로 의뢰 조회 테스트") + @ValueSource(longs = {1,2}) + @ParameterizedTest + @Order(1) + public void testFindRequestByRequestId(long questionId) { + Assertions.assertDoesNotThrow(() -> + System.out.println("Request = " + requestService.findRequestByRequestId(questionId))); + } + + @DisplayName("계정 ID에 따른 의뢰 paging 조회 테스트") + @ValueSource( strings = {"계정1", "계정2"}) + @ParameterizedTest + @Order(2) + public void testFindRequestsByAccountId(String accountId) { + Assertions.assertDoesNotThrow(() -> + System.out.println("Request = " + requestService.findRequestsByAccountId(accountId))); + } + + @DisplayName("의뢰 수정 테스트") + @MethodSource("modifiedRequest") + @ParameterizedTest + @Order(3) + public void testUpdateRequest(RequestUpdateRequestDTO RequestUpdateRequestDTO, String accountId) { + Assertions.assertDoesNotThrow(() -> + System.out.println(requestService.updateRequest(RequestUpdateRequestDTO, accountId))); + } + + @DisplayName("의뢰 삭제 테스트") + @ValueSource(longs = {0,1}) + @ParameterizedTest + @Order(4) + public void testDeleteRequest(long requestId) { + Assertions.assertDoesNotThrow(() -> + requestService.deleteRequest(requestId)); + } +}