From 73a36274ec571bacf90367750ee882ce192518eb Mon Sep 17 00:00:00 2001 From: minkyung Date: Sun, 4 Jan 2026 18:00:40 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EB=AC=BC=20=EC=88=98=EC=A0=95=20=EB=98=90=EB=8A=94=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=8B=9C=EC=97=90=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=82=B4=EC=97=90=EB=A7=8C=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/VerificationServiceImpl.java | 124 ++++++++++++++++-- .../backend/global/response/ErrorCode.java | 1 + 2 files changed, 115 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java b/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java index aa73b3a3..493bc117 100644 --- a/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java +++ b/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java @@ -1,8 +1,13 @@ package com.hrr.backend.domain.verification.service; +import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.util.List; + +import com.hrr.backend.domain.challenge.entity.ChallengeDayJoin; +import com.hrr.backend.global.common.enums.ChallengeDays; import com.hrr.backend.domain.comment.dto.CommentListResponseDto; import com.hrr.backend.domain.comment.dto.CommentResponseDto; @@ -376,10 +381,10 @@ public VerificationDetailResponseDto updateVerification(Long verificationId, Lon Verification verification = verificationRepository.findById(verificationId) .orElseThrow(() -> new GlobalException(ErrorCode.VERIFICATION_NOT_FOUND)); - // 차단된 게시글 접근 시 예외 발생 - if (verification.getStatus() == VerificationStatus.BLOCKED) { - throw new GlobalException(ErrorCode.ACCESS_DENIED_REPORTED_POST); - } + // 차단된 게시글 접근 시 예외 발생 + if (verification.getStatus() == VerificationStatus.BLOCKED) { + throw new GlobalException(ErrorCode.ACCESS_DENIED_REPORTED_POST); + } // 작성자 본인인지 권한 체크 User author = verification.getRoundRecord() @@ -390,6 +395,9 @@ public VerificationDetailResponseDto updateVerification(Long verificationId, Lon throw new GlobalException(ErrorCode.VERIFICATION_ACCESS_DENIED); } + // 인증 요일 + 인증 시간대 안에서만 수정 가능 + validateVerificationEditDeleteWindow(verification); + // 엔티티 업데이트 verification.update( requestDto.getTitle(), @@ -433,12 +441,12 @@ public void deleteVerification(Long verificationId, Long currentUserId) { Verification verification = verificationRepository.findById(verificationId) .orElseThrow(() -> new GlobalException(ErrorCode.VERIFICATION_NOT_FOUND)); - // 차단된 게시글 접근 시 예외 발생 - if (verification.getStatus() == VerificationStatus.BLOCKED) { - throw new GlobalException(ErrorCode.ACCESS_DENIED_REPORTED_POST); - } + // 차단된 게시글 접근 시 예외 발생 + if (verification.getStatus() == VerificationStatus.BLOCKED) { + throw new GlobalException(ErrorCode.ACCESS_DENIED_REPORTED_POST); + } - // 작성자 본인인지 권한 체크 + // 작성자 본인인지 권한 체크 User author = verification.getRoundRecord() .getUserChallenge() .getUser(); @@ -446,6 +454,10 @@ public void deleteVerification(Long verificationId, Long currentUserId) { if (!author.getId().equals(currentUserId)) { throw new GlobalException(ErrorCode.VERIFICATION_ACCESS_DENIED); } + + // 인증 요일 + 인증 시간대 안에서만 삭제 가능 + validateVerificationEditDeleteWindow(verification); + verificationRepository.delete(verification); } @@ -479,4 +491,96 @@ public SliceResponseDto getVerificationHisto // SliceResponseDto로 변환하여 반환 return new SliceResponseDto<>(dtoSlice); } -} \ No newline at end of file + + // 수정/삭제 시간 제한 로직 (최소 범위) + + private void validateVerificationEditDeleteWindow(Verification verification) { + Challenge challenge = verification.getRoundRecord() + .getRound() + .getChallenge(); + + LocalDateTime now = LocalDateTime.now(); + + // 1) 지금이 "인증 시간대"가 아니면 수정/삭제 불가 + if (!isWithinVerificationTime(challenge, now.toLocalTime())) { + throw new GlobalException(ErrorCode.VERIFICATION_EDIT_DELETE_NOT_ALLOWED); + } + + // 2) 지금 인증 윈도우가 어느 "인증 요일(기준 날짜)"에 속하는지 계산 + LocalDate currentWindowDate = getWindowAnchorDate(challenge, now); + + // 3) 그 날짜가 챌린지의 인증 요일에 포함되지 않으면 불가 + if (!isVerificationDay(challenge, currentWindowDate)) { + throw new GlobalException(ErrorCode.VERIFICATION_EDIT_DELETE_NOT_ALLOWED); + } + + // 4) 이 인증글이 "현재 인증 윈도우"에 속하는 글이 아니면 불가 + LocalDate postWindowDate = getWindowAnchorDate(challenge, verification.getCreatedAt()); + if (!postWindowDate.equals(currentWindowDate)) { + throw new GlobalException(ErrorCode.VERIFICATION_EDIT_DELETE_NOT_ALLOWED); + } + } + + /** + * 인증 시간대 포함 여부 (start <= end 일반 케이스 + start > end 자정 넘어가는 케이스 대응) + */ + private boolean isWithinVerificationTime(Challenge challenge, LocalTime now) { + LocalTime start = challenge.getVerifyStartTime(); + LocalTime end = challenge.getVerifyEndTime(); + + // 일반 케이스: 09:00 ~ 22:00 + if (start.isBefore(end) || start.equals(end)) { + return !now.isBefore(start) && !now.isAfter(end); + } + + // 자정 넘어가는 케이스: 22:00 ~ 02:00 + return !now.isBefore(start) || !now.isAfter(end); + } + + /** + * "인증 윈도우 기준 날짜(anchor date)" 계산 + * - 일반 케이스(start<=end): anchor = 해당 시각의 날짜 + * - 자정 넘어가는 케이스(start>end): + * - 22:00~23:59 -> anchor = 오늘 + * - 00:00~02:00 -> anchor = 어제(시작 요일 기준) + */ + private LocalDate getWindowAnchorDate(Challenge challenge, LocalDateTime dateTime) { + LocalTime start = challenge.getVerifyStartTime(); + LocalTime end = challenge.getVerifyEndTime(); + + if (start.isBefore(end) || start.equals(end)) { + return dateTime.toLocalDate(); + } + + // overnight + LocalTime t = dateTime.toLocalTime(); + if (!t.isBefore(start)) { + return dateTime.toLocalDate(); + } + return dateTime.toLocalDate().minusDays(1); + } + + /** + * 특정 날짜가 챌린지 인증 요일인지 확인 + */ + private boolean isVerificationDay(Challenge challenge, LocalDate date) { + ChallengeDays targetDay = mapToChallengeDays(date.getDayOfWeek()); + List days = challenge.getChallengeDays(); + + return days.stream() + .anyMatch(join -> join.getDayOfWeek() == targetDay); + } + + private ChallengeDays mapToChallengeDays(DayOfWeek dayOfWeek) { + return switch (dayOfWeek) { + case MONDAY -> ChallengeDays.MONDAY; + case TUESDAY -> ChallengeDays.TUESDAY; + case WEDNESDAY -> ChallengeDays.WEDNESDAY; + case THURSDAY -> ChallengeDays.THURSDAY; + case FRIDAY -> ChallengeDays.FRIDAY; + case SATURDAY -> ChallengeDays.SATURDAY; + case SUNDAY -> ChallengeDays.SUNDAY; + }; + } + +} diff --git a/src/main/java/com/hrr/backend/global/response/ErrorCode.java b/src/main/java/com/hrr/backend/global/response/ErrorCode.java index 1abfb33d..fcecaa0b 100644 --- a/src/main/java/com/hrr/backend/global/response/ErrorCode.java +++ b/src/main/java/com/hrr/backend/global/response/ErrorCode.java @@ -123,6 +123,7 @@ public enum ErrorCode implements BaseCode{ ACCESS_DENIED_REPORTED_POST(HttpStatus.CONFLICT, "VERIFICATION40917", "신고 5회 누적으로 제한된 게시글입니다."), CANNOT_REPORT_OWN_POST(HttpStatus.CONFLICT, "VERIFICATION40918", "자기 자신의 인증 게시글은 신고할 수 없습니다."), ALREADY_REPORTED(HttpStatus.CONFLICT, "VERIFICATION40919", "이미 신고한 게시글입니다."), + VERIFICATION_EDIT_DELETE_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "VERIFICATION40020", "인증 요일 및 인증 시간 내에만 수정/삭제할 수 있습니다."), // file upload FILE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE5001", "파일 업로드에 실패했습니다."), From bd879e2719ff87c69280baa0411eb0c2a6e1241a Mon Sep 17 00:00:00 2001 From: minkyung Date: Mon, 5 Jan 2026 00:54:42 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EA=B8=80=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EB=AC=BC=EC=9D=98=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=A5=BC=20image1,=20image2,=20image3?= =?UTF-8?q?=EB=A1=9C=20=EB=8A=98=EB=A6=AC=EA=B3=A0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/VerificationConverter.java | 60 ++++++++++++++++--- .../dto/VerificationDetailResponseDto.java | 4 ++ .../dto/VerificationRequestDto.java | 5 +- .../dto/VerificationResponseDto.java | 7 +++ .../dto/VerificationUpdateRequestDto.java | 4 +- .../verification/entity/Verification.java | 40 ++++++++++--- .../service/VerificationServiceImpl.java | 8 ++- .../V2.24__add_text_image_to_verification.sql | 4 ++ 8 files changed, 110 insertions(+), 22 deletions(-) create mode 100644 src/main/resources/db/migration/V2.24__add_text_image_to_verification.sql diff --git a/src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java b/src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java index feaedd46..4458ebba 100644 --- a/src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java +++ b/src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java @@ -32,9 +32,22 @@ public VerificationResponseDto.FeedDto toFeedDto(Verification verification) { boolean hasLink = verification.getTextUrl() != null && !verification.getTextUrl().isBlank(); // 이미지 URL S3 Full Path 변환 (필요 시) - String fullImageUrl = verification.getPhotoUrl() != null ? - s3UrlUtil.toFullUrl(verification.getPhotoUrl()) : null; - + String fullImageUrl = null; + + if (verification.getType() != null && verification.getType().name().equals("CAMERA")) { + if (verification.getPhotoUrl() != null) { + fullImageUrl = s3UrlUtil.toFullUrl(verification.getPhotoUrl()); + } + } else { + String key = firstNonNull( + verification.getTextImage1(), + verification.getTextImage2(), + verification.getTextImage3() + ); + if (key != null) { + fullImageUrl = s3UrlUtil.toFullUrl(key); + } + } return VerificationResponseDto.FeedDto.builder() .verificationId(verification.getId()) .type(verification.getType()) @@ -68,6 +81,11 @@ public VerificationResponseDto.StatDto toStatDto( * 인증글 생성 직후 응답 DTO 변환 (단건 상세) */ public VerificationResponseDto.CreateResponseDto toResponseDto(Verification verification) { + String fullPhotoUrl = verification.getPhotoUrl() != null ? s3UrlUtil.toFullUrl(verification.getPhotoUrl()) : null; + + String fullTextImage1 = verification.getTextImage1() != null ? s3UrlUtil.toFullUrl(verification.getTextImage1()) : null; + String fullTextImage2 = verification.getTextImage2() != null ? s3UrlUtil.toFullUrl(verification.getTextImage2()) : null; + String fullTextImage3 = verification.getTextImage3() != null ? s3UrlUtil.toFullUrl(verification.getTextImage3()) : null; return VerificationResponseDto.CreateResponseDto.builder() .verificationId(verification.getId()) .roundId(verification.getRoundRecord().getRound().getId()) @@ -76,7 +94,10 @@ public VerificationResponseDto.CreateResponseDto toResponseDto(Verification veri .title(verification.getTitle()) .content(verification.getContent()) .textUrl(verification.getTextUrl()) - .photoUrl(s3UrlUtil.toFullUrl(verification.getPhotoUrl())) + .photoUrl(fullPhotoUrl) + .textImage1(fullTextImage1) + .textImage2(fullTextImage2) + .textImage3(fullTextImage3) .isQuestion(verification.getIsQuestion()) .status(verification.getStatus()) .createdAt(verification.getCreatedAt()) @@ -118,7 +139,12 @@ public VerificationDetailResponseDto toDetailDto( UserChallenge userChallenge = roundRecord.getUserChallenge(); User user = userChallenge.getUser(); - String photoUrl = s3UrlUtil.toFullUrl(verification.getPhotoUrl()); + String fullPhotoUrl = verification.getPhotoUrl() != null ? s3UrlUtil.toFullUrl(verification.getPhotoUrl()) : null; + + String fullTextImage1 = verification.getTextImage1() != null ? s3UrlUtil.toFullUrl(verification.getTextImage1()) : null; + String fullTextImage2 = verification.getTextImage2() != null ? s3UrlUtil.toFullUrl(verification.getTextImage2()) : null; + String fullTextImage3 = verification.getTextImage3() != null ? s3UrlUtil.toFullUrl(verification.getTextImage3()) : null; + VerificationDetailResponseDto.UserInfo userInfo = VerificationDetailResponseDto.UserInfo.builder() @@ -146,7 +172,10 @@ public VerificationDetailResponseDto toDetailDto( .title(verification.getTitle()) .content(verification.getContent()) .textUrl(verification.getTextUrl()) - .photoUrl(photoUrl) + .photoUrl(fullPhotoUrl) + .textImage1(fullTextImage1) + .textImage2(fullTextImage2) + .textImage3(fullTextImage3) .isQuestion(verification.getIsQuestion()) .isResolved(verification.getIsResolved()) .status(verification.getStatus()) @@ -158,7 +187,7 @@ public VerificationDetailResponseDto toDetailDto( .canEdit(canEdit) .canDelete(canDelete) .canSelectComment(canSelectComment) - .canWriteComment(true) // 정책 상 언제든 댓글 작성 가능 + .canWriteComment(true) .adoptedCommentId(adoptedCommentId) .showResolvedBadge(verification.getIsResolved()) .commentCount(comments != null && comments.getComments() != null @@ -173,6 +202,11 @@ public VerificationDetailResponseDto toDetailDto( * Verification 엔티티를 HistoryDto로 변환 */ public VerificationResponseDto.HistoryDto toHistoryDto(Verification verification) { + String fullPhotoUrl = verification.getPhotoUrl() != null ? s3UrlUtil.toFullUrl(verification.getPhotoUrl()) : null; + + String fullTextImage1 = verification.getTextImage1() != null ? s3UrlUtil.toFullUrl(verification.getTextImage1()) : null; + String fullTextImage2 = verification.getTextImage2() != null ? s3UrlUtil.toFullUrl(verification.getTextImage2()) : null; + String fullTextImage3 = verification.getTextImage3() != null ? s3UrlUtil.toFullUrl(verification.getTextImage3()) : null; return VerificationResponseDto.HistoryDto.builder() .verificationId(verification.getId()) .challengeId(verification.getRoundRecord().getUserChallenge().getChallenge().getId()) @@ -180,10 +214,18 @@ public VerificationResponseDto.HistoryDto toHistoryDto(Verification verification .type(verification.getType().name()) .title(verification.getTitle()) .content(verification.getContent()) - .photoUrl(verification.getPhotoUrl() != null ? - s3UrlUtil.toFullUrl(verification.getPhotoUrl()) : null) + .photoUrl(fullPhotoUrl) .textUrl(verification.getTextUrl()) + .textImage1(fullTextImage1) + .textImage2(fullTextImage2) + .textImage3(fullTextImage3) .verifiedAt(verification.getCreatedAt()) .build(); } + private String firstNonNull(String a, String b, String c) { + if (a != null) return a; + if (b != null) return b; + if (c != null) return c; + return null; + } } diff --git a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationDetailResponseDto.java b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationDetailResponseDto.java index 3d98b0e4..a07b6bbf 100644 --- a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationDetailResponseDto.java +++ b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationDetailResponseDto.java @@ -31,6 +31,10 @@ public class VerificationDetailResponseDto { private String textUrl; private String photoUrl; + private String textImage1; + private String textImage2; + private String textImage3; + private Boolean isQuestion; private Boolean isResolved; private VerificationStatus status; diff --git a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java index 050803a8..54c07f53 100644 --- a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java +++ b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java @@ -20,7 +20,10 @@ public class VerificationRequestDto { private String textUrl; - private String photoUrl; + // textImage1/2/3 (null 허용, 최대 3개 필드) + private String textImage1; + private String textImage2; + private String textImage3; private Boolean isQuestion; } \ No newline at end of file diff --git a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java index dc2f1add..074cc673 100644 --- a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java +++ b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java @@ -118,6 +118,9 @@ public static class CreateResponseDto { private String content; private String photoUrl; private String textUrl; + private String textImage1; + private String textImage2; + private String textImage3; private Boolean isQuestion; @Schema(description = "상태 (TEMPORARY / COMPLETED)") @@ -169,6 +172,10 @@ public static class HistoryDto { @Schema(description = "글 URL (글 인증)", example = "https://blog.example.com/post/123") private String textUrl; + private String textImage1; + private String textImage2; + private String textImage3; + @Schema(description = "인증 일시", example = "2025-09-18T08:00:00") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime verifiedAt; diff --git a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java index 27b7b9a4..9cf6fdd2 100644 --- a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java +++ b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java @@ -12,5 +12,7 @@ public class VerificationUpdateRequestDto { @Size(max = 200, message = "내용은 200자를 초과할 수 없습니다.") private String content; private String textUrl; - private String photoUrl; + private String textImage1; + private String textImage2; + private String textImage3; } diff --git a/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java b/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java index b81eb3c0..918f057e 100644 --- a/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java +++ b/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java @@ -54,6 +54,9 @@ public class Verification extends BaseEntity { private String photoUrl; private String textUrl; + private String textImage1; + private String textImage2; + private String textImage3; private Boolean isQuestion; @@ -84,7 +87,9 @@ public static Verification createTextVerification( String title, String content, String textUrl, - String photoUrl, + String textImage1, + String textImage2, + String textImage3, Boolean isQuestion, Long roundId ) { @@ -96,7 +101,9 @@ public static Verification createTextVerification( .title(title) .content(content) .textUrl(textUrl) - .photoUrl(photoUrl) + .textImage1(textImage1) + .textImage2(textImage2) + .textImage3(textImage3) .isQuestion(isQuestion) .status(VerificationStatus.COMPLETED) .isResolved(false) // feat/90 필드 초기화 @@ -142,26 +149,41 @@ public void setUserChallenge(UserChallenge uc) { this.userChallenge = uc; } - public void update(String title, String content, String textUrl, String photoUrl) { + public void update( + String title, + String content, + String textUrl, + String textImage1, + String textImage2, + String textImage3 + ) { if (title != null) { if (title.isBlank()) { throw new GlobalException(ErrorCode.VERIFICATION_TITLE_REQUIRED); - } + } this.title = title; } + if (content != null) { if (content.isBlank()) { - throw new GlobalException(ErrorCode.VERIFICATION_TEXT_REQUIRED);} + throw new GlobalException(ErrorCode.VERIFICATION_TEXT_REQUIRED); + } this.content = content; } + if (textUrl != null) { this.textUrl = textUrl; } - if (photoUrl != null) { - this.photoUrl = photoUrl; + + if (textImage1 != null) { + this.textImage1 = textImage1; + } + if (textImage2 != null) { + this.textImage2 = textImage2; + } + if (textImage3 != null) { + this.textImage3 = textImage3; } } - - } diff --git a/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java b/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java index 16dd7722..c3815b2e 100644 --- a/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java +++ b/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java @@ -200,7 +200,9 @@ public VerificationResponseDto.CreateResponseDto createTextVerification( request.getTitle(), request.getContent(), request.getTextUrl(), - request.getPhotoUrl(), + request.getTextImage1(), + request.getTextImage2(), + request.getTextImage3(), request.getIsQuestion() != null ? request.getIsQuestion() : false, roundId ); @@ -403,7 +405,9 @@ public VerificationDetailResponseDto updateVerification(Long verificationId, Lon requestDto.getTitle(), requestDto.getContent(), requestDto.getTextUrl(), - requestDto.getPhotoUrl() + requestDto.getTextImage1(), + requestDto.getTextImage2(), + requestDto.getTextImage3() ); // 댓글 목록 + 상세 DTO 구성 diff --git a/src/main/resources/db/migration/V2.24__add_text_image_to_verification.sql b/src/main/resources/db/migration/V2.24__add_text_image_to_verification.sql new file mode 100644 index 00000000..cdef69b3 --- /dev/null +++ b/src/main/resources/db/migration/V2.24__add_text_image_to_verification.sql @@ -0,0 +1,4 @@ +ALTER TABLE verification + ADD COLUMN text_image1 VARCHAR(255) NULL, + ADD COLUMN text_image2 VARCHAR(255) NULL, + ADD COLUMN text_image3 VARCHAR(255) NULL; From a34323cb73bf5d37dcab5b361cc5491b21534f5b Mon Sep 17 00:00:00 2001 From: minkyung Date: Mon, 5 Jan 2026 01:47:35 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor:=20enum=20=EC=83=81=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EC=A7=81=EC=A0=91=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=B9=84=EA=B5=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/verification/converter/VerificationConverter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java b/src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java index 4458ebba..5b827e33 100644 --- a/src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java +++ b/src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import com.hrr.backend.domain.user.entity.UserChallenge; +import com.hrr.backend.domain.verification.entity.enums.VerificationPostType; import com.hrr.backend.global.response.SliceResponseDto; import com.hrr.backend.domain.round.entity.RoundRecord; import org.springframework.stereotype.Component; @@ -34,7 +35,7 @@ public VerificationResponseDto.FeedDto toFeedDto(Verification verification) { // 이미지 URL S3 Full Path 변환 (필요 시) String fullImageUrl = null; - if (verification.getType() != null && verification.getType().name().equals("CAMERA")) { + if (verification.getType() != null && verification.getType() == VerificationPostType.CAMERA) { if (verification.getPhotoUrl() != null) { fullImageUrl = s3UrlUtil.toFullUrl(verification.getPhotoUrl()); } From b3c1ba859f8ac8435e83bf0bdb8b7dfa307b3873 Mon Sep 17 00:00:00 2001 From: minkyung Date: Mon, 5 Jan 2026 01:56:38 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EB=B9=97=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/VerificationRequestDto.java | 4 ++- .../dto/VerificationUpdateRequestDto.java | 4 +++ .../verification/entity/Verification.java | 25 +++++++++++++------ .../backend/global/response/ErrorCode.java | 2 +- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java index 54c07f53..a75432f4 100644 --- a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java +++ b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java @@ -20,9 +20,11 @@ public class VerificationRequestDto { private String textUrl; - // textImage1/2/3 (null 허용, 최대 3개 필드) + @Schema(description = "글 인증 첨부 이미지1(S3 Key)", nullable = true) private String textImage1; + @Schema(description = "글 인증 첨부 이미지2(S3 Key)", nullable = true) private String textImage2; + @Schema(description = "글 인증 첨부 이미지3(S3 Key)", nullable = true) private String textImage3; private Boolean isQuestion; diff --git a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java index 9cf6fdd2..2f065a40 100644 --- a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java +++ b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java @@ -1,5 +1,6 @@ package com.hrr.backend.domain.verification.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Size; import lombok.Builder; import lombok.Getter; @@ -12,7 +13,10 @@ public class VerificationUpdateRequestDto { @Size(max = 200, message = "내용은 200자를 초과할 수 없습니다.") private String content; private String textUrl; + @Schema(description = "글 인증 첨부 이미지1(S3 Key)", nullable = true) private String textImage1; + @Schema(description = "글 인증 첨부 이미지2(S3 Key)", nullable = true) private String textImage2; + @Schema(description = "글 인증 첨부 이미지3(S3 Key)", nullable = true) private String textImage3; } diff --git a/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java b/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java index 918f057e..7981e24c 100644 --- a/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java +++ b/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java @@ -170,20 +170,29 @@ public void update( } this.content = content; } - - if (textUrl != null) { - this.textUrl = textUrl; - } - if (textImage1 != null) { - this.textImage1 = textImage1; + this.textImage1 = normalizeOptionalImageKey(textImage1); } if (textImage2 != null) { - this.textImage2 = textImage2; + this.textImage2 = normalizeOptionalImageKey(textImage2); } if (textImage3 != null) { - this.textImage3 = textImage3; + this.textImage3 = normalizeOptionalImageKey(textImage3); + } + + if (isQuestion != null) { + this.isQuestion = isQuestion; + } + } + private String normalizeOptionalImageKey(String key) { + String trimmed = key.trim(); + if (trimmed.isEmpty()) { + throw new GlobalException(ErrorCode.VERIFICATION_INVALID_IMAGE_KEY); + } + if ("null".equalsIgnoreCase(trimmed)) { + throw new GlobalException(ErrorCode.VERIFICATION_INVALID_IMAGE_KEY); } + return trimmed; } } diff --git a/src/main/java/com/hrr/backend/global/response/ErrorCode.java b/src/main/java/com/hrr/backend/global/response/ErrorCode.java index fcecaa0b..2896e280 100644 --- a/src/main/java/com/hrr/backend/global/response/ErrorCode.java +++ b/src/main/java/com/hrr/backend/global/response/ErrorCode.java @@ -124,7 +124,7 @@ public enum ErrorCode implements BaseCode{ CANNOT_REPORT_OWN_POST(HttpStatus.CONFLICT, "VERIFICATION40918", "자기 자신의 인증 게시글은 신고할 수 없습니다."), ALREADY_REPORTED(HttpStatus.CONFLICT, "VERIFICATION40919", "이미 신고한 게시글입니다."), VERIFICATION_EDIT_DELETE_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "VERIFICATION40020", "인증 요일 및 인증 시간 내에만 수정/삭제할 수 있습니다."), - + VERIFICATION_INVALID_IMAGE_KEY(HttpStatus.BAD_REQUEST, "VERIFICATION4008", "이미지 키는 null 또는 공백이 아닌 값이어야 합니다."), // file upload FILE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE5001", "파일 업로드에 실패했습니다."), FILE_SIZE_EXCEEDED(HttpStatus.BAD_REQUEST, "FILE4002", "파일 크기가 제한을 초과했습니다."), From ed8d8dbb8815ce10b8464dfc8cac5a0b1303615a Mon Sep 17 00:00:00 2001 From: minkyung Date: Mon, 5 Jan 2026 14:44:25 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/hrr/backend/global/response/ErrorCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/hrr/backend/global/response/ErrorCode.java b/src/main/java/com/hrr/backend/global/response/ErrorCode.java index 2896e280..4b47a8f6 100644 --- a/src/main/java/com/hrr/backend/global/response/ErrorCode.java +++ b/src/main/java/com/hrr/backend/global/response/ErrorCode.java @@ -124,7 +124,7 @@ public enum ErrorCode implements BaseCode{ CANNOT_REPORT_OWN_POST(HttpStatus.CONFLICT, "VERIFICATION40918", "자기 자신의 인증 게시글은 신고할 수 없습니다."), ALREADY_REPORTED(HttpStatus.CONFLICT, "VERIFICATION40919", "이미 신고한 게시글입니다."), VERIFICATION_EDIT_DELETE_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "VERIFICATION40020", "인증 요일 및 인증 시간 내에만 수정/삭제할 수 있습니다."), - VERIFICATION_INVALID_IMAGE_KEY(HttpStatus.BAD_REQUEST, "VERIFICATION4008", "이미지 키는 null 또는 공백이 아닌 값이어야 합니다."), + VERIFICATION_INVALID_IMAGE_KEY(HttpStatus.BAD_REQUEST, "VERIFICATION40021", "이미지 키는 null 또는 공백이 아닌 값이어야 합니다."), // file upload FILE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE5001", "파일 업로드에 실패했습니다."), FILE_SIZE_EXCEEDED(HttpStatus.BAD_REQUEST, "FILE4002", "파일 크기가 제한을 초과했습니다."), From b7cb616e1a1828181b6acf17032a7eb07049f00e Mon Sep 17 00:00:00 2001 From: minkyung Date: Mon, 5 Jan 2026 14:59:37 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix:=20=EB=A9=B1=EB=93=B1=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/verification/dto/VerificationUpdateRequestDto.java | 1 + .../hrr/backend/domain/verification/entity/Verification.java | 3 ++- .../domain/verification/service/VerificationServiceImpl.java | 3 ++- .../db/migration/V2.24__add_text_image_to_verification.sql | 4 ---- .../db/migration/V2.25__add_text_image_to_verification.sql | 3 +++ 5 files changed, 8 insertions(+), 6 deletions(-) delete mode 100644 src/main/resources/db/migration/V2.24__add_text_image_to_verification.sql create mode 100644 src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql diff --git a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java index 2f065a40..27601082 100644 --- a/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java +++ b/src/main/java/com/hrr/backend/domain/verification/dto/VerificationUpdateRequestDto.java @@ -19,4 +19,5 @@ public class VerificationUpdateRequestDto { private String textImage2; @Schema(description = "글 인증 첨부 이미지3(S3 Key)", nullable = true) private String textImage3; + private Boolean isQuestion; } diff --git a/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java b/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java index 7981e24c..eea8c965 100644 --- a/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java +++ b/src/main/java/com/hrr/backend/domain/verification/entity/Verification.java @@ -155,7 +155,8 @@ public void update( String textUrl, String textImage1, String textImage2, - String textImage3 + String textImage3, + Boolean isQuestion ) { if (title != null) { if (title.isBlank()) { diff --git a/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java b/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java index c3815b2e..e8064b18 100644 --- a/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java +++ b/src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java @@ -407,7 +407,8 @@ public VerificationDetailResponseDto updateVerification(Long verificationId, Lon requestDto.getTextUrl(), requestDto.getTextImage1(), requestDto.getTextImage2(), - requestDto.getTextImage3() + requestDto.getTextImage3(), + requestDto.getIsQuestion() ); // 댓글 목록 + 상세 DTO 구성 diff --git a/src/main/resources/db/migration/V2.24__add_text_image_to_verification.sql b/src/main/resources/db/migration/V2.24__add_text_image_to_verification.sql deleted file mode 100644 index cdef69b3..00000000 --- a/src/main/resources/db/migration/V2.24__add_text_image_to_verification.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE verification - ADD COLUMN text_image1 VARCHAR(255) NULL, - ADD COLUMN text_image2 VARCHAR(255) NULL, - ADD COLUMN text_image3 VARCHAR(255) NULL; diff --git a/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql b/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql new file mode 100644 index 00000000..8aa3c8cb --- /dev/null +++ b/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql @@ -0,0 +1,3 @@ +ALTER TABLE verification ADD COLUMN IF NOT EXISTS text_image1 VARCHAR(255) NULL; +ALTER TABLE verification ADD COLUMN IF NOT EXISTS text_image2 VARCHAR(255) NULL; +ALTER TABLE verification ADD COLUMN IF NOT EXISTS text_image3 VARCHAR(255) NULL; From 91ef5240a0dd8a1b5c54aaae429a3c232a478ab1 Mon Sep 17 00:00:00 2001 From: minkyung Date: Mon, 5 Jan 2026 15:20:15 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20db=20migration=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EB=A9=B1=EB=93=B1=EC=84=B1=20=EC=A1=B0=EA=B1=B4=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../V2.25__add_text_image_to_verification.sql | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql b/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql index 8aa3c8cb..7c4c70b1 100644 --- a/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql +++ b/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql @@ -1,3 +1,44 @@ -ALTER TABLE verification ADD COLUMN IF NOT EXISTS text_image1 VARCHAR(255) NULL; -ALTER TABLE verification ADD COLUMN IF NOT EXISTS text_image2 VARCHAR(255) NULL; -ALTER TABLE verification ADD COLUMN IF NOT EXISTS text_image3 VARCHAR(255) NULL; +DROP PROCEDURE IF EXISTS add_verification_text_images; + +DELIMITER $$ + +CREATE PROCEDURE add_verification_text_images() +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'verification' + AND COLUMN_NAME = 'text_image1' + ) THEN +ALTER TABLE verification + ADD COLUMN text_image1 VARCHAR(255) NULL; +END IF; + + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'verification' + AND COLUMN_NAME = 'text_image2' + ) THEN +ALTER TABLE verification + ADD COLUMN text_image2 VARCHAR(255) NULL; +END IF; + + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'verification' + AND COLUMN_NAME = 'text_image3' + ) THEN +ALTER TABLE verification + ADD COLUMN text_image3 VARCHAR(255) NULL; +END IF; +END $$ + +DELIMITER ; + +CALL add_verification_text_images(); +DROP PROCEDURE IF EXISTS add_verification_text_images; From 1660c9e312d4b6bd4aed4e3d4d58b87d0ded8e7f Mon Sep 17 00:00:00 2001 From: minkyung Date: Mon, 5 Jan 2026 17:31:09 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20varchar=20=EA=B8=B8=EC=9D=B4=20512?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db/migration/V2.25__add_text_image_to_verification.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql b/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql index 7c4c70b1..a04dd498 100644 --- a/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql +++ b/src/main/resources/db/migration/V2.25__add_text_image_to_verification.sql @@ -12,7 +12,7 @@ BEGIN AND COLUMN_NAME = 'text_image1' ) THEN ALTER TABLE verification - ADD COLUMN text_image1 VARCHAR(255) NULL; + ADD COLUMN text_image1 VARCHAR(512) NULL; END IF; IF NOT EXISTS ( @@ -23,7 +23,7 @@ END IF; AND COLUMN_NAME = 'text_image2' ) THEN ALTER TABLE verification - ADD COLUMN text_image2 VARCHAR(255) NULL; + ADD COLUMN text_image2 VARCHAR(512) NULL; END IF; IF NOT EXISTS ( @@ -34,7 +34,7 @@ END IF; AND COLUMN_NAME = 'text_image3' ) THEN ALTER TABLE verification - ADD COLUMN text_image3 VARCHAR(255) NULL; + ADD COLUMN text_image3 VARCHAR(512) NULL; END IF; END $$