Skip to content

Commit

Permalink
[BE] 템플릿 수정 및 삭제 기능 구현 (#148)
Browse files Browse the repository at this point in the history
* chore: 스프링독 설정 파일 작성

* refactor(codezap): ERD 변경에 따른 불필요한 도메인 삭제

Co-authored-by: hoeseong123 <[email protected]>

* refactor(domain): 의미 있는 상수 추출

Co-authored-by: hoeseong123 <[email protected]>

* refactor(codezap): global 패키지 하위 항목을 용도에 따라 패키징

Co-authored-by: hoeseong123 <[email protected]>

* refactor(codezap): 패키지 이름 변경(thumbnail_snippet에서 언더바 제거)

Co-authored-by: hoeseong123 <[email protected]>

* feat(exception): 커스텀 exception 추가

Co-authored-by: hoeseong123 <[email protected]>

* refactor(domain): Table 어노테이션 삭제

Co-authored-by: hoeseong123 <[email protected]>

* refactor(domain): ThumbnailSnippet의 template 속성에 not null 제약조건 추가

Co-authored-by: hoeseong123 <[email protected]>

* chore: restAssured 의존성 추가

Co-authored-by: hoeseong123 <[email protected]>

* test(service): TemplateSerivce 테스트 코드 추가

Co-authored-by: hoeseong123 <[email protected]>

* chore: validation 의존성 추가

Co-authored-by: hoeseong123 <[email protected]>

* fix(workflows): 실패하는 배포 코드 삭제

* refactor(template): Snippet 도메인을 Template 하위로 이동

Co-authored-by: hoeseong123 <[email protected]>

* feat(codezap): 템플릿 DTO 유효성 검증 및 테스트 추가

Co-authored-by: hoeseong123 <[email protected]>

* refactor(template): ThumbnailSnippet 도메인을 Template 하위로 이동

Co-authored-by: hoeseong123 <[email protected]>

* feat(workflows): action runner를 이용한 백엔드 CD 파이프라인 구축

* refactor(workflows): 불필요한 주석 제거 및 코드 개선

- 주석 제거
- 실행 확인 시 sleep 줄이기
- 실행 중인 jar 파일 확인 폴더 한정
- 불필요한 중괄호 제거

* fix(workflows): 공백 제거

* chore: 의존성 구분을 위해 개행 추가

Co-authored-by: hoeseong123 <[email protected]>

* refactor(serialization): 패키지 이름 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(template): Location 헤더 수정 및 테스트 추가

Co-authored-by: hoeseong123 <[email protected]>

* test(integration): Template 목록 조회 및 상세 조회 테스트 코드 추가

Co-authored-by: hoeseong123 <[email protected]>

* refactor(template): 메서드 네이밍 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(domain): 상수 네이밍 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(response): dto 필드 네이밍 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(response): 테스트 코드에서 불필요한 매직 스트링 제거

Co-authored-by: hoeseong123 <[email protected]>

* refactor(exception): 가독성을 위해 줄바꿈 및 사용하는 정적 팩토리 메서드 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(controller): 빈 문자열 파라미터 제거

Co-authored-by: hoeseong123 <[email protected]>

* refactor(domain): not null 제약조건 설정 방법 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(domain): 불필요한 JoinColumn 제거

Co-authored-by: hoeseong123 <[email protected]>

* refactor(response): DTO 이름을 직관적으로 변경 및 내부 클래스로 이동

Co-authored-by: hoeseong123 <[email protected]>

* refactor(response): 파라미터를 2개 이상 받는 정팩메 이름을 of 로 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(exception): 예외 메시지 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(integration): 메서드 사이에 개행 추가

Co-authored-by: hoeseong123 <[email protected]>

* refactor(template): id를 받지 않는 도메인 생성자에

Co-authored-by: hoeseong123 <[email protected]>

* test(service): repository 의존성 추가 및 테스트 코드 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(integration): 최대 길이 상수화

Co-authored-by: hoeseong123 <[email protected]>

* refactor(serialization): timezone 설정 제거

Co-authored-by: hoeseong123 <[email protected]>

* refactor(controller): IntegrationTest를 ControllerTest로 이름 변경

Co-authored-by: hoeseong123 <[email protected]>

* refactor(exception): MethodArgumentNotValid 예외처리 메서드 NPE 경고 해결

Co-authored-by: hoeseong123 <[email protected]>

* feat(codezap): 바이트 길이 검증하는 기능 추가 및 테스트 코드 작성

Co-authored-by: hoeseong123 <[email protected]>

* feat(template): 스니펫 순서 검증하는 기능 추가 및 테스트 코드 작성

Co-authored-by: hoeseong123 <[email protected]>

* refactor(validation): 스니펫 순서 검증하는 로직 수정

Co-authored-by: hoeseong123 <[email protected]>

* refactor(codezap): 코드 스타일 정렬

Co-authored-by: hoeseong123 <[email protected]>

* docs: 템플릿 생성, 목록 및 단건 조회 API의 실패 응답 문서화

Co-authored-by: kyum-q <[email protected]>

* docs: 문서화 시 응답, 요청의 mime 타입 지정

Co-authored-by: kyum-q <[email protected]>

* docs: request 객체 설명 추가

Co-authored-by: kyum-q <[email protected]>

* docs: 생성 요청 시 스니펫 순서 예외 상황 추가

Co-authored-by: kyum-q <[email protected]>

* docs: 응답 dto 설명 작성

Co-authored-by: kyum-q <[email protected]>

* docs: 템플릿 수정 API 문서화

Co-authored-by: kyum-q <[email protected]>

* docs: 템플릿 삭제 API 문서 작성

Co-authored-by: kyum-q <[email protected]>

* docs: API 문서에 예외 응답 메시지를 알리는 문구 추가

Co-authored-by: kyum-q <[email protected]>

* docs: 생성 API의 실패 응답 중 썸네일 스니펫 잘못된 경우 메시지 예시 수정

Co-authored-by: kyum-q <[email protected]>

* docs: 템플릿 수정 문서에서 수정된 스니펫 내역을 삭제, 생성 스니펫을 제외한 모든 스니펫 내역으로 변경

Co-authored-by: kyum-q <[email protected]>

* docs: 오타 수정

Co-authored-by: kyum-q <[email protected]>

* refactor(request): 제약 조건 추가

Co-authored-by: zangsu <[email protected]>

* feat(template): 템플릿 수정 기능 구

Co-authored-by: zangsu <[email protected]>

* feat(codezap): 템플릿 수정시 스니펫 순서 검증 및 테스트 작성

Co-authored-by: zangsu <[email protected]>

* style(service): 코드 스타일 정리

Co-authored-by: zangsu <[email protected]>

* test(controller): 존재하지 않는 템플릿 조회에 대한 테스트 코드 추가

Co-authored-by: zangsu <[email protected]>

* test(controller): 복잡한 쿼리 메서드 테스트 추가

Co-authored-by: zangsu <[email protected]>

* refactor(template): 테스트 코드의 주석 정리

Co-authored-by: zangsu <[email protected]>

* refactor(exception): 에러 메시지 추출 시 중복되는 코드 제거

Co-authored-by: zangsu <[email protected]>

* style(controller): 줄바꿈 변경

Co-authored-by: zangsu <[email protected]>

* style(validation): 불필요한 개행 제거

Co-authored-by: zangsu <[email protected]>

* style(request): 불필요한 주석 제거

Co-authored-by: zangsu <[email protected]>

* refactor(service): 첫 번째 순서를 의미하는 숫자 상수화

Co-authored-by: zangsu <[email protected]>

* test(template): 가독성을 위한 Nested 추가 및 크기 검증 메서드 변경

Co-authored-by: zangsu <[email protected]>

* style(exception): 불필요한 임포트 제거

Co-authored-by: zangsu <[email protected]>

* refactor(service): 썸네일 수정 로직 변경

Co-authored-by: zangsu <[email protected]>

* refactor(request): 스니펫 순서를 검증에 사용되는 클래스명 변경

Co-authored-by: zangsu <[email protected]>

* refactor(controller): 초기 데이터 생성 시 service를 사용하도록 변경

Co-authored-by: zangsu <[email protected]>

* refactor(workflows): cd 아티팩터 다운로드 개선

* refactor(template): 템플릿 dto를 생성하는 로직을 별도의 메서드로 분리

Co-authored-by: zangsu <[email protected]>

* refactor(service): id 비교시 equals를 사용하도록 변경

Co-authored-by: zangsu <[email protected]>

* style(request): 개행 추가

Co-authored-by: zangsu <[email protected]>

* refactor(service): 첫 번째 순서를 상수로 변경

Co-authored-by: zangsu <[email protected]>

* refactor(service): 템플릿 수정 기능 리팩토링

Co-authored-by: zangsu <[email protected]>

* refactor(controller): 테스트코드 가독성 리팩토링

Co-authored-by: zangsu <[email protected]>

* docs: 템플릿 삭제 API 문서 수정

Co-authored-by: zangsu <[email protected]>

* feat(template): 템플릿 삭제 기능 구현

Co-authored-by: zangsu <[email protected]>

---------

Co-authored-by: ‘jminkkk’ <[email protected]>
Co-authored-by: zangsu <[email protected]>
Co-authored-by: Zeus6768 <[email protected]>
Co-authored-by: kyum-q <[email protected]>
Co-authored-by: kyum-q <[email protected]>
  • Loading branch information
6 people authored Jul 25, 2024
1 parent eded502 commit 894ace6
Show file tree
Hide file tree
Showing 21 changed files with 544 additions and 194 deletions.
27 changes: 7 additions & 20 deletions .github/workflows/backend_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build BootJar
run: ./gradlew bootJar
working-directory: ${{ env.build-directory }}
Expand All @@ -45,31 +45,18 @@ jobs:
needs: build
runs-on: self-hosted
steps:
- name: Get Artifact Download URL
id: get-artifact-url
run: |
artifacts=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/actions/artifacts)
artifact_id=$(echo "$artifacts" | jq -r '.artifacts[0].id')
if [[ -z "$artifact_id" || "$artifact_id" == "null" ]]; then
echo "Artifact ID not found or invalid"
exit 1
fi
download_url="https://api.github.com/repos/${{ github.repository }}/actions/artifacts/$artifact_id/zip"
echo "DOWNLOAD_URL=${download_url}" >> $GITHUB_ENV
- name: Download Artifact
run: |
curl -L -o ${{ secrets.WORK_DIRECTORY }}/code-zap-jar.zip -H \
"Authorization: token ${{ secrets.GITHUB_TOKEN }}" ${{ env.DOWNLOAD_URL }}
uses: actions/download-artifact@v4
with:
name: code-zap-jar
path: ${{ secrets.WORK_DIRECTORY }}

