Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b1dbd49
Controller, Service, Repository 기초 작업 완료
scholar-star Oct 6, 2025
1e63d38
CRUD 기능 구현 완료
scholar-star Oct 8, 2025
43cb730
EntityNotFoundException handler 추가, findByName 수정
scholar-star Oct 8, 2025
299dc06
DataAccessException(SQL) 관련 handler 추가
scholar-star Oct 8, 2025
ac326f8
update/delete의 Postbody로 id를 포함하여 보내던 부분을 @PathVariable로 리팩토링
scholar-star Oct 10, 2025
a24b3aa
기존 수정사항 반영, Entity와 Controller, Service 토대 만들기
scholar-star Nov 1, 2025
676e68d
Controller와 Service get, create 진행, Entity에 Join 연결
scholar-star Nov 1, 2025
7b94fda
Controller와 Service get, create 진행, Entity에 Join 연결
scholar-star Nov 1, 2025
7b53c52
User Entity 추가, Reply에 Post와 User의 의존 관계 연결
scholar-star Nov 2, 2025
90ce723
service, controller 토대 만들기
scholar-star Nov 2, 2025
3df566e
GlobalExceptionHandler로 예외 처리 옮기기, Controller 기본 채워넣기
scholar-star Nov 3, 2025
7fe66e9
ReplyRepository findby 형태 변경(postId->Posts), 오류 해결
scholar-star Nov 4, 2025
5f6e179
Post 리팩토링
scholar-star Nov 4, 2025
bb8aaf7
Post, Reply Like 추가
scholar-star Nov 9, 2025
0ba9159
좋아요 기능 추가 Test 완료
scholar-star Nov 11, 2025
6bb6db2
좋아요 기능 추가 Test
scholar-star Nov 11, 2025
39a61b6
Revert "좋아요 기능 추가 Test"
scholar-star Nov 11, 2025
e52f1a2
수정사항 반영 첫 번째
scholar-star Nov 12, 2025
b759e4b
수정사항 반영 두 번째
scholar-star Nov 13, 2025
3042d2a
Post 검색 기능 추가
scholar-star Nov 14, 2025
b6cfd3d
JWT 토큰 처리 & 인가 매커니즘 구현
scholar-star Nov 27, 2025
60a4232
회원가입 & 로그인 정상동작
scholar-star Nov 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/com/example/devSns/DevSnsApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@

@SpringBootApplication
public class DevSnsApplication {

public static void main(String[] args) {
SpringApplication.run(DevSnsApplication.class, args);
}

}
}
32 changes: 32 additions & 0 deletions src/main/java/com/example/devSns/authorities/MemberDetails.java
Original file line number Diff line number Diff line change
@@ -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<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(() -> user.getRole().toString());
return authorities;
}

@Override
public String getUsername() {
return user.getLoginID();
}

@Override
public String getPassword() {
return null;
}
}

Original file line number Diff line number Diff line change
@@ -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;
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/example/devSns/authorities/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.devSns.authorities;

public enum Role {
ROLE_USER,
ROLE_ADMIN
}
33 changes: 33 additions & 0 deletions src/main/java/com/example/devSns/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -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 {
Comment on lines +14 to +17
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spring Security를 사용하셨네요! 모듈이 복잡한 편이라서 나중에 인증/인가 전체 흐름 관련해서 파트별로 기능을 정리해두면 좋을 것 같아요~ 해당 부분 템플릿화해두면 초기 프로젝트 진행 시 인증/인가 부분을 빠르게 처리해서 넘겨줄 수 있어서 좋습니다


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());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

보통 세션 기반 인증과 달리 토큰 기반 인증을 사용할 때 csrf를 비활성화 합니다! 그런데 JWT를 사용할 때 csrf를 diable하는 이유가 뭘까요?

return http.build();
}
}
59 changes: 59 additions & 0 deletions src/main/java/com/example/devSns/controllers/PostController.java
Original file line number Diff line number Diff line change
@@ -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<List<PostResponse>> showPosts() {
List<PostResponse> posts = postService.findAll();
return new ResponseEntity<>(posts, HttpStatus.OK);
}

@GetMapping("/show/{userID}")
public ResponseEntity<List<PostResponse>> showPost(@PathVariable Long userID) {
List<PostResponse> post = postService.findByUserID(userID);
return new ResponseEntity<>(post, HttpStatus.OK);
}

@GetMapping("/search/{content}")
public ResponseEntity<List<PostResponse>> searchPost(@PathVariable String content) {
List<PostResponse> post = postService.findByContent(content);
return new ResponseEntity<>(post, HttpStatus.OK);
}

@PostMapping("/add")
public ResponseEntity<PostResponse> addPost(@RequestBody PostDTO postDTO) {
PostResponse postResponse = postService.save(postDTO);
return new ResponseEntity<>(postResponse, HttpStatus.CREATED);
}

