diff --git a/build.gradle b/build.gradle index 610d6a6..56d88da 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,14 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.projectlombok:lombok' + implementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'org.postgresql:postgresql' + annotationProcessor 'org.projectlombok:lombok' } tasks.named('test') { diff --git a/src/main/java/com/example/devSns/DevSnsApplication.java b/src/main/java/com/example/devSns/DevSnsApplication.java index b965724..e30538d 100644 --- a/src/main/java/com/example/devSns/DevSnsApplication.java +++ b/src/main/java/com/example/devSns/DevSnsApplication.java @@ -5,9 +5,8 @@ @SpringBootApplication public class DevSnsApplication { - public static void main(String[] args) { SpringApplication.run(DevSnsApplication.class, args); - } + } } diff --git a/src/main/java/com/example/devSns/authorities/MemberDetails.java b/src/main/java/com/example/devSns/authorities/MemberDetails.java new file mode 100644 index 0000000..dedcde4 --- /dev/null +++ b/src/main/java/com/example/devSns/authorities/MemberDetails.java @@ -0,0 +1,32 @@ +package com.example.devSns.authorities; + +import com.example.devSns.entities.Users; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; + +@RequiredArgsConstructor +public class MemberDetails implements UserDetails { + private final Users user; + + @Override + public Collection getAuthorities() { + Collection authorities = new ArrayList<>(); + authorities.add(() -> user.getRole().toString()); + return authorities; + } + + @Override + public String getUsername() { + return user.getLoginID(); + } + + @Override + public String getPassword() { + return null; + } +} + diff --git a/src/main/java/com/example/devSns/authorities/MemberDetailsService.java b/src/main/java/com/example/devSns/authorities/MemberDetailsService.java new file mode 100644 index 0000000..c6b6c88 --- /dev/null +++ b/src/main/java/com/example/devSns/authorities/MemberDetailsService.java @@ -0,0 +1,19 @@ +package com.example.devSns.authorities; + +import com.example.devSns.entities.Users; +import com.example.devSns.repositories.UserRepository; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +public class MemberDetailsService implements UserDetailsService { + private UserRepository userRepository; + + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Users user = userRepository.findByLoginID(username).orElseThrow(); + MemberDetails memberDetails = new MemberDetails(user); + return memberDetails; + } +} diff --git a/src/main/java/com/example/devSns/authorities/Role.java b/src/main/java/com/example/devSns/authorities/Role.java new file mode 100644 index 0000000..ff5c47c --- /dev/null +++ b/src/main/java/com/example/devSns/authorities/Role.java @@ -0,0 +1,6 @@ +package com.example.devSns.authorities; + +public enum Role { + ROLE_USER, + ROLE_ADMIN +} diff --git a/src/main/java/com/example/devSns/config/WebConfig.java b/src/main/java/com/example/devSns/config/WebConfig.java new file mode 100644 index 0000000..b6a2158 --- /dev/null +++ b/src/main/java/com/example/devSns/config/WebConfig.java @@ -0,0 +1,33 @@ +package com.example.devSns.config; + +import com.example.devSns.util.JwtFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +// Interceptor 추가 가능 +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class WebConfig { + + private final JwtFilter jwtFilter; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(authorizeRequests -> + authorizeRequests.requestMatchers("/sns/signup", "/sns/login").permitAll() + .anyRequest().authenticated()).csrf(csrf -> csrf.disable()); + return http.build(); + } +} diff --git a/src/main/java/com/example/devSns/controllers/PostController.java b/src/main/java/com/example/devSns/controllers/PostController.java new file mode 100644 index 0000000..f0ee0b3 --- /dev/null +++ b/src/main/java/com/example/devSns/controllers/PostController.java @@ -0,0 +1,59 @@ +package com.example.devSns.controllers; + +import com.example.devSns.dto.PostDTO; +import com.example.devSns.dto.PostResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import com.example.devSns.services.PostService; +import java.util.List; + +@RestController +@RequestMapping("/sns") +@RequiredArgsConstructor +public class PostController { + private final PostService postService; + + @GetMapping("/show") + public ResponseEntity> showPosts() { + List posts = postService.findAll(); + return new ResponseEntity<>(posts, HttpStatus.OK); + } + + @GetMapping("/show/{userID}") + public ResponseEntity> showPost(@PathVariable Long userID) { + List post = postService.findByUserID(userID); + return new ResponseEntity<>(post, HttpStatus.OK); + } + + @GetMapping("/search/{content}") + public ResponseEntity> searchPost(@PathVariable String content) { + List post = postService.findByContent(content); + return new ResponseEntity<>(post, HttpStatus.OK); + } + + @PostMapping("/add") + public ResponseEntity addPost(@RequestBody PostDTO postDTO) { + PostResponse postResponse = postService.save(postDTO); + return new ResponseEntity<>(postResponse, HttpStatus.CREATED); + } + + @PutMapping("/{id}") + public ResponseEntity updatePost(@PathVariable Long id, @RequestBody PostDTO postDTO) { + PostResponse postResponse = postService.update(id, postDTO); + return new ResponseEntity<>(postResponse, HttpStatus.OK); + } + + @PatchMapping("/{id}/likeit") + public ResponseEntity likePost(@PathVariable Long id) { + PostResponse postResponse = postService.likePost(id); + return new ResponseEntity<>(postResponse, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public ResponseEntity deletePost(@PathVariable Long id) { + postService.delete(id); + return new ResponseEntity<>("Post deleted", HttpStatus.OK); + } +} diff --git a/src/main/java/com/example/devSns/controllers/ReplyController.java b/src/main/java/com/example/devSns/controllers/ReplyController.java new file mode 100644 index 0000000..5ee439b --- /dev/null +++ b/src/main/java/com/example/devSns/controllers/ReplyController.java @@ -0,0 +1,53 @@ +package com.example.devSns.controllers; + +import com.example.devSns.dto.ReplyDTO; +import com.example.devSns.dto.ReplyResponse; +import com.example.devSns.services.ReplyService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/sns/reply") +@RequiredArgsConstructor +public class ReplyController { + private final ReplyService replyService; + + @GetMapping("/{postId}/replies") + public ResponseEntity> getReplies(@PathVariable long postId) { + List replies = replyService.replyGetAll(postId); + ResponseEntity> responseEntity = new ResponseEntity<>(replies, HttpStatus.OK); + return responseEntity; + } + + @PostMapping("/{postId}/add") + public ResponseEntity writeReply(@PathVariable long postId, @RequestBody ReplyDTO reply) { + ReplyResponse replyResponse = replyService.writeReply(postId, reply); + ResponseEntity responseEntity = new ResponseEntity<>(replyResponse, HttpStatus.OK); + return responseEntity; + } + + @PutMapping("/{replyId}") + public ResponseEntity updateReply(@PathVariable long replyId, @RequestBody ReplyDTO reply) { + ReplyResponse replyResponse = replyService.updateReply(replyId, reply); + ResponseEntity responseEntity = new ResponseEntity<>(replyResponse, HttpStatus.OK); + return responseEntity; + } + + @PatchMapping("/{id}") + public ResponseEntity likeReply(@PathVariable long id) { + ReplyResponse replyResponse = replyService.likeReply(id); + ResponseEntity responseEntity = new ResponseEntity<>(replyResponse, HttpStatus.OK); + return responseEntity; + } + + @DeleteMapping("/{replyId}") + public ResponseEntity deleteReply(@PathVariable long replyId) { + String deleteCheck = replyService.deleteReply(replyId); + ResponseEntity responseEntity = new ResponseEntity<>(deleteCheck, HttpStatus.OK); + return responseEntity; + } +} diff --git a/src/main/java/com/example/devSns/controllers/UserController.java b/src/main/java/com/example/devSns/controllers/UserController.java new file mode 100644 index 0000000..47bfa6e --- /dev/null +++ b/src/main/java/com/example/devSns/controllers/UserController.java @@ -0,0 +1,34 @@ +package com.example.devSns.controllers; + +import com.example.devSns.dto.JwtDTO; +import com.example.devSns.dto.LoginDTO; +import com.example.devSns.dto.UserDTO; +import com.example.devSns.services.UserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/sns") +@RequiredArgsConstructor +@Slf4j +public class UserController { + private final UserService userService; + + @PostMapping("/signup") + public ResponseEntity signup(@RequestBody UserDTO userInfo) { + String resp = userService.signUp(userInfo); + ResponseEntity response = new ResponseEntity(resp, HttpStatus.OK); + return response; + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginDTO loginDTO) { + return new ResponseEntity<>(userService.login(loginDTO), HttpStatus.OK); + } +} diff --git a/src/main/java/com/example/devSns/dto/JwtDTO.java b/src/main/java/com/example/devSns/dto/JwtDTO.java new file mode 100644 index 0000000..b5c4df6 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/JwtDTO.java @@ -0,0 +1,8 @@ +package com.example.devSns.dto; + +import lombok.Builder; + +@Builder +public record JwtDTO( + String token +) {} diff --git a/src/main/java/com/example/devSns/dto/LoginDTO.java b/src/main/java/com/example/devSns/dto/LoginDTO.java new file mode 100644 index 0000000..fd9cd0c --- /dev/null +++ b/src/main/java/com/example/devSns/dto/LoginDTO.java @@ -0,0 +1,6 @@ +package com.example.devSns.dto; + +public record LoginDTO ( + String loginID, + String password +) {} diff --git a/src/main/java/com/example/devSns/dto/PostDTO.java b/src/main/java/com/example/devSns/dto/PostDTO.java new file mode 100644 index 0000000..68b504a --- /dev/null +++ b/src/main/java/com/example/devSns/dto/PostDTO.java @@ -0,0 +1,22 @@ +package com.example.devSns.dto; + +import com.example.devSns.entities.Posts; +import com.example.devSns.entities.Users; +import jakarta.validation.constraints.NotBlank; + +import java.time.LocalDateTime; + +public record PostDTO( + @NotBlank Long userId, + @NotBlank String username, + String content +) { + public static Posts dtoToEntity(PostDTO postDTO, Users user) { + Posts postEntity = Posts.builder() + .users(user) + .content(postDTO.content()) + .createat(LocalDateTime.now()) + .build(); + return postEntity; + } +} diff --git a/src/main/java/com/example/devSns/dto/PostResponse.java b/src/main/java/com/example/devSns/dto/PostResponse.java new file mode 100644 index 0000000..90a3316 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/PostResponse.java @@ -0,0 +1,27 @@ +package com.example.devSns.dto; + +import com.example.devSns.entities.Posts; +import lombok.*; + +import java.time.LocalDateTime; + +@Builder +public record PostResponse( + Long id, + String username, + String content, + Integer like, + LocalDateTime createAt, + LocalDateTime updateAt +) { + public static PostResponse entityToDto(Posts post) { + return PostResponse.builder() + .id(post.getId()) + .username(post.getUsers().getUsername()) + .content(post.getContent()) + .like(post.getLikeit()) + .createAt(post.getCreateat()) + .updateAt(post.getUpdateat()) + .build(); + } +} diff --git a/src/main/java/com/example/devSns/dto/ReplyDTO.java b/src/main/java/com/example/devSns/dto/ReplyDTO.java new file mode 100644 index 0000000..a8acbe9 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/ReplyDTO.java @@ -0,0 +1,21 @@ +package com.example.devSns.dto; + +import com.example.devSns.entities.Posts; +import com.example.devSns.entities.Replies; +import com.example.devSns.entities.Users; + +import java.time.LocalDateTime; + +public record ReplyDTO( + Long userID, + String comment +) { + public static Replies dtoToEntity(Posts post, Users user, ReplyDTO replyDTO) { + return Replies.builder() + .posts(post) + .users(user) + .createAt(LocalDateTime.now()) + .reply(replyDTO.comment()) + .build(); + } +} diff --git a/src/main/java/com/example/devSns/dto/ReplyResponse.java b/src/main/java/com/example/devSns/dto/ReplyResponse.java new file mode 100644 index 0000000..13cb09e --- /dev/null +++ b/src/main/java/com/example/devSns/dto/ReplyResponse.java @@ -0,0 +1,21 @@ +package com.example.devSns.dto; + +import com.example.devSns.entities.Replies; +import lombok.Builder; + +@Builder +public record ReplyResponse( + long replyId, + String username, + String comment, + Integer like +) { + public static ReplyResponse entityToDTO(Replies replies) { + return ReplyResponse.builder() + .replyId(replies.getId()) + .username(replies.getUsers().getUsername()) + .comment(replies.getReply()) + .like(replies.getLikeit()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/devSns/dto/UserDTO.java b/src/main/java/com/example/devSns/dto/UserDTO.java new file mode 100644 index 0000000..d6bfbea --- /dev/null +++ b/src/main/java/com/example/devSns/dto/UserDTO.java @@ -0,0 +1,28 @@ +package com.example.devSns.dto; + +import com.example.devSns.entities.Users; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + + +import java.time.LocalDate; + +public record UserDTO ( + String username, + String loginID, + String password, + Integer age, + LocalDate birthDay +) { + public static Users dtoToEntity(UserDTO userDTO) { + PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + return Users.builder() + .username(userDTO.username()) + .loginID(userDTO.loginID()) + .password(passwordEncoder.encode(userDTO.password())) + .birthday(userDTO.birthDay()) + .age(userDTO.age()) + .build(); + } +} diff --git a/src/main/java/com/example/devSns/entities/Posts.java b/src/main/java/com/example/devSns/entities/Posts.java new file mode 100644 index 0000000..9c92f07 --- /dev/null +++ b/src/main/java/com/example/devSns/entities/Posts.java @@ -0,0 +1,49 @@ +package com.example.devSns.entities; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Posts { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @NotNull + @Column + private String content; + + @JoinColumn(name = "users_id") + @ManyToOne + @NotNull + private Users users; + + @ColumnDefault("0") + private int likeit; + + @Column + private LocalDateTime createat; + + @Column + private LocalDateTime updateat; + + public void setUpdateat(LocalDateTime updateat) { + this.updateat = updateat; + } + + public void setContent(String content) { + this.content = content; + } + + public void setLikeit(int likeit) { + this.likeit = likeit; + } +} diff --git a/src/main/java/com/example/devSns/entities/Replies.java b/src/main/java/com/example/devSns/entities/Replies.java new file mode 100644 index 0000000..0ed9432 --- /dev/null +++ b/src/main/java/com/example/devSns/entities/Replies.java @@ -0,0 +1,52 @@ +package com.example.devSns.entities; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import java.time.LocalDateTime; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Builder +public class Replies { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + @JoinColumn(name = "posts_id") + @ManyToOne + @NotNull + private Posts posts; + + @JoinColumn(name = "users_id") + @ManyToOne + @NotNull + private Users users; + + @Column + @ColumnDefault("0") // Table 생성 시점 기본값 + private int likeit; + + @NotNull + private String reply; + + @NotNull + private LocalDateTime createAt; + + private LocalDateTime updateAt; + + public void setReply(String reply) { + this.reply = reply; + } + + public void setUpdateAt(LocalDateTime updateAt) { + this.updateAt = updateAt; + } + + public void setLikeit(int likeit) { + this.likeit = likeit; + } +} diff --git a/src/main/java/com/example/devSns/entities/Users.java b/src/main/java/com/example/devSns/entities/Users.java new file mode 100644 index 0000000..c489577 --- /dev/null +++ b/src/main/java/com/example/devSns/entities/Users.java @@ -0,0 +1,39 @@ +package com.example.devSns.entities; + +import com.example.devSns.authorities.Role; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Builder +public class Users { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + @NotNull + private String loginID; + + @NotNull + private String password; + + @NotNull + private String username; + + private Integer age; + + private LocalDate birthday; + + @Builder.Default + @Enumerated(EnumType.STRING) + private Role role = Role.ROLE_USER; +} diff --git a/src/main/java/com/example/devSns/repositories/PostRepository.java b/src/main/java/com/example/devSns/repositories/PostRepository.java new file mode 100644 index 0000000..1e0d690 --- /dev/null +++ b/src/main/java/com/example/devSns/repositories/PostRepository.java @@ -0,0 +1,15 @@ +package com.example.devSns.repositories; + +import com.example.devSns.entities.Posts; +import com.example.devSns.entities.Users; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface PostRepository extends JpaRepository { + public List findAll(); + public Optional findById(Integer id); + public List findByUsers(Users user); + public List findByContentContaining(String partialContent); +} diff --git a/src/main/java/com/example/devSns/repositories/ReplyRepository.java b/src/main/java/com/example/devSns/repositories/ReplyRepository.java new file mode 100644 index 0000000..8735736 --- /dev/null +++ b/src/main/java/com/example/devSns/repositories/ReplyRepository.java @@ -0,0 +1,12 @@ +package com.example.devSns.repositories; + +import com.example.devSns.entities.Posts; +import com.example.devSns.entities.Replies; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ReplyRepository extends JpaRepository { + public List findByPosts(Posts posts); + public Replies findById(long id); +} diff --git a/src/main/java/com/example/devSns/repositories/UserRepository.java b/src/main/java/com/example/devSns/repositories/UserRepository.java new file mode 100644 index 0000000..0bf51ef --- /dev/null +++ b/src/main/java/com/example/devSns/repositories/UserRepository.java @@ -0,0 +1,13 @@ +package com.example.devSns.repositories; + +import com.example.devSns.entities.Users; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + public Users findById(long userId); + public List findByUsernameContaining(String partialUsername); + public Optional findByLoginID(String loginID); +} diff --git a/src/main/java/com/example/devSns/services/PostService.java b/src/main/java/com/example/devSns/services/PostService.java new file mode 100644 index 0000000..1d88fb0 --- /dev/null +++ b/src/main/java/com/example/devSns/services/PostService.java @@ -0,0 +1,88 @@ +package com.example.devSns.services; + +import com.example.devSns.dto.PostDTO; +import com.example.devSns.dto.PostResponse; +import com.example.devSns.entities.Posts; +import com.example.devSns.entities.Users; +import com.example.devSns.repositories.UserRepository; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.example.devSns.repositories.PostRepository; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static com.example.devSns.dto.PostDTO.dtoToEntity; +import static com.example.devSns.dto.PostResponse.entityToDto; + +@Service +@RequiredArgsConstructor +public class PostService { + private final PostRepository postRepository; + private final UserRepository userRepository; + + @Transactional // 트랜잭션 보장 + public PostResponse save(PostDTO postDTO) { + Users user = userRepository.findById(postDTO.userId()).orElseThrow(() -> new EntityNotFoundException("존재하지 않는 사용자입니다.")); + Posts postEntity = dtoToEntity(postDTO, user); + Posts resultEntity = postRepository.save(postEntity); + return entityToDto(resultEntity); + } + + @Transactional + public List findAll() { // 전체 post 조회 + List posts = postRepository.findAll(); + List postResponses = new ArrayList<>(); + for (Posts post : posts) { + postResponses.add(entityToDto(post)); + } + return postResponses; + } + + @Transactional + public List findByUserID(Long userID) { + Users user = userRepository.findById(userID).orElseThrow(() -> new EntityNotFoundException("존재하지 않는 사용자입니다."));// 작성자 기준 post 조회 + List postsByName = postRepository.findByUsers(user); + List postResponses = new ArrayList<>(); + for (Posts post : postsByName) { + postResponses.add(entityToDto(post)); + } + return postResponses; + } + + @Transactional + public List findByContent(String content) { + List postsByContent = postRepository.findByContentContaining(content); + List postResponses = new ArrayList<>(); + for (Posts post : postsByContent) { + postResponses.add(entityToDto(post)); + } + return postResponses; + } + + @Transactional + public PostResponse update(Long id, PostDTO postDTO) { // 수정된 post 반영 + Posts postEntity = postRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("수정하려는 Post를 찾을 수 없습니다.")); + postEntity.setContent(postDTO.content()); + postEntity.setUpdateat(LocalDateTime.now()); + return entityToDto(postEntity); + } + + @Transactional + public PostResponse likePost(Long id) { + Posts postEntity = postRepository.findById(id).orElseThrow(EntityNotFoundException::new); + postEntity.setLikeit(postEntity.getLikeit() + 1); + return entityToDto(postEntity); + } + + @Transactional + public void delete(Long id) { // post 삭제 + Posts object = postRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("삭제하려는 Post를 찾을 수 없습니다.")); + postRepository.delete(object); + } +} diff --git a/src/main/java/com/example/devSns/services/ReplyService.java b/src/main/java/com/example/devSns/services/ReplyService.java new file mode 100644 index 0000000..0a5e0fc --- /dev/null +++ b/src/main/java/com/example/devSns/services/ReplyService.java @@ -0,0 +1,77 @@ +package com.example.devSns.services; + +import com.example.devSns.dto.ReplyDTO; +import com.example.devSns.dto.ReplyResponse; +import com.example.devSns.entities.Posts; +import com.example.devSns.entities.Replies; +import com.example.devSns.entities.Users; +import com.example.devSns.repositories.PostRepository; +import com.example.devSns.repositories.ReplyRepository; +import com.example.devSns.repositories.UserRepository; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.PathVariable; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Service +@Slf4j +@RequiredArgsConstructor +public class ReplyService { + private final ReplyRepository replyRepository; + private final PostRepository postRepository; + private final UserRepository userRepository; + + @Transactional(readOnly = true) + public List replyGetAll(@PathVariable long postId) { + Posts post = postRepository.findById(postId).orElseThrow(() -> new EntityNotFoundException("존재하지 않는 게시글입니다.")); + List replies = replyRepository.findByPosts(post); + List repliesResponse = new ArrayList<>(); + for (Replies reply: replies) { + repliesResponse.add(ReplyResponse.entityToDTO(reply)); + } + return repliesResponse; + } + + @Transactional + public ReplyResponse writeReply(@PathVariable long postId, ReplyDTO reply) { + Posts post = postRepository.findById(postId).orElseThrow(() -> new EntityNotFoundException("존재하지 않는 게시글입니다.")); + Users user = userRepository.findById(reply.userID()).orElseThrow(() -> new EntityNotFoundException("존재하지 않는 사용자입니다.")); + Replies replyEntity = ReplyDTO.dtoToEntity(post, user, reply); + replyRepository.save(replyEntity); + return ReplyResponse.entityToDTO(replyEntity); + } + + @Transactional + public ReplyResponse likeReply(@PathVariable long replyId) { + Replies replyEntity = replyRepository.findById(replyId); + replyEntity.setLikeit(replyEntity.getLikeit() + 1); + replyRepository.save(replyEntity); + return ReplyResponse.entityToDTO(replyEntity); + } + + @Transactional + public ReplyResponse updateReply (@PathVariable long replyId, ReplyDTO reply) { + Users user = userRepository.findById(reply.userID()).orElseThrow(() -> new EntityNotFoundException("존재하지 않는 사용자입니다.")); + if(user.getId() != reply.userID()) { + throw new SecurityException(); + } + Replies replyEntity = replyRepository.findById(replyId); + replyEntity.setReply(reply.comment()); + replyEntity.setUpdateAt(LocalDateTime.now()); + replyRepository.save(replyEntity); + return ReplyResponse.entityToDTO(replyEntity); + } + + @Transactional + public String deleteReply(@PathVariable long replyId) { + Replies replyEntity = replyRepository.findById(replyId); + replyRepository.delete(replyEntity); + return "성공적으로 삭제되었습니다"; + } +} diff --git a/src/main/java/com/example/devSns/services/UserService.java b/src/main/java/com/example/devSns/services/UserService.java new file mode 100644 index 0000000..fe50510 --- /dev/null +++ b/src/main/java/com/example/devSns/services/UserService.java @@ -0,0 +1,45 @@ +package com.example.devSns.services; + +import com.example.devSns.authorities.Role; +import com.example.devSns.dto.JwtDTO; +import com.example.devSns.dto.LoginDTO; +import com.example.devSns.dto.UserDTO; +import com.example.devSns.entities.Users; +import com.example.devSns.repositories.UserRepository; +import com.example.devSns.util.JwtUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +@Slf4j +public class UserService { + private final UserRepository userRepository; + private final JwtUtil jwtUtil; + private final PasswordEncoder passwordEncoder; + + @Transactional + public String signUp(UserDTO userDTO) { + Users user = UserDTO.dtoToEntity(userDTO); + userRepository.save(user); + return "회원가입 성공!"; + } + + public JwtDTO login(LoginDTO loginDTO) { + Users findUser = userRepository.findByLoginID(loginDTO.loginID()) + .orElseThrow(() -> new AuthenticationCredentialsNotFoundException("잘못된 아이디 또는 비밀번호입니다.")); + if(!passwordEncoder.matches(loginDTO.password(), findUser.getPassword())) + throw new AuthenticationCredentialsNotFoundException("잘못된 아이디 또는 비밀번호입니다."); + else { + String jwt = jwtUtil.generateToken(findUser.getLoginID(), findUser.getUsername(), Role.ROLE_USER); + JwtDTO jwtDTO = JwtDTO.builder() + .token(jwt) + .build(); + return jwtDTO; + } + } +} diff --git a/src/main/java/com/example/devSns/util/JwtFilter.java b/src/main/java/com/example/devSns/util/JwtFilter.java new file mode 100644 index 0000000..372e61d --- /dev/null +++ b/src/main/java/com/example/devSns/util/JwtFilter.java @@ -0,0 +1,44 @@ +package com.example.devSns.util; + +import com.example.devSns.authorities.MemberDetailsService; +import com.example.devSns.authorities.Role; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtFilter extends OncePerRequestFilter { + private final JwtUtil jwtUtil; + private final MemberDetailsService memberDetailsService; + + @Override + protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) + throws IOException, ServletException { + String authorizationHeader = req.getHeader("Authorization"); + + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { // Authorization header : 앞에 Bearer 있는지 확인 + String token = authorizationHeader.substring(7); // token 추출 + if (jwtUtil.validateToken(token)) { + String userID = jwtUtil.getUserID(token); + Role userRole = jwtUtil.getRole(token); + + UserDetails userDetail = memberDetailsService.loadUserByUsername(userID); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + SecurityContextHolder.getContext().setAuthentication(auth); + } + chain.doFilter(req, resp); + } + + chain.doFilter(req, resp); + } +} diff --git a/src/main/java/com/example/devSns/util/JwtUtil.java b/src/main/java/com/example/devSns/util/JwtUtil.java new file mode 100644 index 0000000..d451932 --- /dev/null +++ b/src/main/java/com/example/devSns/util/JwtUtil.java @@ -0,0 +1,68 @@ +package com.example.devSns.util; + +import com.example.devSns.authorities.Role; +import com.example.devSns.dto.UserDTO; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Component +public class JwtUtil { + @Value("${secretKey}") + private String secretKey; + + public Jws parseToken(String token) { + Key key = Keys.hmacShaKeyFor(secretKey.getBytes()); + + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + } + + public String generateToken(String loginID, String username, Role role) { + Key key = Keys.hmacShaKeyFor(secretKey.getBytes()); + + Map claims = new HashMap<>(); + + claims.put("loginID", loginID); + claims.put("username", username); + claims.put("role", role); + + String jwtToken = Jwts.builder() + .setHeaderParam("typ", "JWT") + .setHeaderParam("alg", "HS256") + .setClaims(claims) + .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 30)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + return jwtToken; + } + + public boolean validateToken(String token) { + try { + parseToken(token); + return true; + } catch(JwtException e) { + return false; + } + } + + public String getUserID(String token) { + Key key = Keys.hmacShaKeyFor(secretKey.getBytes()); + Claims claims = parseToken(token).getBody(); + return claims.get("loginID", String.class); + } + + public Role getRole(String token) { + Claims claims = parseToken(token).getBody(); + return Role.valueOf(claims.get("role", String.class)); + } +} diff --git a/src/main/java/exceptions/GlobalExceptionHandler.java b/src/main/java/exceptions/GlobalExceptionHandler.java new file mode 100644 index 0000000..f2db8be --- /dev/null +++ b/src/main/java/exceptions/GlobalExceptionHandler.java @@ -0,0 +1,42 @@ +package exceptions; + +import jakarta.persistence.EntityNotFoundException; +import org.springframework.dao.DataAccessException; +import org.springframework.http.HttpStatus; +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; + +import javax.naming.AuthenticationException; + +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + @ExceptionHandler + public ResponseEntity handleEntityNotFoundException(EntityNotFoundException e) { + ResponseEntity response = + new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); + return response; + } + + @ExceptionHandler + public ResponseEntity handleDataAccessException(DataAccessException e) { + ResponseEntity response = + new ResponseEntity<>("DB 오류", HttpStatus.INTERNAL_SERVER_ERROR); + return response; + } + + @ExceptionHandler + public ResponseEntity handleSecurityException(SecurityException e) { + ResponseEntity response = + new ResponseEntity<>("접근 권한 없음", HttpStatus.FORBIDDEN); + return response; + } + + @ExceptionHandler + public ResponseEntity handleLoginErrorException(AuthenticationException e) { + ResponseEntity response = + new ResponseEntity<>("잘못된 아이디 또는 비밀번호입니다.", HttpStatus.UNAUTHORIZED); + return response; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f3f10af..2326d4f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,10 @@ spring.application.name=devSns +spring.datasource.url=jdbc:postgresql://localhost:5432/crud +spring.datasource.username=crudsns +spring.datasource.password=crud^124 +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + diff --git a/src/test/java/com/example/devSns/likeTest.java b/src/test/java/com/example/devSns/likeTest.java new file mode 100644 index 0000000..c320614 --- /dev/null +++ b/src/test/java/com/example/devSns/likeTest.java @@ -0,0 +1,90 @@ +package com.example.devSns; + +import com.example.devSns.dto.PostResponse; +import com.example.devSns.dto.ReplyResponse; +import com.example.devSns.entities.Posts; +import com.example.devSns.entities.Replies; +import com.example.devSns.entities.Users; +import com.example.devSns.repositories.PostRepository; +import com.example.devSns.repositories.ReplyRepository; +import com.example.devSns.repositories.UserRepository; +import com.example.devSns.services.PostService; +import com.example.devSns.services.ReplyService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.time.LocalDateTime; + +@SpringBootTest +public class likeTest { + @Autowired + ReplyRepository replyRepository; + + @Autowired + PostRepository postRepository; + + @Autowired + UserRepository userRepository; + + @Autowired + private ReplyService replyService; + + @Autowired + private PostService postService; + + private long postId; + private long replyId; + + @BeforeEach + void setUp() { + replyRepository.deleteAll(); + postRepository.deleteAll(); + userRepository.deleteAll(); + + Users user1 = Users.builder() + .age(28) + .username("Java") + .build(); + + userRepository.save(user1); + + Users user2 = Users.builder() + .age(57) + .username("Unix") + .build(); + userRepository.save(user2); + + Posts post = Posts.builder() + .content("Hello, Java!") + .users(user1) + .build(); + postRepository.save(post); + postId = post.getId(); + + Replies reply = Replies.builder() + .posts(post) + .reply("Me too") + .createAt(LocalDateTime.now()) + .users(user2) + .build(); + + replyRepository.save(reply); + replyId = reply.getId(); + } + + @Test + public void likePostTest() { + PostResponse postResponse = postService.likePost(postId); + Assertions.assertEquals(1, postResponse.like()); + } + + @Test + public void likeReplyTest() { + ReplyResponse replyResponse = replyService.likeReply(replyId); + Assertions.assertEquals(1, replyResponse.like()); + } +} diff --git a/src/test/java/com/example/devSns/searchTest.java b/src/test/java/com/example/devSns/searchTest.java new file mode 100644 index 0000000..1785fe3 --- /dev/null +++ b/src/test/java/com/example/devSns/searchTest.java @@ -0,0 +1,83 @@ +package com.example.devSns; + +import com.example.devSns.dto.PostResponse; +import com.example.devSns.entities.Posts; +import com.example.devSns.entities.Replies; +import com.example.devSns.entities.Users; +import com.example.devSns.repositories.PostRepository; +import com.example.devSns.repositories.ReplyRepository; +import com.example.devSns.repositories.UserRepository; +import com.example.devSns.services.PostService; +import com.example.devSns.services.ReplyService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.time.LocalDateTime; +import java.util.List; + +@SpringBootTest +public class searchTest { + @Autowired + ReplyRepository replyRepository; + + @Autowired + PostRepository postRepository; + + @Autowired + UserRepository userRepository; + + @Autowired + private ReplyService replyService; + + @Autowired + private PostService postService; + + private long postId; + private long replyId; + + @BeforeEach + void setUp() { + replyRepository.deleteAll(); + postRepository.deleteAll(); + userRepository.deleteAll(); + + Users user1 = Users.builder() + .age(28) + .username("Java") + .build(); + + userRepository.save(user1); + + Users user2 = Users.builder() + .age(57) + .username("Unix") + .build(); + userRepository.save(user2); + + Posts post = Posts.builder() + .content("Hello, Java!") + .users(user1) + .build(); + postRepository.save(post); + postId = post.getId(); + + Replies reply = Replies.builder() + .posts(post) + .reply("Me too") + .createAt(LocalDateTime.now()) + .users(user2) + .build(); + + replyRepository.save(reply); + replyId = reply.getId(); + } + + @Test + public void likePostTest() { + List list = postService.findByContent("Hello"); + Assertions.assertEquals(1, list.size()); + } +}