Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-security'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Spring Security 의존성이 활성화되었습니다

Spring Security 의존성을 활성화하셨네요. 이는 인증 및 권한 부여 기능을 프로젝트에 추가합니다. 다만, Spring Security를 활성화하면 기본적으로 모든 엔드포인트가 인증을 필요로 하게 됩니다. 이를 위한 적절한 보안 구성이 프로젝트에 추가되었는지 확인해주세요.

프로젝트에 SecurityConfig 또는 WebSecurityConfigurerAdapter 구현체가 있는지 확인해보세요:


🏁 Script executed:

#!/bin/bash
# 보안 설정 파일 존재 확인
find src/main/java -type f -name "*Security*.java" -o -name "*WebSecurity*.java" | sort

Length of output: 89


보안 설정 클래스 미확인

Spring Security 의존성을 추가하셨으나, src/main/java 경로에서 *Security*.java 또는 *WebSecurity*.java 파일이 검색되지 않습니다. 이 상태라면 기본 설정으로 모든 엔드포인트가 인증을 필요로 하며, 기본 로그인 페이지와 기본 계정이 생성될 수 있습니다. 다음 사항을 검토 및 조치해주세요:

  • 사용자 정의 보안 설정 클래스(SecurityConfig)를 추가하여 인증/인가 정책을 명시적으로 구성
  • @Configuration@EnableWebSecurity 애노테이션을 사용해 WebSecurityConfigurerAdapter 또는 SecurityFilterChain 구현체 생성
  • 엔드포인트별 접근 제어(antMatchers 등), CSRF·CORS 설정 여부 확인

예시:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(auth -> auth
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(withDefaults());
        return http.build();
    }
}

위와 같이 보안 설정을 추가하여 프로젝트에 필요한 인증·인가 로직을 구현해주세요.

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/aibe/hosik/analysis/Analysis.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package aibe.hosik.analysis;

import aibe.hosik.apply.Apply;
import aibe.hosik.apply.entity.Apply;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package aibe.hosik.apply;
package aibe.hosik.apply.controller;

import aibe.hosik.apply.service.ApplyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package aibe.hosik.apply;
package aibe.hosik.apply.entity;

import aibe.hosik.common.TimeEntity;
import aibe.hosik.post.entity.Post;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package aibe.hosik.apply;
package aibe.hosik.apply.repository;

