Skip to content
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
// Spring Boot Starters:
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'

// OAuth2
implementation 'org.springframework.session:spring-session-data-redis'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.MalformedJwtException;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.fontory.fontorybe.authentication.domain.exception.AuthenticationRequiredException;
import org.fontory.fontorybe.authentication.domain.exception.InvalidRefreshTokenException;
Expand All @@ -29,7 +30,9 @@
import org.fontory.fontorybe.member.domain.exception.MemberNotFoundException;
import org.fontory.fontorybe.member.domain.exception.MemberOwnerMismatchException;
import org.fontory.fontorybe.provide.domain.exception.ProvideNotFoundException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
Expand All @@ -39,6 +42,16 @@
@RequiredArgsConstructor
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public BaseErrorResponse validationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(", "));
return new BaseErrorResponse(message);
}

@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler({MemberNotFoundException.class, FontNotFoundException.class, BookmarkNotFoundException.class})
public BaseErrorResponse notFoundException(Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.fontory.fontorybe.font.controller.dto.FontProgressResponse;
import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO;
import org.fontory.fontorybe.font.controller.dto.FontResponse;
import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO;
import org.fontory.fontorybe.font.controller.dto.FontUpdateResponse;
import org.fontory.fontorybe.font.controller.port.FontService;
import org.fontory.fontorybe.font.domain.Font;
Expand Down Expand Up @@ -119,27 +118,6 @@ public ResponseEntity<?> getFontProgress(@Login UserPrincipal userPrincipal) {
.body(fontsProgress);
}

@Operation(summary = "폰트 정보 수정")
@Parameter(name = "fontId", description = "수정할 폰트 ID")
@PutMapping("/{fontId}")
public ResponseEntity<?> updateFont(
@RequestBody @Valid FontUpdateDTO fontUpdateDTO,
@PathVariable Long fontId,
@Login UserPrincipal userPrincipal
) {
Long memberId = userPrincipal.getId();
log.info("Request received: Update font ID: {} by member ID: {}, request: {}",
fontId, memberId, toJson(fontUpdateDTO));

FontUpdateResponse fontUpdateResponse = fontService.update(memberId, fontId, fontUpdateDTO);
log.info("Response sent: Font ID: {} updated successfully, name: {}",
fontUpdateResponse.getId(), fontUpdateResponse.getName());

return ResponseEntity
.status(HttpStatus.OK)
.body(fontUpdateResponse);
}

@Operation(summary = "내가 제작한 폰트")
@GetMapping("/members")
public ResponseEntity<?> getFonts(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ public class FontCreateDTO {

@NotBlank(message = "폰트 이름은 필수 입력 값입니다.")
@Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.")
@Pattern(regexp = "^[가-힣0-9]{2,30}$", message = "한글과 숫자만 입력할 수 있습니다. (예: 가나다체123)")
private String name;

@NotBlank(message = "폰트 영어 이름은 필수 입력 값입니다.")
@Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.")
@Pattern(regexp = "^[a-zA-Z0-9]{2,30}$", message = "영문 대소문자와 숫자만 입력할 수 있습니다. (예: ABCD123)")
private String engName;

@NotBlank(message = "폰트 예시는 필수 입력 값입니다.")
@Size(min = 10, max = 255, message = "폰트 예시는 10자 이상 255자 이하로 입력해주세요.")
private String example;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
public interface FontService {
Font create(Long memberId, FontCreateDTO fontCreateDTO, FileUploadResult fileDetails);
List<FontProgressResponse> getFontProgress(Long memberId);
FontUpdateResponse update(Long memberId, Long fontId, FontUpdateDTO fontUpdateDTO);
Font getOrThrowById(Long id);
Page<FontResponse> getFonts(Long memberId, int page, int size);
FontResponse getFont(Long fondId, Long memberId);
Expand Down
45 changes: 8 additions & 37 deletions src/main/java/org/fontory/fontorybe/font/domain/Font.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.fontory.fontorybe.file.domain.FileUploadResult;
import org.fontory.fontorybe.font.controller.dto.FontCreateDTO;
import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO;
import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO;
import org.fontory.fontorybe.font.infrastructure.entity.FontStatus;

@Getter
Expand All @@ -22,6 +20,8 @@ public class Font {

private String name;

private String engName;

private FontStatus status;

private String example;
Expand Down Expand Up @@ -53,6 +53,7 @@ public void increaseDownloadCount() {
public static Font from(FontCreateDTO fontCreateDTO, Long memberId, String key) {
return Font.builder()
.name(fontCreateDTO.getName())
.engName(fontCreateDTO.getEngName())
.status(FontStatus.PROGRESS)
.example(fontCreateDTO.getExample())
.key(key)
Expand All @@ -62,12 +63,13 @@ public static Font from(FontCreateDTO fontCreateDTO, Long memberId, String key)
.build();
}

public Font update(FontUpdateDTO fontUpdateDTO) {
public Font updateProgress(FontProgressUpdateDTO fontProgressUpdateDTO) {
return Font.builder()
.name(fontUpdateDTO.getName())
.example(fontUpdateDTO.getExample())
.name(this.name)
.engName(this.engName)
.example(this.example)
.id(this.id)
.status(this.status)
.status(fontProgressUpdateDTO.getStatus())
.downloadCount(this.downloadCount)
.bookmarkCount(this.bookmarkCount)
.key(this.key)
Expand All @@ -76,35 +78,4 @@ public Font update(FontUpdateDTO fontUpdateDTO) {
.updatedAt(this.updatedAt)
.build();
}

public Font updateProgress(FontProgressUpdateDTO fontProgressUpdateDTO, Long fontId) {

if (fontProgressUpdateDTO.getStatus() == FontStatus.DONE) {
return Font.builder()
.name(this.name)
.example(this.example)
.id(this.id)
.status(fontProgressUpdateDTO.getStatus())
.downloadCount(this.downloadCount)
.bookmarkCount(this.bookmarkCount)
.key(this.key)
.memberId(this.memberId)
.createdAt(this.createdAt)
.updatedAt(this.updatedAt)
.build();
} else {
return Font.builder()
.name(this.name)
.example(this.example)
.id(this.id)
.status(fontProgressUpdateDTO.getStatus())
.downloadCount(this.downloadCount)
.bookmarkCount(this.bookmarkCount)
.key(this.key)
.memberId(this.memberId)
.createdAt(this.createdAt)
.updatedAt(this.updatedAt)
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class FontEntity extends BaseEntity {

private String name;

private String engName;

@Enumerated(EnumType.STRING)
private FontStatus status;

Expand All @@ -49,6 +51,7 @@ public Font toModel() {
return Font.builder()
.id(id)
.name(name)
.engName(engName)
.status(status)
.example(example)
.downloadCount(downloadCount)
Expand All @@ -64,6 +67,7 @@ public static FontEntity from(Font font) {
return FontEntity.builder()
.id(font.getId())
.name(font.getName())
.engName(font.getEngName())
.status(font.getStatus())
.example(font.getExample())
.downloadCount(font.getDownloadCount())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import org.fontory.fontorybe.font.controller.dto.FontProgressResponse;
import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO;
import org.fontory.fontorybe.font.controller.dto.FontResponse;
import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO;
import org.fontory.fontorybe.font.controller.dto.FontUpdateResponse;
import org.fontory.fontorybe.font.controller.port.FontService;
import org.fontory.fontorybe.font.domain.Font;
Expand Down Expand Up @@ -65,7 +64,7 @@ public Font create(Long memberId, FontCreateDTO fontCreateDTO, FileUploadResult
throw new FontDuplicateNameExistsException();
}

checkContainsBadWord(fontCreateDTO.getName(), fontCreateDTO.getExample());
checkFontNameAndExampleContainsBadWord(fontCreateDTO.getName(), fontCreateDTO.getEngName(), fontCreateDTO.getExample());

FileMetadata fileMetadata = fileService.getOrThrowById(fileDetails.getId());

Expand Down Expand Up @@ -97,23 +96,6 @@ public List<FontProgressResponse> getFontProgress(Long memberId) {
return result;
}

@Override
@Transactional
public FontUpdateResponse update(Long memberId, Long fontId, FontUpdateDTO fontUpdateDTO) {
log.info("Service executing: Updating font ID: {} for member ID: {}", fontId, memberId);
Member member = memberLookupService.getOrThrowById(memberId);
Font targetFont = getOrThrowById(fontId);

checkFontOwnership(member.getId(), targetFont.getMemberId());
checkContainsBadWord(fontUpdateDTO.getName(), fontUpdateDTO.getExample());

Font updatedFont = fontRepository.save(targetFont.update(fontUpdateDTO));
String woff2Url = cloudStorageService.getWoff2Url(updatedFont.getKey());

log.info("Service completed: Font ID: {} updated successfully", fontId);
return FontUpdateResponse.from(updatedFont, woff2Url);
}

@Override
@Transactional(readOnly = true)
public Font getOrThrowById(Long id) {
Expand Down Expand Up @@ -316,7 +298,7 @@ public FontUpdateResponse updateProgress(Long fontId, FontProgressUpdateDTO font
log.info("Service executing: Updating font ID: {}", fontId);
Font targetFont = getOrThrowById(fontId);

Font updatedFont = fontRepository.save(targetFont.updateProgress(fontProgressUpdateDTO, fontId));
Font updatedFont = fontRepository.save(targetFont.updateProgress(fontProgressUpdateDTO));
String woff2Url = cloudStorageService.getWoff2Url(updatedFont.getKey());

if (fontProgressUpdateDTO.getStatus() == FontStatus.DONE) {
Expand Down Expand Up @@ -375,11 +357,11 @@ private void checkFontStatusIsDone(Font targetFont) {
}
}

private void checkContainsBadWord(String name, String example) {
log.debug("Service detail: Checking bad word: name={}, example={}", name, example);
private void checkFontNameAndExampleContainsBadWord(String name, String engName, String example) {
log.debug("Service detail: Checking bad word: name={}, engName={} example={}", name, engName, example);

if (badWordFiltering.blankCheck(name) || badWordFiltering.blankCheck(example)) {
log.warn("Service warning: Font contains bad word: name={}, example={}", name, example);
if (badWordFiltering.blankCheck(name) || badWordFiltering.blankCheck(engName) || badWordFiltering.blankCheck(example)) {
log.warn("Service warning: Font contains bad word: name={}, engName={}, example={}", name, engName, example);
throw new FontContainsBadWordException();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class FontRequestProduceDto {
private Long fontId;
private String fileKey;
private String fontName;
private String fontEngName;
private String templateURL;
private String author;
private String requestUUID;
Expand All @@ -25,6 +26,7 @@ public static FontRequestProduceDto from(Font font, Member member, String templa
.fileKey(font.getKey())
.fontId(font.getId())
.fontName(font.getName())
.fontEngName(font.getEngName())
.templateURL(templateUrl)
.author(member.getNickname())
.requestUUID(MDC.get("requestId"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

import jakarta.servlet.http.Cookie;
import org.fontory.fontorybe.authentication.application.port.JwtTokenProvider;
Expand All @@ -30,7 +29,6 @@
import org.fontory.fontorybe.file.domain.FileUploadResult;
import org.fontory.fontorybe.font.controller.dto.FontCreateDTO;
import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO;
import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO;
import org.fontory.fontorybe.font.infrastructure.entity.FontStatus;
import org.fontory.fontorybe.font.service.port.FontRequestProducer;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -83,7 +81,8 @@ class FontControllerIntegrationTest {
private final String existFontKey = "key";
private final String existFontTemplateExtension = "jpg";

private final String newFontName = "newFontName";
private final String newFontName = "제작한글폰트1";
private final String newFontEngName = "newFontEngName";
private final String newFontExample = "newFontExample";

private final String updateFontName = "updateFontName";
Expand Down Expand Up @@ -127,6 +126,7 @@ void addFontSuccess() throws Exception {
// given
FontCreateDTO createDTO = FontCreateDTO.builder()
.name(newFontName)
.engName(newFontEngName)
.example(newFontExample)
.build();

Expand Down Expand Up @@ -203,51 +203,6 @@ void getFontProgressWithoutAuthHeader() throws Exception {
.andExpect(jsonPath("$.errorMessage").value("Authentication Required."));
}

@Test
@DisplayName("PUT /fonts/{fontId} - update font success with valid Authorization header")
void updateFontSuccess() throws Exception {
// given
FontUpdateDTO updateDTO = FontUpdateDTO.builder()
.name(updateFontName)
.example(updateFontExample)
.build();

String jsonRequest = objectMapper.writeValueAsString(updateDTO);

// when & then
mockMvc.perform(put("/fonts/{fontId}", existFontId)
.cookie(new Cookie("accessToken", validAccessToken))
.contentType(MediaType.APPLICATION_JSON)
.content(jsonRequest))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is(existFontId.intValue())))
.andExpect(jsonPath("$.name", is(updateFontName)))
.andExpect(jsonPath("$.status").isNotEmpty())
.andExpect(jsonPath("$.example", is(updateFontExample)))
.andExpect(jsonPath("$.memberId").isNotEmpty())
.andExpect(jsonPath("$.createdAt").isNotEmpty());
}

@Test
@DisplayName("PUT /fonts/{fontId} - update font without Authorization header returns 401")
void updateFontWithoutAuthHeader() throws Exception {
// given
FontUpdateDTO updateDTO = FontUpdateDTO.builder()
.name(updateFontName)
.example(updateFontExample)
.build();

String jsonRequest = objectMapper.writeValueAsString(updateDTO);

// when & then
mockMvc.perform(put("/fonts/{fontId}", existFontId)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonRequest))
.andExpect(status().isUnauthorized())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.errorMessage").value("Authentication Required."));
}

@Test
@DisplayName("GET /fonts/members - success with valid Authorization header")
void getMyFontsSuccess() throws Exception {
Expand Down
Loading
Loading