Skip to content
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

템플릿 CRUD API의 실패 응답 문서화 #109

Merged
merged 11 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;

@OpenAPIDefinition(info = @io.swagger.v3.oas.annotations.info.Info(title = "코드잽 API", version = "v1"))
@Configuration
public class SpringDocConfiguration {

Expand All @@ -16,7 +14,10 @@ public OpenAPI openAPI() {
Info info = new Info()
.title("코드잽 API")
.version("v1.0")
.description("코드잽 API 명세서입니다.");
.description("""
코드잽 API 명세서입니다. \n
모든 예외 응답의 메시지는 "detail" : "내부 서버 오류입니다." 와 같은 형태로 응답합니다. \n
""");

return new OpenAPI()
.info(info);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,68 @@
package codezap.template.controller;

import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;

import codezap.template.dto.request.CreateTemplateRequest;
import codezap.template.dto.request.UpdateTemplateRequest;
import codezap.template.dto.response.FindAllTemplatesResponse;
import codezap.template.dto.response.FindTemplateByIdResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "템플릿 CRUD API", description = "템플릿 생성, 단건 및 목록 조회, 삭제, 수정 API")
public interface SpringDocTemplateController {

@Operation(summary = "템플릿 생성", description = """
새로운 템플릿을 생성합니다. \n
새로운 템플릿을 생성합니다. \n
템플릿의 제목, 썸네일 스니펫의 순서, 스니펫 목록이 필요합니다. \n
스니펫 목록은 파일 이름, 소스 코드, 해당 스니펫의 순서가 필요합니다. \n
* 썸네일 스니펫은 1로 고정입니다. (2024.07.15 기준) \n
* 모든 스니펫 순서는 1부터 시작합니다. \n
* 스니펫 순서는 오름차 순으로 정렬하여 보내야 합니다. \n
""")
@ApiResponse(responseCode = "201", description = "회원 예약 생성 성공", headers = {
@Header(name = "생성된 템플릿의 API 경로", example = "/templates/1")})
@ApiResponse(responseCode = "400", content = @Content(schema = @Schema(implementation = ProblemDetail.class),
examples = {
@ExampleObject(summary = "모든 필드 중 null인 값이 있는 경우",
name = "메시지 예시: 템플릿 이름 null 입니다."),
@ExampleObject(summary = "제목 또는 스니펫 파일명이 255자를 초과한 경우",
name = "메시지 예시: 제목은 최대 255자까지 입력 가능합니다."),
@ExampleObject(summary = "썸네일 스니펫의 순서가 1이 아닌 경우",
name = "메시지 예시: 썸네일 스니펫의 순서가 잘못되었습니다."),
@ExampleObject(summary = "스니펫 순서가 잘못된 경우 (ex. 1 -> 3 -> 2 순으로 스니펫 목록이 온 경우)",
name = "메시지 예시: 스니펫 순서가 잘못되었습니다."),
@ExampleObject(summary = "스니펫 내용 65,535 byte를 초과한 경우",
name = "메시지 예시: 파일 내용은 최대 65,535 byte까지 입력 가능합니다.")}))
ResponseEntity<Void> create(CreateTemplateRequest createTemplateRequest);

@Operation(summary = "템플릿 목록 조회", description = "작성된 모든 템플릿을 조회합니다.")
@ApiResponse(responseCode = "200", description = "조회 성공",
content = {@Content(schema = @Schema(implementation = FindAllTemplatesResponse.class))})
ResponseEntity<FindAllTemplatesResponse> getTemplates();

@Operation(summary = "템플릿 단건 조회", description = "해당하는 식별자의 템플릿을 조회합니다.")
ResponseEntity<FindTemplateByIdResponse> getTemplateById(@PathVariable Long id);
}
@ApiResponse(responseCode = "200", description = "템플릿 단건 조회 성공",
content = {@Content(schema = @Schema(implementation = FindAllTemplatesResponse.class))})
@ApiResponse(responseCode = "400", description = "해당하는 id 값인 템플릿이 없는 경우",
content = {@Content(schema = @Schema(implementation = ProblemDetail.class))})
ResponseEntity<FindTemplateByIdResponse> getTemplateById(Long id);

@Operation(summary = "템플릿 수정", description = "해당하는 식별자의 템플릿을 수정합니다.")
@ApiResponse(responseCode = "200", description = "템플릿 수정 성공")
@ApiResponse(responseCode = "400", description = "해당하는 id 값인 템플릿이 없는 경우",
content = {@Content(schema = @Schema(implementation = ProblemDetail.class))})
ResponseEntity<Void> updateTemplate(Long id, UpdateTemplateRequest updateTemplateRequest);

@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,9 @@

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;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -13,6 +15,7 @@
import org.springframework.web.bind.annotation.RestController;

import codezap.template.dto.request.CreateTemplateRequest;
import codezap.template.dto.request.UpdateTemplateRequest;
import codezap.template.dto.response.FindAllTemplatesResponse;
import codezap.template.dto.response.FindTemplateByIdResponse;
import codezap.template.service.TemplateService;
Expand Down Expand Up @@ -42,4 +45,14 @@ public ResponseEntity<FindAllTemplatesResponse> getTemplates() {
public ResponseEntity<FindTemplateByIdResponse> getTemplateById(@PathVariable Long id) {
return ResponseEntity.ok(templateService.findById(id));
}

@PostMapping("/{id}")
public ResponseEntity<Void> updateTemplate(@PathVariable Long id, UpdateTemplateRequest updateTemplateRequest) {
throw new NotImplementedException();
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTemplate(@PathVariable Long id) {
throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@
import jakarta.validation.constraints.Size;

import codezap.global.validation.ByteLength;
import io.swagger.v3.oas.annotations.media.Schema;

public record CreateSnippetRequest(
@Schema(description = "파일 이름", example = "Main.java")
@NotNull(message = "파일 이름이 null 입니다.")
@Size(max = 255, message = "파일 이름은 최대 255자까지 입력 가능합니다.")
String filename,

@Schema(description = "소스 코드", example = "public class Main { // ...")
@NotNull(message = "파일 내용이 null 입니다.")
@ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.")
String content,

@Schema(description = "스니펫 순서", example = "1")
@NotNull(message = "스니펫 순서가 null 입니다.")
int ordinal
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
import jakarta.validation.constraints.Size;

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

public record CreateTemplateRequest(
@Schema(description = "템플릿 이름", example = "스프링 로그인 구현")
@NotNull(message = "템플릿 이름이 null 입니다.")
@Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.")
String title,

@Schema(description = "템플릿의 스니펫 내역")
@NotNull(message = "스니펫 리스트가 null 입니다.")
@IncreasedIndex(message = "스니펫 순서가 잘못되었습니다.")
@Valid
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package codezap.template.dto.request;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import codezap.global.validation.ByteLength;
import io.swagger.v3.oas.annotations.media.Schema;

public record UpdateSnippetRequest(
@Schema(description = "파일 식별자", example = "0")
Long id,

@Schema(description = "파일 이름", example = "Main.java")
@NotNull(message = "파일 이름이 null 입니다.")
@Size(max = 255, message = "파일 이름은 최대 255자까지 입력 가능합니다.")
String filename,

@Schema(description = "소스 코드", example = "public class Main { // ...")
@NotNull(message = "파일 내용이 null 입니다.")
@ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.")
String content,

@Schema(description = "스니펫 순서", example = "1")
@NotNull(message = "스니펫 순서가 null 입니다.")
int ordinal
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package codezap.template.dto.request;

import java.util.List;

import io.swagger.v3.oas.annotations.media.Schema;

public record UpdateTemplateRequest(
@Schema(description = "템플릿 이름", example = "스프링 로그인 구현")
String title,

@Schema(description = "새로 추가한 스니펫 내역")
List<CreateSnippetRequest> createSnippets,

@Schema(description = "삭제, 생성 스니펫을 제외한 모든 스니펫 내역")
List<UpdateSnippetRequest> updateSnippets,

@Schema(description = "삭제한 스니펫 식별자")
List<Long> deleteSnippetIds
) {
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package codezap.template.dto.response;

import codezap.template.domain.Snippet;
import io.swagger.v3.oas.annotations.media.Schema;

public record FindAllSnippetByTemplateResponse(
@Schema(description = "파일 식별자", example = "0")
Long id,
@Schema(description = "파일 이름", example = "Main.java")
String filename,
@Schema(description = "소스 코드", example = "public class Main { // ...")
String content,
@Schema(description = "스니펫의 순서", example = "1")
int ordinal
) {
public static FindAllSnippetByTemplateResponse from(Snippet snippet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import java.util.List;

import codezap.template.domain.ThumbnailSnippet;
import io.swagger.v3.oas.annotations.media.Schema;

public record FindAllTemplatesResponse(
@Schema(description = "템플릿 목록")
List<ItemResponse> templates
) {
public static FindAllTemplatesResponse from(List<ThumbnailSnippet> thumbnailSnippets) {
Expand All @@ -16,9 +18,13 @@ public static FindAllTemplatesResponse from(List<ThumbnailSnippet> thumbnailSnip
}

public record ItemResponse(
@Schema(description = "템플릿 식별자", example = "0")
Long id,
@Schema(description = "템플릿 이름", example = "스프링 로그인 구현")
String title,
@Schema(description = "목록 조회 시 보여질 대표 스니펫 정보")
FindThumbnailSnippetResponse thumbnailSnippet,
@Schema(description = "템플릿 수정 시간", example = "2024-11-11 12:00", type = "string")
LocalDateTime modifiedAt
) {
public static ItemResponse from(ThumbnailSnippet thumbnailSnippet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@

import codezap.template.domain.Snippet;
import codezap.template.domain.Template;
import io.swagger.v3.oas.annotations.media.Schema;

public record FindTemplateByIdResponse(
@Schema(description = "템플릿 식별자", example = "0")
Long id,
@Schema(description = "템플릿 이름", example = "스프링 로그인 구현")
String title,
@Schema(description = "스니펫 목록")
List<FindAllSnippetByTemplateResponse> snippets,
@Schema(description = "템플릿 수정 시간", example = "2024-11-11 12:00", type = "string")
LocalDateTime modifiedAt
) {
public static FindTemplateByIdResponse of(Template template, List<Snippet> snippets) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package codezap.template.dto.response;

import codezap.template.domain.Snippet;
import io.swagger.v3.oas.annotations.media.Schema;

public record FindThumbnailSnippetResponse(
@Schema(description = "파일 이름", example = "Main.java")
String filename,
@Schema(description = "목록 조회 시 보여질 코드", example = "public class Main { // ...")
String thumbnailContent
) {
public static FindThumbnailSnippetResponse from(Snippet snippet) {
Expand Down
3 changes: 3 additions & 0 deletions backend/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ spring:
hibernate:
format_sql: true
show_sql: true
springdoc:
default-consumes-media-type: application/json
default-produces-media-type: application/json