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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## 게시판 만들기
- 혼자 공부하면서 백엔드 개발 중
- 차츰차츰 여러 기능을 추가하고 있는중
# 프로젝트

- 개인 프로젝트
- spring으로 게시판 만들기
- 새로운 기술 적용해 계속 발전시키는 중
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,64 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/boards/{boardId}")
@RequestMapping("/api/boards")
public class LikeController {

private final LikeService likeService;

@PostMapping("/like")
@PostMapping("/{boardId}/like")
public ResponseEntity<ApiResponse<LikeDto>> like(
@PathVariable Long boardId,
Authentication auth){


String userId = auth.getName();
boolean isLiked = likeService.isLikeByUserId(boardId, userId);
String message = isLiked ? "좋아요 누름" : "좋아요 취소";

boolean isliked = likeService.toggleLike(boardId, userId);

String message;
if(isliked){
message = "좋아요를 눌렀습니다.";
}else{
message = "좋아요를 취소했습니다.";
}
return ResponseEntity.ok(ApiResponse.success(message,null));
}

@GetMapping("/like/count")
@GetMapping("/{boardId}/like/count")
public ResponseEntity<ApiResponse<Long>> getLikeCount(
@PathVariable Long boardId){

Long likeCount = likeService.getLikeCount(boardId);
return ResponseEntity.ok(ApiResponse.success("좋아요 개수 조회", likeCount));
}

@GetMapping("/like/users")
@GetMapping("/like/{boardId}")
public ResponseEntity<ApiResponse<Page<LikeDto>>> getLikeUsers(
@PathVariable Long boardId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20")int size,
Authentication auth){


@RequestParam(defaultValue = "20")int size){
Pageable pageable = PageRequest.of(page, size);
Page<LikeDto> users = likeService.getLikeUsers(boardId, pageable);

return ResponseEntity.ok(ApiResponse.success("좋아요 누른 사람 목록 조회", users));

}

@GetMapping("/like/{userId}")
public ResponseEntity<ApiResponse<Page<LikeDto>>> getLikeBoards(
@PathVariable String userId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size){
Pageable pageable = PageRequest.of(page, size);
Page<LikeDto> userLikeBoards = likeService.getUserLikeBoards(userId, pageable);

return ResponseEntity.ok(ApiResponse.success("사용자가 좋아요 누른 게시글", userLikeBoards));
}
Comment on lines +45 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

/like/{boardId}/like/{userId}가 동일한 URL 패턴 → 애플리케이션 구동 불가

두 엔드포인트의 경로가 모두 GET /api/boards/like/{variable}로 동일합니다. Spring MVC는 경로 변수의 타입(Long vs String)으로 라우팅을 구분하지 않으며, 이 경우 IllegalStateException: Ambiguous handler methods 예외가 발생합니다. 애플리케이션이 정상적으로 시작되지 않습니다.

두 엔드포인트를 구분 가능한 경로로 분리하세요.

🐛 제안 수정
-    `@GetMapping`("/like/{boardId}")
+    `@GetMapping`("/{boardId}/like/users")
     public ResponseEntity<ApiResponse<Page<LikeDto>>> getLikeUsers(
             `@PathVariable` Long boardId,
             `@RequestParam`(defaultValue = "0") int page,
-            `@RequestParam`(defaultValue = "20")int size){
+            `@RequestParam`(defaultValue = "20") int size){
         Pageable pageable = PageRequest.of(page, size);
         Page<LikeDto> users = likeService.getLikeUsers(boardId, pageable);
         return ResponseEntity.ok(ApiResponse.success("좋아요 누른 사람 목록 조회", users));
     }

-    `@GetMapping`("/like/{userId}")
+    `@GetMapping`("/users/{userId}/like")
     public ResponseEntity<ApiResponse<Page<LikeDto>>> getLikeBoards(
             `@PathVariable` String userId,
             `@RequestParam`(defaultValue = "0") int page,
             `@RequestParam`(defaultValue = "20") int size){
         Pageable pageable = PageRequest.of(page, size);
         Page<LikeDto> userLikeBoards = likeService.getUserLikeBoards(userId, pageable);
         return ResponseEntity.ok(ApiResponse.success("사용자가 좋아요 누른 게시글", userLikeBoards));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/kms/springboard/like/controller/LikeController.java` around
lines 45 - 66, The two controller methods getLikeUsers and getLikeBoards
currently share the same GET mapping pattern ("/like/{...}") causing ambiguous
handler errors; change the mappings to distinct paths (e.g.,
`@GetMapping`("/like/board/{boardId}") for getLikeUsers and
`@GetMapping`("/like/user/{userId}") for getLikeBoards), update any clients/tests
to call the new endpoints, and keep the existing method signatures and calls to
likeService.getLikeUsers(...) and likeService.getUserLikeBoards(...) unchanged.





}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import com.kms.springboard.like.entity.LikeEntity;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
public interface LikeService {
boolean toggleLike(Long boardId, String userId);
Long getLikeCount(Long boardId);
boolean isLikeByUserId(Long boardId, String userId);
Page<LikeDto> getLikeUsers(Long boardId, Pageable pageable);
Page<LikeDto> getUserLikeBoards(String userId, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public class LikeServiceImpl implements LikeService {

@Override
public boolean toggleLike(Long boardId, String userId) {

boolean exists = likeRepository.existsByBoardIdAndUserId(boardId, userId);

if (exists) {
Expand All @@ -63,20 +62,6 @@ public Long getLikeCount(Long boardId) {
return count;
}

@Override
public boolean isLikeByUserId(Long boardId, String userId) {
String cacheKey = USER_LIKED_PREFIX + boardId + ":" + userId;
String cached = redisTemplate.opsForValue().get(cacheKey);

if(cached != null) {
return Boolean.parseBoolean(cached);
}
boolean isLiked = likeRepository.existsByBoardIdAndUserId(boardId, userId);
cacheUserLiked(boardId,userId,isLiked);

return isLiked;
}

@Override
@Transactional(readOnly = true)
public Page<LikeDto> getLikeUsers(Long boardId, Pageable pageable) {
Expand All @@ -89,6 +74,7 @@ public Page<LikeDto> getUserLikeBoards(String userId, Pageable pageable) {
Page<LikeEntity> likes = likeRepository.findByUserId(userId,pageable);
return likes.map(LikeDto::convertToDto);
}

private void addLike(Long boardId, String userId) {
BoardEntity board = boardRepository.findById(boardId)
.orElseThrow(() -> new EntityNotFoundException("게시글을 찾을 수 없습니다"));
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/kms/springboard/member/dto/MemberDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public class MemberDto {
@NotBlank(message = "이름을 입력해주세요")
private String username;

@NotBlank(message = "닉네임을 입력해주세요")
private String nickname;
Comment on lines +25 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

nickname 필드에 @Size 제약 누락

userId, email, password에는 모두 @Size 최댓값이 지정되어 있지만 nickname에는 없습니다. 대응하는 엔티티 컬럼에도 length 지정이 없어, 비정상적으로 긴 문자열이 입력될 수 있습니다.

✏️ 제안 수정
-    `@NotBlank`(message = "닉네임을 입력해주세요")
+    `@NotBlank`(message = "닉네임을 입력해주세요")
+    `@Size`(min = 2, max = 30, message = "닉네임은 2~30자여야 합니다")
     private String nickname;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/kms/springboard/member/dto/MemberDto.java` around lines 25
- 26, The MemberDto.nickname field is missing a `@Size`(max=...) constraint and
the corresponding entity column has no length limit, allowing excessively long
nicknames; add a `@Size`(max=XX, message="...") annotation to the nickname field
in MemberDto (choose the same max as other fields or a sensible limit like 30),
and update the corresponding Member entity's nickname column annotation (e.g.,
`@Column`(length=XX)) to enforce the same maximum at the DB/model level so DTO
validation and the entity schema match.


@NotBlank(message = "이메일을 입력해주세요")
@Email(message = "이메일 형식이 올바르지 않습니다")
@Size(max = 255, message = "이메일은 255자 이하여야 합니다")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
public class MemberEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "memberId")
@Column(name = "member_id")
private Long id;

@Column(name = "username", nullable = false, length = 100)
private String username;

@Column(name = "userId", nullable = false, length = 50)
@Column(name = "user_id", nullable = false, length = 50)
private String userId;

@Column(name = "password", nullable = false)
Expand All @@ -39,6 +39,9 @@ public class MemberEntity {
@Column(name = "email" ,nullable = false, length = 255)
private String email;

@Column(name = "nickname",nullable = false, unique = true)
private String nickname;




Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public MemberEntity saveDto(MemberDto memberDto) {
MemberEntity member = MemberEntity.builder()
.userId(normalizedUserId)
.username(memberDto.getUsername())
.nickname(memberDto.getNickname())
.email(normalizedEmail)
.password(encoded)
.build();
Expand Down