- name: Run Deploy Script
run: |
cd ${{ secrets.WORK_DIRECTORY }}
unzip -o code-zap-jar.zip
RUNNER_TRACKING_ID="" && ./deploy.sh
- name: Verify Deploy Succeed
run: |
sleep 3
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package codezap.global.exception;

import java.util.stream.Collectors;
import java.util.List;

import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -28,13 +28,14 @@ public ResponseEntity<ProblemDetail> handleMethodArgumentNotValidException(
MethodArgumentNotValidException exception
) {
BindingResult bindingResult = exception.getBindingResult();
List<String> errorMessages = bindingResult.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.toList();

return ResponseEntity.badRequest()
.body(ProblemDetail.forStatusAndDetail(
HttpStatus.BAD_REQUEST,
bindingResult.getFieldErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining())
)
HttpStatus.BAD_REQUEST,
String.join("\n", errorMessages))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,5 @@ public interface SpringDocTemplateController {

@Operation(summary = "템플릿 삭제", description = "해당하는 식별자의 템플릿을 삭제합니다.")
@ApiResponse(responseCode = "204", description = "템플릿 삭제 성공")
@ApiResponse(responseCode = "400", description = "해당하는 id 값인 템플릿이 없는 경우",
content = {@Content(schema = @Schema(implementation = ProblemDetail.class))})
ResponseEntity<Void> deleteTemplate(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import jakarta.validation.Valid;

import org.apache.commons.lang3.NotImplementedException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down Expand Up @@ -47,12 +46,17 @@ public ResponseEntity<FindTemplateByIdResponse> getTemplateById(@PathVariable Lo
}

@PostMapping("/{id}")
public ResponseEntity<Void> updateTemplate(@PathVariable Long id, UpdateTemplateRequest updateTemplateRequest) {
throw new NotImplementedException();
public ResponseEntity<Void> updateTemplate(
@PathVariable Long id,
@Valid @RequestBody UpdateTemplateRequest updateTemplateRequest
) {
templateService.update(id, updateTemplateRequest);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTemplate(@PathVariable Long id) {
throw new NotImplementedException();
templateService.deleteById(id);
return ResponseEntity.noContent().build();
}
}
6 changes: 6 additions & 0 deletions backend/src/main/java/codezap/template/domain/Snippet.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,10 @@ public String getThumbnailContent() {
.limit(THUMBNAIL_SNIPPET_LINE_HEIGHT)
.collect(Collectors.joining(CODE_LINE_BREAK));
}

public void updateSnippet(String filename, String content, Integer ordinal) {
this.filename = filename;
this.content = content;
this.ordinal = ordinal;
}
}
4 changes: 4 additions & 0 deletions backend/src/main/java/codezap/template/domain/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ public class Template extends BaseTimeEntity {
public Template(String title) {
this.title = title;
}

public void updateTitle(String title) {
this.title = title;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ public ThumbnailSnippet(Template template, Snippet snippet) {
this.template = template;
this.snippet = snippet;
}

public void updateThumbnailSnippet(Snippet snippet) {
this.snippet = snippet;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import codezap.template.dto.request.validation.IncreasedIndex;
import codezap.template.dto.request.validation.ValidatedSnippetsOrdinalRequest;
import io.swagger.v3.oas.annotations.media.Schema;

public record CreateTemplateRequest(
Expand All @@ -17,8 +17,11 @@ public record CreateTemplateRequest(

@Schema(description = "템플릿의 스니펫 내역")
@NotNull(message = "스니펫 리스트가 null 입니다.")
@IncreasedIndex(message = "스니펫 순서가 잘못되었습니다.")
@Valid
List<CreateSnippetRequest> snippets
) {
) implements ValidatedSnippetsOrdinalRequest {
@Override
public List<Integer> extractSnippetsOrdinal() {
return snippets.stream().map(CreateSnippetRequest::ordinal).toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

public record UpdateSnippetRequest(
@Schema(description = "파일 식별자", example = "0")
@NotNull(message = "파일 id가 null 입니다.")
Long id,

@Schema(description = "파일 이름", example = "Main.java")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
package codezap.template.dto.request;

import java.util.List;
import java.util.stream.Stream;

import jakarta.validation.constraints.NotNull;

import codezap.template.dto.request.validation.ValidatedSnippetsOrdinalRequest;
import io.swagger.v3.oas.annotations.media.Schema;

public record UpdateTemplateRequest(
@Schema(description = "템플릿 이름", example = "스프링 로그인 구현")
@NotNull(message = "템플릿 이름이 null 입니다.")
String title,

@Schema(description = "새로 추가한 스니펫 내역")
@NotNull(message = "createSnippets 리스트가 null 입니다.")
List<CreateSnippetRequest> createSnippets,

@Schema(description = "삭제, 생성 스니펫을 제외한 모든 스니펫 내역")
@NotNull(message = "updateSnippets 리스트가 null 입니다.")
List<UpdateSnippetRequest> updateSnippets,

@Schema(description = "삭제한 스니펫 식별자")
@NotNull(message = "deleteSnippetIds 리스트가 null 입니다.")
List<Long> deleteSnippetIds
) {
) implements ValidatedSnippetsOrdinalRequest {
@Override
public List<Integer> extractSnippetsOrdinal() {
return Stream.concat(
updateSnippets.stream().map(UpdateSnippetRequest::ordinal),
createSnippets.stream().map(CreateSnippetRequest::ordinal)
).toList();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

@Target(ElementType.FIELD)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IncreasedIndexValidator.class)
public @interface IncreasedIndex {
@Constraint(validatedBy = SnippetsOrdinalValidator.class)
public @interface SnippetsOrdinal {

String message();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package codezap.template.dto.request.validation;

import java.util.List;
import java.util.stream.IntStream;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class SnippetsOrdinalValidator implements ConstraintValidator<SnippetsOrdinal, ValidatedSnippetsOrdinalRequest> {

@Override
public boolean isValid(ValidatedSnippetsOrdinalRequest validatedSnippetsOrdinalRequest,
ConstraintValidatorContext constraintValidatorContext
) {
List<Integer> indexes = validatedSnippetsOrdinalRequest.extractSnippetsOrdinal();
return IntStream.range(0, indexes.size())
.allMatch(index -> indexes.get(index) == index + 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package codezap.template.dto.request.validation;

import java.util.List;

@SnippetsOrdinal(message = "스니펫 순서가 잘못되었습니다.")
public interface ValidatedSnippetsOrdinalRequest {

List<Integer> extractSnippetsOrdinal();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,23 @@
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpStatus;

import codezap.global.exception.CodeZapException;
import codezap.template.domain.Snippet;
import codezap.template.domain.Template;

public interface SnippetRepository extends JpaRepository<Snippet, Long> {
default Snippet fetchById(Long id) {
return findById(id).orElseThrow(
() -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 스니펫이 존재하지 않습니다."));
}

List<Snippet> findAllByTemplate(Template template);

Snippet findByTemplateAndOrdinal(Template template, int ordinal);

List<Snippet> findAllByTemplateAndOrdinal(Template template, int ordinal);

void deleteByTemplateId(Long id);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package codezap.template.repository;

import java.util.NoSuchElementException;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpStatus;

import codezap.global.exception.CodeZapException;
import codezap.template.domain.Template;

public interface TemplateRepository extends JpaRepository<Template, Long> {

default Template getById(Long id) {
default Template fetchById(Long id) {
return findById(id).orElseThrow(
() -> new NoSuchElementException("식별자 " + id + "에 해당하는 템플릿이 존재하지 않습니다."));
() -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 템플릿이 존재하지 않습니다."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import org.springframework.data.jpa.repository.JpaRepository;

import codezap.template.domain.Template;
import codezap.template.domain.ThumbnailSnippet;

public interface ThumbnailSnippetRepository extends JpaRepository<ThumbnailSnippet, Long> {
ThumbnailSnippet findByTemplate(Template template);

void deleteByTemplateId(Long id);
}
Loading

0 comments on commit 894ace6

Please sign in to comment.