@PutMapping("/{id}")
public ResponseEntity<PostResponse> updatePost(@PathVariable Long id, @RequestBody PostDTO postDTO) {
PostResponse postResponse = postService.update(id, postDTO);
return new ResponseEntity<>(postResponse, HttpStatus.OK);
}

@PatchMapping("/{id}/likeit")
public ResponseEntity<PostResponse> likePost(@PathVariable Long id) {
PostResponse postResponse = postService.likePost(id);
return new ResponseEntity<>(postResponse, HttpStatus.OK);
}

@DeleteMapping("/{id}")
public ResponseEntity<String> deletePost(@PathVariable Long id) {
postService.delete(id);
return new ResponseEntity<>("Post deleted", HttpStatus.OK);
}
}
53 changes: 53 additions & 0 deletions src/main/java/com/example/devSns/controllers/ReplyController.java
Original file line number Diff line number Diff line change
@@ -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<List<ReplyResponse>> getReplies(@PathVariable long postId) {
List<ReplyResponse> replies = replyService.replyGetAll(postId);
ResponseEntity<List<ReplyResponse>> responseEntity = new ResponseEntity<>(replies, HttpStatus.OK);
return responseEntity;
}

@PostMapping("/{postId}/add")
public ResponseEntity<ReplyResponse> writeReply(@PathVariable long postId, @RequestBody ReplyDTO reply) {
ReplyResponse replyResponse = replyService.writeReply(postId, reply);
ResponseEntity<ReplyResponse> responseEntity = new ResponseEntity<>(replyResponse, HttpStatus.OK);
return responseEntity;
}

@PutMapping("/{replyId}")
public ResponseEntity<ReplyResponse> updateReply(@PathVariable long replyId, @RequestBody ReplyDTO reply) {
ReplyResponse replyResponse = replyService.updateReply(replyId, reply);
ResponseEntity<ReplyResponse> responseEntity = new ResponseEntity<>(replyResponse, HttpStatus.OK);
return responseEntity;
}

@PatchMapping("/{id}")
public ResponseEntity<ReplyResponse> likeReply(@PathVariable long id) {
ReplyResponse replyResponse = replyService.likeReply(id);
ResponseEntity<ReplyResponse> responseEntity = new ResponseEntity<>(replyResponse, HttpStatus.OK);
return responseEntity;
}

@DeleteMapping("/{replyId}")
public ResponseEntity<String> deleteReply(@PathVariable long replyId) {
String deleteCheck = replyService.deleteReply(replyId);
ResponseEntity<String> responseEntity = new ResponseEntity<>(deleteCheck, HttpStatus.OK);
return responseEntity;
Comment on lines +50 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return ResponseEntity.status(HttpStatus.OK).body(response); 형식으로 한 번에 작성할 수도 있습니다! 그리고 응답의 경우 응답 속성이 하나더라도 DTO 형식으로 반환하면 통일성 관점에서 더 깔끔할 것 같아요! 필드 수정 시에도 더 유연하게 처리 가능합니다!

}
}
34 changes: 34 additions & 0 deletions src/main/java/com/example/devSns/controllers/UserController.java
Original file line number Diff line number Diff line change
@@ -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<String> signup(@RequestBody UserDTO userInfo) {
String resp = userService.signUp(userInfo);
ResponseEntity<String> response = new ResponseEntity(resp, HttpStatus.OK);
return response;
}

@PostMapping("/login")
public ResponseEntity<JwtDTO> login(@RequestBody LoginDTO loginDTO) {
return new ResponseEntity<>(userService.login(loginDTO), HttpStatus.OK);
}
}
8 changes: 8 additions & 0 deletions src/main/java/com/example/devSns/dto/JwtDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.devSns.dto;

import lombok.Builder;

@Builder
public record JwtDTO(
String token
) {}
6 changes: 6 additions & 0 deletions src/main/java/com/example/devSns/dto/LoginDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.devSns.dto;

public record LoginDTO (
String loginID,
String password
) {}
22 changes: 22 additions & 0 deletions src/main/java/com/example/devSns/dto/PostDTO.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
27 changes: 27 additions & 0 deletions src/main/java/com/example/devSns/dto/PostResponse.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Comment on lines +8 to +27
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

빌더의 경우 필요한 파라미터만 유연하게 넣어서 구성할 때 이점이 있는데, 여기서는 항상 모든 필드를 채우는 형식이라서 @Builder보다는 record에서 제공하는 생성자를 써도 될 것 같아요!

21 changes: 21 additions & 0 deletions src/main/java/com/example/devSns/dto/ReplyDTO.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/example/devSns/dto/ReplyResponse.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading