diff --git a/.gitignore b/.gitignore index c2065bc..2285eeb 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +### 보안을 위한 제거 ### +application.yml \ No newline at end of file diff --git a/build.gradle b/build.gradle index 728ec9c..2d7db46 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,15 @@ 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' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + runtimeOnly 'com.mysql:mysql-connector-j' + testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/java/com/example/demo/DemoApplication.java b/src/main/java/com/example/demo/DemoApplication.java index 094d95b..2a9bdbe 100644 --- a/src/main/java/com/example/demo/DemoApplication.java +++ b/src/main/java/com/example/demo/DemoApplication.java @@ -9,5 +9,4 @@ public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } - } diff --git a/src/main/java/com/example/demo/config/SecurityConfig.java b/src/main/java/com/example/demo/config/SecurityConfig.java new file mode 100644 index 0000000..93f4802 --- /dev/null +++ b/src/main/java/com/example/demo/config/SecurityConfig.java @@ -0,0 +1,48 @@ +package com.example.demo.config; + +import com.example.demo.security.JwtAuthenticationFilter; +import com.example.demo.security.JwtTokenProvider; +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.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +public class SecurityConfig { + + private final JwtTokenProvider jwtTokenProvider; + + public SecurityConfig(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .formLogin(FormLoginConfigurer::disable) + .httpBasic(HttpBasicConfigurer::disable) + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests((authorizeHttpRequests) -> + authorizeHttpRequests + .requestMatchers(HttpMethod.POST,"/members").permitAll() + .requestMatchers("/members/login").permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) + .build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/example/demo/controller/MemberController.java b/src/main/java/com/example/demo/controller/MemberController.java index ddb18ec..8723d39 100644 --- a/src/main/java/com/example/demo/controller/MemberController.java +++ b/src/main/java/com/example/demo/controller/MemberController.java @@ -2,6 +2,8 @@ import java.util.List; +import com.example.demo.controller.dto.request.MemberLoginRequest; +import com.example.demo.controller.dto.response.LoginResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -47,6 +49,11 @@ public ResponseEntity create( return ResponseEntity.ok(response); } + @PostMapping("/members/login") + public ResponseEntity login(@RequestBody MemberLoginRequest request) { + return ResponseEntity.ok(memberService.login(request)); + } + @PutMapping("/members/{id}") public ResponseEntity updateMember( @PathVariable Long id, 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..ebdba47 --- /dev/null +++ b/src/main/java/com/example/demo/controller/dto/request/MemberLoginRequest.java @@ -0,0 +1,8 @@ +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/LoginResponse.java b/src/main/java/com/example/demo/controller/dto/response/LoginResponse.java new file mode 100644 index 0000000..40579bb --- /dev/null +++ b/src/main/java/com/example/demo/controller/dto/response/LoginResponse.java @@ -0,0 +1,8 @@ +package com.example.demo.controller.dto.response; + +public record LoginResponse( + String message, + String token +) { + +} diff --git a/src/main/java/com/example/demo/domain/Article.java b/src/main/java/com/example/demo/domain/Article.java index e0183db..0bd1d99 100644 --- a/src/main/java/com/example/demo/domain/Article.java +++ b/src/main/java/com/example/demo/domain/Article.java @@ -1,70 +1,95 @@ package com.example.demo.domain; +import jakarta.persistence.*; + import java.time.LocalDateTime; +@Entity +@Table(name = "article") public class Article { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") private Long id; - private Long authorId; - private Long boardId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id", nullable = false) + private Member author; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "board_id", nullable = false) + private Board board; + + @Column(name = "title", nullable = false) private String title; + + @Lob + @Column(name = "content", nullable = false) private String content; + + @Column(name = "created_date", insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") private LocalDateTime createdAt; + + @Column(name = "modified_date", insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") private LocalDateTime modifiedAt; public Article( Long id, - Long authorId, - Long boardId, + 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; } - 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(); } - public void update(Long boardId, String title, String description) { - this.boardId = boardId; + protected Article() {} + + public void update(Board board, String title, String description) { + 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; - } - public Long getId() { return id; } + public Member getAuthor() { + return author; + } + public Long getAuthorId() { - return authorId; + return author.getId(); + } + + public Board getBoard() { + return board; } public Long getBoardId() { - return boardId; + return board.getId(); } public String getTitle() { diff --git a/src/main/java/com/example/demo/domain/Board.java b/src/main/java/com/example/demo/domain/Board.java index 992e2c6..caadd8b 100644 --- a/src/main/java/com/example/demo/domain/Board.java +++ b/src/main/java/com/example/demo/domain/Board.java @@ -1,16 +1,34 @@ package com.example.demo.domain; +import jakarta.persistence.*; + +import java.util.List; + +@Entity +@Table(name = "board", uniqueConstraints = { + @UniqueConstraint(columnNames = "name", name = "UK_BOARD_NAME") +}) public class Board { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") private Long id; + + @Column(name = "name", nullable = false, length = 100) private String name; - public Board(Long id, String name) { - this.id = id; + @OneToMany(mappedBy = "board", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + private List
articles; + + protected Board() {} + + public Board(String name) { this.name = name; } - public Board(String name) { + public Board(Long id, String name) { + this.id = id; 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..a4caa16 100644 --- a/src/main/java/com/example/demo/domain/Member.java +++ b/src/main/java/com/example/demo/domain/Member.java @@ -1,12 +1,32 @@ package com.example.demo.domain; +import jakarta.persistence.*; + +import java.util.List; + +@Entity +@Table(name = "member", uniqueConstraints = { + @UniqueConstraint(columnNames = "email", name = "UK_MEMBER_EMAIL") +}) public class Member { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") private Long id; + + @Column(name = "name", nullable = false, length = 100) private String name; + + @Column(name = "email", nullable = false, length = 100) private String email; + + @Column(name = "password", nullable = false) private String password; + @OneToMany(mappedBy = "author", fetch = FetchType.LAZY) + private List
articles; + public Member(Long id, String name, String email, String password) { this.id = id; this.name = name; @@ -25,6 +45,8 @@ public void update(String name, String email) { this.email = email; } + protected Member() {} + public void setId(Long id) { this.id = id; } diff --git a/src/main/java/com/example/demo/exception/BaseExceptionType.java b/src/main/java/com/example/demo/exception/BaseExceptionType.java new file mode 100644 index 0000000..4c413f7 --- /dev/null +++ b/src/main/java/com/example/demo/exception/BaseExceptionType.java @@ -0,0 +1,8 @@ +package com.example.demo.exception; + +public interface BaseExceptionType { + + int getErrorCode(); + int getHttpStatus(); + String getErrorMessage(); +} diff --git a/src/main/java/com/example/demo/exception/BoardExceptionType.java b/src/main/java/com/example/demo/exception/BoardExceptionType.java new file mode 100644 index 0000000..57a378f --- /dev/null +++ b/src/main/java/com/example/demo/exception/BoardExceptionType.java @@ -0,0 +1,31 @@ +package com.example.demo.exception; + +public enum BoardExceptionType implements BaseExceptionType { + + NOT_FOUND_BOARD(1200, 404, "존재하지 않는 게시판입니다"), + BOARD_HAS_POSTS(1201, 400, "게시판에 게시물이 존재합니다"); + + private int errorCode; + private int httpStatus; + private String errorMessage; + BoardExceptionType(int errorCode, int httpStatus, String errorMessage) { + this.errorCode = errorCode; + this.httpStatus = httpStatus; + this.errorMessage = errorMessage; + } + + @Override + public int getErrorCode() { + return errorCode; + } + + @Override + public int getHttpStatus() { + return httpStatus; + } + + @Override + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/com/example/demo/exception/CommonExceptionType.java b/src/main/java/com/example/demo/exception/CommonExceptionType.java new file mode 100644 index 0000000..92d172d --- /dev/null +++ b/src/main/java/com/example/demo/exception/CommonExceptionType.java @@ -0,0 +1,31 @@ +package com.example.demo.exception; + +public enum CommonExceptionType implements BaseExceptionType{ + + BAD_REQUEST_NULL_VALUE(1000, 400, "요청 중 null 값이 존재합니다"); + + private int errorCode; + private int httpStatus; + private String errorMessage; + + CommonExceptionType(int errorCode, int httpStatus, String errorMessage) { + this.errorCode = errorCode; + this.httpStatus = httpStatus; + this.errorMessage = errorMessage; + } + + @Override + public int getErrorCode() { + return errorCode; + } + + @Override + public int getHttpStatus() { + return httpStatus; + } + + @Override + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/com/example/demo/exception/CustomException.java b/src/main/java/com/example/demo/exception/CustomException.java new file mode 100644 index 0000000..f38bcc5 --- /dev/null +++ b/src/main/java/com/example/demo/exception/CustomException.java @@ -0,0 +1,15 @@ +package com.example.demo.exception; + +public class CustomException extends RuntimeException{ + + private BaseExceptionType exceptionType; + + public CustomException(BaseExceptionType exceptionType) { + super(exceptionType.getErrorMessage()); + this.exceptionType = exceptionType; + } + + public BaseExceptionType getExceptionType() { + return exceptionType; + } +} 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..877ca64 --- /dev/null +++ b/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java @@ -0,0 +1,45 @@ +package com.example.demo.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomException(CustomException e) { + return new ResponseEntity<>(Error.create(e.getExceptionType()), HttpStatus.valueOf(e.getExceptionType().getHttpStatus())); + } + + public static class Error { + private int code; + private int status; + private String message; + + private Error() {} + + private Error(int code, int status, String message) { + this.code = code; + this.status = status; + this.message = message; + } + + static GlobalExceptionHandler.Error create(BaseExceptionType e) { + return new GlobalExceptionHandler.Error(e.getErrorCode(), e.getHttpStatus(), e.getErrorMessage()); + } + + public int getCode() { + return code; + } + + public int getStatus() { + return status; + } + + public String getMessage() { + return message; + } + } +} diff --git a/src/main/java/com/example/demo/exception/MemberExceptionType.java b/src/main/java/com/example/demo/exception/MemberExceptionType.java new file mode 100644 index 0000000..2bc7f7a --- /dev/null +++ b/src/main/java/com/example/demo/exception/MemberExceptionType.java @@ -0,0 +1,33 @@ +package com.example.demo.exception; + +public enum MemberExceptionType implements BaseExceptionType { + + NOT_FOUND_MEMBER(1300, 404, "존재하지 않는 사용자입니다"), + MEMBER_HAS_POSTS(1301, 400, "사용자가 작성한 게시물이 존재합니다"), + DUPLICATED_EMAIL(1302, 409, "이미 존재하는 이메일입니다"); + + private int errorCode; + private int httpStatus; + private String errorMessage; + + MemberExceptionType(int errorCode, int httpStatus, String errorMessage) { + this.errorCode = errorCode; + this.httpStatus = httpStatus; + this.errorMessage = errorMessage; + } + + @Override + public int getErrorCode() { + return errorCode; + } + + @Override + public int getHttpStatus() { + return httpStatus; + } + + @Override + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/com/example/demo/exception/PostExceptionType.java b/src/main/java/com/example/demo/exception/PostExceptionType.java new file mode 100644 index 0000000..1a6c652 --- /dev/null +++ b/src/main/java/com/example/demo/exception/PostExceptionType.java @@ -0,0 +1,33 @@ +package com.example.demo.exception; + +public enum PostExceptionType implements BaseExceptionType { + + NOT_FOUND_POST(1100, 404, "존재하지 않는 게시물입니다"), + INVALID_MEMBER_REFERENCE(1101, 400, "유효하지않은 사용자에 대한 참조요청입니다"), + INVALID_BOARD_REFERENCE(1102, 400, "유효하지않은 게시판에 대한 참조요청입니다"); + + private int errorCode; + private int httpStatus; + private String errorMessage; + + PostExceptionType(int errorCode, int httpStatus, String errorMessage) { + this.errorCode = errorCode; + this.httpStatus = httpStatus; + this.errorMessage = errorMessage; + } + + @Override + public int getErrorCode() { + return errorCode; + } + + @Override + public int getHttpStatus() { + return httpStatus; + } + + @Override + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/com/example/demo/repository/ArticleRepositoryCustom.java b/src/main/java/com/example/demo/repository/ArticleRepositoryCustom.java new file mode 100644 index 0000000..1a242ed --- /dev/null +++ b/src/main/java/com/example/demo/repository/ArticleRepositoryCustom.java @@ -0,0 +1,8 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Article; + +public interface ArticleRepositoryCustom { + + Article saveAndRefresh(Article article); +} diff --git a/src/main/java/com/example/demo/repository/ArticleRepositoryCustomImpl.java b/src/main/java/com/example/demo/repository/ArticleRepositoryCustomImpl.java new file mode 100644 index 0000000..6271689 --- /dev/null +++ b/src/main/java/com/example/demo/repository/ArticleRepositoryCustomImpl.java @@ -0,0 +1,21 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Article; +import jakarta.persistence.EntityManager; + +public class ArticleRepositoryCustomImpl implements ArticleRepositoryCustom { + + private final EntityManager entityManager; + + public ArticleRepositoryCustomImpl(EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Override + public Article saveAndRefresh(Article article) { + entityManager.persist(article); + entityManager.flush(); + entityManager.refresh(article); + return article; + } +} diff --git a/src/main/java/com/example/demo/repository/ArticleRepositoryJdbc.java b/src/main/java/com/example/demo/repository/ArticleRepositoryJdbc.java deleted file mode 100644 index c9a272e..0000000 --- a/src/main/java/com/example/demo/repository/ArticleRepositoryJdbc.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.example.demo.repository; - -import java.sql.PreparedStatement; -import java.util.List; - -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); - } - - @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()); - } - - @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()); - } - - @Override - public void deleteById(Long id) { - jdbcTemplate.update(""" - DELETE FROM article - WHERE id = ? - """, id); - } -} diff --git a/src/main/java/com/example/demo/repository/ArticleRepositoryJpa.java b/src/main/java/com/example/demo/repository/ArticleRepositoryJpa.java new file mode 100644 index 0000000..99b7ca6 --- /dev/null +++ b/src/main/java/com/example/demo/repository/ArticleRepositoryJpa.java @@ -0,0 +1,61 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Article; +import jakarta.persistence.EntityManager; + +import java.util.List; + +public class ArticleRepositoryJpa implements ArticleRepository { + + private final EntityManager entityManager; + + public ArticleRepositoryJpa(EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Override + public List
findAll() { + return entityManager.createQuery("SELECT a FROM Article a", Article.class).getResultList(); + } + + @Override + public List
findAllByBoardId(Long boardId) { + return entityManager.createQuery("SELECT a FROM Article a WHERE a.boardId = :boardId", Article.class) + .setParameter("boardId", boardId) + .getResultList(); + } + + @Override + public List
findAllByMemberId(Long memberId) { + return entityManager.createQuery("SELECT a FROM Article a WHERE a.authorId = :memberId", Article.class) + .setParameter("memberId", memberId) + .getResultList(); + } + + @Override + public Article findById(Long id) { + return entityManager.find(Article.class, id); + } + + @Override + public Article insert(Article article) { + entityManager.persist(article); + entityManager.flush(); + entityManager.refresh(article); + return article; + } + + @Override + public Article update(Article article) { + Article mergedArticle = entityManager.merge(article); + entityManager.flush(); + entityManager.refresh(mergedArticle); + return mergedArticle; + } + + @Override + public void deleteById(Long id) { + Article article = entityManager.find(Article.class, id); + if (article != null) entityManager.remove(article); + } +} diff --git a/src/main/java/com/example/demo/repository/ArticleRepositorySpringDataJpa.java b/src/main/java/com/example/demo/repository/ArticleRepositorySpringDataJpa.java new file mode 100644 index 0000000..f1f4585 --- /dev/null +++ b/src/main/java/com/example/demo/repository/ArticleRepositorySpringDataJpa.java @@ -0,0 +1,13 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Article; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ArticleRepositorySpringDataJpa extends JpaRepository, ArticleRepositoryCustom { + + List
findAllByBoard_Id(Long boardId); + + List
findAllByAuthor_Id(Long authorId); +} diff --git a/src/main/java/com/example/demo/repository/BoardRepositoryJdbc.java b/src/main/java/com/example/demo/repository/BoardRepositoryJdbc.java index c4fd6f6..8303a66 100644 --- a/src/main/java/com/example/demo/repository/BoardRepositoryJdbc.java +++ b/src/main/java/com/example/demo/repository/BoardRepositoryJdbc.java @@ -3,6 +3,7 @@ import java.sql.PreparedStatement; import java.util.List; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; @@ -11,7 +12,6 @@ import com.example.demo.domain.Board; -@Repository public class BoardRepositoryJdbc implements BoardRepository { private final JdbcTemplate jdbcTemplate; @@ -35,11 +35,15 @@ public List findAll() { @Override public Board findById(Long id) { - return jdbcTemplate.queryForObject(""" + try { + return jdbcTemplate.queryForObject(""" SELECT id, name FROM board WHERE id = ? """, boardRowMapper, id); + } catch (EmptyResultDataAccessException e) { + return null; + } } @Override diff --git a/src/main/java/com/example/demo/repository/BoardRepositoryJpa.java b/src/main/java/com/example/demo/repository/BoardRepositoryJpa.java new file mode 100644 index 0000000..d519a2d --- /dev/null +++ b/src/main/java/com/example/demo/repository/BoardRepositoryJpa.java @@ -0,0 +1,42 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Board; +import jakarta.persistence.EntityManager; + +import java.util.List; + +public class BoardRepositoryJpa implements BoardRepository { + + private final EntityManager entityManager; + + public BoardRepositoryJpa(EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Override + public List findAll() { + return entityManager.createQuery("SELECT b FROM Board b", Board.class).getResultList(); + } + + @Override + public Board findById(Long id) { + return entityManager.find(Board.class, id); + } + + @Override + public Board insert(Board board) { + entityManager.persist(board); + return board; + } + + @Override + public void deleteById(Long id) { + Board board = entityManager.find(Board.class, id); + if (board != null) entityManager.remove(board); + } + + @Override + public Board update(Board board) { + return entityManager.merge(board); + } +} diff --git a/src/main/java/com/example/demo/repository/BoardRepositorySpringDataJpa.java b/src/main/java/com/example/demo/repository/BoardRepositorySpringDataJpa.java new file mode 100644 index 0000000..ff6a782 --- /dev/null +++ b/src/main/java/com/example/demo/repository/BoardRepositorySpringDataJpa.java @@ -0,0 +1,8 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Board; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BoardRepositorySpringDataJpa extends JpaRepository { + +} diff --git a/src/main/java/com/example/demo/repository/MemberRepositoryJdbc.java b/src/main/java/com/example/demo/repository/MemberRepositoryJdbc.java index 30d2262..fae585d 100644 --- a/src/main/java/com/example/demo/repository/MemberRepositoryJdbc.java +++ b/src/main/java/com/example/demo/repository/MemberRepositoryJdbc.java @@ -1,79 +1,8 @@ package com.example.demo.repository; -import java.sql.PreparedStatement; -import java.util.List; - -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; -@Repository -public class MemberRepositoryJdbc implements MemberRepository { - - private final JdbcTemplate jdbcTemplate; - - public MemberRepositoryJdbc(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - private static final RowMapper memberRowMapper = (rs, rowNum) -> new Member( - rs.getLong("id"), - rs.getString("name"), - rs.getString("email"), - rs.getString("password") - ); - - @Override - public List findAll() { - return jdbcTemplate.query(""" - SELECT id, name, email, password - FROM member - """, memberRowMapper); - } - - @Override - public Member findById(Long id) { - return jdbcTemplate.queryForObject(""" - SELECT id, name, email, password - FROM member - WHERE id = ? - """, memberRowMapper, id); - } - - @Override - public Member insert(Member member) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(con -> { - PreparedStatement ps = con.prepareStatement(""" - INSERT INTO member (name, email, password) VALUES (?, ?, ?) - """, new String[]{"id"}); - ps.setString(1, member.getName()); - ps.setString(2, member.getEmail()); - ps.setString(3, member.getPassword()); - return ps; - }, keyHolder); - return findById(keyHolder.getKey().longValue()); - } - - @Override - public Member update(Member member) { - jdbcTemplate.update(""" - UPDATE member - SET name = ?, email = ? - WHERE id = ? - """, member.getName(), member.getEmail(), member.getId()); - return findById(member.getId()); - } +public interface MemberRepositoryJdbc extends MemberRepository{ - @Override - public void deleteById(Long id) { - jdbcTemplate.update(""" - DELETE FROM member - WHERE id = ? - """, id); - } + Member findByEmail(String email); } diff --git a/src/main/java/com/example/demo/repository/MemberRepositoryJdbcImpl.java b/src/main/java/com/example/demo/repository/MemberRepositoryJdbcImpl.java new file mode 100644 index 0000000..637b2bd --- /dev/null +++ b/src/main/java/com/example/demo/repository/MemberRepositoryJdbcImpl.java @@ -0,0 +1,96 @@ +package com.example.demo.repository; + +import java.sql.PreparedStatement; +import java.util.List; + +import org.springframework.dao.EmptyResultDataAccessException; +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; + +public class MemberRepositoryJdbcImpl implements MemberRepositoryJdbc { + + private final JdbcTemplate jdbcTemplate; + + public MemberRepositoryJdbcImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + private static final RowMapper memberRowMapper = (rs, rowNum) -> new Member( + rs.getLong("id"), + rs.getString("name"), + rs.getString("email"), + rs.getString("password") + ); + + @Override + public List findAll() { + return jdbcTemplate.query(""" + SELECT id, name, email, password + FROM member + """, memberRowMapper); + } + + @Override + public Member findById(Long id) { + try { + return jdbcTemplate.queryForObject(""" + SELECT id, name, email, password + FROM member + WHERE id = ? + """, memberRowMapper, id); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + + @Override + public Member findByEmail(String email) { + try { + return jdbcTemplate.queryForObject(""" + SELECT id, name, email, password + FROM member + WHERE email = ? + """, memberRowMapper, email); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + + @Override + public Member insert(Member member) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(con -> { + PreparedStatement ps = con.prepareStatement(""" + INSERT INTO member (name, email, password) VALUES (?, ?, ?) + """, new String[]{"id"}); + ps.setString(1, member.getName()); + ps.setString(2, member.getEmail()); + ps.setString(3, member.getPassword()); + return ps; + }, keyHolder); + return findById(keyHolder.getKey().longValue()); + } + + @Override + public Member update(Member member) { + jdbcTemplate.update(""" + UPDATE member + SET name = ?, email = ? + WHERE id = ? + """, member.getName(), member.getEmail(), member.getId()); + 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/MemberRepositoryJpa.java b/src/main/java/com/example/demo/repository/MemberRepositoryJpa.java new file mode 100644 index 0000000..ef4fd49 --- /dev/null +++ b/src/main/java/com/example/demo/repository/MemberRepositoryJpa.java @@ -0,0 +1,54 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Member; +import jakarta.persistence.EntityManager; +import jakarta.persistence.NoResultException; + +import java.util.List; + +public class MemberRepositoryJpa implements MemberRepositoryJdbc{ + + private final EntityManager entityManager; + + public MemberRepositoryJpa(EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Override + public List findAll() { + return entityManager.createQuery("SELECT m FROM Member m", Member.class).getResultList(); + } + + @Override + public Member findById(Long id) { + return entityManager.find(Member.class, id); + } + + @Override + public Member findByEmail(String email) { + try { + return entityManager.createQuery("SELECT m FROM Member m WHERE m.email = :email", Member.class) + .setParameter("email", email) + .getSingleResult(); + } catch (NoResultException e) { + return null; + } + } + + @Override + public Member insert(Member member) { + entityManager.persist(member); + return member; + } + + @Override + public Member update(Member member) { + return entityManager.merge(member); + } + + @Override + public void deleteById(Long id) { + Member member = entityManager.find(Member.class, id); + if (member != null) entityManager.remove(member); + } +} diff --git a/src/main/java/com/example/demo/repository/MemberRepositorySpringDataJpa.java b/src/main/java/com/example/demo/repository/MemberRepositorySpringDataJpa.java new file mode 100644 index 0000000..5fdc9d3 --- /dev/null +++ b/src/main/java/com/example/demo/repository/MemberRepositorySpringDataJpa.java @@ -0,0 +1,11 @@ +package com.example.demo.repository; + +import com.example.demo.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MemberRepositorySpringDataJpa extends JpaRepository { + + Optional findByEmail(String email); +} diff --git a/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java b/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..145097a --- /dev/null +++ b/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java @@ -0,0 +1,34 @@ +package com.example.demo.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String token = jwtTokenProvider.resolveToken(request); + + if (token != null && jwtTokenProvider.validateToken(token)) { + Authentication auth = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(auth); + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/example/demo/security/JwtTokenProvider.java b/src/main/java/com/example/demo/security/JwtTokenProvider.java new file mode 100644 index 0000000..a027e83 --- /dev/null +++ b/src/main/java/com/example/demo/security/JwtTokenProvider.java @@ -0,0 +1,69 @@ +package com.example.demo.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.*; + +@Component +public class JwtTokenProvider { + + @Value("${jwt.secret}") + private String secretKey; + private Key key; + private final long accessTokenValidTime = (60 * 1000) * 30; + + @PostConstruct + protected void init() { + byte[] decodedKey = Base64.getDecoder().decode(secretKey); + this.key = Keys.hmacShaKeyFor(decodedKey); + } + + public String generateToken(String account) { + Claims claims = Jwts.claims().setSubject(account); + Date now = new Date(); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + accessTokenValidTime)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + public Authentication getAuthentication(String token) { + String account = getAccount(token); + return new UsernamePasswordAuthenticationToken(account, "", null); + } + + public String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } + + public boolean validateToken(String token) { + try { + Jws claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + return !claims.getBody().getExpiration().before(new Date()); + } catch (Exception e) { + return false; + } + } + + private String getAccount(String token) { + return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getSubject(); + } +} diff --git a/src/main/java/com/example/demo/service/ArticleService.java b/src/main/java/com/example/demo/service/ArticleService.java index 7f8610b..4e055b0 100644 --- a/src/main/java/com/example/demo/service/ArticleService.java +++ b/src/main/java/com/example/demo/service/ArticleService.java @@ -2,6 +2,8 @@ import java.util.List; +import com.example.demo.exception.*; +import com.example.demo.repository.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -11,22 +13,23 @@ 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 static com.example.demo.exception.BoardExceptionType.NOT_FOUND_BOARD; +import static com.example.demo.exception.MemberExceptionType.NOT_FOUND_MEMBER; +import static com.example.demo.exception.PostExceptionType.*; @Service @Transactional(readOnly = true) public class ArticleService { - private final ArticleRepository articleRepository; - private final MemberRepository memberRepository; - private final BoardRepository boardRepository; + private final ArticleRepositorySpringDataJpa articleRepository; + private final MemberRepositorySpringDataJpa memberRepository; + private final BoardRepositorySpringDataJpa boardRepository; public ArticleService( - ArticleRepository articleRepository, - MemberRepository memberRepository, - BoardRepository boardRepository + ArticleRepositorySpringDataJpa articleRepository, + MemberRepositorySpringDataJpa memberRepository, + BoardRepositorySpringDataJpa boardRepository ) { this.articleRepository = articleRepository; this.memberRepository = memberRepository; @@ -34,18 +37,24 @@ public ArticleService( } public ArticleResponse getById(Long id) { - Article article = articleRepository.findById(id); - Member member = memberRepository.findById(article.getAuthorId()); - Board board = boardRepository.findById(article.getBoardId()); + Article article = articleRepository.findById(id) + .orElseThrow(() -> new CustomException(NOT_FOUND_POST)); + + Member member = article.getAuthor(); + Board board = article.getBoard(); + return ArticleResponse.of(article, member, board); } public List getByBoardId(Long boardId) { - List
articles = articleRepository.findAllByBoardId(boardId); + List
articles = articleRepository.findAllByBoard_Id(boardId); return articles.stream() .map(article -> { - Member member = memberRepository.findById(article.getAuthorId()); - Board board = boardRepository.findById(article.getBoardId()); + Member member = memberRepository.findById(article.getAuthorId()) + .orElseThrow(() -> new CustomException(NOT_FOUND_MEMBER)); + + Board board = boardRepository.findById(article.getBoardId()) + .orElseThrow(() -> new CustomException(NOT_FOUND_BOARD)); return ArticleResponse.of(article, member, board); }) .toList(); @@ -53,26 +62,54 @@ public List getByBoardId(Long boardId) { @Transactional public ArticleResponse create(ArticleCreateRequest request) { + if (!isValid(request)) throw new CustomException(CommonExceptionType.BAD_REQUEST_NULL_VALUE); + + Member existedMember = memberRepository.findById(request.authorId()) + .orElseThrow(() -> new CustomException(INVALID_MEMBER_REFERENCE)); + + Board existedBoard = boardRepository.findById(request.boardId()) + .orElseThrow(() -> new CustomException(INVALID_BOARD_REFERENCE)); + Article article = new Article( - request.authorId(), - request.boardId(), + existedMember, + existedBoard, request.title(), request.description() ); - Article saved = articleRepository.insert(article); - Member member = memberRepository.findById(saved.getAuthorId()); - Board board = boardRepository.findById(saved.getBoardId()); + + Article saved = articleRepository.saveAndRefresh(article); + Member member = memberRepository.findById(saved.getAuthorId()) + .orElseThrow(() -> new CustomException(NOT_FOUND_MEMBER)); + Board board = boardRepository.findById(saved.getBoardId()) + .orElseThrow(() -> new CustomException(NOT_FOUND_BOARD)); + return ArticleResponse.of(saved, member, board); } + private boolean isValid(ArticleCreateRequest request) { + return request.authorId() != null + && request.boardId() != null + && request.title() != null + && request.description() != null; + } + @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()); - return ArticleResponse.of(article, member, board); + Article article = articleRepository.findById(id) + .orElseThrow(() -> new CustomException(NOT_FOUND_POST)); + + Board existedBoard = boardRepository.findById(request.boardId()) + .orElseThrow(() -> new CustomException(INVALID_BOARD_REFERENCE)); + + article.update(existedBoard, request.title(), request.description()); + + Article updated = articleRepository.saveAndRefresh(article); + Member member = memberRepository.findById(updated.getAuthorId()) + .orElseThrow(() -> new CustomException(NOT_FOUND_MEMBER)); + Board board = boardRepository.findById(article.getBoardId()) + .orElseThrow(() -> new CustomException(NOT_FOUND_BOARD)); + + return ArticleResponse.of(updated, member, board); } @Transactional diff --git a/src/main/java/com/example/demo/service/BoardService.java b/src/main/java/com/example/demo/service/BoardService.java index ffff891..e628336 100644 --- a/src/main/java/com/example/demo/service/BoardService.java +++ b/src/main/java/com/example/demo/service/BoardService.java @@ -2,6 +2,12 @@ import java.util.List; +import com.example.demo.domain.Article; +import com.example.demo.exception.BoardExceptionType; +import com.example.demo.exception.CommonExceptionType; +import com.example.demo.exception.CustomException; +import com.example.demo.repository.ArticleRepositorySpringDataJpa; +import com.example.demo.repository.BoardRepositorySpringDataJpa; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -9,16 +15,17 @@ 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; @Service @Transactional(readOnly = true) public class BoardService { - private final BoardRepository boardRepository; + private final BoardRepositorySpringDataJpa boardRepository; + private final ArticleRepositorySpringDataJpa articleRepository; - public BoardService(BoardRepository boardRepository) { + public BoardService(BoardRepositorySpringDataJpa boardRepository, ArticleRepositorySpringDataJpa articleRepository) { this.boardRepository = boardRepository; + this.articleRepository = articleRepository; } public List getBoards() { @@ -28,27 +35,39 @@ public List getBoards() { } public BoardResponse getBoardById(Long id) { - Board board = boardRepository.findById(id); + Board board = boardRepository.findById(id) + .orElseThrow(() -> new CustomException(BoardExceptionType.NOT_FOUND_BOARD)); + return BoardResponse.from(board); } @Transactional public BoardResponse createBoard(BoardCreateRequest request) { + if (request.name() == null) throw new CustomException(CommonExceptionType.BAD_REQUEST_NULL_VALUE); + Board board = new Board(request.name()); - Board saved = boardRepository.insert(board); + Board saved = boardRepository.save(board); return BoardResponse.from(saved); } @Transactional public void deleteBoard(Long id) { + /* + 영속성 전이 및 고아 객체 적용 확인을 위한 주석처리 + List
articles = articleRepository.findAllByBoard_Id(id); + if (!articles.isEmpty()) throw new CustomException(BoardExceptionType.BOARD_HAS_POSTS); + */ + boardRepository.deleteById(id); } @Transactional public BoardResponse update(Long id, BoardUpdateRequest request) { - Board board = boardRepository.findById(id); + Board board = boardRepository.findById(id) + .orElseThrow(() -> new CustomException(BoardExceptionType.NOT_FOUND_BOARD)); + board.update(request.name()); - Board updated = boardRepository.update(board); + Board updated = boardRepository.save(board); return BoardResponse.from(updated); } } diff --git a/src/main/java/com/example/demo/service/MemberService.java b/src/main/java/com/example/demo/service/MemberService.java index 04c1bc8..d32e7a5 100644 --- a/src/main/java/com/example/demo/service/MemberService.java +++ b/src/main/java/com/example/demo/service/MemberService.java @@ -1,7 +1,19 @@ package com.example.demo.service; import java.util.List; +import java.util.Optional; +import com.example.demo.controller.dto.request.MemberLoginRequest; +import com.example.demo.controller.dto.response.LoginResponse; +import com.example.demo.domain.Article; +import com.example.demo.exception.CommonExceptionType; +import com.example.demo.exception.CustomException; +import com.example.demo.exception.MemberExceptionType; +import com.example.demo.repository.ArticleRepositorySpringDataJpa; +import com.example.demo.repository.MemberRepositorySpringDataJpa; +import com.example.demo.security.JwtTokenProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -9,20 +21,30 @@ 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.repository.MemberRepository; @Service @Transactional(readOnly = true) public class MemberService { - private final MemberRepository memberRepository; + private final MemberRepositorySpringDataJpa memberRepository; + private final ArticleRepositorySpringDataJpa articleRepository; + private final PasswordEncoder passwordEncoder; + private final JwtTokenProvider jwtTokenProvider; - public MemberService(MemberRepository memberRepository) { + public MemberService(MemberRepositorySpringDataJpa memberRepository, + ArticleRepositorySpringDataJpa articleRepository, + PasswordEncoder passwordEncoder, + JwtTokenProvider jwtTokenProvider) { this.memberRepository = memberRepository; + this.articleRepository = articleRepository; + this.passwordEncoder = passwordEncoder; + this.jwtTokenProvider = jwtTokenProvider; } public MemberResponse getById(Long id) { - Member member = memberRepository.findById(id); + Member member = memberRepository.findById(id) + .orElseThrow(() -> new CustomException(MemberExceptionType.NOT_FOUND_MEMBER)); + return MemberResponse.from(member); } @@ -35,22 +57,54 @@ public List getAll() { @Transactional public MemberResponse create(MemberCreateRequest request) { - Member member = memberRepository.insert( - new Member(request.name(), request.email(), request.password()) + if (!isValid(request)) throw new CustomException(CommonExceptionType.BAD_REQUEST_NULL_VALUE); + + String encodedPassword = passwordEncoder.encode(request.password()); + + Member member = memberRepository.save( + new Member(request.name(), request.email(), encodedPassword) ); return MemberResponse.from(member); } + private boolean isValid(MemberCreateRequest request) { + return request.name() != null + && request.email() != null + && request.password() != null; + } + + public LoginResponse login(MemberLoginRequest request) { + Member member = memberRepository.findByEmail(request.email()) + .orElseThrow(() -> new BadCredentialsException("잘못된 계정정보입니다.")); + + if (!passwordEncoder.matches(request.password(), member.getPassword())) { + throw new BadCredentialsException("잘못된 계정정보입니다."); + } + + String message = member.getName() + "님 환영합니다"; + + return new LoginResponse(message, jwtTokenProvider.generateToken(member.getEmail())); + } + @Transactional public void delete(Long id) { + List
articles = articleRepository.findAllByAuthor_Id(id); + if (!articles.isEmpty()) throw new CustomException(MemberExceptionType.MEMBER_HAS_POSTS); + memberRepository.deleteById(id); } @Transactional public MemberResponse update(Long id, MemberUpdateRequest request) { - Member member = memberRepository.findById(id); + Member member = memberRepository.findById(id) + .orElseThrow(() -> new CustomException(MemberExceptionType.NOT_FOUND_MEMBER)); + + Optional DuplicatedMember = memberRepository.findByEmail(request.email()); + + if (DuplicatedMember.isPresent()) throw new CustomException(MemberExceptionType.DUPLICATED_EMAIL); + member.update(request.name(), request.email()); - memberRepository.update(member); + memberRepository.save(member); return MemberResponse.from(member); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ff69a9e..e1a81f8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,17 @@ spring: datasource: - url: jdbc:mysql://localhost:3306/bcsd # 본인의 환경에 맞게 수정한다. - username: root # 본인의 환경에 맞게 수정한다. - password: qwer1234 # 본인의 환경에 맞게 수정한다. + url: jdbc:mysql://localhost:3306/bcsd?serverTimezone=Asia/Seoul + username: root + password: kk25781179@@ driver-class-name: com.mysql.cj.jdbc.Driver + + jpa: + hibernate: + ddl-auto: none + show-sql: true + properties: + hibernate: + format_sql: true + +jwt: + secret: cf42d45e2e99cded4137f7307acec9b399b0de326f9b47a8115ca634bf7ed35113c33ae538a8a1f4bb7ff681726ad80b53509283a0e6e014e047b63ab662b352 \ No newline at end of file