diff --git a/backend/src/main/java/kernel360/techpick/core/exception/feature/tag/ApiTagErrorCode.java b/backend/src/main/java/kernel360/techpick/core/exception/feature/tag/ApiTagErrorCode.java index 6f722060..087f5fbc 100644 --- a/backend/src/main/java/kernel360/techpick/core/exception/feature/tag/ApiTagErrorCode.java +++ b/backend/src/main/java/kernel360/techpick/core/exception/feature/tag/ApiTagErrorCode.java @@ -8,10 +8,12 @@ public enum ApiTagErrorCode implements ApiErrorCode { /** * Tag Error Code (TG) - * */ - TAG_DUPLICATION ("TG-000", HttpStatus.BAD_REQUEST, "중복된 태그 생성 요청"), - - TAG_INVALID_NAME ("TG-001", HttpStatus.BAD_REQUEST, "유효하지 않은 태그 이름"), + */ + TAG_NOT_FOUND("TG-000", HttpStatus.BAD_REQUEST, "존재하지 않는 태그"), + TAG_ALREADY_EXIST("TG-001", HttpStatus.BAD_REQUEST, "이미 존재하는 태그"), + TAG_INVALID_NAME("TG-002", HttpStatus.BAD_REQUEST, "유효하지 않은 태그 이름"), + UNAUTHORIZED_TAG_ACCESS("TG-003", HttpStatus.UNAUTHORIZED, "잘못된 태그 접근"), + TAG_INVALID_ORDER("TG-004", HttpStatus.BAD_REQUEST, "유효하지 않은 태그 순서"), ; @@ -31,11 +33,23 @@ public enum ApiTagErrorCode implements ApiErrorCode { this.errorMessage = message; } - @Override public String getCode() { return this.code; } + @Override + public String getCode() { + return this.code; + } - @Override public String getMessage() { return this.errorMessage; } + @Override + public String getMessage() { + return this.errorMessage; + } - @Override public HttpStatus getHttpStatus() { return this.httpStatus; } + @Override + public HttpStatus getHttpStatus() { + return this.httpStatus; + } - @Override public String toString() { return convertCodeToString(this); } + @Override + public String toString() { + return convertCodeToString(this); + } } diff --git a/backend/src/main/java/kernel360/techpick/core/exception/feature/tag/ApiTagException.java b/backend/src/main/java/kernel360/techpick/core/exception/feature/tag/ApiTagException.java index b4516c3a..b7a168e9 100644 --- a/backend/src/main/java/kernel360/techpick/core/exception/feature/tag/ApiTagException.java +++ b/backend/src/main/java/kernel360/techpick/core/exception/feature/tag/ApiTagException.java @@ -11,8 +11,26 @@ private ApiTagException(ApiErrorCode errorCode) { /** * TODO: Implement static factory method - * */ - public static ApiTagException TAG_DUPLICATE() { - return new ApiTagException(ApiTagErrorCode.TAG_DUPLICATION); + */ + + public static ApiTagException TAG_NOT_FOUND() { + return new ApiTagException(ApiTagErrorCode.TAG_NOT_FOUND); + } + + public static ApiTagException TAG_ALREADY_EXIST() { + return new ApiTagException(ApiTagErrorCode.TAG_ALREADY_EXIST); + } + + public static ApiTagException TAG_INVALID_NAME() { + return new ApiTagException(ApiTagErrorCode.TAG_INVALID_NAME); } + + public static ApiTagException UNAUTHORIZED_TAG_ACCESS() { + return new ApiTagException(ApiTagErrorCode.UNAUTHORIZED_TAG_ACCESS); + } + + public static ApiTagException TAG_INVALID_ORDER() { + return new ApiTagException(ApiTagErrorCode.TAG_INVALID_ORDER); + } + } diff --git a/backend/src/main/java/kernel360/techpick/core/model/tag/Tag.java b/backend/src/main/java/kernel360/techpick/core/model/tag/Tag.java index e8309512..0cc09d47 100644 --- a/backend/src/main/java/kernel360/techpick/core/model/tag/Tag.java +++ b/backend/src/main/java/kernel360/techpick/core/model/tag/Tag.java @@ -35,15 +35,28 @@ public class Tag { @Column(name = "name", nullable = false) private String name; + // order로 하니까 db 키워드랑 겹쳐 안됨.. + @Column(name = "tag_order", nullable = false) + private int tagOrder; + // 사용자 FK @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; // TODO: 엔티티 사용자가 정적 팩토리 메소드로 필요한 함수를 구현 하세요 + public static Tag createTag(String name, int order, User user) { + return new Tag(name, order, user); + } + + public void updateTag(String name, int tagOrder) { + this.name = name; + this.tagOrder = tagOrder; + } - private Tag(String name, User user) { + private Tag(String name, int tagOrder, User user) { this.name = name; + this.tagOrder = tagOrder; this.user = user; } } diff --git a/backend/src/main/java/kernel360/techpick/feature/pick/repository/PickTagRepository.java b/backend/src/main/java/kernel360/techpick/feature/pick/repository/PickTagRepository.java new file mode 100644 index 00000000..e8b18c77 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/feature/pick/repository/PickTagRepository.java @@ -0,0 +1,16 @@ +package kernel360.techpick.feature.pick.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import kernel360.techpick.core.model.pick.PickTag; + +// TODO: tag 삭제 시 pick이 존재하는지 여부 확인을 위해 임시로 PickTagRepository 생성 +// 이후 PickTagProvider 가 구현될 경우 책임 이관 예정 +@Repository +public interface PickTagRepository extends JpaRepository { + + boolean existsByTag_Id(Long tag_id); + + void deleteByTag_Id(Long tag_id); +} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagApi.java b/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagApi.java new file mode 100644 index 00000000..e7fbb540 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagApi.java @@ -0,0 +1,76 @@ +package kernel360.techpick.feature.tag.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import kernel360.techpick.feature.tag.model.dto.TagCreateRequest; +import kernel360.techpick.feature.tag.model.dto.TagResponse; +import kernel360.techpick.feature.tag.model.dto.TagUpdateRequest; + +@Tag(name = "태그 API", description = "태그 API") +public interface TagApi { + + @Operation( + summary = "태그 생성", + description = "태그를 생성합니다. 생성된 태그는 가장 마지막 순서에 위치합니다." + ) + @ApiResponses( + @ApiResponse( + responseCode = "200", + description = "태그를 정상적으로 생성했습니다." + ) + ) + ResponseEntity createTag( + Authentication authentication, + @RequestBody(description = "태그 생성 정보", required = true) + TagCreateRequest request + ); + + @Operation( + summary = "태그 리스트 조회", + description = "자신이 등록한 태그 리스트를 조회한다. 태그는 order 오름차순으로 정렬되어있음." + ) + @ApiResponses( + @ApiResponse( + responseCode = "200", + description = "태그 리스트를 정상적으로 조회했습니다." + ) + ) + ResponseEntity> getTagListByUser(Authentication authentication); + + @Operation( + summary = "태그 수정", + description = "자신이 등록한 태그 리스트를 수정한다." + ) + @ApiResponses( + @ApiResponse( + responseCode = "200", + description = "태그 리스트를 정상적으로 수정했습니다." + ) + ) + ResponseEntity> updateTagList( + Authentication authentication, + @RequestBody(description = "태그 수정 정보", required = true) + List tagUpdateRequests + ); + + @Operation( + summary = "태그 삭제", + description = "자신이 등록한 태그를 삭제한다." + ) + @ApiResponses( + @ApiResponse( + responseCode = "200", + description = "태그를 정상적으로 삭제했습니다." + ) + ) + ResponseEntity deleteTagById(Authentication authentication, Long tagId); + +} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagApiV1.java b/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagApiV1.java deleted file mode 100644 index c72bf73d..00000000 --- a/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagApiV1.java +++ /dev/null @@ -1,26 +0,0 @@ -package kernel360.techpick.feature.tag.controller; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import kernel360.techpick.feature.tag.model.dto.TagCreateRequest; - -public interface TagApiV1 { - - @Operation( - summary = "사용자 정의 태그 생성 API", description = "사용자가 태그를 생성합니다." - // TODO: 추가 적인 작업 진행 - // 참고: https://github.com/Kernel360/E2E2-TALKKA/blob/develop/server/src/main/java/com/talkka/server/bookmark/controller/BookmarkApi.java - ) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "태그 생성 완료"), - // TODO: Response 추가 - }) - @PostMapping - ResponseEntity createTag(@RequestBody TagCreateRequest request); -} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagController.java b/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagController.java new file mode 100644 index 00000000..f0403500 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagController.java @@ -0,0 +1,66 @@ +package kernel360.techpick.feature.tag.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +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; + +import kernel360.techpick.feature.tag.model.dto.TagCreateRequest; +import kernel360.techpick.feature.tag.model.dto.TagResponse; +import kernel360.techpick.feature.tag.model.dto.TagUpdateRequest; +import kernel360.techpick.feature.tag.service.TagService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/tag") +public class TagController implements TagApi { + + private final TagService tagService; + + @Override + @PostMapping + public ResponseEntity createTag(Authentication auth, @RequestBody TagCreateRequest request) { + + return ResponseEntity.ok(tagService.createTag(getUserId(auth), request)); + } + + @Override + @GetMapping + public ResponseEntity> getTagListByUser(Authentication auth) { + + return ResponseEntity.ok(tagService.getTagListByUser(getUserId(auth))); + } + + @Override + @PutMapping + public ResponseEntity> updateTagList(Authentication auth, + @RequestBody List tagUpdateRequests) { + + return ResponseEntity.ok(tagService.updateTagList(getUserId(auth), tagUpdateRequests)); + } + + @Override + @DeleteMapping("/{tagId}") + public ResponseEntity deleteTagById(Authentication auth, @PathVariable Long tagId) { + + tagService.deleteById(getUserId(auth), tagId); + + return ResponseEntity.noContent().build(); + } + + // TODO: 직접 token에 있는 userId를 주입받을 방법 있는지 확인 후 리팩토링 필요 + private Long getUserId(Authentication authentication) { + + return (Long)authentication.getPrincipal(); + } + +} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagPrivateControllerV1.java b/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagPrivateControllerV1.java deleted file mode 100644 index fcdee888..00000000 --- a/backend/src/main/java/kernel360/techpick/feature/tag/controller/TagPrivateControllerV1.java +++ /dev/null @@ -1,30 +0,0 @@ -package kernel360.techpick.feature.tag.controller; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import kernel360.techpick.feature.tag.model.dto.TagCreateRequest; -import kernel360.techpick.feature.tag.service.TagService; -import lombok.RequiredArgsConstructor; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/tag") -public class TagPrivateControllerV1 implements TagApiV1 { - - private final TagService tagService; - - @PostMapping - public ResponseEntity createTag(@RequestBody TagCreateRequest request) { - - return ResponseEntity.status(HttpStatus.CREATED).build(); - } -} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/exception/TagCustomException.java b/backend/src/main/java/kernel360/techpick/feature/tag/exception/TagCustomException.java deleted file mode 100644 index 01a159d1..00000000 --- a/backend/src/main/java/kernel360/techpick/feature/tag/exception/TagCustomException.java +++ /dev/null @@ -1,5 +0,0 @@ -package kernel360.techpick.feature.tag.exception; - -// 예시용 클래스 -public class TagCustomException extends Exception { -} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/model/TagMapper.java b/backend/src/main/java/kernel360/techpick/feature/tag/model/TagMapper.java new file mode 100644 index 00000000..597eca3c --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/feature/tag/model/TagMapper.java @@ -0,0 +1,33 @@ +package kernel360.techpick.feature.tag.model; + +import org.springframework.stereotype.Component; + +import kernel360.techpick.core.exception.feature.user.ApiUserException; +import kernel360.techpick.core.model.tag.Tag; +import kernel360.techpick.core.model.user.User; +import kernel360.techpick.feature.tag.model.dto.TagCreateRequest; +import kernel360.techpick.feature.tag.model.dto.TagResponse; +import kernel360.techpick.feature.user.UserRepository; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class TagMapper { + + private final UserRepository userRepository; + + public Tag createTag(TagCreateRequest request, int order, Long userId) throws ApiUserException { + + User user = userRepository.findById(userId).orElseThrow(ApiUserException::USER_NOT_FOUND); + return Tag.createTag(request.name(), order, user); + } + + public TagResponse createTagResponse(Tag tag) { + return new TagResponse( + tag.getId(), + tag.getName(), + tag.getTagOrder(), + tag.getUser().getId() + ); + } +} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/model/TagProvider.java b/backend/src/main/java/kernel360/techpick/feature/tag/model/TagProvider.java new file mode 100644 index 00000000..939328d2 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/feature/tag/model/TagProvider.java @@ -0,0 +1,55 @@ +package kernel360.techpick.feature.tag.model; + +import java.util.Collection; +import java.util.List; + +import org.springframework.stereotype.Component; + +import kernel360.techpick.core.exception.feature.tag.ApiTagException; +import kernel360.techpick.core.model.tag.Tag; +import kernel360.techpick.feature.tag.repository.TagRepository; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class TagProvider { + + private final TagRepository tagRepository; + + public Tag save(Tag tag) { + return tagRepository.save(tag); + } + + public List saveAll(Collection tags) { + return tagRepository.saveAll(tags); + } + + public Tag findById(Long id) throws ApiTagException { + return tagRepository.findById(id).orElseThrow(ApiTagException::TAG_NOT_FOUND); + } + + public List findAllByUserIdOrderByTagOrder(Long userId) { + return tagRepository.findAllByUserIdOrderByTagOrder(userId); + } + + public boolean existsByUserIdAndName(Long id, String name) throws ApiTagException { + return tagRepository.existsByUserIdAndName(id, name); + } + + public void deleteById(Long id) throws ApiTagException { + tagRepository.deleteById(id); + } + + public int getNextOrderByUserId(Long userId) { + + var tag = tagRepository.findFirstByUserIdOrderByTagOrderDesc(userId); + + // 순서는 0부터 시작 + return tag.map(value -> value.getTagOrder() + 1).orElseGet(() -> 0); + } + + public TagUpdater getUserTag(Long userId) { + return TagUpdater.fromTagList(tagRepository.findAllByUserId(userId)); + } + +} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/model/TagUpdater.java b/backend/src/main/java/kernel360/techpick/feature/tag/model/TagUpdater.java new file mode 100644 index 00000000..1b83bea5 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/feature/tag/model/TagUpdater.java @@ -0,0 +1,61 @@ +package kernel360.techpick.feature.tag.model; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import kernel360.techpick.core.exception.feature.tag.ApiTagException; +import kernel360.techpick.core.model.tag.Tag; +import kernel360.techpick.feature.tag.model.dto.TagUpdateRequest; + +public class TagUpdater { + + private final Map tagMap; + + public static TagUpdater fromTagList(List tagList) { + + Map tagMap = new HashMap<>(); + tagList.forEach(tag -> tagMap.put(tag.getId(), tag)); + return new TagUpdater(tagMap); + } + + public void updateTag(TagUpdateRequest request) { + + Tag target = getTagById(request.id()); + target.updateTag(request.name(), request.order()); + } + + public void validateTagOrder() throws ApiTagException { + + Set orderSet = new HashSet<>(); + + for (Tag tag : tagMap.values()) { + // 중복되거나 음수면 유효하지 않은 tag order + if (!orderSet.add(tag.getTagOrder()) || tag.getTagOrder() < 0) { + throw ApiTagException.TAG_INVALID_ORDER(); + } + } + } + + public Collection getTags() { + + return tagMap.values(); + } + + private TagUpdater(Map tagMap) { + + this.tagMap = tagMap; + } + + private Tag getTagById(Long tagId) { + + if (tagMap.containsKey(tagId)) { + return tagMap.get(tagId); + } + throw ApiTagException.UNAUTHORIZED_TAG_ACCESS(); + } + +} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagCreateRequest.java b/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagCreateRequest.java index d51fd6c3..4d487297 100644 --- a/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagCreateRequest.java +++ b/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagCreateRequest.java @@ -1,5 +1,6 @@ package kernel360.techpick.feature.tag.model.dto; public record TagCreateRequest( - String tagName -) {} + String name +) { +} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagCreateResponse.java b/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagCreateResponse.java deleted file mode 100644 index c589d1c0..00000000 --- a/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagCreateResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package kernel360.techpick.feature.tag.model.dto; - -public record TagCreateResponse( - String tagName -) {} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagResponse.java b/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagResponse.java new file mode 100644 index 00000000..e66f9e92 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagResponse.java @@ -0,0 +1,9 @@ +package kernel360.techpick.feature.tag.model.dto; + +public record TagResponse( + Long id, + String name, + int order, + Long userId +) { +} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagUpdateRequest.java b/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagUpdateRequest.java new file mode 100644 index 00000000..913224a9 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/feature/tag/model/dto/TagUpdateRequest.java @@ -0,0 +1,8 @@ +package kernel360.techpick.feature.tag.model.dto; + +public record TagUpdateRequest( + Long id, + String name, + int order +) { +} diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/repository/TagRepository.java b/backend/src/main/java/kernel360/techpick/feature/tag/repository/TagRepository.java index c2c27538..9b3c0f40 100644 --- a/backend/src/main/java/kernel360/techpick/feature/tag/repository/TagRepository.java +++ b/backend/src/main/java/kernel360/techpick/feature/tag/repository/TagRepository.java @@ -1,13 +1,20 @@ package kernel360.techpick.feature.tag.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import kernel360.techpick.core.model.tag.Tag; -import kernel360.techpick.core.model.user.User; public interface TagRepository extends JpaRepository { - List findAllByUser(User user); + boolean existsByUserIdAndName(Long userId, String name); + + List findAllByUserIdOrderByTagOrder(Long user_id); + + List findAllByUserId(Long user_id); + + Optional findFirstByUserIdOrderByTagOrderDesc(Long user_id); + } diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/service/TagService.java b/backend/src/main/java/kernel360/techpick/feature/tag/service/TagService.java index 84ab0c47..e71bed99 100644 --- a/backend/src/main/java/kernel360/techpick/feature/tag/service/TagService.java +++ b/backend/src/main/java/kernel360/techpick/feature/tag/service/TagService.java @@ -1,26 +1,92 @@ package kernel360.techpick.feature.tag.service; import java.util.List; +import java.util.Objects; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import kernel360.techpick.core.exception.feature.tag.ApiTagException; +import kernel360.techpick.core.exception.feature.user.ApiUserException; import kernel360.techpick.core.model.tag.Tag; -import kernel360.techpick.core.model.user.User; -import kernel360.techpick.feature.tag.repository.TagRepository; +import kernel360.techpick.feature.pick.repository.PickTagRepository; +import kernel360.techpick.feature.tag.model.TagMapper; +import kernel360.techpick.feature.tag.model.TagProvider; +import kernel360.techpick.feature.tag.model.TagUpdater; +import kernel360.techpick.feature.tag.model.dto.TagCreateRequest; +import kernel360.techpick.feature.tag.model.dto.TagResponse; +import kernel360.techpick.feature.tag.model.dto.TagUpdateRequest; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class TagService { - private final TagRepository tagRepository; + private final TagMapper tagMapper; + private final TagProvider tagProvider; + private final PickTagRepository pickTagRepository; - public List getTagByUser(User user) { - return tagRepository.findAllByUser(user); + @Transactional + public TagResponse createTag(Long userId, TagCreateRequest request) throws ApiTagException, ApiUserException { + + validateTagNameExists(userId, request.name()); + + int lastOrder = tagProvider.getNextOrderByUserId(userId); + Tag tag = tagProvider.save(tagMapper.createTag(request, lastOrder, userId)); + + return tagMapper.createTagResponse(tag); + } + + @Transactional(readOnly = true) + public List getTagListByUser(Long userId) { + + List tagList = tagProvider.findAllByUserIdOrderByTagOrder(userId); + + return tagList.stream() + .map(tagMapper::createTagResponse) + .toList(); + } + + @Transactional + public List updateTagList(Long userId, List tagUpdateRequests) throws + ApiTagException { + + TagUpdater tagUpdater = tagProvider.getUserTag(userId); + + for (var req : tagUpdateRequests) { + tagUpdater.updateTag(req); + } + tagUpdater.validateTagOrder(); + + return tagProvider.saveAll(tagUpdater.getTags()) + .stream() + .map(tagMapper::createTagResponse) + .toList(); + } + + @Transactional + public void deleteById(Long userId, Long tagId) throws ApiTagException { + + Tag targetTag = tagProvider.findById(tagId); + validateTagAccess(userId, targetTag); + // 해당 태그를 등록한 픽에서 해당 태그를 모두 삭제 + // TODO: PickTagProvider 구현되면 PickTagProvider를 의존하도록 리팩토링 필요 + pickTagRepository.deleteByTag_Id(tagId); + tagProvider.deleteById(tagId); + } + + private void validateTagAccess(Long userId, Tag tag) throws ApiTagException { + + if (tag == null || !Objects.equals(userId, tag.getUser().getId())) { + throw ApiTagException.UNAUTHORIZED_TAG_ACCESS(); + } + } + + private void validateTagNameExists(Long userId, String name) throws ApiTagException { + + if (tagProvider.existsByUserIdAndName(userId, name)) { + throw ApiTagException.TAG_ALREADY_EXIST(); + } } - /** - * - 한 사용자의 모든 태그명은 유일해야 하며, 어길 경우 예외를 발생시킵니다. - */ - public void createTagByName(String name) {} } diff --git a/backend/src/main/java/kernel360/techpick/feature/tag/util/TagConverter.java b/backend/src/main/java/kernel360/techpick/feature/tag/util/TagConverter.java deleted file mode 100644 index a5570fda..00000000 --- a/backend/src/main/java/kernel360/techpick/feature/tag/util/TagConverter.java +++ /dev/null @@ -1,4 +0,0 @@ -package kernel360.techpick.feature.tag.util; - -public class TagConverter { -}