import aibe.hosik.apply.entity.Apply;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ApplyRepository extends JpaRepository<Apply, Long> {
// post id로 지원서 조회

Comment on lines +7 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

코멘트만 있고 실제 메서드 구현이 없습니다

"post id로 지원서 조회"라는 코멘트가 있지만 실제 메서드가 구현되어 있지 않습니다. 모집글과 지원서 간의 연관관계를 처리하기 위해서는 이 기능이 필요할 것으로 보입니다.

아래와 같이 메서드를 구현하는 것이 좋을 것 같습니다:

public interface ApplyRepository extends JpaRepository<Apply, Long> {
    // post id로 지원서 조회
+   List<Apply> findByPostId(Long postId);

}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// post id로 지원서 조회
public interface ApplyRepository extends JpaRepository<Apply, Long> {
// post id로 지원서 조회
List<Apply> findByPostId(Long postId);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package aibe.hosik.apply;
package aibe.hosik.apply.service;

import aibe.hosik.apply.repository.ApplyRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand Down
14 changes: 0 additions & 14 deletions src/main/java/aibe/hosik/post/PostController.java

This file was deleted.

7 changes: 0 additions & 7 deletions src/main/java/aibe/hosik/post/PostRepository.java

This file was deleted.

12 changes: 0 additions & 12 deletions src/main/java/aibe/hosik/post/PostService.java

This file was deleted.

68 changes: 68 additions & 0 deletions src/main/java/aibe/hosik/post/controller/PostController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package aibe.hosik.post.controller;

import aibe.hosik.post.dto.PostDetailDTO;
import aibe.hosik.post.dto.PostRequestDTO;
import aibe.hosik.post.dto.PostResponseDTO;
import aibe.hosik.post.entity.Post;
import aibe.hosik.post.service.PostService;
import aibe.hosik.skill.repository.PostSkillRepository;
import aibe.hosik.user.User;
import aibe.hosik.user.UserRepository;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.coyote.Response;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;

// 테스트
@Slf4j
@RestController
@RequestMapping("/api/posts")
@RequiredArgsConstructor
@Tag(name = "Post", description = "모집글 API") // Swagger Tag
public class PostController {
private final PostService postService;
private final UserRepository userRepository;
private final PostSkillRepository postSkillRepository;
Comment on lines +29 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

컨트롤러에서 직접 Repository 접근은 지양하세요.

컨트롤러 레이어에서 직접 UserRepositoryPostSkillRepository를 주입받아 사용하는 것은 계층 구조의 원칙을 위반합니다. 이러한 데이터 액세스는 서비스 레이어를 통해 이루어져야 합니다. 컨트롤러는 서비스 레이어와만 상호작용하고, 서비스 레이어가 리포지토리와 상호작용하는 구조가 바람직합니다.

리포지토리 접근 로직을 서비스 계층으로 이동하는 것을 권장합니다:

  public class PostController {
    private final PostService postService;
-   private final UserRepository userRepository;
-   private final PostSkillRepository postSkillRepository;

  @PostMapping
  public ResponseEntity<?> createPost(@RequestBody PostRequestDTO dto, @AuthenticationPrincipal User user){
    Post createPost = postService.createPost(dto, user);
-   // 스킬 조회
-   List<String> skills = postSkillRepository.findSkillByPostId(createPost.getId());
    // dto 반환
    // TODO : currentCount 로직 구현 후 변환
-   PostResponseDTO responseDTO = PostResponseDTO.from(createPost, skills, 0);
+   PostResponseDTO responseDTO = postService.getPostResponse(createPost);
    return ResponseEntity.ok(responseDTO);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private final PostService postService;
private final UserRepository userRepository;
private final PostSkillRepository postSkillRepository;
public class PostController {
private final PostService postService;
@PostMapping
public ResponseEntity<?> createPost(@RequestBody PostRequestDTO dto, @AuthenticationPrincipal User user) {
Post createPost = postService.createPost(dto, user);
// dto 반환
// TODO : currentCount 로직 구현 후 변환
PostResponseDTO responseDTO = postService.getPostResponse(createPost);
return ResponseEntity.ok(responseDTO);
}
}


@Operation(summary="모집글 등록", description="모집글을 등록합니다.")
@PostMapping
public ResponseEntity<?> createPost(@RequestBody PostRequestDTO dto, @AuthenticationPrincipal User user){
Post createPost = postService.createPost(dto, user);
// 스킬 조회
List<String> skills = postSkillRepository.findSkillByPostId(createPost.getId());
// dto 반환
// TODO : currentCount 로직 구현 후 변환
PostResponseDTO responseDTO = PostResponseDTO.from(createPost, skills, 0);
return ResponseEntity.ok(responseDTO);
}
// 테스트용
@Operation(summary="모집글 등록 테스트", description="[TEST] 모집글을 등록합니다.")
@PostMapping("/mock")
public ResponseEntity<?> createPostForSwagger(@RequestBody PostRequestDTO dto){

// 테스트용 userId
User mockUser = userRepository.findById(1L).orElseThrow();
Post createPost = postService.createPost(dto, mockUser);
List<String> skills = postSkillRepository.findSkillByPostId(createPost.getId());
PostResponseDTO responseDTO = PostResponseDTO.from(createPost, skills, 0);
return ResponseEntity.ok(responseDTO);
}
Comment on lines +45 to +55
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

테스트용 엔드포인트에 대한 보안 및 설계 개선이 필요합니다.

테스트용 엔드포인트는 다음과 같은 문제점이 있습니다:

  1. 하드코딩된 userId(1L)는 해당 사용자가 존재하지 않을 경우 예외가 발생할 수 있습니다.
  2. 프로덕션 환경에서 테스트 엔드포인트가 노출될 수 있습니다.
  3. 정규 createPost 메서드와 코드 중복이 발생합니다.

다음과 같은 방법으로 개선할 수 있습니다:

  1. 테스트 엔드포인트는 개발/테스트 환경에서만 활성화되도록 설정
  2. 예외 처리 추가
  3. 하드코딩된 ID 대신 더 안전한 방법 사용
+ @Profile({"dev", "test"}) // 개발 및 테스트 환경에서만 활성화
@Operation(summary="모집글 등록 테스트", description="[TEST] 모집글을 등록합니다.")
@PostMapping("/mock")
public ResponseEntity<?> createPostForSwagger(@RequestBody PostRequestDTO dto){
  // 테스트용 userId
- User mockUser = userRepository.findById(1L).orElseThrow();
+ User mockUser = userRepository.findById(1L)
+     .orElseThrow(() -> new IllegalStateException("테스트용 사용자(ID: 1)가 존재하지 않습니다."));
  
  // createPost 메서드를 재사용하여 중복 코드 제거
- Post createPost = postService.createPost(dto, mockUser);
- List<String> skills = postSkillRepository.findSkillByPostId(createPost.getId());
- PostResponseDTO responseDTO = PostResponseDTO.from(createPost, skills, 0);
- return ResponseEntity.ok(responseDTO);
+ return createPost(dto, mockUser);
}

Committable suggestion skipped: line range outside the PR's diff.


@Operation(summary="모집글 조회", description = "모집글 목록을 조회합니다.")
@GetMapping
public ResponseEntity<List<PostResponseDTO>> getAllPosts(){
return ResponseEntity.ok(postService.getAllPosts());
}
Comment on lines +58 to +61
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

페이지네이션 구현이 필요합니다.

getAllPosts() 메서드는 모든 게시글을 한 번에 반환합니다. 게시글 수가 많아지면 성능 문제가 발생할 수 있습니다. 페이지네이션을 구현하여 한 번에 로드되는 데이터의 양을 제한하는 것이 좋습니다.

다음과 같이 페이지네이션을 구현할 수 있습니다:

@Operation(summary="모집글 조회", description = "모집글 목록을 조회합니다.")
@GetMapping
- public ResponseEntity<List<PostResponseDTO>> getAllPosts(){
-   return ResponseEntity.ok(postService.getAllPosts());
+ public ResponseEntity<Page<PostResponseDTO>> getAllPosts(
+     @RequestParam(defaultValue = "0") int page,
+     @RequestParam(defaultValue = "10") int size,
+     @RequestParam(defaultValue = "id") String sortBy) {
+   Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy).descending());
+   return ResponseEntity.ok(postService.getAllPosts(pageable));
}

Committable suggestion skipped: line range outside the PR's diff.


@Operation(summary="모집글 상세 조회", description="모집글 게시글을 상세 조회합니다")
@GetMapping("/{postId}")
public ResponseEntity<PostDetailDTO> getPostDetail(@PathVariable Long postId){
return ResponseEntity.ok(postService.getPostDetail(postId));
}
Comment on lines +63 to +67
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

예외 처리가 필요합니다.

getPostDetail() 메서드는 존재하지 않는 ID에 대한 예외 처리가 명시적으로 구현되어 있지 않습니다. 서비스 계층에서 예외를 던질 수 있으므로, 컨트롤러에서 이를 적절히 처리하는 것이 좋습니다.

다음과 같이 예외 처리를 추가할 수 있습니다:

@Operation(summary="모집글 상세 조회", description="모집글 게시글을 상세 조회합니다")
@GetMapping("/{postId}")
public ResponseEntity<PostDetailDTO> getPostDetail(@PathVariable Long postId){
+   try {
      return ResponseEntity.ok(postService.getPostDetail(postId));
+   } catch (NoSuchElementException e) {
+       log.error("게시글을 찾을 수 없습니다. ID: {}", postId, e);
+       return ResponseEntity.notFound().build();
+   } catch (Exception e) {
+       log.error("게시글 조회 중 오류 발생. ID: {}", postId, e);
+       return ResponseEntity.internalServerError().build();
+   }
}

또는 전역 예외 처리기(Global Exception Handler)를 구현하여 모든 컨트롤러에서 발생하는 예외를 중앙에서 처리하는 것을 고려해 보세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Operation(summary="모집글 상세 조회", description="모집글 게시글을 상세 조회합니다")
@GetMapping("/{postId}")
public ResponseEntity<PostDetailDTO> getPostDetail(@PathVariable Long postId){
return ResponseEntity.ok(postService.getPostDetail(postId));
}
@Operation(summary="모집글 상세 조회", description="모집글 게시글을 상세 조회합니다")
@GetMapping("/{postId}")
public ResponseEntity<PostDetailDTO> getPostDetail(@PathVariable Long postId){
try {
return ResponseEntity.ok(postService.getPostDetail(postId));
} catch (NoSuchElementException e) {
log.error("게시글을 찾을 수 없습니다. ID: {}", postId, e);
return ResponseEntity.notFound().build();
} catch (Exception e) {
log.error("게시글 조회 중 오류 발생. ID: {}", postId, e);
return ResponseEntity.internalServerError().build();
}
}

}
15 changes: 15 additions & 0 deletions src/main/java/aibe/hosik/post/dto/MatchedUserDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package aibe.hosik.post.dto;

import aibe.hosik.post.entity.Post;

import java.util.List;

public record MatchedUserDTO(
Long userId,
String username,
String nickname,
String image,
String introduction
) {

}
41 changes: 41 additions & 0 deletions src/main/java/aibe/hosik/post/dto/PostDetailDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package aibe.hosik.post.dto;

import aibe.hosik.post.entity.Post;
import aibe.hosik.post.entity.PostCategory;
import aibe.hosik.post.entity.PostType;

import java.time.LocalDate;
import java.util.List;

public record PostDetailDTO(
Long id,
String title,
String content,
Integer headCount,
String image,
String requirementPersonality,
LocalDate endedAt,

String category,
String type,

List<String> skills,

// 현재 선택된 목록 보여주기
List<MatchedUserDTO> matchedUsers
) {
public static PostDetailDTO from(Post post, List<String> skills, List<MatchedUserDTO> matchedUsers) {
return new PostDetailDTO(
post.getId(),
post.getTitle(),
post.getContent(),
post.getHeadCount(),
post.getImage(),
post.getRequirementPersonality(),
post.getEndedAt(),
post.getCategory().toString(),
post.getType().toString(),
skills,
matchedUsers
);
}}
Comment on lines +27 to +41
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

열거형(Enum) 타입 안전성 향상 필요

현재 categorytype 필드는 문자열로 변환되어 반환되고 있습니다. 이는 타입 안전성을 떨어뜨리고, 클라이언트 측에서 적절한 열거형 매핑이 어려울 수 있습니다. Enum 값을 그대로 유지하거나, 문자열로 변환하더라도 열거형 값에 대한 추가 정보를 제공하는 것이 좋습니다.

또한 코드의 마지막 부분에서 닫는 괄호 }} 형식에 문제가 있습니다.

 public static PostDetailDTO from(Post post, List<String> skills, List<MatchedUserDTO> matchedUsers) {
     return new PostDetailDTO(
             post.getId(),
             post.getTitle(),
             post.getContent(),
             post.getHeadCount(),
             post.getImage(),
             post.getRequirementPersonality(),
             post.getEndedAt(),
-            post.getCategory().toString(),
-            post.getType().toString(),
+            post.getCategory().name(),  // toString() 대신 name() 사용
+            post.getType().name(),      // toString() 대신 name() 사용
             skills,
             matchedUsers
     );
-}}
+  }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static PostDetailDTO from(Post post, List<String> skills, List<MatchedUserDTO> matchedUsers) {
return new PostDetailDTO(
post.getId(),
post.getTitle(),
post.getContent(),
post.getHeadCount(),
post.getImage(),
post.getRequirementPersonality(),
post.getEndedAt(),
post.getCategory().toString(),
post.getType().toString(),
skills,
matchedUsers
);
}}
public static PostDetailDTO from(Post post, List<String> skills, List<MatchedUserDTO> matchedUsers) {
return new PostDetailDTO(
post.getId(),
post.getTitle(),
post.getContent(),
post.getHeadCount(),
post.getImage(),
post.getRequirementPersonality(),
post.getEndedAt(),
post.getCategory().name(), // toString() 대신 name() 사용
post.getType().name(), // toString() 대신 name() 사용
skills,
matchedUsers
);
}
}

