Skip to content

Commit 81bfc59

Browse files
authored
Merge branch 'develop' into chore/#64-swaggerSetting-cw
2 parents 6141064 + ec1d36f commit 81bfc59

16 files changed

Lines changed: 456 additions & 7 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ out/
3636
### VS Code ###
3737
.vscode/
3838

39-
### Environment variables ###
39+
### Environment variables ###::wq
40+
4041
.env
4142
*.env
4243
.env

build.gradle

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ plugins {
44
id 'io.spring.dependency-management' version '1.1.7'
55
}
66

7+
ext {
8+
queryDslVersion = "5.0.0"
9+
}
10+
711
group = 'com.example'
812
version = '0.0.1-SNAPSHOT'
913

@@ -65,7 +69,6 @@ dependencies {
6569
// Test
6670
testImplementation 'org.springframework.boot:spring-boot-starter-test'
6771
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
68-
}
6972

7073
tasks.named('test') {
7174
useJUnitPlatform()
@@ -89,4 +92,6 @@ sourceSets {
8992

9093
tasks.withType(JavaCompile).configureEach {
9194
options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
92-
}
95+
}
96+
97+

src/main/java/com/example/FixLog/config/SecurityConfig.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
4646
.requestMatchers(HttpMethod.PATCH,"/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
4747
// 그 외 모든 요청은 인증 필요
4848

49-
5049
.anyRequest().authenticated()
5150
)
5251
.headers(headers -> headers.frameOptions(frame -> frame.disable())) // H2 콘솔
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.search.SearchPostDto;
6+
import com.example.FixLog.service.SearchService;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.data.domain.Page;
9+
import org.springframework.data.domain.PageRequest;
10+
import org.springframework.data.domain.Pageable;
11+
import org.springframework.data.domain.Sort;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RequestParam;
15+
import org.springframework.web.bind.annotation.RestController;
16+
17+
import java.util.List;
18+
19+
@RestController
20+
@RequiredArgsConstructor
21+
@RequestMapping("/main")
22+
public class SearchController {
23+
24+
private final SearchService searchService;
25+
26+
@GetMapping("/search")
27+
public Response<PageResponseDto<SearchPostDto>> search(
28+
@RequestParam(required = false) String keyword,
29+
@RequestParam(required = false) List<String> tags,
30+
@RequestParam(defaultValue = "0") int page,
31+
@RequestParam(defaultValue = "10") int size) {
32+
33+
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt"));
34+
Page<SearchPostDto> result = searchService.searchPosts(keyword, tags, pageable);
35+
36+
PageResponseDto<SearchPostDto> responseDto = PageResponseDto.from(result, dto -> dto);
37+
return Response.success("검색 성공", responseDto);
38+
}
39+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.example.FixLog.dto.search;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
import java.time.LocalDateTime;
7+
import java.util.List;
8+
9+
@Getter
10+
@Builder
11+
public class SearchPostDto {
12+
private Long postId;
13+
private String title;
14+
private String content;
15+
private String writerNickname;
16+
private String writerProfileImage;
17+
private List<String> tags; // 예: [“spring-boot”, “jwt”, “java”]
18+
private LocalDateTime createdAt;
19+
private int likeCount;
20+
private int bookmarkCount;
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.example.FixLog.dto.search;
2+
3+
import lombok.Getter;
4+
5+
import java.util.List;
6+
7+
/**
8+
* 게시글 검색 요청 DTO
9+
* - keyword와 태그 카테고리 정보를 담아야 됨
10+
*/
11+
@Getter
12+
public class SearchRequestDto {
13+
14+
private String keyword; // 텍스트 검색어
15+
16+
private String bigCategory; // 택1 (ex: 백엔드)
17+
private String majorCategory; // 택1 (ex: Spring)
18+
private String middleCategory; // 택1 (ex: JPA)
19+
private List<String> minorCategories; // 복수 선택 가능 (ex: JWT, CI/CD 등)
20+
21+
private Integer page; // 1부터 시작
22+
private String sort; // 정렬 기준 (ex: recent, popular)
23+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.example.FixLog.mock;
2+
3+
import com.example.FixLog.domain.bookmark.Bookmark;
4+
import com.example.FixLog.domain.like.PostLike;
5+
import com.example.FixLog.domain.member.Member;
6+
import com.example.FixLog.domain.post.Post;
7+
import com.example.FixLog.domain.post.PostTag;
8+
import com.example.FixLog.domain.tag.Tag;
9+
import com.example.FixLog.repository.MemberRepository;
10+
import com.example.FixLog.repository.bookmark.BookmarkFolderRepository;
11+
import com.example.FixLog.repository.bookmark.BookmarkRepository;
12+
import com.example.FixLog.repository.like.PostLikeRepository;
13+
import com.example.FixLog.repository.post.PostRepository;
14+
import com.example.FixLog.repository.post.PostTagRepository;
15+
import com.example.FixLog.repository.tag.TagRepository;
16+
import jakarta.transaction.Transactional;
17+
import lombok.RequiredArgsConstructor;
18+
import org.springframework.boot.CommandLineRunner;
19+
import org.springframework.core.annotation.Order;
20+
import org.springframework.stereotype.Component;
21+
22+
import java.time.LocalDateTime;
23+
import java.util.*;
24+
import java.util.stream.Collectors;
25+
26+
27+
@Component
28+
@Order(6)
29+
@RequiredArgsConstructor
30+
public class PostMockDataInitializer implements CommandLineRunner {
31+
32+
private final MemberRepository memberRepository;
33+
private final PostRepository postRepository;
34+
private final TagRepository tagRepository;
35+
private final PostTagRepository postTagRepository;
36+
private final PostLikeRepository postLikeRepository;
37+
private final BookmarkRepository bookmarkRepository;
38+
private final BookmarkFolderRepository bookmarkFolderRepository;
39+
40+
@Override
41+
@Transactional
42+
public void run(String... args) {
43+
if (postRepository.count() > 8) return; // 기존 1개 외 추가
44+
45+
Optional<Member> memberOpt = memberRepository.findByEmail("test2@example.com");
46+
if (memberOpt.isEmpty()) {
47+
System.out.println("test2@example.com 회원 없음");
48+
return;
49+
}
50+
51+
Member member = memberOpt.get();
52+
Map<String, Tag> tagMap = tagRepository.findAll().stream()
53+
.collect(Collectors.toMap(Tag::getTagName, tag -> tag));
54+
55+
List<String[]> config = List.of(
56+
new String[]{"백엔드", "스프링부트", "자바", "NullPointerException", "500 Internal Server Error"},
57+
new String[]{"프론트엔드", "리액트", "자바스크립트", "Cannot read property of undefined", "상태(state) 업데이트 누락"},
58+
new String[]{"머신러닝", "케라스", "파이썬", "OutOfMemoryError", "HTTP 에러"},
59+
new String[]{"백엔드", "노드", "JSON", "CORS 정책 오류", "404 Not Found"},
60+
new String[]{"프론트엔드", "넥스트", "CSS", "스타일 깨짐", "렌더링 무한 루프"},
61+
new String[]{"머신러닝", "사이킷런", "R", "ClassNotFoundException", "Permission Error"}
62+
);
63+
64+
for (int i = 0; i < config.size(); i++) {
65+
String[] tags = config.get(i);
66+
Post post = Post.builder()
67+
.userId(member)
68+
.postTitle("테스트 업그레이드 " + (i + 2))
69+
.coverImage("https://cdn.example.com/images/test" + (i + 2) + ".jpg")
70+
.problem("이 게시물은 문제 설명이 200자를 넘도록 작성되었습니다. 문제 발생 상황, 재현 과정, 로그, 화면 캡처 등 다양한 정보가 포함될 수 있습니다. 이 텍스트는 말줄임표가 잘 붙는지 확인하기 위한 용도로 작성되었으며, 검색 결과에서는 200자까지만 보여야 합니다. 이후 내용은 생략될 수 있습니다. 추가 텍스트를 더 붙입니다. 더 붙입니다. 더 붙입니다.")
71+
.errorMessage("이건 에러다 keyword 포함")
72+
.environment("환경 정보")
73+
.reproduceCode("재현 코드")
74+
.solutionCode("해결 코드")
75+
.causeAnalysis("원인 분석")
76+
.referenceLink("https://example.com")
77+
.extraContent("추가 설명")
78+
.createdAt(LocalDateTime.now())
79+
.editedAt(LocalDateTime.now())
80+
.build();
81+
postRepository.save(post);
82+
83+
List<PostTag> postTags = Arrays.stream(tags)
84+
.map(tagMap::get)
85+
.filter(Objects::nonNull)
86+
.map(tag -> new PostTag(post, tag))
87+
.toList();
88+
postTagRepository.saveAll(postTags);
89+
90+
postLikeRepository.save(new PostLike(member, post));
91+
92+
bookmarkFolderRepository.findFirstByUserId(member).ifPresent(folder -> {
93+
Bookmark bookmark = new Bookmark(folder, post);
94+
bookmarkRepository.save(bookmark);
95+
});
96+
}
97+
98+
System.out.println("test2@example.com 사용자의 게시물 6개 + 태그/좋아요/북마크 생성 완료");
99+
}
100+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.example.FixLog.mock;
2+
3+
import com.example.FixLog.domain.tag.Tag;
4+
import com.example.FixLog.repository.tag.TagRepository;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.boot.CommandLineRunner;
7+
import org.springframework.core.annotation.Order;
8+
import org.springframework.stereotype.Component;
9+
10+
import java.util.List;
11+
import java.util.ArrayList;
12+
import java.util.Set;
13+
import java.util.stream.Collectors;
14+
15+
import static com.example.FixLog.domain.tag.TagCategory.*;
16+
17+
@Component
18+
@RequiredArgsConstructor
19+
@Order(4)
20+
public class TagMockDataInitializer implements CommandLineRunner {
21+
22+
private final TagRepository tagRepository;
23+
24+
@Override
25+
public void run(String... args) {
26+
// 현재 저장된 태그 이름들 (중복 방지용)
27+
Set<String> existingTagNames = tagRepository.findAll()
28+
.stream()
29+
.map(Tag::getTagName)
30+
.collect(Collectors.toSet());
31+
32+
List<Tag> tagsToInsert = new ArrayList<>();
33+
34+
// BIG_CATEGORY
35+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(BIG_CATEGORY, "백엔드", "backend"));
36+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(BIG_CATEGORY, "머신러닝", "machine learning"));
37+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(BIG_CATEGORY, "프론트엔드", "frontend"));
38+
39+
// MAJOR_CATEGORY
40+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MAJOR_CATEGORY, "장고", "django"));
41+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MAJOR_CATEGORY, "스프링부트", "spring-boot"));
42+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MAJOR_CATEGORY, "넥스트", "next.js"));
43+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MAJOR_CATEGORY, "케라스", "keras"));
44+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MAJOR_CATEGORY, "파이토치", "pytorch"));
45+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MAJOR_CATEGORY, "사이킷런", "scikit-learn"));
46+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MAJOR_CATEGORY, "노드", "node.js"));
47+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MAJOR_CATEGORY, "리액트", "react"));
48+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MAJOR_CATEGORY, "리액트 네이티브", "react-native"));
49+
50+
// MIDDLE_CATEGORY
51+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MIDDLE_CATEGORY, "CSS", "css"));
52+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MIDDLE_CATEGORY, "자바스크립트", "javascript"));
53+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MIDDLE_CATEGORY, "R", "r"));
54+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MIDDLE_CATEGORY, "JSON", "json"));
55+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MIDDLE_CATEGORY, "자바", "java"));
56+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MIDDLE_CATEGORY, "Haskell", "haskell"));
57+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MIDDLE_CATEGORY, "파이썬", "python"));
58+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MIDDLE_CATEGORY, "C", "c"));
59+
60+
// MINOR_CATEGORY
61+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "NullPointerException", "null-pointer-exception"));
62+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "500 Internal Server Error", "500-error"));
63+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "CORS 정책 오류", "cors-error"));
64+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "Database connection timeout", "db-timeout"));
65+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "ClassNotFoundException", "class-not-found"));
66+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "Cannot read property of undefined", "undefined-property"));
67+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "상태(state) 업데이트 누락", "state-missing"));
68+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "HTTP 에러", "http-error"));
69+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "렌더링 무한 루프", "render-loop"));
70+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "스타일 깨짐", "style-break"));
71+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "404 Not Found", "404-error"));
72+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "Permission Error", "permission-error"));
73+
addIfNotExist(tagsToInsert, existingTagNames, Tag.of(MINOR_CATEGORY, "OutOfMemoryError", "out-of-memory"));
74+
75+
if (!tagsToInsert.isEmpty()) {
76+
tagRepository.saveAll(tagsToInsert);
77+
System.out.println("중복 없는 목업 태그 " + tagsToInsert.size() + "개 삽입 완료");
78+
} else {
79+
System.out.println("삽입할 신규 태그가 없습니다.");
80+
}
81+
}
82+
83+
private void addIfNotExist(List<Tag> list, Set<String> existingNames, Tag tag) {
84+
if (!existingNames.contains(tag.getTagName())) {
85+
list.add(tag);
86+
}
87+
}
88+
}

src/main/java/com/example/FixLog/repository/post/PostRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import java.util.List;
1111

12-
public interface PostRepository extends JpaRepository<Post, Long> {
12+
public interface PostRepository extends JpaRepository<Post, Long>, PostRepositoryCustom{
1313
List<Post> findTop12ByOrderByCreatedAtDesc();
1414
List<Post> findTop12ByOrderByPostLikesDesc();
1515
Page<Post> findAllByOrderByCreatedAtDesc(Pageable pageable);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.example.FixLog.repository.post;
2+
3+
import com.example.FixLog.dto.search.SearchPostDto;
4+
import org.springframework.data.domain.Page;
5+
import org.springframework.data.domain.Pageable;
6+
7+
import java.util.List;
8+
9+
public interface PostRepositoryCustom {
10+
Page<SearchPostDto> searchByKeywordAndTags(String keyword, List<String> tags, Pageable pageable);
11+
}

0 commit comments

Comments
 (0)