diff --git a/.gitignore b/.gitignore index c2065bc..0cb0fde 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +!**/src/main/resources/application.yml ### STS ### .apt_generated diff --git a/build.gradle b/build.gradle index 728ec9c..08c0779 100644 --- a/build.gradle +++ b/build.gradle @@ -19,8 +19,12 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.mysql:mysql-connector-j' - testImplementation 'org.springframework.boot:spring-boot-starter-test' +// testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'io.jsonwebtoken:jjwt:0.9.1' + implementation 'javax.xml.bind:jaxb-api:2.3.0' } tasks.named('test') { diff --git a/src/main/java/com/example/demo/configuration/AuthenticationConfig.java b/src/main/java/com/example/demo/configuration/AuthenticationConfig.java new file mode 100644 index 0000000..440918d --- /dev/null +++ b/src/main/java/com/example/demo/configuration/AuthenticationConfig.java @@ -0,0 +1,44 @@ +package com.example.demo.configuration; + +import com.example.demo.jwt.JwtFilter; +import com.example.demo.service.MemberService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class AuthenticationConfig { + + private final MemberService memberService; + + @Value("${jwt.secret}") + private String secretKey; + + public AuthenticationConfig(MemberService memberService) { + this.memberService = memberService; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + httpSecurity + .csrf(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/members/join").permitAll() + .requestMatchers("/members/login").permitAll() + .requestMatchers(HttpMethod.POST).authenticated() + .requestMatchers(HttpMethod.GET).permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(new JwtFilter(memberService, secretKey), UsernamePasswordAuthenticationFilter.class); + return httpSecurity.build(); + } + +} diff --git a/src/main/java/com/example/demo/configuration/EncoderConfig.java b/src/main/java/com/example/demo/configuration/EncoderConfig.java new file mode 100644 index 0000000..caff823 --- /dev/null +++ b/src/main/java/com/example/demo/configuration/EncoderConfig.java @@ -0,0 +1,15 @@ +package com.example.demo.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class EncoderConfig { + + @Bean + public BCryptPasswordEncoder encoder() { + return new BCryptPasswordEncoder(); + } + +} diff --git a/src/main/java/com/example/demo/controller/ArticleController.java b/src/main/java/com/example/demo/controller/ArticleController.java index b28e4dd..dc2694c 100644 --- a/src/main/java/com/example/demo/controller/ArticleController.java +++ b/src/main/java/com/example/demo/controller/ArticleController.java @@ -1,69 +1,72 @@ package com.example.demo.controller; -import java.net.URI; -import java.util.List; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - import com.example.demo.controller.dto.request.ArticleCreateRequest; -import com.example.demo.controller.dto.response.ArticleResponse; import com.example.demo.controller.dto.request.ArticleUpdateRequest; +import com.example.demo.controller.dto.response.ArticleResponse; +import com.example.demo.exception.RestApiException; +import com.example.demo.exception.error.ArticleErrorCode; +import com.example.demo.exception.error.CommonErrorCode; import com.example.demo.service.ArticleService; +import com.example.demo.service.BoardService; +import com.example.demo.service.MemberService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; +import java.util.List; @RestController +@RequestMapping("/articles") public class ArticleController { private final ArticleService articleService; + private final MemberService memberService; + private final BoardService boardService; - public ArticleController(ArticleService articleService) { + public ArticleController(ArticleService articleService, MemberService memberService, BoardService boardService) { this.articleService = articleService; + this.memberService = memberService; + this.boardService = boardService; } - @GetMapping("/articles") - public ResponseEntity> getArticles( - @RequestParam Long boardId - ) { + @GetMapping() + public ResponseEntity> getArticles(@RequestParam Long boardId) { List response = articleService.getByBoardId(boardId); return ResponseEntity.ok(response); } - @GetMapping("/articles/{id}") - public ResponseEntity getArticle( - @PathVariable Long id - ) { + @GetMapping("/{id}") + public ResponseEntity getArticle(@PathVariable Long id) { + if (articleService.getArticles().stream().noneMatch(res -> res.id().equals(id))) { + throw new RestApiException(CommonErrorCode.RESOURCE_NOT_FOUND); + } ArticleResponse response = articleService.getById(id); return ResponseEntity.ok(response); } - @PostMapping("/articles") - public ResponseEntity crateArticle( - @RequestBody ArticleCreateRequest request - ) { + @PostMapping() + public ResponseEntity crateArticle(@RequestBody ArticleCreateRequest request) { + if (request.boardId() == null || request.authorId() == null || request.title() == null || request.description() == null) { + throw new RestApiException(CommonErrorCode.NULL_PARAMETER); + } + if (boardService.getBoards().stream().noneMatch(res -> res.id().equals(request.boardId())) || memberService.getAll().stream().noneMatch(res -> res.id().equals(request.authorId()))) { + throw new RestApiException(ArticleErrorCode.REFERENCE_ERROR); + } ArticleResponse response = articleService.create(request); return ResponseEntity.created(URI.create("/articles/" + response.id())).body(response); } - @PutMapping("/articles/{id}") - public ResponseEntity updateArticle( - @PathVariable Long id, - @RequestBody ArticleUpdateRequest request - ) { + @PutMapping("/{id}") + public ResponseEntity updateArticle(@PathVariable Long id, @RequestBody ArticleUpdateRequest request) { + if (boardService.getBoards().stream().noneMatch(res -> res.id().equals(request.boardId()))) { + throw new RestApiException(ArticleErrorCode.REFERENCE_ERROR); + } ArticleResponse response = articleService.update(id, request); return ResponseEntity.ok(response); } - @DeleteMapping("/articles/{id}") - public ResponseEntity updateArticle( - @PathVariable Long id - ) { + @DeleteMapping("/{id}") + public ResponseEntity updateArticle(@PathVariable Long id) { articleService.delete(id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/example/demo/controller/BoardController.java b/src/main/java/com/example/demo/controller/BoardController.java index ada81dc..edc4344 100644 --- a/src/main/java/com/example/demo/controller/BoardController.java +++ b/src/main/java/com/example/demo/controller/BoardController.java @@ -1,50 +1,56 @@ package com.example.demo.controller; -import java.util.List; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - import com.example.demo.controller.dto.request.BoardCreateRequest; import com.example.demo.controller.dto.request.BoardUpdateRequest; import com.example.demo.controller.dto.response.BoardResponse; +import com.example.demo.exception.RestApiException; +import com.example.demo.exception.error.BoardErrorCode; +import com.example.demo.exception.error.CommonErrorCode; +import com.example.demo.service.ArticleService; import com.example.demo.service.BoardService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController +@RequestMapping("/boards") public class BoardController { private final BoardService boardService; + private final ArticleService articleService; - public BoardController(BoardService boardService) { + public BoardController(BoardService boardService, ArticleService articleService) { this.boardService = boardService; + this.articleService = articleService; } - @GetMapping("/boards") + @GetMapping() public List getBoards() { return boardService.getBoards(); } - @GetMapping("/boards/{id}") + @GetMapping("/{id}") public BoardResponse getBoard( @PathVariable Long id ) { + if (boardService.getBoards().stream().noneMatch(res -> res.id().equals(id))) { + throw new RestApiException(CommonErrorCode.RESOURCE_NOT_FOUND); + } return boardService.getBoardById(id); } - @PostMapping("/boards") + @PostMapping() public BoardResponse createBoard( @RequestBody BoardCreateRequest request ) { + if (request.name() == null) { + throw new RestApiException(CommonErrorCode.NULL_PARAMETER); + } return boardService.createBoard(request); } - @PutMapping("/boards/{id}") + @PutMapping("/{id}") public BoardResponse updateBoard( @PathVariable Long id, @RequestBody BoardUpdateRequest updateRequest @@ -52,10 +58,15 @@ public BoardResponse updateBoard( return boardService.update(id, updateRequest); } - @DeleteMapping("/boards/{id}") + @DeleteMapping("/{id}") public ResponseEntity deleteBoard( @PathVariable Long id ) { + if (articleService.getArticles() + .stream() + .anyMatch(res -> res.boardId().equals(id))) { + throw new RestApiException(BoardErrorCode.ARTICLE_EXISTENCE); + } boardService.deleteBoard(id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/example/demo/controller/MemberController.java b/src/main/java/com/example/demo/controller/MemberController.java index ddb18ec..a84271c 100644 --- a/src/main/java/com/example/demo/controller/MemberController.java +++ b/src/main/java/com/example/demo/controller/MemberController.java @@ -1,65 +1,77 @@ package com.example.demo.controller; -import java.util.List; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - import com.example.demo.controller.dto.request.MemberCreateRequest; +import com.example.demo.controller.dto.request.MemberLoginRequest; import com.example.demo.controller.dto.request.MemberUpdateRequest; import com.example.demo.controller.dto.response.MemberResponse; +import com.example.demo.exception.RestApiException; +import com.example.demo.exception.error.CommonErrorCode; +import com.example.demo.exception.error.MemberErrorCode; +import com.example.demo.service.ArticleService; import com.example.demo.service.MemberService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController +@RequestMapping("/members") public class MemberController { private final MemberService memberService; + private final ArticleService articleService; - public MemberController(MemberService memberService) { + public MemberController(MemberService memberService, ArticleService articleService) { this.memberService = memberService; + this.articleService = articleService; } - @GetMapping("/members") + @GetMapping() public ResponseEntity> getMembers() { List response = memberService.getAll(); return ResponseEntity.ok(response); } - @GetMapping("/members/{id}") - public ResponseEntity getMember( - @PathVariable Long id - ) { + @GetMapping("/{id}") + public ResponseEntity getMember(@PathVariable Long id) { + if (memberService.getAll().stream().noneMatch(res -> res.id().equals(id))) { + throw new RestApiException(CommonErrorCode.RESOURCE_NOT_FOUND); + } MemberResponse response = memberService.getById(id); return ResponseEntity.ok(response); } - @PostMapping("/members") - public ResponseEntity create( - @RequestBody MemberCreateRequest request - ) { + @PostMapping("/join") + public ResponseEntity create(@RequestBody MemberCreateRequest request) { + if (request.name() == null || request.email() == null || request.password() == null) { + throw new RestApiException(CommonErrorCode.NULL_PARAMETER); + } + if (memberService.getAll().stream().anyMatch((res -> res.email().equals(request.email())))) { + throw new RestApiException(MemberErrorCode.EMAIL_CONFLICT); + } MemberResponse response = memberService.create(request); return ResponseEntity.ok(response); } - @PutMapping("/members/{id}") - public ResponseEntity updateMember( - @PathVariable Long id, - @RequestBody MemberUpdateRequest request - ) { + @PostMapping("/login") + public ResponseEntity login(@RequestBody MemberLoginRequest request) { + return ResponseEntity.ok().body(memberService.login(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateMember(@PathVariable Long id, @RequestBody MemberUpdateRequest request) { + if (memberService.getAll().stream().filter(res -> !res.id().equals(id)).anyMatch(res -> res.email().equals(request.email()))) { + throw new RestApiException(MemberErrorCode.EMAIL_CONFLICT); + } MemberResponse response = memberService.update(id, request); return ResponseEntity.ok(response); } - @DeleteMapping("/members/{id}") - public ResponseEntity deleteMember( - @PathVariable Long id - ) { + @DeleteMapping("/{id}") + public ResponseEntity deleteMember(@PathVariable Long id) { + if (articleService.getArticles().stream().anyMatch(res -> res.authorId().equals(id))) { + throw new RestApiException(MemberErrorCode.ARTICLE_EXISTENCE); + } memberService.delete(id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/example/demo/controller/dto/request/MemberLoginRequest.java b/src/main/java/com/example/demo/controller/dto/request/MemberLoginRequest.java new file mode 100644 index 0000000..5032ab9 --- /dev/null +++ b/src/main/java/com/example/demo/controller/dto/request/MemberLoginRequest.java @@ -0,0 +1,4 @@ +package com.example.demo.controller.dto.request; + +public record MemberLoginRequest(String email, String password) { +} diff --git a/src/main/java/com/example/demo/controller/dto/response/ArticleResponse.java b/src/main/java/com/example/demo/controller/dto/response/ArticleResponse.java index 65f048f..d18c01b 100644 --- a/src/main/java/com/example/demo/controller/dto/response/ArticleResponse.java +++ b/src/main/java/com/example/demo/controller/dto/response/ArticleResponse.java @@ -1,7 +1,5 @@ package com.example.demo.controller.dto.response; -import java.time.LocalDateTime; - import com.example.demo.domain.Article; import com.example.demo.domain.Board; import com.example.demo.domain.Member; @@ -9,6 +7,8 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import java.time.LocalDateTime; + @JsonNaming(SnakeCaseStrategy.class) public record ArticleResponse( Long id, @@ -35,4 +35,16 @@ public static ArticleResponse of(Article article, Member member, Board board) { article.getModifiedAt() ); } + + public static ArticleResponse from(Article article) { + return new ArticleResponse( + article.getId(), + article.getAuthor().getId(), + article.getBoard().getId(), + article.getTitle(), + article.getContent(), + article.getCreatedAt(), + article.getModifiedAt() + ); + } } diff --git a/src/main/java/com/example/demo/domain/Article.java b/src/main/java/com/example/demo/domain/Article.java index e0183db..603a7ad 100644 --- a/src/main/java/com/example/demo/domain/Article.java +++ b/src/main/java/com/example/demo/domain/Article.java @@ -1,70 +1,99 @@ package com.example.demo.domain; +import jakarta.persistence.*; + import java.time.LocalDateTime; +@Entity +@Table(name = "article") public class Article { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private Long authorId; - private Long boardId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id") + private Member author; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "board_id") + private Board board; + + @Column(name = "title") private String title; + + @Column(name = "content") private String content; + + @Column(name = "created_date") private LocalDateTime createdAt; + + @Column(name = "modified_date") private LocalDateTime modifiedAt; + public Article() { + } + public Article( - Long id, - Long authorId, - Long boardId, - String title, - String content, - LocalDateTime createdAt, - LocalDateTime modifiedAt + Long id, + Member author, + Board board, + String title, + String content, + LocalDateTime createdAt, + LocalDateTime modifiedAt ) { this.id = id; - this.authorId = authorId; - this.boardId = boardId; + this.author = author; + this.board = board; this.title = title; this.content = content; this.createdAt = createdAt; this.modifiedAt = modifiedAt; + + author.getArticles().add(this); + board.getArticles().add(this); } - public Article(Long authorId, Long boardId, String title, String content) { + public Article(Member author, Board board, String title, String content) { this.id = null; - this.authorId = authorId; - this.boardId = boardId; + this.author = author; + this.board = board; this.title = title; this.content = content; this.createdAt = LocalDateTime.now(); this.modifiedAt = LocalDateTime.now(); + + author.getArticles().add(this); + board.getArticles().add(this); } - public void update(Long boardId, String title, String description) { - this.boardId = boardId; + public void update(Board board, String title, String description) { + this.board.getArticles().remove(this); + + this.board = board; this.title = title; this.content = description; this.modifiedAt = LocalDateTime.now(); - } - - public void setId(Long id) { - this.id = id; - } - public void setModifiedAt(LocalDateTime modifiedAt) { - this.modifiedAt = modifiedAt; + board.getArticles().add(this); } public Long getId() { return id; } - public Long getAuthorId() { - return authorId; + public void setId(Long id) { + this.id = id; + } + + public Member getAuthor() { + return author; } - public Long getBoardId() { - return boardId; + public Board getBoard() { + return board; } public String getTitle() { @@ -82,4 +111,9 @@ public LocalDateTime getCreatedAt() { public LocalDateTime getModifiedAt() { return modifiedAt; } + + public void setModifiedAt(LocalDateTime modifiedAt) { + this.modifiedAt = modifiedAt; + } + } diff --git a/src/main/java/com/example/demo/domain/Board.java b/src/main/java/com/example/demo/domain/Board.java index 992e2c6..0af9858 100644 --- a/src/main/java/com/example/demo/domain/Board.java +++ b/src/main/java/com/example/demo/domain/Board.java @@ -1,10 +1,24 @@ package com.example.demo.domain; +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "board") public class Board { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(name = "name") private String name; + @OneToMany(mappedBy = "board", orphanRemoval = true, cascade = CascadeType.ALL) + private List
articles = new ArrayList<>(); + public Board(Long id, String name) { this.id = id; this.name = name; @@ -14,6 +28,10 @@ public Board(String name) { this.name = name; } + public Board() { + + } + public Long getId() { return id; } @@ -26,7 +44,12 @@ public String getName() { return name; } + public List
getArticles() { + return articles; + } + public void update(String name) { this.name = name; } + } diff --git a/src/main/java/com/example/demo/domain/Member.java b/src/main/java/com/example/demo/domain/Member.java index fe80d6b..34edc65 100644 --- a/src/main/java/com/example/demo/domain/Member.java +++ b/src/main/java/com/example/demo/domain/Member.java @@ -1,12 +1,30 @@ package com.example.demo.domain; +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "member") public class Member { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(name = "name") private String name; + + @Column(name = "email") private String email; + + @Column(name = "password") private String password; + @OneToMany(mappedBy = "author") + private List
articles = new ArrayList<>(); + public Member(Long id, String name, String email, String password) { this.id = id; this.name = name; @@ -20,6 +38,10 @@ public Member(String name, String email, String password) { this.password = password; } + public Member() { + + } + public void update(String name, String email) { this.name = name; this.email = email; @@ -44,4 +66,9 @@ public String getEmail() { public String getPassword() { return password; } + + public List
getArticles() { + return articles; + } + } diff --git a/src/main/java/com/example/demo/exception/ErrorResponse.java b/src/main/java/com/example/demo/exception/ErrorResponse.java new file mode 100644 index 0000000..43d4b61 --- /dev/null +++ b/src/main/java/com/example/demo/exception/ErrorResponse.java @@ -0,0 +1,4 @@ +package com.example.demo.exception; + +public record ErrorResponse(String status, String message) { +} diff --git a/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java b/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..fd24165 --- /dev/null +++ b/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java @@ -0,0 +1,27 @@ +package com.example.demo.exception; + +import com.example.demo.exception.error.ErrorCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(RestApiException.class) + public ResponseEntity handleCustomException(RestApiException e) { + ErrorCode errorCode = e.getErrorCode(); + return handleExceptionInternal(errorCode); + } + + private ErrorResponse makeErrorResponse(ErrorCode errorCode) { + return new ErrorResponse(errorCode.getHttpStatus().toString(), errorCode.getMessage()); + } + + private ResponseEntity handleExceptionInternal(ErrorCode errorCode) { + ErrorResponse rsp = makeErrorResponse(errorCode); + return ResponseEntity.status(errorCode.getHttpStatus()).body(rsp); + } + +} diff --git a/src/main/java/com/example/demo/exception/RestApiException.java b/src/main/java/com/example/demo/exception/RestApiException.java new file mode 100644 index 0000000..bce9b99 --- /dev/null +++ b/src/main/java/com/example/demo/exception/RestApiException.java @@ -0,0 +1,17 @@ +package com.example.demo.exception; + +import com.example.demo.exception.error.ErrorCode; + +public class RestApiException extends RuntimeException{ + + private final ErrorCode errorCode; + + public RestApiException(ErrorCode errorCode) { + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; + } + +} diff --git a/src/main/java/com/example/demo/exception/error/ArticleErrorCode.java b/src/main/java/com/example/demo/exception/error/ArticleErrorCode.java new file mode 100644 index 0000000..311af26 --- /dev/null +++ b/src/main/java/com/example/demo/exception/error/ArticleErrorCode.java @@ -0,0 +1,27 @@ +package com.example.demo.exception.error; + +import org.springframework.http.HttpStatus; + +public enum ArticleErrorCode implements ErrorCode{ + + REFERENCE_ERROR(HttpStatus.BAD_REQUEST, "존재하지 않는 사용자 또는 게시판입니다."); + + private final HttpStatus httpStatus; + private final String message; + + ArticleErrorCode(HttpStatus httpStatus, String message) { + this.httpStatus = httpStatus; + this.message = message; + } + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/com/example/demo/exception/error/BoardErrorCode.java b/src/main/java/com/example/demo/exception/error/BoardErrorCode.java new file mode 100644 index 0000000..ad7a6c1 --- /dev/null +++ b/src/main/java/com/example/demo/exception/error/BoardErrorCode.java @@ -0,0 +1,27 @@ +package com.example.demo.exception.error; + +import org.springframework.http.HttpStatus; + +public enum BoardErrorCode implements ErrorCode{ + + ARTICLE_EXISTENCE(HttpStatus.BAD_REQUEST, "작성된 게시물이 존재합니다."); + + private final HttpStatus httpStatus; + private final String message; + + BoardErrorCode(HttpStatus httpStatus, String message) { + this.httpStatus = httpStatus; + this.message = message; + } + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/com/example/demo/exception/error/CommonErrorCode.java b/src/main/java/com/example/demo/exception/error/CommonErrorCode.java new file mode 100644 index 0000000..edef237 --- /dev/null +++ b/src/main/java/com/example/demo/exception/error/CommonErrorCode.java @@ -0,0 +1,28 @@ +package com.example.demo.exception.error; + +import org.springframework.http.HttpStatus; + +public enum CommonErrorCode implements ErrorCode{ + + RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "자원이 존재하지 않습니다."), + NULL_PARAMETER(HttpStatus.BAD_REQUEST, "NULL 파라미터가 존재합니다."); + + private final HttpStatus httpStatus; + private final String message; + + CommonErrorCode(HttpStatus httpStatus, String message) { + this.httpStatus = httpStatus; + this.message = message; + } + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/com/example/demo/exception/error/ErrorCode.java b/src/main/java/com/example/demo/exception/error/ErrorCode.java new file mode 100644 index 0000000..93738cb --- /dev/null +++ b/src/main/java/com/example/demo/exception/error/ErrorCode.java @@ -0,0 +1,10 @@ +package com.example.demo.exception.error; + +import org.springframework.http.HttpStatus; + +public interface ErrorCode { + + HttpStatus getHttpStatus(); + String getMessage(); + +} diff --git a/src/main/java/com/example/demo/exception/error/MemberErrorCode.java b/src/main/java/com/example/demo/exception/error/MemberErrorCode.java new file mode 100644 index 0000000..da1de20 --- /dev/null +++ b/src/main/java/com/example/demo/exception/error/MemberErrorCode.java @@ -0,0 +1,30 @@ +package com.example.demo.exception.error; + +import org.springframework.http.HttpStatus; + +public enum MemberErrorCode implements ErrorCode{ + + EMAIL_NOT_FOUND(HttpStatus.NOT_FOUND, "이메일을 찾을 수 없습니다."), + EMAIL_CONFLICT(HttpStatus.CONFLICT, "중복된 이메일입니다."), + ARTICLE_EXISTENCE(HttpStatus.BAD_REQUEST, "작성한 게시물이 존재합니다."), + INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "패스워드를 잘못 입력했습니다."); + + private final HttpStatus httpStatus; + private final String message; + + MemberErrorCode(HttpStatus httpStatus, String message) { + this.httpStatus = httpStatus; + this.message = message; + } + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/com/example/demo/jwt/JwtFilter.java b/src/main/java/com/example/demo/jwt/JwtFilter.java new file mode 100644 index 0000000..743fbcb --- /dev/null +++ b/src/main/java/com/example/demo/jwt/JwtFilter.java @@ -0,0 +1,59 @@ +package com.example.demo.jwt; + +import com.example.demo.service.MemberService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.List; + +public class JwtFilter extends OncePerRequestFilter { + + private final MemberService memberService; + private final String secretKey; + + public JwtFilter(MemberService memberService, String secretKey) { + this.memberService = memberService; + this.secretKey = secretKey; + } + + // 필터 실제 로직 + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + final String authorization = request.getHeader(HttpHeaders.AUTHORIZATION); + + // token 안보내면 block + if (authorization == null || !authorization.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + // token 꺼내기 + String token = authorization.split(" ")[1]; + + // token expired 여부 + if (JwtUtil.isExpired(token, secretKey)) { + filterChain.doFilter(request, response); + return; + } + + // UserName token 에서 꺼내기 + String userName = JwtUtil.getUserName(token, secretKey); + + //인증된 사용자를 나타내는 토큰 객체를 생성하고, 권한 정보를 설정 + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(userName, null, List.of(new SimpleGrantedAuthority("USER"))); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/example/demo/jwt/JwtUtil.java b/src/main/java/com/example/demo/jwt/JwtUtil.java new file mode 100644 index 0000000..dd930cc --- /dev/null +++ b/src/main/java/com/example/demo/jwt/JwtUtil.java @@ -0,0 +1,33 @@ +package com.example.demo.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import java.util.Date; + +public class JwtUtil { + + public static String getUserName(String token, String secretKey) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token) + .getBody().get("userName", String.class); + } + + public static boolean isExpired(String token, String secretKey) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token) + .getBody().getExpiration().before(new Date()); + } + + public static String createJwt(String name, String secretKey, Long expiredMs) { + Claims claims = Jwts.claims(); + claims.put("userName", name); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + } + +} diff --git a/src/main/java/com/example/demo/repository/ArticleRepository.java b/src/main/java/com/example/demo/repository/ArticleRepository.java index be3ebd4..fa1ec0a 100644 --- a/src/main/java/com/example/demo/repository/ArticleRepository.java +++ b/src/main/java/com/example/demo/repository/ArticleRepository.java @@ -1,22 +1,22 @@ package com.example.demo.repository; -import java.util.List; - import com.example.demo.domain.Article; +import org.springframework.data.repository.Repository; + +import java.util.List; -public interface ArticleRepository { +public interface ArticleRepository extends Repository { List
findAll(); List
findAllByBoardId(Long boardId); - List
findAllByMemberId(Long memberId); + List
findAllByAuthorId(Long memberId); Article findById(Long id); - Article insert(Article article); - - Article update(Article article); + Article save(Article article); void deleteById(Long id); + } diff --git a/src/main/java/com/example/demo/repository/ArticleRepositoryEntityManager.java b/src/main/java/com/example/demo/repository/ArticleRepositoryEntityManager.java new file mode 100644 index 0000000..1049966 --- /dev/null +++ b/src/main/java/com/example/demo/repository/ArticleRepositoryEntityManager.java @@ -0,0 +1,52 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Article; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.TypedQuery; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class ArticleRepositoryEntityManager { + +// @PersistenceContext +// private EntityManager entityManager; +// +// public List
findAll() { +// return entityManager.createQuery("SELECT a FROM Article a", Article.class).getResultList(); +// } +// +// public List
findAllByBoardId(Long boardId) { +// String jpql = "SELECT a FROM Article a WHERE a.boardId = :boardId"; +// TypedQuery
query = entityManager.createQuery(jpql, Article.class); +// query.setParameter("boardId", boardId); +// return query.getResultList(); +// } +// +// public List
findAllByMemberId(Long memberId) { +// String jpql = "SELECT a FROM Article a WHERE a.authorId = :authorId"; +// TypedQuery
query = entityManager.createQuery(jpql, Article.class); +// query.setParameter("authorId", memberId); +// return query.getResultList(); +// } +// +// public Article findById(Long id) { +// return entityManager.find(Article.class, id); +// } +// +// public Article insert(Article article) { +// entityManager.persist(article); +// return findById(article.getId()); +// } +// +// public Article update(Article article) { +// return entityManager.merge(article); +// } +// +// public void deleteById(Long id) { +// entityManager.remove(findById(id)); +// } + +} diff --git a/src/main/java/com/example/demo/repository/ArticleRepositoryJdbc.java b/src/main/java/com/example/demo/repository/ArticleRepositoryJdbc.java index c9a272e..389b04d 100644 --- a/src/main/java/com/example/demo/repository/ArticleRepositoryJdbc.java +++ b/src/main/java/com/example/demo/repository/ArticleRepositoryJdbc.java @@ -1,108 +1,100 @@ package com.example.demo.repository; -import java.sql.PreparedStatement; -import java.util.List; - +import com.example.demo.domain.Article; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; -import com.example.demo.domain.Article; - -@Repository -public class ArticleRepositoryJdbc implements ArticleRepository { - - private final JdbcTemplate jdbcTemplate; - - public ArticleRepositoryJdbc(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - private static final RowMapper
articleRowMapper = (rs, rowNum) -> new Article( - rs.getLong("id"), - rs.getLong("author_id"), - rs.getLong("board_id"), - rs.getString("title"), - rs.getString("content"), - rs.getTimestamp("created_date").toLocalDateTime(), - rs.getTimestamp("modified_date").toLocalDateTime() - ); - - @Override - public List
findAll() { - return jdbcTemplate.query(""" - SELECT id, board_id, author_id, title, content, created_date, modified_date - FROM article - """, articleRowMapper); - } - - @Override - public List
findAllByBoardId(Long boardId) { - return jdbcTemplate.query(""" - SELECT id, board_id, author_id, title, content, created_date, modified_date - FROM article - WHERE board_id = ? - """, articleRowMapper, boardId); - } - - @Override - public List
findAllByMemberId(Long memberId) { - return jdbcTemplate.query(""" - SELECT id, board_id, author_id, title, content, created_date, modified_date - FROM article - WHERE author_id = ? - """, articleRowMapper, memberId); - } - - @Override - public Article findById(Long id) { - return jdbcTemplate.queryForObject(""" - SELECT id, board_id, author_id, title, content, created_date, modified_date - FROM article - WHERE id = ? - """, articleRowMapper, id); - } +import java.sql.PreparedStatement; +import java.util.List; - @Override - public Article insert(Article article) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(con -> { - PreparedStatement ps = con.prepareStatement(""" - INSERT INTO article (board_id, author_id, title, content) - VALUES (?, ?, ?, ?) - """, - new String[]{"id"}); - ps.setLong(1, article.getBoardId()); - ps.setLong(2, article.getAuthorId()); - ps.setString(3, article.getTitle()); - ps.setString(4, article.getContent()); - return ps; - }, keyHolder); - return findById(keyHolder.getKey().longValue()); - } +public class ArticleRepositoryJdbc { - @Override - public Article update(Article article) { - jdbcTemplate.update(""" - UPDATE article - SET board_id = ?, title = ?, content = ? - WHERE id = ? - """, - article.getBoardId(), - article.getTitle(), - article.getContent(), - article.getId() - ); - return findById(article.getId()); - } +// private final JdbcTemplate jdbcTemplate; +// +// public ArticleRepositoryJdbc(JdbcTemplate jdbcTemplate) { +// this.jdbcTemplate = jdbcTemplate; +// } +// +// private static final RowMapper
articleRowMapper = (rs, rowNum) -> new Article( +// rs.getLong("id"), +// rs.getLong("author_id"), +// rs.getLong("board_id"), +// rs.getString("title"), +// rs.getString("content"), +// rs.getTimestamp("created_date").toLocalDateTime(), +// rs.getTimestamp("modified_date").toLocalDateTime() +// ); +// +// public List
findAll() { +// return jdbcTemplate.query(""" +// SELECT id, board_id, author_id, title, content, created_date, modified_date +// FROM article +// """, articleRowMapper); +// } +// +// public List
findAllByBoardId(Long boardId) { +// return jdbcTemplate.query(""" +// SELECT id, board_id, author_id, title, content, created_date, modified_date +// FROM article +// WHERE board_id = ? +// """, articleRowMapper, boardId); +// } +// +// public List
findAllByMemberId(Long memberId) { +// return jdbcTemplate.query(""" +// SELECT id, board_id, author_id, title, content, created_date, modified_date +// FROM article +// WHERE author_id = ? +// """, articleRowMapper, memberId); +// } +// +// public Article findById(Long id) { +// return jdbcTemplate.queryForObject(""" +// SELECT id, board_id, author_id, title, content, created_date, modified_date +// FROM article +// WHERE id = ? +// """, articleRowMapper, id); +// } +// +// public Article insert(Article article) { +// KeyHolder keyHolder = new GeneratedKeyHolder(); +// jdbcTemplate.update(con -> { +// PreparedStatement ps = con.prepareStatement(""" +// INSERT INTO article (board_id, author_id, title, content) +// VALUES (?, ?, ?, ?) +// """, +// new String[]{"id"}); +// ps.setLong(1, article.getBoardId()); +// ps.setLong(2, article.getAuthorId()); +// ps.setString(3, article.getTitle()); +// ps.setString(4, article.getContent()); +// return ps; +// }, keyHolder); +// return findById(keyHolder.getKey().longValue()); +// } +// +// public Article update(Article article) { +// jdbcTemplate.update(""" +// UPDATE article +// SET board_id = ?, title = ?, content = ? +// WHERE id = ? +// """, +// article.getBoardId(), +// article.getTitle(), +// article.getContent(), +// article.getId() +// ); +// return findById(article.getId()); +// } +// +// public void deleteById(Long id) { +// jdbcTemplate.update(""" +// DELETE FROM article +// WHERE id = ? +// """, id); +// } - @Override - public void deleteById(Long id) { - jdbcTemplate.update(""" - DELETE FROM article - WHERE id = ? - """, id); - } } diff --git a/src/main/java/com/example/demo/repository/ArticleRepositoryMemory.java b/src/main/java/com/example/demo/repository/ArticleRepositoryMemory.java index 13ba78b..bdd7d5c 100644 --- a/src/main/java/com/example/demo/repository/ArticleRepositoryMemory.java +++ b/src/main/java/com/example/demo/repository/ArticleRepositoryMemory.java @@ -1,70 +1,64 @@ package com.example.demo.repository; +import com.example.demo.domain.Article; + import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -import com.example.demo.domain.Article; - -public class ArticleRepositoryMemory implements ArticleRepository { - - private static final Map articles = new HashMap<>(); - private static final AtomicLong autoincrement = new AtomicLong(1); - - @Override - public List
findAll() { - return articles.entrySet().stream() - .map(it -> { - Article article = it.getValue(); - article.setId(it.getKey()); - return article; - }).toList(); - } - - @Override - public List
findAllByBoardId(Long boardId) { - return articles.entrySet().stream() - .filter(it -> it.getValue().getBoardId().equals(boardId)) - .map(it -> { - Article article = it.getValue(); - article.setId(it.getKey()); - return article; - }).toList(); - } - - @Override - public List
findAllByMemberId(Long memberId) { - return articles.entrySet().stream() - .filter(it -> it.getValue().getAuthorId().equals(memberId)) - .map(it -> { - Article article = it.getValue(); - article.setId(it.getKey()); - return article; - }).toList(); - } - - @Override - public Article findById(Long id) { - return articles.getOrDefault(id, null); - } - - @Override - public Article insert(Article article) { - long id = autoincrement.getAndIncrement(); - articles.put(id, article); - article.setId(id); - return article; - } +public class ArticleRepositoryMemory { - @Override - public Article update(Article article) { - articles.put(article.getId(), article); - return article; - } +// private static final Map articles = new HashMap<>(); +// private static final AtomicLong autoincrement = new AtomicLong(1); +// +// public List
findAll() { +// return articles.entrySet().stream() +// .map(it -> { +// Article article = it.getValue(); +// article.setId(it.getKey()); +// return article; +// }).toList(); +// } +// +// public List
findAllByBoardId(Long boardId) { +// return articles.entrySet().stream() +// .filter(it -> it.getValue().getBoardId().equals(boardId)) +// .map(it -> { +// Article article = it.getValue(); +// article.setId(it.getKey()); +// return article; +// }).toList(); +// } +// +// public List
findAllByMemberId(Long memberId) { +// return articles.entrySet().stream() +// .filter(it -> it.getValue().getAuthorId().equals(memberId)) +// .map(it -> { +// Article article = it.getValue(); +// article.setId(it.getKey()); +// return article; +// }).toList(); +// } +// +// public Article findById(Long id) { +// return articles.getOrDefault(id, null); +// } +// +// public Article insert(Article article) { +// long id = autoincrement.getAndIncrement(); +// articles.put(id, article); +// article.setId(id); +// return article; +// } +// +// public Article update(Article article) { +// articles.put(article.getId(), article); +// return article; +// } +// +// public void deleteById(Long id) { +// articles.remove(id); +// } - @Override - public void deleteById(Long id) { - articles.remove(id); - } } diff --git a/src/main/java/com/example/demo/repository/BoardRepository.java b/src/main/java/com/example/demo/repository/BoardRepository.java index cc2dfd0..578a203 100644 --- a/src/main/java/com/example/demo/repository/BoardRepository.java +++ b/src/main/java/com/example/demo/repository/BoardRepository.java @@ -1,18 +1,18 @@ package com.example.demo.repository; -import java.util.List; - import com.example.demo.domain.Board; +import org.springframework.data.repository.Repository; + +import java.util.List; -public interface BoardRepository { +public interface BoardRepository extends Repository { List findAll(); Board findById(Long id); - Board insert(Board board); + Board save(Board board); void deleteById(Long id); - Board update(Board board); } diff --git a/src/main/java/com/example/demo/repository/BoardRepositoryEntityManager.java b/src/main/java/com/example/demo/repository/BoardRepositoryEntityManager.java new file mode 100644 index 0000000..5ba6c2e --- /dev/null +++ b/src/main/java/com/example/demo/repository/BoardRepositoryEntityManager.java @@ -0,0 +1,37 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Board; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class BoardRepositoryEntityManager { + + @PersistenceContext + private EntityManager entityManager; + + public List findAll() { + return entityManager.createQuery("SELECT b FROM Board b", Board.class).getResultList(); + } + + public Board findById(Long id) { + return entityManager.find(Board.class, id); + } + + public Board insert(Board board) { + entityManager.persist(board); + return findById(board.getId()); + } + + public void deleteById(Long id) { + entityManager.remove(findById(id)); + } + + public Board update(Board board) { + return entityManager.merge(board); + } + +} diff --git a/src/main/java/com/example/demo/repository/BoardRepositoryJdbc.java b/src/main/java/com/example/demo/repository/BoardRepositoryJdbc.java index c4fd6f6..6bc156e 100644 --- a/src/main/java/com/example/demo/repository/BoardRepositoryJdbc.java +++ b/src/main/java/com/example/demo/repository/BoardRepositoryJdbc.java @@ -1,18 +1,16 @@ package com.example.demo.repository; -import java.sql.PreparedStatement; -import java.util.List; - +import com.example.demo.domain.Board; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; -import com.example.demo.domain.Board; +import java.sql.PreparedStatement; +import java.util.List; -@Repository -public class BoardRepositoryJdbc implements BoardRepository { +public class BoardRepositoryJdbc { private final JdbcTemplate jdbcTemplate; @@ -25,7 +23,6 @@ public BoardRepositoryJdbc(JdbcTemplate jdbcTemplate) { rs.getString("name") ); - @Override public List findAll() { return jdbcTemplate.query(""" SELECT id, name @@ -33,7 +30,6 @@ public List findAll() { """, boardRowMapper); } - @Override public Board findById(Long id) { return jdbcTemplate.queryForObject(""" SELECT id, name @@ -42,7 +38,6 @@ public Board findById(Long id) { """, boardRowMapper, id); } - @Override public Board insert(Board board) { KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(con -> { @@ -55,18 +50,17 @@ INSERT INTO board (name) VALUES (?) return findById(keyHolder.getKey().longValue()); } - @Override public void deleteById(Long id) { jdbcTemplate.update(""" DELETE FROM board WHERE id = ? """, id); } - @Override public Board update(Board board) { return jdbcTemplate.queryForObject(""" UPDATE board SET name = ? WHERE id = ? """, boardRowMapper, board.getName(), board.getId() ); } + } diff --git a/src/main/java/com/example/demo/repository/BoardRepositoryMemory.java b/src/main/java/com/example/demo/repository/BoardRepositoryMemory.java index 8cf5ecf..c467088 100644 --- a/src/main/java/com/example/demo/repository/BoardRepositoryMemory.java +++ b/src/main/java/com/example/demo/repository/BoardRepositoryMemory.java @@ -1,13 +1,13 @@ package com.example.demo.repository; +import com.example.demo.domain.Board; + import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -import com.example.demo.domain.Board; - -public class BoardRepositoryMemory implements BoardRepository { +public class BoardRepositoryMemory { private static final Map boards = new HashMap<>(); private static final AtomicLong autoincrement = new AtomicLong(1); @@ -17,7 +17,6 @@ public class BoardRepositoryMemory implements BoardRepository { boards.put(autoincrement.getAndIncrement(), new Board("자유게시판")); } - @Override public List findAll() { return boards.entrySet().stream() .map(it -> { @@ -27,24 +26,21 @@ public List findAll() { }).toList(); } - @Override public Board findById(Long id) { return boards.getOrDefault(id, null); } - @Override public Board insert(Board board) { boards.put(autoincrement.getAndIncrement(), board); return board; } - @Override public void deleteById(Long id) { boards.remove(id); } - @Override public Board update(Board board) { return boards.put(board.getId(), board); } + } diff --git a/src/main/java/com/example/demo/repository/MemberRepository.java b/src/main/java/com/example/demo/repository/MemberRepository.java index 8e2ad14..7b89c51 100644 --- a/src/main/java/com/example/demo/repository/MemberRepository.java +++ b/src/main/java/com/example/demo/repository/MemberRepository.java @@ -1,18 +1,21 @@ package com.example.demo.repository; -import java.util.List; - import com.example.demo.domain.Member; +import org.springframework.data.repository.Repository; -public interface MemberRepository { +import java.util.List; +import java.util.Optional; + +public interface MemberRepository extends Repository { List findAll(); Member findById(Long id); - Member insert(Member member); + Optional findByEmail(String email); - Member update(Member member); + Member save(Member member); void deleteById(Long id); + } diff --git a/src/main/java/com/example/demo/repository/MemberRepositoryEntityManager.java b/src/main/java/com/example/demo/repository/MemberRepositoryEntityManager.java new file mode 100644 index 0000000..a4442a2 --- /dev/null +++ b/src/main/java/com/example/demo/repository/MemberRepositoryEntityManager.java @@ -0,0 +1,37 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Member; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class MemberRepositoryEntityManager { + + @PersistenceContext + private EntityManager entityManager; + + public List findAll() { + return entityManager.createQuery("SELECT m FROM Member m", Member.class).getResultList(); + } + + public Member findById(Long id) { + return entityManager.find(Member.class, id); + } + + public Member insert(Member member) { + entityManager.persist(member); + return findById(member.getId()); + } + + public Member update(Member member) { + return entityManager.merge(member); + } + + public void deleteById(Long id) { + entityManager.remove(findById(id)); + } + +} diff --git a/src/main/java/com/example/demo/repository/MemberRepositoryJdbc.java b/src/main/java/com/example/demo/repository/MemberRepositoryJdbc.java index 30d2262..0aab1ff 100644 --- a/src/main/java/com/example/demo/repository/MemberRepositoryJdbc.java +++ b/src/main/java/com/example/demo/repository/MemberRepositoryJdbc.java @@ -1,18 +1,16 @@ package com.example.demo.repository; -import java.sql.PreparedStatement; -import java.util.List; - +import com.example.demo.domain.Member; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; -import com.example.demo.domain.Member; +import java.sql.PreparedStatement; +import java.util.List; -@Repository -public class MemberRepositoryJdbc implements MemberRepository { +public class MemberRepositoryJdbc { private final JdbcTemplate jdbcTemplate; @@ -27,7 +25,6 @@ public MemberRepositoryJdbc(JdbcTemplate jdbcTemplate) { rs.getString("password") ); - @Override public List findAll() { return jdbcTemplate.query(""" SELECT id, name, email, password @@ -35,7 +32,6 @@ public List findAll() { """, memberRowMapper); } - @Override public Member findById(Long id) { return jdbcTemplate.queryForObject(""" SELECT id, name, email, password @@ -44,7 +40,6 @@ public Member findById(Long id) { """, memberRowMapper, id); } - @Override public Member insert(Member member) { KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(con -> { @@ -59,7 +54,6 @@ INSERT INTO member (name, email, password) VALUES (?, ?, ?) return findById(keyHolder.getKey().longValue()); } - @Override public Member update(Member member) { jdbcTemplate.update(""" UPDATE member @@ -69,11 +63,11 @@ public Member update(Member member) { return findById(member.getId()); } - @Override public void deleteById(Long id) { jdbcTemplate.update(""" DELETE FROM member WHERE id = ? """, id); } + } diff --git a/src/main/java/com/example/demo/repository/MemberRepositoryMemory.java b/src/main/java/com/example/demo/repository/MemberRepositoryMemory.java index b4cf722..c856ced 100644 --- a/src/main/java/com/example/demo/repository/MemberRepositoryMemory.java +++ b/src/main/java/com/example/demo/repository/MemberRepositoryMemory.java @@ -1,13 +1,13 @@ package com.example.demo.repository; +import com.example.demo.domain.Member; + import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -import com.example.demo.domain.Member; - -public class MemberRepositoryMemory implements MemberRepository { +public class MemberRepositoryMemory { private static final Map members = new HashMap<>(); private static final AtomicLong autoincrement = new AtomicLong(1); @@ -17,7 +17,6 @@ public class MemberRepositoryMemory implements MemberRepository { members.put(autoincrement.getAndIncrement(), new Member("최준호", "temp@gmail.com", "password")); } - @Override public List findAll() { return members.entrySet().stream() .map(it -> { @@ -27,12 +26,10 @@ public List findAll() { }).toList(); } - @Override public Member findById(Long id) { return members.getOrDefault(id, null); } - @Override public Member insert(Member member) { long id = autoincrement.getAndIncrement(); members.put(id, member); @@ -40,13 +37,12 @@ public Member insert(Member member) { return member; } - @Override public Member update(Member member) { return members.put(member.getId(), member); } - @Override public void deleteById(Long id) { members.remove(id); } + } diff --git a/src/main/java/com/example/demo/service/ArticleService.java b/src/main/java/com/example/demo/service/ArticleService.java index 7f8610b..7b4478a 100644 --- a/src/main/java/com/example/demo/service/ArticleService.java +++ b/src/main/java/com/example/demo/service/ArticleService.java @@ -1,19 +1,18 @@ package com.example.demo.service; -import java.util.List; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import com.example.demo.controller.dto.request.ArticleCreateRequest; -import com.example.demo.controller.dto.response.ArticleResponse; import com.example.demo.controller.dto.request.ArticleUpdateRequest; +import com.example.demo.controller.dto.response.ArticleResponse; import com.example.demo.domain.Article; import com.example.demo.domain.Board; import com.example.demo.domain.Member; import com.example.demo.repository.ArticleRepository; import com.example.demo.repository.BoardRepository; import com.example.demo.repository.MemberRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; @Service @Transactional(readOnly = true) @@ -33,10 +32,16 @@ public ArticleService( this.boardRepository = boardRepository; } + public List getArticles() { + return articleRepository.findAll().stream() + .map(ArticleResponse::from) + .toList(); + } + public ArticleResponse getById(Long id) { Article article = articleRepository.findById(id); - Member member = memberRepository.findById(article.getAuthorId()); - Board board = boardRepository.findById(article.getBoardId()); + Member member = memberRepository.findById(article.getAuthor().getId()); + Board board = boardRepository.findById(article.getBoard().getId()); return ArticleResponse.of(article, member, board); } @@ -44,8 +49,8 @@ public List getByBoardId(Long boardId) { List
articles = articleRepository.findAllByBoardId(boardId); return articles.stream() .map(article -> { - Member member = memberRepository.findById(article.getAuthorId()); - Board board = boardRepository.findById(article.getBoardId()); + Member member = memberRepository.findById(article.getAuthor().getId()); + Board board = boardRepository.findById(article.getBoard().getId()); return ArticleResponse.of(article, member, board); }) .toList(); @@ -54,24 +59,23 @@ public List getByBoardId(Long boardId) { @Transactional public ArticleResponse create(ArticleCreateRequest request) { Article article = new Article( - request.authorId(), - request.boardId(), + memberRepository.findById(request.authorId()), + boardRepository.findById(request.boardId()), request.title(), request.description() ); - Article saved = articleRepository.insert(article); - Member member = memberRepository.findById(saved.getAuthorId()); - Board board = boardRepository.findById(saved.getBoardId()); + Article saved = articleRepository.save(article); + Member member = saved.getAuthor(); + Board board = saved.getBoard(); return ArticleResponse.of(saved, member, board); } @Transactional public ArticleResponse update(Long id, ArticleUpdateRequest request) { Article article = articleRepository.findById(id); - article.update(request.boardId(), request.title(), request.description()); - Article updated = articleRepository.update(article); - Member member = memberRepository.findById(updated.getAuthorId()); - Board board = boardRepository.findById(article.getBoardId()); + article.update(boardRepository.findById(request.boardId()), request.title(), request.description()); + Member member = article.getAuthor(); + Board board = article.getBoard(); return ArticleResponse.of(article, member, board); } @@ -79,4 +83,5 @@ public ArticleResponse update(Long id, ArticleUpdateRequest request) { public void delete(Long id) { articleRepository.deleteById(id); } + } diff --git a/src/main/java/com/example/demo/service/BoardService.java b/src/main/java/com/example/demo/service/BoardService.java index ffff891..3fcbd61 100644 --- a/src/main/java/com/example/demo/service/BoardService.java +++ b/src/main/java/com/example/demo/service/BoardService.java @@ -1,15 +1,14 @@ package com.example.demo.service; -import java.util.List; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import com.example.demo.controller.dto.request.BoardCreateRequest; import com.example.demo.controller.dto.request.BoardUpdateRequest; import com.example.demo.controller.dto.response.BoardResponse; import com.example.demo.domain.Board; import com.example.demo.repository.BoardRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; @Service @Transactional(readOnly = true) @@ -35,7 +34,7 @@ public BoardResponse getBoardById(Long id) { @Transactional public BoardResponse createBoard(BoardCreateRequest request) { Board board = new Board(request.name()); - Board saved = boardRepository.insert(board); + Board saved = boardRepository.save(board); return BoardResponse.from(saved); } @@ -48,7 +47,7 @@ public void deleteBoard(Long id) { public BoardResponse update(Long id, BoardUpdateRequest request) { Board board = boardRepository.findById(id); board.update(request.name()); - Board updated = boardRepository.update(board); - return BoardResponse.from(updated); + return BoardResponse.from(board); } + } diff --git a/src/main/java/com/example/demo/service/MemberService.java b/src/main/java/com/example/demo/service/MemberService.java index 04c1bc8..36b1fc4 100644 --- a/src/main/java/com/example/demo/service/MemberService.java +++ b/src/main/java/com/example/demo/service/MemberService.java @@ -1,24 +1,38 @@ package com.example.demo.service; -import java.util.List; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import com.example.demo.controller.dto.request.MemberCreateRequest; +import com.example.demo.controller.dto.request.MemberLoginRequest; import com.example.demo.controller.dto.request.MemberUpdateRequest; import com.example.demo.controller.dto.response.MemberResponse; import com.example.demo.domain.Member; +import com.example.demo.exception.RestApiException; +import com.example.demo.exception.error.ErrorCode; +import com.example.demo.exception.error.MemberErrorCode; +import com.example.demo.jwt.JwtUtil; import com.example.demo.repository.MemberRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; @Service @Transactional(readOnly = true) public class MemberService { + @Value("${jwt.secret}") + private String secretKey; + + final private Long expiredMs = 1000 * 60 * 60L; + private final MemberRepository memberRepository; - public MemberService(MemberRepository memberRepository) { + private final PasswordEncoder passwordEncoder; + + public MemberService(MemberRepository memberRepository, PasswordEncoder passwordEncoder) { this.memberRepository = memberRepository; + this.passwordEncoder = passwordEncoder; } public MemberResponse getById(Long id) { @@ -35,8 +49,8 @@ public List getAll() { @Transactional public MemberResponse create(MemberCreateRequest request) { - Member member = memberRepository.insert( - new Member(request.name(), request.email(), request.password()) + Member member = memberRepository.save( + new Member(request.name(), request.email(), passwordEncoder.encode(request.password())) ); return MemberResponse.from(member); } @@ -50,7 +64,17 @@ public void delete(Long id) { public MemberResponse update(Long id, MemberUpdateRequest request) { Member member = memberRepository.findById(id); member.update(request.name(), request.email()); - memberRepository.update(member); return MemberResponse.from(member); } + + public String login(MemberLoginRequest request) { + // 인증과정 + Member selectedMember = memberRepository.findByEmail(request.email()).orElseThrow(() -> new RestApiException(MemberErrorCode.EMAIL_NOT_FOUND)); + if (!passwordEncoder.matches(request.password(), selectedMember.getPassword())) { + throw new RestApiException(MemberErrorCode.INVALID_PASSWORD); + } + + return JwtUtil.createJwt(request.email(), secretKey, expiredMs); + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index ff69a9e..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,6 +0,0 @@ -spring: - datasource: - url: jdbc:mysql://localhost:3306/bcsd # 본인의 환경에 맞게 수정한다. - username: root # 본인의 환경에 맞게 수정한다. - password: qwer1234 # 본인의 환경에 맞게 수정한다. - driver-class-name: com.mysql.cj.jdbc.Driver