37 changes: 37 additions & 0 deletions src/main/java/aibe/hosik/post/dto/PostRequestDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package aibe.hosik.post.dto;

import aibe.hosik.post.entity.Post;
import aibe.hosik.post.entity.PostCategory;
import aibe.hosik.post.entity.PostType;
import aibe.hosik.user.User;

import java.time.LocalDate;
import java.util.List;

public record PostRequestDTO(
String title,
String content,
Integer headCount,
String image,
String requirementPersonality,
LocalDate endedAt,

PostCategory category,
PostType type,

List<String> skills
) {
Comment on lines +11 to +23
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

필드 유효성 검증 추가 필요

현재 PostRequestDTO의 필드들에 대한 유효성 검증이 없습니다. 예를 들면 title이나 content가 비어있거나, headCount가 음수인 경우 등 유효하지 않은 입력이 들어올 수 있습니다. Bean Validation 애노테이션(@NotBlank, @Size, @Min 등)을 사용하여 유효성 검증을 추가하는 것이 좋습니다.

아래와 같이 수정하는 것을 권장합니다:

 public record PostRequestDTO(
+        @NotBlank(message = "제목은 필수 입력값입니다")
+        @Size(max = 100, message = "제목은 100자 이내로 입력해주세요")
         String title,
+        @NotBlank(message = "내용은 필수 입력값입니다")
         String content,
+        @Min(value = 1, message = "인원 수는 최소 1명 이상이어야 합니다")
         Integer headCount,
         String image,
         String requirementPersonality,
+        @NotNull(message = "모집 종료일은 필수 입력값입니다")
+        @Future(message = "모집 종료일은 현재 이후의 날짜여야 합니다")
         LocalDate endedAt,

+        @NotNull(message = "카테고리는 필수 입력값입니다")
         PostCategory category,
+        @NotNull(message = "타입은 필수 입력값입니다")
         PostType type,

+        @NotEmpty(message = "최소 하나 이상의 스킬이 필요합니다")
         List<String> skills
 ) {

Bean Validation을 위해 아래 의존성 추가가 필요합니다:

import jakarta.validation.constraints.*;

public Post toEntity(User user) {
return Post.builder()
.title(title())
.content(content())
.headCount(headCount())
.image(image())
.requirementPersonality(requirementPersonality())
.endedAt(endedAt())
.category(category())
.type(type())
.user(user)
.build();
}
Comment on lines +24 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

toEntity 메서드에 Null 체크 추가 필요

toEntity 메서드에서 null 값을 가진 필드들에 대한 처리가 없습니다. 유효성 검증을 추가하더라도 런타임에서 예상치 못한 NullPointerException이 발생할 수 있습니다.

다음과 같이 빌더 패턴에서 null 체크 로직을 추가하는 것이 좋습니다:

 public Post toEntity(User user) {
+    if (user == null) {
+        throw new IllegalArgumentException("사용자 정보는 필수입니다");
+    }
     return Post.builder()
             .title(title())
             .content(content())
             .headCount(headCount())
             .image(image())
             .requirementPersonality(requirementPersonality())
             .endedAt(endedAt())
             .category(category())
             .type(type())
             .user(user)
             .build();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Post toEntity(User user) {
return Post.builder()
.title(title())
.content(content())
.headCount(headCount())
.image(image())
.requirementPersonality(requirementPersonality())
.endedAt(endedAt())
.category(category())
.type(type())
.user(user)
.build();
}
public Post toEntity(User user) {
if (user == null) {
throw new IllegalArgumentException("사용자 정보는 필수입니다");
}
return Post.builder()
.title(title())
.content(content())
.headCount(headCount())
.image(image())
.requirementPersonality(requirementPersonality())
.endedAt(endedAt())
.category(category())
.type(type())
.user(user)
.build();
}

}
29 changes: 29 additions & 0 deletions src/main/java/aibe/hosik/post/dto/PostResponseDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package aibe.hosik.post.dto;

import aibe.hosik.post.entity.Post;

import java.util.List;

public record PostResponseDTO(
Long id,
String image,
String title,
String content,
String category,
List<String> skills,
Integer headCount,
Integer currentCount
) {

public static PostResponseDTO from(Post post,List<String> skills, Integer currentCount) {
return new PostResponseDTO(post.getId(),
post.getImage(),
post.getTitle(),
post.getContent(),
post.getCategory().toString(),
skills,
post.getHeadCount(),
currentCount
);
}
Comment on lines +18 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

문법 및 Enum 처리 개선 필요

  1. 메서드 인자 사이에 공백이 누락되었습니다 (Post post,List<String>Post post, List<String>).
  2. Enum 값을 처리할 때 toString() 대신 name()을 사용하는 것이 좋습니다.

다음과 같이 수정하는 것을 권장합니다:

-    public static PostResponseDTO from(Post post,List<String> skills, Integer currentCount) {
+    public static PostResponseDTO from(Post post, List<String> skills, Integer currentCount) {
         return new PostResponseDTO(post.getId(),
                 post.getImage(),
                 post.getTitle(),
                 post.getContent(),
-                post.getCategory().toString(),
+                post.getCategory().name(),
                 skills,
                 post.getHeadCount(),
                 currentCount
         );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static PostResponseDTO from(Post post,List<String> skills, Integer currentCount) {
return new PostResponseDTO(post.getId(),
post.getImage(),
post.getTitle(),
post.getContent(),
post.getCategory().toString(),
skills,
post.getHeadCount(),
currentCount
);
}
public static PostResponseDTO from(Post post, List<String> skills, Integer currentCount) {
return new PostResponseDTO(post.getId(),
post.getImage(),
post.getTitle(),
post.getContent(),
post.getCategory().name(),
skills,
post.getHeadCount(),
currentCount
);
}

}
11 changes: 11 additions & 0 deletions src/main/java/aibe/hosik/post/entity/Post.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package aibe.hosik.post.entity;

import aibe.hosik.common.TimeEntity;
import aibe.hosik.skill.entity.PostSkill;
import aibe.hosik.user.User;
import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

@Entity
@NoArgsConstructor
Expand Down Expand Up @@ -34,6 +37,9 @@ public class Post extends TimeEntity {
@Column
private String image;

@Column
private String requirementPersonality;

Comment on lines +40 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

requirementPersonality 필드에 유효성 제약조건 추가 필요

requirementPersonality 필드에 유효성 제약조건이 없습니다. 이 필드에 너무 긴 텍스트가 저장될 경우 데이터베이스 문제가 발생할 수 있습니다. @Size 애노테이션을 사용하여 길이 제한을 추가하는 것이 좋습니다.

-  @Column
+  @Column(length = 500)
+  @Size(max = 500)
   private String requirementPersonality;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Column
private String requirementPersonality;
@Column(length = 500)
@Size(max = 500)
private String requirementPersonality;

@Column(nullable = false)
private LocalDate endedAt;

Expand All @@ -47,4 +53,9 @@ public class Post extends TimeEntity {

@ManyToOne(fetch = FetchType.LAZY)
private User user;

// 양방향 매핑
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PostSkill> postSkills = new ArrayList<>();
Comment on lines +57 to +60
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

양방향 매핑에 대한 헬퍼 메서드 추가 필요

양방향 매핑을 사용할 때는 양쪽에서 관계를 설정하는 헬퍼 메서드를 추가하는 것이 좋습니다. 현재는 관계 설정 책임이 서비스 계층에 있어서 일관성 문제가 발생할 수 있습니다.

포스트와 스킬 간의 양방향 관계를 관리하는 헬퍼 메서드를 추가합니다:

   @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
   @Builder.Default
   private List<PostSkill> postSkills = new ArrayList<>();
+  
+  // 양방향 관계 유지를 위한 헬퍼 메서드
+  public void addPostSkill(PostSkill postSkill) {
+    this.postSkills.add(postSkill);
+    if (postSkill.getPost() != this) {
+      postSkill.setPost(this);
+    }
+  }
+  
+  public void removePostSkill(PostSkill postSkill) {
+    this.postSkills.remove(postSkill);
+    if (postSkill.getPost() == this) {
+      postSkill.setPost(null);
+    }
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 양방향 매핑
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PostSkill> postSkills = new ArrayList<>();
// 양방향 매핑
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PostSkill> postSkills = new ArrayList<>();
// 양방향 관계 유지를 위한 헬퍼 메서드
public void addPostSkill(PostSkill postSkill) {
this.postSkills.add(postSkill);
if (postSkill.getPost() != this) {
postSkill.setPost(this);
}
}
public void removePostSkill(PostSkill postSkill) {
this.postSkills.remove(postSkill);
if (postSkill.getPost() == this) {
postSkill.setPost(null);
}
}

}
23 changes: 23 additions & 0 deletions src/main/java/aibe/hosik/post/repository/PostRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package aibe.hosik.post.repository;

import aibe.hosik.post.entity.Post;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

public interface PostRepository extends JpaRepository<Post, Long> {
// Post 조회 시 postSkills, skill 엔티티 즉시 로딩 지정
//@EntityGraph(attributePaths = {"postSkills", "postSkills.skill"})
@Query("SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.postSkills ps LEFT JOIN FETCH ps.skill")
List<Post> findAllWithSkills();

// PostDetail 조회 시 즉시 로딩 지정
//@EntityGraph(attributePaths = {"postSkills", "postSkills.skill"})
@Query("SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.postSkills ps LEFT JOIN FETCH ps.skill WHERE p.id = :id")
Optional<Post> findByIdWithSkills(@Param("id") Long id);
Comment on lines +19 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

페이징 처리 및 주석 개선 필요

게시글 조회 시 페이징 처리가 구현되어 있지 않습니다. 데이터가 많아질 경우 성능 이슈가 발생할 수 있습니다.

또한 메서드 주석을 추가하여 메서드의 목적과 사용법을 명확히 설명하는 것이 좋습니다.

-    // PostDetail 조회 시 즉시 로딩 지정
+    /**
+     * 특정 ID의 게시글을 연관된 스킬과 함께 조회합니다.
+     * JPQL의 LEFT JOIN FETCH를 사용하여 N+1 문제를 방지합니다.
+     * @param id 조회할 게시글 ID
+     * @return 스킬 정보가 포함된 게시글 (Optional)
+     */
     //@EntityGraph(attributePaths = {"postSkills", "postSkills.skill"})
     @Query("SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.postSkills ps LEFT JOIN FETCH ps.skill WHERE p.id = :id")
     Optional<Post> findByIdWithSkills(@Param("id") Long id);
+
+    /**
+     * 모든 게시글을 페이징하여 조회합니다.
+     * @param pageable 페이징 정보
+     * @return 페이징된 게시글 목록
+     */
+    @Query(value = "SELECT DISTINCT p FROM Post p",
+           countQuery = "SELECT COUNT(p) FROM Post p")
+    Page<Post> findAllWithPaging(Pageable pageable);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// PostDetail 조회 시 즉시 로딩 지정
//@EntityGraph(attributePaths = {"postSkills", "postSkills.skill"})
@Query("SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.postSkills ps LEFT JOIN FETCH ps.skill WHERE p.id = :id")
Optional<Post> findByIdWithSkills(@Param("id") Long id);
/**
* 특정 ID의 게시글을 연관된 스킬과 함께 조회합니다.
* JPQL의 LEFT JOIN FETCH를 사용하여 N+1 문제를 방지합니다.
* @param id 조회할 게시글 ID
* @return 스킬 정보가 포함된 게시글 (Optional)
*/
//@EntityGraph(attributePaths = {"postSkills", "postSkills.skill"})
@Query("SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.postSkills ps LEFT JOIN FETCH ps.skill WHERE p.id = :id")
Optional<Post> findByIdWithSkills(@Param("id") Long id);
/**
* 모든 게시글을 페이징하여 조회합니다.
* @param pageable 페이징 정보
* @return 페이징된 게시글 목록
*/
@Query(value = "SELECT DISTINCT p FROM Post p",
countQuery = "SELECT COUNT(p) FROM Post p")
Page<Post> findAllWithPaging(Pageable pageable);

}
15 changes: 15 additions & 0 deletions src/main/java/aibe/hosik/post/service/PostService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package aibe.hosik.post.service;

import aibe.hosik.post.dto.PostDetailDTO;
import aibe.hosik.post.dto.PostRequestDTO;
import aibe.hosik.post.dto.PostResponseDTO;
import aibe.hosik.post.entity.Post;
import aibe.hosik.user.User;

import java.util.List;

public interface PostService {
List<PostResponseDTO> getAllPosts();
Post createPost(PostRequestDTO dto, User user);
PostDetailDTO getPostDetail(Long postId);
}
Loading