-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/sw 61 폴더 CRUD 로직 마무리 작업 #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
8807b54
16ebb72
775f956
01b8066
073318e
4e8d333
dbd99d5
b9f26d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package com.web.SearchWeb.config; | ||
| package com.web.SearchWeb.config.exception; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.web.SearchWeb.config.security; | ||
|
|
||
| import com.web.SearchWeb.config.exception.BusinessException; | ||
| import com.web.SearchWeb.config.exception.CommonErrorCode; | ||
| import com.web.SearchWeb.member.dto.CustomOAuth2User; | ||
| import com.web.SearchWeb.member.dto.CustomUserDetails; | ||
| import org.springframework.security.core.Authentication; | ||
|
|
||
| public final class SecurityUtils { | ||
|
|
||
| private SecurityUtils() { | ||
| } | ||
|
|
||
| public static Long extractMemberId(Authentication authentication) { | ||
| if (authentication == null || !authentication.isAuthenticated()) { | ||
| throw BusinessException.from(CommonErrorCode.UNAUTHORIZED); | ||
| } | ||
|
|
||
| Object principal = authentication.getPrincipal(); | ||
|
|
||
| if ("anonymousUser".equals(principal)) { | ||
| throw BusinessException.from(CommonErrorCode.UNAUTHORIZED); | ||
| } | ||
|
|
||
| if (principal instanceof CustomUserDetails userDetails) { | ||
| return userDetails.getMemberId(); | ||
| } | ||
|
|
||
| if (principal instanceof CustomOAuth2User oauth2User) { | ||
| return oauth2User.getMemberId(); | ||
| } | ||
|
|
||
| throw BusinessException.from(CommonErrorCode.UNAUTHORIZED); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,53 @@ | ||
| package com.web.SearchWeb.folder.controller.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.Positive; | ||
| import jakarta.validation.constraints.Size; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| public class MemberFolderRequests { | ||
|
|
||
| /** | ||
| * 폴더 생성 요청 | ||
| */ | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| public static class Create { | ||
| public Long ownerMemberId; | ||
| public Long parentFolderId; // null이면 루트 | ||
| // null이면 루트 폴더 | ||
| @Positive(message = "parentFolderId는 양수여야 합니다.") | ||
| public Long parentFolderId; | ||
|
|
||
| @NotBlank(message = "folderName은 비어 있을 수 없습니다.") | ||
| @Size(max = 50, message = "folderName은 최대 50자까지 가능합니다.") | ||
| public String folderName; | ||
|
|
||
| @Size(max = 200, message = "description은 최대 200자까지 가능합니다.") | ||
| public String description; | ||
|
Comment on lines
17
to
27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 요청 DTO 필드는
♻️ 제안 수정 public static class Create {
// null이면 루트 폴더
`@Positive`(message = "parentFolderId는 양수여야 합니다.")
- public Long parentFolderId;
+ private Long parentFolderId;
`@NotBlank`(message = "folderName은 비어 있을 수 없습니다.")
`@Size`(max = 50, message = "folderName은 최대 50자까지 가능합니다.")
- public String folderName;
+ private String folderName;
`@Size`(max = 200, message = "description은 최대 200자까지 가능합니다.")
- public String description;
+ private String description;
}
@@
public static class Update {
`@Size`(max = 50, message = "folderName은 최대 50자까지 가능합니다.")
- public String folderName;
+ private String folderName;
`@Size`(max = 200, message = "description은 최대 200자까지 가능합니다.")
- public String description;
+ private String description;
}
@@
public static class Move {
// null이면 루트로 이동
`@Positive`(message = "newParentFolderId는 양수여야 합니다.")
- public Long newParentFolderId;
+ private Long newParentFolderId;
}Also applies to: 35-40, 48-51 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| /** | ||
| * 폴더 수정 요청 | ||
| */ | ||
| @Getter | ||
| @NoArgsConstructor | ||
| public static class Update { | ||
| @Size(max = 50, message = "folderName은 최대 50자까지 가능합니다.") | ||
| public String folderName; | ||
|
|
||
| @Size(max = 200, message = "description은 최대 200자까지 가능합니다.") | ||
| public String description; | ||
| } | ||
|
|
||
| /** | ||
| * 폴더 이동 요청 | ||
| */ | ||
| @Getter | ||
| @NoArgsConstructor | ||
| public static class Move { | ||
| public Long newParentFolderId; // null이면 루트로 이동 | ||
| // null이면 루트로 이동 | ||
| @Positive(message = "newParentFolderId는 양수여야 합니다.") | ||
| public Long newParentFolderId; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||
| package com.web.SearchWeb.folder.error; | ||||||
|
|
||||||
| import com.web.SearchWeb.config.ErrorCode; | ||||||
| import com.web.SearchWeb.config.exception.ErrorCode; | ||||||
| import lombok.Getter; | ||||||
| import lombok.RequiredArgsConstructor; | ||||||
| import org.springframework.http.HttpStatus; | ||||||
|
|
@@ -9,7 +9,11 @@ | |||||
| @RequiredArgsConstructor | ||||||
| public enum FolderErrorCode implements ErrorCode { | ||||||
| FOLDER_NOT_FOUND(HttpStatus.NOT_FOUND, "F001", "폴더를 찾을 수 없습니다."), | ||||||
| DUPLICATE_FOLDER_NAME(HttpStatus.BAD_REQUEST, "F002", "이미 존재하는 폴더명입니다."); | ||||||
| DUPLICATE_FOLDER_NAME(HttpStatus.BAD_REQUEST, "F002", "이미 존재하는 폴더명입니다."), | ||||||
| FOLDER_FORBIDDEN(HttpStatus.FORBIDDEN,"Foo3" ,"접근이 제한된 폴더입니다." ), | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 에러 코드 값 오타를 수정하세요.
🐛 제안 수정- FOLDER_FORBIDDEN(HttpStatus.FORBIDDEN,"Foo3" ,"접근이 제한된 폴더입니다." ),
+ FOLDER_FORBIDDEN(HttpStatus.FORBIDDEN, "F003", "접근이 제한된 폴더입니다."),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| INVALID_FOLDER_NAME(HttpStatus.BAD_REQUEST,"F004","폴더명으로 적절하지 않습니다" ), | ||||||
| INVALID_FOLDER_MOVE(HttpStatus.BAD_REQUEST, "F005", "유효하지 않은 폴더 이동입니다."), | ||||||
| FOLDER_NOT_EMPTY(HttpStatus.BAD_REQUEST, "F006", "하위 폴더 또는 북마크가 남아 있어 삭제할 수 없습니다."); | ||||||
|
|
||||||
| private final HttpStatus status; | ||||||
| private final String code; | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,13 @@ | ||
| package com.web.SearchWeb.folder.error; | ||
|
|
||
| import com.web.SearchWeb.config.BusinessException; | ||
| import com.web.SearchWeb.config.ErrorCode; | ||
| import com.web.SearchWeb.config.exception.BusinessException; | ||
| import com.web.SearchWeb.config.exception.ErrorCode; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class FolderException extends BusinessException { | ||
|
|
||
| private FolderException(ErrorCode errorCode) { | ||
| public FolderException(ErrorCode errorCode) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
지금 시그니처면 폴더 도메인 예외에 다른 도메인의 ♻️ 제안 수정-import com.web.SearchWeb.config.exception.ErrorCode;
import lombok.Getter;
`@Getter`
public class FolderException extends BusinessException {
- public FolderException(ErrorCode errorCode) {
+ public FolderException(FolderErrorCode errorCode) {
super(errorCode);
}
}🤖 Prompt for AI Agents |
||
| super(errorCode); | ||
| } | ||
|
|
||
| public static class NotFound extends FolderException { | ||
| public NotFound() { | ||
| super(FolderErrorCode.FOLDER_NOT_FOUND); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,17 +6,17 @@ | |
|
|
||
| public interface MemberFolderService { | ||
|
|
||
| Long create(Long ownerMemberId, Long parentFolderId, String folderName, String description); | ||
| Long create(Long loginId, Long parentFolderId, String folderName, String description); | ||
|
|
||
| MemberFolder get(Long memberFolderId); | ||
| MemberFolder get(Long loginId, Long memberFolderId); | ||
|
|
||
| List<MemberFolder> listRootFolders(Long ownerMemberId); | ||
| List<MemberFolder> listRootFolders(Long loginId, Long ownerMemberId); | ||
|
|
||
| List<MemberFolder> listChildren(Long ownerMemberId, Long parentFolderId); | ||
| List<MemberFolder> listChildren(Long loginId, Long ownerMemberId, Long parentFolderId); | ||
|
Comment on lines
+13
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 🧩 Analysis chain🏁 Script executed: #!/bin/bash
fd 'MemberFolder(Controller|ServiceImpl)\.java$' src/main/java -x sh -c '
echo "== $1 ==";
rg -n -C3 "listRootFolders|listChildren|ownerMemberId|loginId" "$1";
' sh {}Repository: Searchweb-Dev/Searchweb-Back Length of output: 8312 조회 API의
다만 API 설계상 🤖 Prompt for AI Agents |
||
|
|
||
| void update(Long memberFolderId, String folderName, String description); | ||
| void update(Long loginId, Long memberFolderId, String folderName, String description); | ||
|
|
||
| void move(Long memberFolderId, Long newParentFolderId); | ||
| void move(Long loginId, Long memberFolderId, Long newParentFolderId); | ||
|
|
||
| void delete(Long memberFolderId); | ||
| void delete(Long loginId, Long memberFolderId); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
memberId가 null인 경우도 여기서 차단하세요.CustomUserDetails#getMemberId()와CustomOAuth2User#getMemberId()가 값을 그대로 반환하므로, 인증 객체가 만들어졌더라도memberId가 null이면 이후 권한 체크가 500으로 깨질 수 있습니다. 이 메서드에서 fail-closed로UNAUTHORIZED를 던지는 편이 안전합니다.🔒 제안 수정
if (principal instanceof CustomUserDetails userDetails) { - return userDetails.getMemberId(); + Long memberId = userDetails.getMemberId(); + if (memberId != null) { + return memberId; + } + throw BusinessException.from(CommonErrorCode.UNAUTHORIZED); } if (principal instanceof CustomOAuth2User oauth2User) { - return oauth2User.getMemberId(); + Long memberId = oauth2User.getMemberId(); + if (memberId != null) { + return memberId; + } + throw BusinessException.from(CommonErrorCode.UNAUTHORIZED); }📝 Committable suggestion
🤖 Prompt for AI Agents