-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/sw 48 Tag 기능 구현 #30
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
Changes from all commits
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 | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,70 @@ | ||||||||||||||||||||||||||
| package com.web.SearchWeb.tag.controller; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import com.web.SearchWeb.config.ApiResponse; | ||||||||||||||||||||||||||
| import com.web.SearchWeb.tag.controller.dto.MemberTagDto; | ||||||||||||||||||||||||||
| import com.web.SearchWeb.tag.domain.MemberTag; | ||||||||||||||||||||||||||
| import com.web.SearchWeb.tag.service.MemberTagService; | ||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||||||||||||||
| import org.springframework.http.ResponseEntity; | ||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.DeleteMapping; | ||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.PathVariable; | ||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.PutMapping; | ||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.RestController; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @RestController | ||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||
| @RequestMapping("/api/tags") | ||||||||||||||||||||||||||
| public class MemberTagController { | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| private final MemberTagService memberTagService; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 생성 | ||||||||||||||||||||||||||
| @PostMapping | ||||||||||||||||||||||||||
| public ResponseEntity<ApiResponse<Long>> create(@RequestBody MemberTagDto.CreateRequest req) { | ||||||||||||||||||||||||||
| Long tagId = memberTagService.create(req.getOwnerMemberId(), req.getTagName()); | ||||||||||||||||||||||||||
| return ResponseEntity | ||||||||||||||||||||||||||
| .status(HttpStatus.CREATED) | ||||||||||||||||||||||||||
| .body(ApiResponse.success(tagId)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+28
to
+35
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. 보안 위험: 현재 PR 설명에 "추가 정제가 필요하다"고 되어 있으므로 향후 구현 계획이 있을 수 있지만, 현재 상태로 배포되면 IDOR(Insecure Direct Object Reference) 취약점이 됩니다. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 단건 조회 | ||||||||||||||||||||||||||
| @GetMapping("/{tagId}") | ||||||||||||||||||||||||||
| public ResponseEntity<ApiResponse<MemberTagDto.Response>> get(@PathVariable Long tagId) { | ||||||||||||||||||||||||||
| MemberTag tag = memberTagService.get(tagId); | ||||||||||||||||||||||||||
| return ResponseEntity.ok(ApiResponse.success(MemberTagDto.Response.from(tag))); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 목록 조회 | ||||||||||||||||||||||||||
| @GetMapping("/owners/{ownerMemberId}") | ||||||||||||||||||||||||||
| public ResponseEntity<ApiResponse<List<MemberTagDto.Response>>> list(@PathVariable Long ownerMemberId) { | ||||||||||||||||||||||||||
| List<MemberTagDto.Response> responses = memberTagService.listByOwner(ownerMemberId) | ||||||||||||||||||||||||||
| .stream() | ||||||||||||||||||||||||||
| .map(MemberTagDto.Response::from) | ||||||||||||||||||||||||||
| .collect(Collectors.toList()); | ||||||||||||||||||||||||||
| return ResponseEntity.ok(ApiResponse.success(responses)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 수정 | ||||||||||||||||||||||||||
| @PutMapping("/{tagId}") | ||||||||||||||||||||||||||
| public ResponseEntity<ApiResponse<Void>> update( | ||||||||||||||||||||||||||
| @PathVariable Long tagId, | ||||||||||||||||||||||||||
| @RequestBody MemberTagDto.UpdateRequest req | ||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||
| memberTagService.update(tagId, req.getTagName()); | ||||||||||||||||||||||||||
| return ResponseEntity.ok(ApiResponse.success(null)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 삭제 | ||||||||||||||||||||||||||
| @DeleteMapping("/{tagId}") | ||||||||||||||||||||||||||
| public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long tagId) { | ||||||||||||||||||||||||||
| memberTagService.delete(tagId); | ||||||||||||||||||||||||||
| return ResponseEntity.ok(ApiResponse.success(null)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+64
to
+69
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 삭제 응답으로 삭제 성공 시 반환할 데이터가 없으므로 ♻️ 수정 제안 `@DeleteMapping`("/{tagId}")
-public ResponseEntity<ApiResponse<Void>> delete(`@PathVariable` Long tagId) {
+public ResponseEntity<Void> delete(`@PathVariable` Long tagId) {
memberTagService.delete(tagId);
- return ResponseEntity.ok(ApiResponse.success(null));
+ return ResponseEntity.noContent().build();
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.web.SearchWeb.tag.controller.dto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.web.SearchWeb.tag.domain.MemberTag; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.AccessLevel; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Builder; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.NoArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class MemberTagDto { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static class CreateRequest { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long ownerMemberId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String tagName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static class UpdateRequest { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String tagName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+22
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. 🛠️ Refactor suggestion | 🟠 Major 요청 DTO에 Bean Validation 어노테이션 추가 권장
♻️ 수정 제안+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
`@Getter`
`@NoArgsConstructor`
public static class CreateRequest {
+ `@NotNull`
private Long ownerMemberId;
+ `@NotBlank`
private String tagName;
}
`@Getter`
`@NoArgsConstructor`
public static class UpdateRequest {
+ `@NotBlank`
private String tagName;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static class Response { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long tagId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long ownerMemberId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String tagName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Builder | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Response(Long tagId, Long ownerMemberId, String tagName) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.tagId = tagId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.ownerMemberId = ownerMemberId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.tagName = tagName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static Response from(MemberTag tag) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response.builder() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .tagId(tag.getMemberTagId()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .ownerMemberId(tag.getOwnerMemberId()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .tagName(tag.getTagName()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.web.SearchWeb.tag.dao; | ||
|
|
||
| import com.web.SearchWeb.tag.domain.MemberTag; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface MemberTagJpaDao extends JpaRepository<MemberTag, Long> { | ||
|
|
||
| List<MemberTag> findAllByOwnerMemberId(Long ownerMemberId); | ||
|
|
||
| Optional<MemberTag> findByOwnerMemberIdAndTagName(Long ownerMemberId, String tagName); | ||
|
|
||
| boolean existsByOwnerMemberIdAndTagName(Long ownerMemberId, String tagName); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.web.SearchWeb.tag.domain; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.Column; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.Entity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.GeneratedValue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.GenerationType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.Id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.Table; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.AccessLevel; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.AllArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Builder; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.NoArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @AllArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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
♻️ 수정 제안-@AllArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Builder | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Entity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Table(name = "member_tag") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class MemberTag { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(name = "member_tag_id") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long memberTagId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(name = "owner_member_id") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long ownerMemberId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(name = "tag_name") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String tagName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+29
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. 🛠️ Refactor suggestion | 🟠 Major
두 필드 모두 비즈니스 로직상 필수값이지만 JPA ♻️ 수정 제안- `@Column`(name = "owner_member_id")
+ `@Column`(name = "owner_member_id", nullable = false)
private Long ownerMemberId;
- `@Column`(name = "tag_name")
+ `@Column`(name = "tag_name", nullable = false, length = 50)
private String tagName;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void changeName(String tagName) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (tagName == null || tagName.isBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new IllegalArgumentException("tagName must not be blank"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.tagName = tagName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.web.SearchWeb.tag.error; | ||
|
|
||
| import com.web.SearchWeb.config.ErrorCode; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum TagErrorCode implements ErrorCode { | ||
| Tag_NOT_FOUND(HttpStatus.NOT_FOUND, "T001", "태그를 찾을 수 없습니다."); | ||
|
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. 🛠️ Refactor suggestion | 🟠 Major Enum 상수 네이밍 컨벤션 위반: Java enum 상수는 ♻️ 수정 제안- Tag_NOT_FOUND(HttpStatus.NOT_FOUND, "T001", "태그를 찾을 수 없습니다.");
+ TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "T001", "태그를 찾을 수 없습니다.");🤖 Prompt for AI Agents |
||
| private final HttpStatus status; | ||
| private final String code; | ||
| private final String message; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.web.SearchWeb.tag.error; | ||
|
|
||
| import com.web.SearchWeb.config.BusinessException; | ||
| import com.web.SearchWeb.config.ErrorCode; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class TagException extends BusinessException { | ||
|
|
||
| public TagException(ErrorCode errorCode) { | ||
| super(errorCode); | ||
| } | ||
| public static class NotFound extends TagException { | ||
| public NotFound() { | ||
| super(TagErrorCode.Tag_NOT_FOUND); | ||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.web.SearchWeb.tag.service; | ||
|
|
||
| import com.web.SearchWeb.tag.domain.MemberTag; | ||
| import java.util.List; | ||
|
|
||
| public interface MemberTagService { | ||
|
|
||
| Long create(Long ownerMemberId, String tagName); | ||
|
|
||
| MemberTag get(Long memberTagId); | ||
|
|
||
| List<MemberTag> listByOwner(Long ownerMemberId); | ||
|
|
||
| void update(Long memberTagId, String tagName); | ||
|
|
||
| void delete(Long memberTagId); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,72 @@ | ||||||||||||||||||||||||||||
| package com.web.SearchWeb.tag.service; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import com.web.SearchWeb.tag.dao.MemberTagJpaDao; | ||||||||||||||||||||||||||||
| import com.web.SearchWeb.tag.domain.MemberTag; | ||||||||||||||||||||||||||||
| import com.web.SearchWeb.tag.error.TagException; | ||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||
| public class MemberTagServiceImpl implements MemberTagService { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private final MemberTagJpaDao memberTagJpaDao; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||
| public Long create(Long ownerMemberId, String tagName) { | ||||||||||||||||||||||||||||
| validateTagName(tagName); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (memberTagJpaDao.existsByOwnerMemberIdAndTagName(ownerMemberId, tagName)) { | ||||||||||||||||||||||||||||
| throw new IllegalArgumentException("Tag already exists."); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| MemberTag tag = MemberTag.builder() | ||||||||||||||||||||||||||||
| .ownerMemberId(ownerMemberId) | ||||||||||||||||||||||||||||
| .tagName(tagName) | ||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return memberTagJpaDao.save(tag).getMemberTagId(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+32
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.
또한 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||
| @Transactional(readOnly = true) | ||||||||||||||||||||||||||||
| public MemberTag get(Long memberTagId) { | ||||||||||||||||||||||||||||
| return memberTagJpaDao.findById(memberTagId) | ||||||||||||||||||||||||||||
| .orElseThrow(TagException.NotFound::new); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||
| @Transactional(readOnly = true) | ||||||||||||||||||||||||||||
| public List<MemberTag> listByOwner(Long ownerMemberId) { | ||||||||||||||||||||||||||||
| return memberTagJpaDao.findAllByOwnerMemberId(ownerMemberId); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||
| public void update(Long memberTagId, String tagName) { | ||||||||||||||||||||||||||||
| validateTagName(tagName); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| MemberTag tag = memberTagJpaDao.findById(memberTagId) | ||||||||||||||||||||||||||||
| .orElseThrow(TagException.NotFound::new); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| tag.changeName(tagName); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||
| public void delete(Long memberTagId) { | ||||||||||||||||||||||||||||
| if (!memberTagJpaDao.existsById(memberTagId)) { | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| memberTagJpaDao.deleteById(memberTagId); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+58
to
+65
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: # First, let's check the file and surrounding context
cd /tmp && find . -type f -name "MemberTagServiceImpl.java" 2>/dev/null | head -5Repository: Searchweb-Dev/Searchweb-Back Length of output: 54 🌐 Web query:
💡 Result: In Spring Data JPA 3.x, the intended contract for
However, Spring Data JPA 3.0.0 (and early 3.0.x) did not follow that contract: That mismatch was tracked as a Spring Data JPA bug. [3] Community notes (and common guidance) indicate the behavior was fixed in Spring Data JPA 3.0.2+, so later 3.x lines (e.g., 3.1/3.2) should align with the “silently ignored” contract. [4] Sources: [1] 🏁 Script executed: # Check for build configuration to understand the Spring Data JPA version
find . -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" | head -5 | xargs cat 2>/dev/null | grep -i "spring-data-jpa\|spring.data" -A 2 -B 2 | head -40Repository: Searchweb-Dev/Searchweb-Back Length of output: 54 🏁 Script executed: # Search for build configuration files more comprehensively
find . -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" \) 2>/dev/nullRepository: Searchweb-Dev/Searchweb-Back Length of output: 86 🏁 Script executed: # Also check for the MemberTagServiceImpl file location
find . -type f -name "MemberTagServiceImpl.java" 2>/dev/nullRepository: Searchweb-Dev/Searchweb-Back Length of output: 143 🏁 Script executed: # Look for gradle or maven wrapper to understand project structure
ls -la | grep -E "gradlew|mvnw|pom|gradle"Repository: Searchweb-Dev/Searchweb-Back Length of output: 372 🏁 Script executed: # Check the build.gradle file for Spring Data JPA version
cat -n build.gradleRepository: Searchweb-Dev/Searchweb-Back Length of output: 2007 🏁 Script executed: # Also read the MemberTagServiceImpl file to see the complete context
cat -n ./src/main/java/com/web/SearchWeb/tag/service/MemberTagServiceImpl.javaRepository: Searchweb-Dev/Searchweb-Back Length of output: 2687 🏁 Script executed: # Search for test files related to MemberTagService
find . -type f -name "*MemberTagService*Test*.java" 2>/dev/nullRepository: Searchweb-Dev/Searchweb-Back Length of output: 54 🏁 Script executed: # Search for any tests in the test directory
find . -type f -path "*/test/*" -name "*MemberTag*" 2>/dev/nullRepository: Searchweb-Dev/Searchweb-Back Length of output: 54 🏁 Script executed: # Check if there's any interface definition that might explain the contract
find . -type f -name "MemberTagService.java" 2>/dev/nullRepository: Searchweb-Dev/Searchweb-Back Length of output: 139 🏁 Script executed: # Read the MemberTagService interface
cat -n ./src/main/java/com/web/SearchWeb/tag/service/MemberTagService.javaRepository: Searchweb-Dev/Searchweb-Back Length of output: 573 🏁 Script executed: # Search for any test classes in the project
find . -type f -path "*/test/java/*" -name "*.java" 2>/dev/null | head -20Repository: Searchweb-Dev/Searchweb-Back Length of output: 136 🏁 Script executed: # Look for any comments or documentation in the codebase about the delete behavior
rg "delete.*idempotent|idempotent.*delete|silent.*delete|delete.*silent" -iRepository: Searchweb-Dev/Searchweb-Back Length of output: 54
현재 코드는 Spring Data JPA 3.3.1에서
♻️ 간소화 제안 `@Override`
`@Transactional`
public void delete(Long memberTagId) {
- if (!memberTagJpaDao.existsById(memberTagId)) {
- return;
- }
memberTagJpaDao.deleteById(memberTagId);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private void validateTagName(String tagName) { | ||||||||||||||||||||||||||||
| if (tagName == null || tagName.isBlank()) { | ||||||||||||||||||||||||||||
| throw new IllegalArgumentException("tagName must not be blank"); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+67
to
+71
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
서비스와 엔티티 양쪽에서 동일한 null/blank 검증을 수행합니다. 도메인 엔티티의 검증은 도메인 무결성 보호를 위해 유지하되, 서비스 레이어에서는 별도 예외 타입(예: 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
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.
🛠️ Refactor suggestion | 🟠 Major
@RequestBody에@Valid어노테이션 누락DTO에 Bean Validation 어노테이션을 추가한다면, 컨트롤러의
@RequestBody파라미터에@Valid를 붙여야 자동 검증이 동작합니다.update엔드포인트(Line 58)에도 동일하게 적용해야 합니다.♻️ 수정 제안
🤖 Prompt for AI Agents