Skip to content

Commit ca918f2

Browse files
authored
Merge branch 'develop' into main v.1
2 parents 81272de + 2b57c18 commit ca918f2

76 files changed

Lines changed: 2741 additions & 3 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.coderabbit.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# .coderabbit.yaml
2+
language: "ko-KR" # 한국말로 설정
3+
early_access: false
4+
reviews:
5+
profile: "chill" # 리뷰 너무 빡세게는 안한다는 뜻
6+
request_changes_workflow: true # 코드래빗이 리뷰 끝나면 알아서 PR 승인하게끔
7+
high_level_summary: true
8+
poem: true
9+
review_status: true
10+
collapse_walkthrough: false
11+
auto_review:
12+
enabled: true
13+
drafts: false
14+
chat:
15+
auto_reply: true

build.gradle

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ repositories {
2525

2626
dependencies {
2727
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
28-
implementation 'org.springframework.boot:spring-boot-starter-security'
28+
implementation 'org.springframework.boot:spring-boot-starter-security'
2929
implementation 'org.springframework.boot:spring-boot-starter-web'
30-
compileOnly 'org.projectlombok:lombok'
30+
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
31+
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
32+
runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.11.5')
3133
runtimeOnly 'com.h2database:h2'
3234
runtimeOnly 'com.mysql:mysql-connector-j'
35+
compileOnly 'org.projectlombok:lombok'
3336
annotationProcessor 'org.projectlombok:lombok'
3437
testImplementation 'org.springframework.boot:spring-boot-starter-test'
35-
testImplementation 'org.springframework.security:spring-security-test'
3638
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
3739
}
3840

gradlew

100644100755
File mode changed.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.example.FixLog.config;
2+
3+
import com.example.FixLog.domain.member.Member;
4+
import com.example.FixLog.exception.CustomException;
5+
import com.example.FixLog.repository.MemberRepository;
6+
import com.example.FixLog.util.JwtUtil;
7+
import java.io.IOException;
8+
import jakarta.servlet.FilterChain;
9+
import jakarta.servlet.ServletException;
10+
import jakarta.servlet.http.HttpServletRequest;
11+
import jakarta.servlet.http.HttpServletResponse;
12+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
13+
import org.springframework.security.core.Authentication;
14+
import org.springframework.security.core.context.SecurityContextHolder;
15+
import org.springframework.util.StringUtils;
16+
import org.springframework.web.filter.OncePerRequestFilter;
17+
import com.example.FixLog.exception.ErrorCode;
18+
19+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
20+
21+
private final JwtUtil jwtUtil;
22+
private final MemberRepository memberRepository;
23+
24+
public JwtAuthenticationFilter(JwtUtil jwtUtil, MemberRepository memberRepository) {
25+
this.jwtUtil = jwtUtil;
26+
this.memberRepository = memberRepository;
27+
}
28+
29+
@Override
30+
protected void doFilterInternal(HttpServletRequest request,
31+
HttpServletResponse response,
32+
FilterChain filterChain)
33+
throws ServletException, IOException {
34+
35+
String token = resolveToken(request);
36+
37+
if (token != null && jwtUtil.isTokenValid(token)) {
38+
String email = jwtUtil.getEmailFromToken(token);
39+
Member member = memberRepository.findByEmail(email)
40+
.orElseThrow(() -> new CustomException(ErrorCode.USER_NICKNAME_NOT_FOUND));
41+
42+
Authentication auth = new UsernamePasswordAuthenticationToken(member, null, member.getAuthorities());
43+
SecurityContextHolder.getContext().setAuthentication(auth);
44+
}
45+
46+
filterChain.doFilter(request, response);
47+
}
48+
49+
private String resolveToken(HttpServletRequest request) {
50+
String bearerToken = request.getHeader("Authorization");
51+
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
52+
return bearerToken.substring(7);
53+
}
54+
return null;
55+
}
56+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.example.FixLog.config;
2+
3+
import com.example.FixLog.repository.MemberRepository;
4+
import com.example.FixLog.util.JwtUtil;
5+
import jakarta.servlet.Filter;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.http.HttpMethod;
10+
import org.springframework.security.authentication.AuthenticationManager;
11+
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
12+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13+
import org.springframework.security.web.SecurityFilterChain;
14+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
15+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
16+
import org.springframework.security.crypto.password.PasswordEncoder;
17+
18+
@Configuration
19+
@RequiredArgsConstructor
20+
public class SecurityConfig {
21+
22+
private final JwtUtil jwtUtil;
23+
private final MemberRepository memberRepository;
24+
25+
@Bean
26+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
27+
http
28+
.csrf(csrf -> csrf.disable())
29+
.authorizeHttpRequests(auth -> auth
30+
.requestMatchers(HttpMethod.POST, "/members/signup").permitAll()
31+
.requestMatchers(HttpMethod.POST, "/auth/login").permitAll()
32+
.requestMatchers(HttpMethod.GET, "/members/check-email").permitAll()
33+
.requestMatchers(HttpMethod.GET, "/members/check-nickname").permitAll()
34+
.requestMatchers(HttpMethod.GET, "/h2-console/**").permitAll()
35+
.anyRequest().authenticated()
36+
)
37+
.headers(headers -> headers.frameOptions(frame -> frame.disable())) // H2 콘솔용
38+
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
39+
40+
return http.build();
41+
}
42+
43+
@Bean
44+
public Filter jwtAuthenticationFilter() {
45+
return new JwtAuthenticationFilter(jwtUtil, memberRepository);
46+
}
47+
48+
@Bean
49+
public PasswordEncoder passwordEncoder() {
50+
return new BCryptPasswordEncoder();
51+
}
52+
53+
// 인증 매니저 (선택: 로그인 시 AuthenticationManager 사용 가능)
54+
@Bean
55+
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
56+
return config.getAuthenticationManager();
57+
}
58+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.example.FixLog.controller;
2+
3+
import com.example.FixLog.dto.Response;
4+
import com.example.FixLog.dto.member.LoginRequestDto;
5+
import com.example.FixLog.dto.member.LoginResponseDto;
6+
import com.example.FixLog.service.AuthService;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.web.bind.annotation.*;
10+
11+
@RestController
12+
@RequestMapping("/auth")
13+
@RequiredArgsConstructor
14+
public class AuthController {
15+
16+
private final AuthService authService;
17+
18+
@PostMapping("/login")
19+
public ResponseEntity<Response<LoginResponseDto>> login(@RequestBody LoginRequestDto requestDto) {
20+
LoginResponseDto result = authService.login(requestDto);
21+
return ResponseEntity.ok(Response.success("로그인 성공", result));
22+
}
23+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.example.FixLog.controller;
2+
3+
import com.example.FixLog.dto.PageResponseDto;
4+
import com.example.FixLog.dto.Response;
5+
import com.example.FixLog.dto.bookmark.request.BookmarkFolderCreateRequest;
6+
import com.example.FixLog.dto.bookmark.request.BookmarkFolderUpdateRequest;
7+
import com.example.FixLog.dto.bookmark.request.BookmarkMoveRequest;
8+
import com.example.FixLog.dto.bookmark.response.BookmarkFolderCreateResponse;
9+
import com.example.FixLog.dto.bookmark.response.BookmarkFolderReadResponse;
10+
import com.example.FixLog.dto.post.MyPostPageResponseDto;
11+
import com.example.FixLog.service.BookmarkFolderService;
12+
import com.example.FixLog.service.BookmarkService;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
16+
import org.springframework.security.core.userdetails.UserDetails;
17+
import org.springframework.web.bind.annotation.*;
18+
19+
@RestController
20+
@RequiredArgsConstructor
21+
@RequestMapping("/bookmark-folders")
22+
public class BookmarkFolderController {
23+
private final BookmarkFolderService bookmarkFolderService;
24+
private final BookmarkService bookmarkService;
25+
26+
// 북마크 폴더 생성
27+
@PostMapping
28+
public ResponseEntity<?> createFolder(
29+
@RequestBody BookmarkFolderCreateRequest request,
30+
@RequestParam String requesterEmail
31+
) {
32+
BookmarkFolderCreateResponse response = bookmarkFolderService.createFolder(request.folderName(), requesterEmail);
33+
34+
return ResponseEntity.ok(Response.success("북마크 폴더 생성 성공", response));
35+
}
36+
37+
// 북마크 폴더 이름 수정
38+
@PatchMapping("/{folder_id}")
39+
public ResponseEntity<Response<Void>> updateFolderName(
40+
@PathVariable Long folderId,
41+
@RequestParam String requesterEmail,
42+
@RequestBody BookmarkFolderUpdateRequest request
43+
) {
44+
bookmarkFolderService.updateFolderName(folderId, requesterEmail, request.folderName());
45+
return ResponseEntity.ok(Response.success("폴더 이름 수정 완료", null));
46+
}
47+
48+
// 북마크 폴더 이동
49+
@PatchMapping("/{bookmarkId}/move")
50+
public ResponseEntity<Response<Void>> moveBookmark(
51+
@PathVariable Long bookmarkId,
52+
@RequestParam String requesterEmail,
53+
@RequestBody BookmarkMoveRequest request
54+
) {
55+
bookmarkService.moveBookmarkToFolder(bookmarkId, request.folderId(), requesterEmail);
56+
return ResponseEntity.ok(Response.success("북마크 다른 폴더로 이동 성공", null));
57+
}
58+
59+
60+
// 북마크 폴더 삭제
61+
@DeleteMapping("/{folderId}")
62+
public ResponseEntity<Response<Void>> deleteFolder(
63+
@PathVariable Long folderId,
64+
@RequestParam String requesterEmail
65+
) {
66+
bookmarkFolderService.deleteFolder(folderId, requesterEmail);
67+
return ResponseEntity.ok(Response.success("북마크 폴더 삭제 완료", null));
68+
}
69+
70+
// 북마크 폴더 목록 전체 조회 - MVP
71+
@GetMapping
72+
public ResponseEntity<Response<PageResponseDto<BookmarkFolderReadResponse>>> getFolders(
73+
@AuthenticationPrincipal UserDetails userDetails,
74+
@RequestParam(defaultValue = "0") int page,
75+
@RequestParam(defaultValue = "20") int size
76+
) {
77+
String email = userDetails.getUsername();
78+
PageResponseDto<BookmarkFolderReadResponse> response = bookmarkFolderService.getFoldersByEmail(email, page, size);
79+
return ResponseEntity.ok(Response.success("북마크 폴더 목록 전체 조회 성공", response));
80+
}
81+
82+
// 특정 폴더의 북마크 목록 조회 -MVP
83+
@GetMapping("/{folderId}/bookmarks")
84+
public ResponseEntity<Response<PageResponseDto<MyPostPageResponseDto>>> getBookmarksByFolder(
85+
@PathVariable Long folderId,
86+
@AuthenticationPrincipal UserDetails userDetails,
87+
@RequestParam(defaultValue = "0") int page,
88+
@RequestParam(defaultValue = "6") int size,
89+
@RequestParam(defaultValue = "0") int sort
90+
) {
91+
String email = userDetails.getUsername();
92+
PageResponseDto<MyPostPageResponseDto> data = bookmarkService.getBookmarksInFolder(email, folderId, page, sort, size);
93+
return ResponseEntity.ok(Response.success("특정 폴더의 북마크 목록 조회 성공", data));
94+
}
95+
96+
97+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.example.FixLog.controller;
2+
3+
import com.example.FixLog.dto.Response;
4+
import com.example.FixLog.dto.follow.request.FollowRequestDto;
5+
import com.example.FixLog.dto.follow.request.UnfollowRequestDto;
6+
import com.example.FixLog.dto.follow.response.FollowResponseDto;
7+
import com.example.FixLog.dto.follow.response.FollowerListResponseDto;
8+
import com.example.FixLog.dto.follow.response.FollowingListResponseDto;
9+
import com.example.FixLog.service.FollowService;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
13+
import org.springframework.security.core.userdetails.UserDetails;
14+
import org.springframework.web.bind.annotation.*;
15+
16+
import java.util.List;
17+
18+
@RestController
19+
@RequestMapping("/follow")
20+
@RequiredArgsConstructor
21+
public class FollowController {
22+
private final FollowService followService;
23+
24+
// 팔로우하기
25+
@PostMapping
26+
public ResponseEntity<Response<FollowResponseDto>> follow(
27+
@RequestBody FollowRequestDto followRequestDto,
28+
@AuthenticationPrincipal UserDetails userDetails // jwt 구현 전까지 임시 사용 -> 이후 AuthenticationPrincipal 사용 예정
29+
){
30+
String requesterEmail = userDetails.getUsername();
31+
FollowResponseDto result = followService.follow(requesterEmail, followRequestDto.getTargetMemberId());
32+
return ResponseEntity.ok(Response.success("팔로우 완료", result));
33+
}
34+
35+
// 언팔로우하기
36+
@PostMapping("/unfollow")
37+
public ResponseEntity<Response<Void>> unfollow(
38+
@RequestBody UnfollowRequestDto requestDto,
39+
@AuthenticationPrincipal UserDetails userDetails
40+
) {
41+
String requesterEmail = userDetails.getUsername();
42+
followService.unfollow(requesterEmail, requestDto.getTargetMemberId());
43+
return ResponseEntity.ok(Response.success("언팔로우 완료", null));
44+
}
45+
46+
// 나를 팔로우하는 목록 조회
47+
@GetMapping("/followers")
48+
public ResponseEntity<Response<List<FollowerListResponseDto>>> getMyFollowers(
49+
@AuthenticationPrincipal UserDetails userDetails
50+
) {
51+
String requesterEmail = userDetails.getUsername();
52+
List<FollowerListResponseDto> followers = followService.getMyFollowers(requesterEmail);
53+
return ResponseEntity.ok(Response.success("나를 팔로우하는 목록 조회 성공", followers));
54+
}
55+
56+
// 내가 팔로우하는 목록 조회
57+
@GetMapping("/followings")
58+
public ResponseEntity<Response<List<FollowingListResponseDto>>> getMyFollowings(
59+
@AuthenticationPrincipal UserDetails userDetails
60+
) {
61+
String requesterEmail = userDetails.getUsername();
62+
List<FollowingListResponseDto> followings = followService.getMyFollowings(requesterEmail);
63+
return ResponseEntity.ok(Response.success("내가 팔로우 중인 목록 조회 성공", followings));
64+
}
65+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.example.FixLog.controller;
2+
3+
import com.example.FixLog.dto.Response;
4+
import com.example.FixLog.dto.main.MainPageResponseDto;
5+
import com.example.FixLog.service.MainPageService;
6+
import org.springframework.web.bind.annotation.*;
7+
8+
@RestController
9+
@RequestMapping("/main")
10+
public class MainPageController {
11+
private final MainPageService mainPageService;
12+
13+
public MainPageController(MainPageService mainPageService){
14+
this.mainPageService = mainPageService;
15+
}
16+
17+
@GetMapping
18+
public Response<Object> mainPageView(@RequestParam(value = "sort", defaultValue = "0") int sort,
19+
@RequestParam(value = "page", defaultValue = "12") int size){
20+
MainPageResponseDto mainPageView = mainPageService.mainPageView(sort, size);
21+
return Response.success("메인페이지 불러오기 성공", mainPageView);
22+
}
23+
24+
@GetMapping("/full")
25+
public Response<Object> mainPageFullView(@RequestParam(value = "sort", defaultValue = "0") int sort,
26+
@RequestParam(value = "page", defaultValue = "1") int page,
27+
@RequestParam(value = "page", defaultValue = "12") int size){
28+
MainPageResponseDto mainPageFullView = mainPageService.mainPageFullView(sort, page, size);
29+
return Response.success("메인페이지 전체보기 성공", mainPageFullView);
30+
}
31+
}

0 commit comments

Comments
 (0)