From a060d2771e320d00e792bc86b7f0421e0dd6747d Mon Sep 17 00:00:00 2001 From: jung Date: Tue, 10 Sep 2024 19:47:10 +0900 Subject: [PATCH 1/5] =?UTF-8?q?swagger=20=EC=97=B0=EA=B2=B0=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../codeview/main/config/SecurityConfig.java | 6 +++ .../codeview/main/config/SwaggerConfig.java | 39 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/main/java/codeview/main/config/SwaggerConfig.java diff --git a/build.gradle b/build.gradle index bdae515..6ca8315 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,7 @@ repositories { } dependencies { + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation 'javax.servlet:javax.servlet-api:4.0.1' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' diff --git a/src/main/java/codeview/main/config/SecurityConfig.java b/src/main/java/codeview/main/config/SecurityConfig.java index 6038e3d..b5aeba2 100644 --- a/src/main/java/codeview/main/config/SecurityConfig.java +++ b/src/main/java/codeview/main/config/SecurityConfig.java @@ -49,6 +49,12 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable).disable()) .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(request -> request.requestMatchers( + new AntPathRequestMatcher("/swagger"), + new AntPathRequestMatcher("/swagger-ui.html"), + new AntPathRequestMatcher("/swagger-ui/**"), + new AntPathRequestMatcher("/api-docs"), + new AntPathRequestMatcher("/api-docs/**"), + new AntPathRequestMatcher("/v3/api-docs/**"), new AntPathRequestMatcher("/"), new AntPathRequestMatcher("/home"), new AntPathRequestMatcher("/login"), diff --git a/src/main/java/codeview/main/config/SwaggerConfig.java b/src/main/java/codeview/main/config/SwaggerConfig.java new file mode 100644 index 0000000..0d0eea8 --- /dev/null +++ b/src/main/java/codeview/main/config/SwaggerConfig.java @@ -0,0 +1,39 @@ +package codeview.main.config; + + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + private static final String BEARER_TOKEN_PREFIX = "Bearer"; + + @Bean + public OpenAPI openAPI(){ + String securityJwtName = "JWT"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityJwtName); + + + Components components = new Components() + .addSecuritySchemes(securityJwtName, new SecurityScheme() + .name(securityJwtName) + .type(SecurityScheme.Type.HTTP) + .scheme(BEARER_TOKEN_PREFIX) + .bearerFormat(securityJwtName)); + + Info info = new Info() + .version("v1.0.0") + .title("codeView API"); + + + return new OpenAPI() + .components(components) + .info(info); + } + +} From 3cecabf4d241c9295402279f5c2361ff3b7fb9f3 Mon Sep 17 00:00:00 2001 From: jung Date: Tue, 10 Sep 2024 19:51:19 +0900 Subject: [PATCH 2/5] =?UTF-8?q?swagger=20=EC=97=B0=EA=B2=B0=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/codeview/main/config/SwaggerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/codeview/main/config/SwaggerConfig.java b/src/main/java/codeview/main/config/SwaggerConfig.java index 0d0eea8..5cdc2a1 100644 --- a/src/main/java/codeview/main/config/SwaggerConfig.java +++ b/src/main/java/codeview/main/config/SwaggerConfig.java @@ -18,7 +18,6 @@ public OpenAPI openAPI(){ String securityJwtName = "JWT"; SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityJwtName); - Components components = new Components() .addSecuritySchemes(securityJwtName, new SecurityScheme() .name(securityJwtName) @@ -26,6 +25,7 @@ public OpenAPI openAPI(){ .scheme(BEARER_TOKEN_PREFIX) .bearerFormat(securityJwtName)); + Info info = new Info() .version("v1.0.0") .title("codeView API"); From 4550f99f77f28ae13155b8c9e3bc6272b345bbb5 Mon Sep 17 00:00:00 2001 From: jung Date: Wed, 11 Sep 2024 13:11:22 +0900 Subject: [PATCH 3/5] =?UTF-8?q?swagger=20=EC=97=B0=EA=B2=B0=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codeview/main/config/SwaggerConfig.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/codeview/main/config/SwaggerConfig.java b/src/main/java/codeview/main/config/SwaggerConfig.java index 5cdc2a1..1b5bf09 100644 --- a/src/main/java/codeview/main/config/SwaggerConfig.java +++ b/src/main/java/codeview/main/config/SwaggerConfig.java @@ -15,15 +15,12 @@ public class SwaggerConfig { @Bean public OpenAPI openAPI(){ - String securityJwtName = "JWT"; - SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityJwtName); Components components = new Components() - .addSecuritySchemes(securityJwtName, new SecurityScheme() - .name(securityJwtName) + .addSecuritySchemes("Authorization", new SecurityScheme() .type(SecurityScheme.Type.HTTP) - .scheme(BEARER_TOKEN_PREFIX) - .bearerFormat(securityJwtName)); + .scheme("bearer") + .bearerFormat("JWT")); Info info = new Info() @@ -31,9 +28,12 @@ public OpenAPI openAPI(){ .title("codeView API"); + return new OpenAPI() + .addSecurityItem(new SecurityRequirement().addList("Authorization")) .components(components) .info(info); + } } From 623fe03a1dbe61da8fdfcab3cb2a69c81beb635e Mon Sep 17 00:00:00 2001 From: rogi-rogi Date: Sat, 14 Sep 2024 02:17:11 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat=20-=20blog=20api=20=EA=B0=9C=EB=B0=9C?= =?UTF-8?q?=20-=20board=20api=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/controller/BlogController.java | 49 +++++++++++++ .../main/controller/BoardController.java | 70 ++++++++----------- .../java/codeview/main/dto/ApiResponse.java | 20 ------ .../java/codeview/main/dto/BoardRequest.java | 9 --- .../codeview/main/dto/blog/BlogReqDto.java | 8 +++ .../codeview/main/dto/blog/BlogResDto.java | 15 ++++ .../codeview/main/dto/board/BoardReqDto.java | 9 +++ .../BoardResDto.java} | 6 +- src/main/java/codeview/main/entity/Blog.java | 49 +++++++++++++ src/main/java/codeview/main/entity/Board.java | 34 +++++---- src/main/java/codeview/main/entity/User.java | 26 +++++++ .../main/exception/BusinessException.java | 13 ++++ .../exception/GlobalExceptionHandler.java | 13 ++++ .../main/exception/code/BlogErrorCode.java | 13 ++++ .../main/exception/code/CommonErrorCode.java | 15 ++++ .../main/exception/code/ErrorCode.java | 23 ++++++ .../main/exception/code/UserErrorCode.java | 14 ++++ .../codeview/main/global/ApiResponse.java | 29 ++++++++ .../codeview/main/global/SuccessCode.java | 4 ++ .../main/repository/BlogRepository.java | 12 ++++ .../main/repository/BoardRepository.java | 3 + .../main/repository/UserRepository.java | 10 +++ .../codeview/main/service/BlogService.java | 56 +++++++++++++++ .../codeview/main/service/BoardService.java | 48 ++++++++----- src/main/resources/application.yml | 2 +- 25 files changed, 448 insertions(+), 102 deletions(-) create mode 100644 src/main/java/codeview/main/controller/BlogController.java delete mode 100644 src/main/java/codeview/main/dto/ApiResponse.java delete mode 100644 src/main/java/codeview/main/dto/BoardRequest.java create mode 100644 src/main/java/codeview/main/dto/blog/BlogReqDto.java create mode 100644 src/main/java/codeview/main/dto/blog/BlogResDto.java create mode 100644 src/main/java/codeview/main/dto/board/BoardReqDto.java rename src/main/java/codeview/main/dto/{BoardResponse.java => board/BoardResDto.java} (69%) create mode 100644 src/main/java/codeview/main/entity/Blog.java create mode 100644 src/main/java/codeview/main/entity/User.java create mode 100644 src/main/java/codeview/main/exception/BusinessException.java create mode 100644 src/main/java/codeview/main/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/codeview/main/exception/code/BlogErrorCode.java create mode 100644 src/main/java/codeview/main/exception/code/CommonErrorCode.java create mode 100644 src/main/java/codeview/main/exception/code/ErrorCode.java create mode 100644 src/main/java/codeview/main/exception/code/UserErrorCode.java create mode 100644 src/main/java/codeview/main/global/ApiResponse.java create mode 100644 src/main/java/codeview/main/global/SuccessCode.java create mode 100644 src/main/java/codeview/main/repository/BlogRepository.java create mode 100644 src/main/java/codeview/main/repository/UserRepository.java create mode 100644 src/main/java/codeview/main/service/BlogService.java diff --git a/src/main/java/codeview/main/controller/BlogController.java b/src/main/java/codeview/main/controller/BlogController.java new file mode 100644 index 0000000..861cb95 --- /dev/null +++ b/src/main/java/codeview/main/controller/BlogController.java @@ -0,0 +1,49 @@ +package codeview.main.controller; + + +import codeview.main.dto.blog.BlogReqDto; +import codeview.main.dto.blog.BlogResDto; +import codeview.main.global.ApiResponse; +import codeview.main.service.BlogService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/blog") +public class BlogController { + private final BlogService blogService; + + @PostMapping + public ApiResponse createBlog(Long userId, BlogReqDto blogReqDto){ + BlogResDto blogResDto = new BlogResDto(blogService.createBlog(userId, blogReqDto)); + return ApiResponse.createResponse("S100", "SUCCESS", blogResDto); + } + + @GetMapping + public ApiResponse> getBlogList(Long userId) { + List blogResDtoList = blogService.getBlogList(userId).stream().map(BlogResDto::new).toList(); + return ApiResponse.createResponse("S101", "SUCCESS", blogResDtoList); + } + + @GetMapping("/{id}") + public ApiResponse getBlog(@PathVariable Long id) { + BlogResDto blogResDto = new BlogResDto(blogService.getBlog(id)); + return ApiResponse.createResponse("S101", "SUCCESS", blogResDto); + } + + @PutMapping("/{id}") + public ApiResponse updateBlog(Long userId, @PathVariable Long id, BlogReqDto blogReqDto) { + BlogResDto blogResDto = new BlogResDto(blogService.updateBlog(userId, id, blogReqDto)); + return ApiResponse.createResponse("S102", "SUCCESS", blogResDto); + } + + @DeleteMapping("/{id}") + public ApiResponse deleteBlog(Long userId, @PathVariable Long id) { + blogService.deleteBlog(userId, id); + return ApiResponse.createResponse("S103", "SUCCESS", null); + } +} diff --git a/src/main/java/codeview/main/controller/BoardController.java b/src/main/java/codeview/main/controller/BoardController.java index a08fdbc..1c48288 100644 --- a/src/main/java/codeview/main/controller/BoardController.java +++ b/src/main/java/codeview/main/controller/BoardController.java @@ -1,8 +1,8 @@ package codeview.main.controller; -import codeview.main.dto.ApiResponse; -import codeview.main.dto.BoardRequest; -import codeview.main.dto.BoardResponse; +import codeview.main.global.ApiResponse; +import codeview.main.dto.board.BoardReqDto; +import codeview.main.dto.board.BoardResDto; import codeview.main.entity.Board; import codeview.main.service.BoardService; import lombok.RequiredArgsConstructor; @@ -15,51 +15,41 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/board") +@RequestMapping("/{blogId}/board") public class BoardController { private final BoardService boardService; - @GetMapping("/{id}") - public ResponseEntity getBoardById(@PathVariable Long id) { - Optional findBoardObj = boardService.findBoardById(id); - if (findBoardObj.isPresent()) { - BoardResponse response = new BoardResponse(findBoardObj.get()); - return ResponseEntity.ok(response); - } else { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); - } + + + @PostMapping + public ApiResponse createBoard(Long userId, @PathVariable Long blogId, @RequestBody BoardReqDto boardReqDto) { + BoardResDto boardResponse = new BoardResDto(boardService.createBoard(userId, blogId, boardReqDto)); + return ApiResponse.createResponse("S100", "Board Write Success", boardResponse); } - @PostMapping("/write") - public ResponseEntity> boardSave(@RequestBody Board board) { - Board saveBoard = boardService.save(board); - BoardResponse boardResponse = new BoardResponse(saveBoard); - ApiResponse response = new ApiResponse<>(HttpStatus.CREATED, "Board Write Success", boardResponse); - return ResponseEntity.status(HttpStatus.CREATED) - .body(response); + + @GetMapping + public ApiResponse getBoardById(@RequestParam("boardId") Long boardId) { + BoardResDto boardResDto = new BoardResDto(boardService.getBoard(boardId)); + return ApiResponse.createResponse("S101", "Success create board", boardResDto); } - @PutMapping("/{id}") - public ResponseEntity updateBoard( - @PathVariable Long id, - @RequestBody BoardRequest boardRequest) { - Board updatedBoard = new Board(); - updatedBoard.setTitle(boardRequest.getTitle()); - try { - Board savedBoard = boardService.updateBoard(id, updatedBoard); - BoardResponse responseDto = new BoardResponse(savedBoard); - return ResponseEntity.ok(responseDto); - } catch (RuntimeException e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); - } + + @PutMapping + public ApiResponse updateBoard( + Long userId, + @PathVariable Long blogId, + @RequestParam("boardId") Long boardId, + @RequestBody BoardReqDto boardReqDto) { + boardService.updateBoard(userId, blogId, boardId, boardReqDto); + return ApiResponse.createResponse("S102", "Success update board"); } @DeleteMapping("/{id}") - public ResponseEntity deleteBoard(@PathVariable Long id) { - try { - boardService.deleteBoard(id); - return ResponseEntity.noContent().build(); - }catch (RuntimeException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); - } + public ApiResponse deleteBoard( + Long userId, + @PathVariable Long blogId + ) { + boardService.deleteBoard(userId, blogId); + return ApiResponse.createResponse("S103", "Success delete board"); } } diff --git a/src/main/java/codeview/main/dto/ApiResponse.java b/src/main/java/codeview/main/dto/ApiResponse.java deleted file mode 100644 index 89677fa..0000000 --- a/src/main/java/codeview/main/dto/ApiResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package codeview.main.dto; - - -import lombok.Getter; -import lombok.Setter; -import org.springframework.http.HttpStatus; - -@Getter -@Setter -public class ApiResponse { - private HttpStatus httpStatus; - private String message; - private T data; - - public ApiResponse(HttpStatus httpStatus, String message, T data) { - this.httpStatus = httpStatus; - this.message = message; - this.data = data; - } -} \ No newline at end of file diff --git a/src/main/java/codeview/main/dto/BoardRequest.java b/src/main/java/codeview/main/dto/BoardRequest.java deleted file mode 100644 index 1e5d805..0000000 --- a/src/main/java/codeview/main/dto/BoardRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package codeview.main.dto; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class BoardRequest { - private String title; -} \ No newline at end of file diff --git a/src/main/java/codeview/main/dto/blog/BlogReqDto.java b/src/main/java/codeview/main/dto/blog/BlogReqDto.java new file mode 100644 index 0000000..53c6989 --- /dev/null +++ b/src/main/java/codeview/main/dto/blog/BlogReqDto.java @@ -0,0 +1,8 @@ +package codeview.main.dto.blog; + +import lombok.Getter; + +@Getter +public class BlogReqDto { + private String name; +} diff --git a/src/main/java/codeview/main/dto/blog/BlogResDto.java b/src/main/java/codeview/main/dto/blog/BlogResDto.java new file mode 100644 index 0000000..bfc9932 --- /dev/null +++ b/src/main/java/codeview/main/dto/blog/BlogResDto.java @@ -0,0 +1,15 @@ +package codeview.main.dto.blog; + +import codeview.main.entity.Blog; +import lombok.Getter; + +@Getter +public class BlogResDto { + + + public BlogResDto(Blog blog) { + if (blog != null) { + + } + } +} diff --git a/src/main/java/codeview/main/dto/board/BoardReqDto.java b/src/main/java/codeview/main/dto/board/BoardReqDto.java new file mode 100644 index 0000000..1e72012 --- /dev/null +++ b/src/main/java/codeview/main/dto/board/BoardReqDto.java @@ -0,0 +1,9 @@ +package codeview.main.dto.board; +import lombok.Getter; +import lombok.Setter; + +@Getter +public class BoardReqDto { + private String title; + private String content; +} \ No newline at end of file diff --git a/src/main/java/codeview/main/dto/BoardResponse.java b/src/main/java/codeview/main/dto/board/BoardResDto.java similarity index 69% rename from src/main/java/codeview/main/dto/BoardResponse.java rename to src/main/java/codeview/main/dto/board/BoardResDto.java index d539ce9..6c8df14 100644 --- a/src/main/java/codeview/main/dto/BoardResponse.java +++ b/src/main/java/codeview/main/dto/board/BoardResDto.java @@ -1,4 +1,4 @@ -package codeview.main.dto; +package codeview.main.dto.board; import codeview.main.entity.Board; import lombok.Getter; @@ -6,11 +6,11 @@ @Getter @Setter -public class BoardResponse { +public class BoardResDto { private Long id; private String title; - public BoardResponse(Board board) { + public BoardResDto(Board board) { this.id = board.getId(); this.title = board.getTitle(); } diff --git a/src/main/java/codeview/main/entity/Blog.java b/src/main/java/codeview/main/entity/Blog.java new file mode 100644 index 0000000..1b93c85 --- /dev/null +++ b/src/main/java/codeview/main/entity/Blog.java @@ -0,0 +1,49 @@ +package codeview.main.entity; + +import codeview.main.dto.blog.BlogReqDto; +import codeview.main.dto.blog.BlogResDto; +import codeview.main.dto.board.BoardReqDto; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Entity +@NoArgsConstructor +public class Blog { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="user_id") + private User user; + + @OneToMany(mappedBy = "blog", cascade = CascadeType.ALL, orphanRemoval = true) + private List boardList = new ArrayList<>(); + + public Blog(BlogReqDto blogReqDto) { + this.name = blogReqDto.getName(); + } + + public void update(BlogReqDto blogReqDto) { + if (blogReqDto != null) { + this.name = blogReqDto.getName(); + } + } + public void addUser(User user) { + this.user = user; + } + + public void addBoard(Board board) { + boardList.add(board); + board.addBlog(this); + } + public void removeBoard(Board board) { + boardList.remove(board); + board.addBlog(null); + } +} diff --git a/src/main/java/codeview/main/entity/Board.java b/src/main/java/codeview/main/entity/Board.java index 34c43cb..09af47c 100644 --- a/src/main/java/codeview/main/entity/Board.java +++ b/src/main/java/codeview/main/entity/Board.java @@ -1,25 +1,33 @@ package codeview.main.entity; +import codeview.main.dto.board.BoardReqDto; import jakarta.persistence.*; import lombok.*; -import java.util.List; - @Entity -@Getter @Setter +@Getter @NoArgsConstructor public class Board { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String title; -// -// @Column @OneToOne -// private Content content; -// -// @Column @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) -// private List commentList; + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="blog_id") + private Blog blog; - public Board(String title) { - this.title = title; + + public Board(BoardReqDto boardReqDto) { + this.title = boardReqDto.getTitle(); + this.content = boardReqDto.getContent(); + } + public void addBlog(Blog blog) { + this.blog = blog; + } + public void update(BoardReqDto boardReqDto) { + this.title = boardReqDto.getTitle(); + this.content = boardReqDto.getContent(); } } \ No newline at end of file diff --git a/src/main/java/codeview/main/entity/User.java b/src/main/java/codeview/main/entity/User.java new file mode 100644 index 0000000..73605fc --- /dev/null +++ b/src/main/java/codeview/main/entity/User.java @@ -0,0 +1,26 @@ +package codeview.main.entity; + +import jakarta.persistence.*; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +public class User { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List blogList = new ArrayList<>(); + + public void addBlog(Blog blog) { + blogList.add(blog); + blog.addUser(this); // Blog에 User 설정 + } + + public void removeBlog(Blog blog) { + blogList.remove(blog); + } +} diff --git a/src/main/java/codeview/main/exception/BusinessException.java b/src/main/java/codeview/main/exception/BusinessException.java new file mode 100644 index 0000000..92543ef --- /dev/null +++ b/src/main/java/codeview/main/exception/BusinessException.java @@ -0,0 +1,13 @@ +package codeview.main.exception; + +import codeview.main.exception.code.ErrorCode; +import lombok.Getter; + +@Getter +public class BusinessException extends RuntimeException { + private final ErrorCode errorCode; + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/codeview/main/exception/GlobalExceptionHandler.java b/src/main/java/codeview/main/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..4499473 --- /dev/null +++ b/src/main/java/codeview/main/exception/GlobalExceptionHandler.java @@ -0,0 +1,13 @@ +package codeview.main.exception; + +import codeview.main.global.ApiResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(BusinessException.class) + public ApiResponse handleBusinessException(BusinessException businessException) { + return ApiResponse.createErrorResponse(businessException.getErrorCode()); + } +} diff --git a/src/main/java/codeview/main/exception/code/BlogErrorCode.java b/src/main/java/codeview/main/exception/code/BlogErrorCode.java new file mode 100644 index 0000000..0a0785f --- /dev/null +++ b/src/main/java/codeview/main/exception/code/BlogErrorCode.java @@ -0,0 +1,13 @@ +package codeview.main.exception.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum BlogErrorCode implements ErrorCode { + BLOG_NOT_FOUND("Blog not found", "B100"); + + private final String message; + private final String status; +} diff --git a/src/main/java/codeview/main/exception/code/CommonErrorCode.java b/src/main/java/codeview/main/exception/code/CommonErrorCode.java new file mode 100644 index 0000000..2489f9c --- /dev/null +++ b/src/main/java/codeview/main/exception/code/CommonErrorCode.java @@ -0,0 +1,15 @@ +package codeview.main.exception.code; + +import codeview.main.exception.code.ErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum CommonErrorCode implements ErrorCode { + NOT_FOUND_RESOURCE("Not found resource", "G100"), + INTERNAL_SERVER_ERROR("Internal server error", "G999"); + + private final String message; + private final String status; +} diff --git a/src/main/java/codeview/main/exception/code/ErrorCode.java b/src/main/java/codeview/main/exception/code/ErrorCode.java new file mode 100644 index 0000000..a6c7998 --- /dev/null +++ b/src/main/java/codeview/main/exception/code/ErrorCode.java @@ -0,0 +1,23 @@ +package codeview.main.exception.code; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +//@Getter +//public enum ErrorCode { +// USER_NOT_FOUND(HttpStatus.NOT_FOUND, "User not found"), +// BLOG_NOT_FOUND(HttpStatus.NOT_FOUND, "Blog not found"), +// BOARD_NOT_FOUND(HttpStatus.NOT_FOUND, "Board not found"); +// +// private final HttpStatus httpStatus; +// private final String message; +// +// ErrorCode(HttpStatus httpStatus, String message) { +// this.httpStatus = httpStatus; +// this.message = message; +// } +//} +public interface ErrorCode { + String getMessage(); + String getStatus(); +} \ No newline at end of file diff --git a/src/main/java/codeview/main/exception/code/UserErrorCode.java b/src/main/java/codeview/main/exception/code/UserErrorCode.java new file mode 100644 index 0000000..975ba65 --- /dev/null +++ b/src/main/java/codeview/main/exception/code/UserErrorCode.java @@ -0,0 +1,14 @@ +package codeview.main.exception.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum UserErrorCode implements ErrorCode { + USER_NOT_FOUND("User not found", "U100"), + UNAUTHORIZED_USER("an unauthorized user", "U200"); + + private final String message; + private final String status; +} diff --git a/src/main/java/codeview/main/global/ApiResponse.java b/src/main/java/codeview/main/global/ApiResponse.java new file mode 100644 index 0000000..db57632 --- /dev/null +++ b/src/main/java/codeview/main/global/ApiResponse.java @@ -0,0 +1,29 @@ +package codeview.main.global; + + +import codeview.main.exception.code.ErrorCode; +import lombok.*; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +public class ApiResponse { + private String status; + private String message; + private T data; + + public static ApiResponse createResponse(String status, String message, T data) { + return new ApiResponse<>(status, message, data); + } + public static ApiResponse createResponse(String status, String message) { + return ApiResponse.createResponse(status, message, null); + } + + + public static ApiResponse createErrorResponse(ErrorCode errorCode) { + return new ApiResponse<>(errorCode.getStatus(), errorCode.getMessage(), null); + } + public static ApiResponse createErrorResponse(ErrorCode errorCode, T data) { + return new ApiResponse<>(errorCode.getStatus(), errorCode.getMessage(), data); + } +} \ No newline at end of file diff --git a/src/main/java/codeview/main/global/SuccessCode.java b/src/main/java/codeview/main/global/SuccessCode.java new file mode 100644 index 0000000..cd8631a --- /dev/null +++ b/src/main/java/codeview/main/global/SuccessCode.java @@ -0,0 +1,4 @@ +package codeview.main.global; + +public enum SuccessCode { +} diff --git a/src/main/java/codeview/main/repository/BlogRepository.java b/src/main/java/codeview/main/repository/BlogRepository.java new file mode 100644 index 0000000..66f10e9 --- /dev/null +++ b/src/main/java/codeview/main/repository/BlogRepository.java @@ -0,0 +1,12 @@ +package codeview.main.repository; + +import codeview.main.entity.Blog; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface BlogRepository extends JpaRepository { + List findByUser_Id(Long userId); + Optional findByIdAndUser_Id(Long blogId, Long userId); +} diff --git a/src/main/java/codeview/main/repository/BoardRepository.java b/src/main/java/codeview/main/repository/BoardRepository.java index 262b5b4..f17c741 100644 --- a/src/main/java/codeview/main/repository/BoardRepository.java +++ b/src/main/java/codeview/main/repository/BoardRepository.java @@ -4,6 +4,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface BoardRepository extends JpaRepository { + Optional findByIdAndBlog_Id(Long boardId, Long blogId); } diff --git a/src/main/java/codeview/main/repository/UserRepository.java b/src/main/java/codeview/main/repository/UserRepository.java new file mode 100644 index 0000000..61a7211 --- /dev/null +++ b/src/main/java/codeview/main/repository/UserRepository.java @@ -0,0 +1,10 @@ +package codeview.main.repository; + +import codeview.main.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByIdAndUser_Id(Long userId, Long blogId); +} diff --git a/src/main/java/codeview/main/service/BlogService.java b/src/main/java/codeview/main/service/BlogService.java new file mode 100644 index 0000000..499add5 --- /dev/null +++ b/src/main/java/codeview/main/service/BlogService.java @@ -0,0 +1,56 @@ +package codeview.main.service; + +import codeview.main.dto.blog.BlogReqDto; +import codeview.main.entity.Blog; +import codeview.main.entity.User; +import codeview.main.exception.BusinessException; +import codeview.main.exception.code.BlogErrorCode; +import codeview.main.exception.code.ErrorCode; +import codeview.main.exception.code.UserErrorCode; +import codeview.main.repository.BlogRepository; +import codeview.main.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class BlogService { + private final BlogRepository blogRepository; + private final UserRepository userRepository; + @Transactional + public Blog createBlog(Long userId, BlogReqDto blogReqDto) { + User user = userRepository.findById(userId).orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND)); + Blog blog = new Blog(blogReqDto); + user.addBlog(blog); + return blogRepository.save(blog); + } + @Transactional + public List getBlogList(Long userId) { + return blogRepository.findByUser_Id(userId); + } + @Transactional + public Blog getBlog(Long blogId) { + return blogRepository.findById(blogId) + .orElseThrow(() -> new BusinessException(BlogErrorCode.BLOG_NOT_FOUND)); + } + @Transactional + public Blog updateBlog(Long userId, Long blogId, BlogReqDto blogReqDto) { + Blog blog = blogRepository.findByIdAndUser_Id(blogId, userId) + .orElseThrow(() -> new BusinessException(BlogErrorCode.BLOG_NOT_FOUND)); + blog.update(blogReqDto); + return blogRepository.save(blog); + } + @Transactional + public void deleteBlog(Long userId, Long blogId) { + User user = userRepository.findById(userId).orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_FOUND)); + Blog blog = blogRepository.findById(blogId).orElseThrow(()-> new BusinessException(BlogErrorCode.BLOG_NOT_FOUND)); + if (user.getId().equals(blog.getUser().getId())) { + user.removeBlog(blog); + } else { + throw new BusinessException(UserErrorCode.UNAUTHORIZED_USER); + } + } +} diff --git a/src/main/java/codeview/main/service/BoardService.java b/src/main/java/codeview/main/service/BoardService.java index cf0246e..81960a9 100644 --- a/src/main/java/codeview/main/service/BoardService.java +++ b/src/main/java/codeview/main/service/BoardService.java @@ -1,9 +1,17 @@ package codeview.main.service; +import codeview.main.dto.board.BoardReqDto; +import codeview.main.entity.Blog; import codeview.main.entity.Board; +import codeview.main.exception.BusinessException; +import codeview.main.exception.code.BlogErrorCode; +import codeview.main.exception.code.CommonErrorCode; +import codeview.main.exception.code.UserErrorCode; +import codeview.main.repository.BlogRepository; import codeview.main.repository.BoardRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @@ -11,25 +19,33 @@ @RequiredArgsConstructor public class BoardService { private final BoardRepository boardRepository; - public Board save(Board board) { - return boardRepository.save(board); + private final BlogRepository blogRepository; + + @Transactional + public Board createBoard(Long userId, Long blogId, BoardReqDto boardReqDto){ + Blog blog = blogRepository.findByIdAndUser_Id(blogId, userId).orElseThrow(() -> new BusinessException(UserErrorCode.UNAUTHORIZED_USER)); + Board board = new Board(boardReqDto); + blog.addBoard(board); + boardRepository.save(board); + return board; } - public Optional findBoardById(Long id) { - return boardRepository.findById(id); + @Transactional + public Board getBoard(Long boardId) { + return boardRepository.findById(boardId).orElseThrow(() -> new BusinessException(CommonErrorCode.NOT_FOUND_RESOURCE)); } - public Board updateBoard(Long id, Board updateBoard) { - return boardRepository.findById(id) - .map(board -> { - board.setTitle(updateBoard.getTitle()); - return board; - }) - .orElseThrow(() -> new RuntimeException("Board not found")); + @Transactional + public void updateBoard(Long userId, Long blogId, Long boardId, BoardReqDto boardReqDto) { + Board board = boardRepository.findByIdAndBlog_Id(boardId, blogId).orElseThrow(() -> new BusinessException(CommonErrorCode.NOT_FOUND_RESOURCE)); + if (board.getBlog().getUser().getId().equals(userId)) { + board.update(boardReqDto); + } } - public void deleteBoard(Long id) { - if (boardRepository.existsById(id)) { - boardRepository.deleteById(id); - } else { - throw new RuntimeException("Board not found"); + @Transactional + public void deleteBoard(Long userId, Long blogId) { + Board board = boardRepository.findById(blogId).orElseThrow(() -> new BusinessException(CommonErrorCode.NOT_FOUND_RESOURCE)); + Blog blog = board.getBlog(); + if (board.getBlog().getUser().getId().equals(userId)) { + blog.removeBoard(board); } } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4bf15cc..f28a1c4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,7 +2,7 @@ spring: profiles: active: local group: - local: local, common, secret-dev + local: local, common, secret blue: blue, common, secret-deploy green: green, common, secret-deploy From 5e12200bdb232afd7c4b5d862c177896783a8100 Mon Sep 17 00:00:00 2001 From: BluIsMe Date: Sat, 14 Sep 2024 15:32:55 +0900 Subject: [PATCH 5/5] =?UTF-8?q?OAuth2SuccessHandler=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/handler/OAuth2SuccessHandler.java | 23 ++++-- .../main/config/CustomRequestFilter.java | 2 +- .../codeview/main/config/SecurityConfig.java | 8 +- .../handler/OAuth2SuccessHandlerTest.java | 81 ++++++++++--------- 4 files changed, 66 insertions(+), 48 deletions(-) diff --git a/src/main/java/codeview/main/auth/handler/OAuth2SuccessHandler.java b/src/main/java/codeview/main/auth/handler/OAuth2SuccessHandler.java index 0daa477..3b40741 100644 --- a/src/main/java/codeview/main/auth/handler/OAuth2SuccessHandler.java +++ b/src/main/java/codeview/main/auth/handler/OAuth2SuccessHandler.java @@ -28,13 +28,20 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String accessToken = tokenProvider.generateAccessToken(authentication); String refreshToken = tokenProvider.generateRefreshToken(authentication, accessToken); - // JSON 형태로 응답 - Map tokens = new HashMap<>(); - tokens.put("accessToken", accessToken); - tokens.put("refreshToken", refreshToken); - - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(objectMapper.writeValueAsString(tokens)); + + String redirectUrl = request.getParameter("redirect"); + + + if (redirectUrl == null || redirectUrl.isEmpty()) { + redirectUrl = "http://localhost:3000"; + } + + + String redirectWithTokens = String.format("%s/auth/token?accessToken=%s&refreshToken=%s", + redirectUrl, accessToken, refreshToken); + + + response.sendRedirect(redirectWithTokens); } } + diff --git a/src/main/java/codeview/main/config/CustomRequestFilter.java b/src/main/java/codeview/main/config/CustomRequestFilter.java index 8322d1a..0bf1cb8 100644 --- a/src/main/java/codeview/main/config/CustomRequestFilter.java +++ b/src/main/java/codeview/main/config/CustomRequestFilter.java @@ -10,7 +10,7 @@ import java.io.IOException; @Component -public class CustomRequestFilter extends OncePerRequestFilter { +public class CustomRequestFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) diff --git a/src/main/java/codeview/main/config/SecurityConfig.java b/src/main/java/codeview/main/config/SecurityConfig.java index 6038e3d..3e17d45 100644 --- a/src/main/java/codeview/main/config/SecurityConfig.java +++ b/src/main/java/codeview/main/config/SecurityConfig.java @@ -49,6 +49,12 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable).disable()) .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(request -> request.requestMatchers( + new AntPathRequestMatcher("/swagger"), + new AntPathRequestMatcher("/swagger-ui.html"), + new AntPathRequestMatcher("/swagger-ui/**"), + new AntPathRequestMatcher("/api-docs"), + new AntPathRequestMatcher("/api-docs/**"), + new AntPathRequestMatcher("/v3/api-docs/**"), new AntPathRequestMatcher("/"), new AntPathRequestMatcher("/home"), new AntPathRequestMatcher("/login"), @@ -81,4 +87,4 @@ public CorsConfigurationSource corsConfigurationSource() { source.registerCorsConfiguration("/**", config); return source; } -} +} \ No newline at end of file diff --git a/src/test/java/codeview/main/auth/handler/OAuth2SuccessHandlerTest.java b/src/test/java/codeview/main/auth/handler/OAuth2SuccessHandlerTest.java index 4c1b631..7a3ee97 100644 --- a/src/test/java/codeview/main/auth/handler/OAuth2SuccessHandlerTest.java +++ b/src/test/java/codeview/main/auth/handler/OAuth2SuccessHandlerTest.java @@ -1,69 +1,74 @@ package codeview.main.auth.handler; import codeview.main.auth.jwt.TokenProvider; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.core.Authentication; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import java.io.IOException; -import java.io.PrintWriter; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.junit.jupiter.api.Assertions.assertEquals; -import com.fasterxml.jackson.databind.ObjectMapper; -import static org.mockito.Mockito.*; - -class OAuth2SuccessHandlerTest { +@AutoConfigureMockMvc +@SpringBootTest +public class OAuth2SuccessHandlerTest { @Mock private TokenProvider tokenProvider; - @Mock - private HttpServletRequest request; - - @Mock - private HttpServletResponse response; - @Mock private Authentication authentication; @InjectMocks private OAuth2SuccessHandler oAuth2SuccessHandler; + private MockMvc mockMvc; + @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); + public void setup() { + mockMvc = MockMvcBuilders.standaloneSetup(oAuth2SuccessHandler).build(); } - @Test - void testOnAuthenticationSuccess() throws IOException, ServletException { - when(tokenProvider.generateAccessToken(authentication)).thenReturn("mockAccessToken"); - when(tokenProvider.generateRefreshToken(authentication, "mockAccessToken")).thenReturn("mockRefreshToken"); - - PrintWriter writer = mock(PrintWriter.class); - when(response.getWriter()).thenReturn(writer); + public void testOnAuthenticationSuccess_withRedirect() throws Exception { + // 가짜 토큰 생성 + String accessToken = "testAccessToken"; + String refreshToken = "testRefreshToken"; + String redirectUrl = "http://localhost:3000"; + + // TokenProvider 모의 설정 + when(tokenProvider.generateAccessToken(authentication)).thenReturn(accessToken); + when(tokenProvider.generateRefreshToken(authentication, accessToken)).thenReturn(refreshToken); + + // OAuth2SuccessHandler가 요청을 처리하는지 테스트 + mockMvc.perform(get("/oauth2/authorization/kakao") + .param("redirect", redirectUrl)) + .andExpect(redirectedUrl(String.format("%s/auth/token?accessToken=%s&refreshToken=%s", + redirectUrl, accessToken, refreshToken))); + } - oAuth2SuccessHandler.onAuthenticationSuccess(request, response, authentication); + @Test - verify(response).setContentType("application/json"); - verify(response).setCharacterEncoding("UTF-8"); - verify(response).setStatus(HttpServletResponse.SC_OK); + public void testOnAuthenticationSuccess_withoutRedirect() throws Exception { + // 가짜 토큰 생성 + String accessToken = "testAccessToken"; + String refreshToken = "testRefreshToken"; + String defaultRedirectUrl = "http://localhost:3000"; - // JSON 문자열을 파싱하여 비교 - ObjectMapper objectMapper = new ObjectMapper(); - String expectedJson = "{\"code\":200,\"result\":{\"accessToken\":\"mockAccessToken\",\"refreshToken\":\"mockRefreshToken\"}}"; - String actualJson = "{\"result\":{\"accessToken\":\"mockAccessToken\",\"refreshToken\":\"mockRefreshToken\"},\"code\":200}"; + // TokenProvider 모의 설정 + when(tokenProvider.generateAccessToken(authentication)).thenReturn(accessToken); + when(tokenProvider.generateRefreshToken(authentication, accessToken)).thenReturn(refreshToken); - assertEquals(objectMapper.readTree(expectedJson), objectMapper.readTree(actualJson)); + // 리다이렉트 파라미터가 없을 때 기본 URL로 리디렉트 되는지 확인 + mockMvc.perform(get("/oauth2/authorization/kakao")) + .andExpect(redirectedUrl(String.format("%s/auth/token?accessToken=%s&refreshToken=%s", + defaultRedirectUrl, accessToken, refreshToken))); } - - }