Skip to content

Commit

Permalink
[feat] 태그 API 구현 (#71)
Browse files Browse the repository at this point in the history
* refactor: 불필요한 클래스 삭제

* refactor: 불필요한 클래스 삭제

* feat: Tag 예외처리 및 에러코드 구현

* refactor: order필드 및 생성 및 삭제를 위한 정적 메소드 추가

* feat: 태그 crud 구현

* feat: 태그 컨트롤러 및 swagger 명세 구현

* feat: (임시) 태그 삭제시 검증을 위한 PickTagRepository 구현

* fix: createTag 메소드에 ApiUserException 추가

* refactor: tag 삭제방식 변경

* refactor: validate 로직 분리 - TagValidator 추가

* refactor: TagRepository 사용하지 않는 메소드 삭제

* refactor: mapper, provider 패키지 구조 변경

* feat: order 중복 에러 추가 및 pick 존재 에러 삭제

* feat: 사용자가 등록한 태그를 map 타입으로 제공하는 메소드 추가

* feat: 중복 order 검증 로직 추가

* refactor: Tag update 로직 수정 - 중복 order 검증, 사용자 태그 검증 로직 변경

* refactor: delete 로직에서 메소드명 typo 수정 및 주석 추가

* refactor: typo 수정

* refactor: tag order 가 음수일때 검증 로직 추가

* refactor: tag order 가 0부터 시작하도록 수정

* refactor: orElse -> orElseGet 으로 리팩토링

* refactor: format refactoring

* refactor: tag update시 검증 로직 변경

* refactor: snake case -> camel case로 리팩토링

* refactor: Tag 엔티티 order 필드명 수정 -> order -> tagOrder

* refactor: userTagList 제공 메소드 정렬기준에 따라 분리 - OrderByTagOrder, OrderByTagId

* refactor: userTagList 제공 메소드 정렬기준에 따라 분리 - OrderByTagOrder, OrderByTagId

* refactor: updateTag 로직 수정 validator에서 update할 tag의 index를 가져옴

* refactor: 불필요한 메소드 삭제

* refactor: TagUpdater 클래스 추가

* refactor: TagUpdater 클래스를 사용해 updateTag 로직 구현

* refactor: format 수정

* refactor: 미사용 메소드 삭제

* refactor: validator 삭제 및 관련 로직 service로 이관

* refactor: 다음 tagOrder 가져오는 메소드 명 명확하게 수정
  • Loading branch information
Gyaak committed Sep 30, 2024
1 parent b4f044b commit 27381b8
Show file tree
Hide file tree
Showing 19 changed files with 468 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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, "유효하지 않은 태그 순서"),

;

Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
15 changes: 14 additions & 1 deletion backend/src/main/java/kernel360/techpick/core/model/tag/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<PickTag, Long> {

boolean existsByTag_Id(Long tag_id);

void deleteByTag_Id(Long tag_id);
}
Original file line number Diff line number Diff line change
@@ -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<TagResponse> createTag(
Authentication authentication,
@RequestBody(description = "태그 생성 정보", required = true)
TagCreateRequest request
);

@Operation(
summary = "태그 리스트 조회",
description = "자신이 등록한 태그 리스트를 조회한다. 태그는 order 오름차순으로 정렬되어있음."
)
@ApiResponses(
@ApiResponse(
responseCode = "200",
description = "태그 리스트를 정상적으로 조회했습니다."
)
)
ResponseEntity<List<TagResponse>> getTagListByUser(Authentication authentication);

@Operation(
summary = "태그 수정",
description = "자신이 등록한 태그 리스트를 수정한다."
)
@ApiResponses(
@ApiResponse(
responseCode = "200",
description = "태그 리스트를 정상적으로 수정했습니다."
)
)
ResponseEntity<List<TagResponse>> updateTagList(
Authentication authentication,
@RequestBody(description = "태그 수정 정보", required = true)
List<TagUpdateRequest> tagUpdateRequests
);

@Operation(
summary = "태그 삭제",
description = "자신이 등록한 태그를 삭제한다."
)
@ApiResponses(
@ApiResponse(
responseCode = "200",
description = "태그를 정상적으로 삭제했습니다."
)
)
ResponseEntity<Void> deleteTagById(Authentication authentication, Long tagId);

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<TagResponse> createTag(Authentication auth, @RequestBody TagCreateRequest request) {

return ResponseEntity.ok(tagService.createTag(getUserId(auth), request));
}

@Override
@GetMapping
public ResponseEntity<List<TagResponse>> getTagListByUser(Authentication auth) {

return ResponseEntity.ok(tagService.getTagListByUser(getUserId(auth)));
}

@Override
@PutMapping
public ResponseEntity<List<TagResponse>> updateTagList(Authentication auth,
@RequestBody List<TagUpdateRequest> tagUpdateRequests) {

return ResponseEntity.ok(tagService.updateTagList(getUserId(auth), tagUpdateRequests));
}

@Override
@DeleteMapping("/{tagId}")
public ResponseEntity<Void> 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();
}

}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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()
);
}
}
Loading

0 comments on commit 27381b8

Please sign in to comment.