diff --git a/src/main/java/com/example/egobook_be/domain/letters/dto/request/CreateLetterRequest.java b/src/main/java/com/example/egobook_be/domain/letters/dto/request/CreateLetterRequest.java index 9c67703..8536cd3 100644 --- a/src/main/java/com/example/egobook_be/domain/letters/dto/request/CreateLetterRequest.java +++ b/src/main/java/com/example/egobook_be/domain/letters/dto/request/CreateLetterRequest.java @@ -23,7 +23,8 @@ public class CreateLetterRequest { @Schema(description = "편지 내용(360자 이하)", example = "요즘 자꾸 불안해져서... 누가 한마디 해주면 좋겠어.") private String text; + @Schema(description = "배경 기본은 하얀색", example = "WHITE", nullable = true) - @NotNull private PlazaLetterColor backgroundColor; + } diff --git a/src/main/java/com/example/egobook_be/domain/letters/dto/response/CreateLetterResponse.java b/src/main/java/com/example/egobook_be/domain/letters/dto/response/CreateLetterResponse.java index 04b44fa..151749a 100644 --- a/src/main/java/com/example/egobook_be/domain/letters/dto/response/CreateLetterResponse.java +++ b/src/main/java/com/example/egobook_be/domain/letters/dto/response/CreateLetterResponse.java @@ -14,5 +14,6 @@ public record CreateLetterResponse( PlazaLetterMode mode, String fromLabel, String backgroundColor, - OffsetDateTime createdAt -) {} \ No newline at end of file + OffsetDateTime createdAt, + String backgroundImageUrl + ) {} \ No newline at end of file diff --git a/src/main/java/com/example/egobook_be/domain/letters/enums/LettersErrorCode.java b/src/main/java/com/example/egobook_be/domain/letters/enums/LettersErrorCode.java index b180ddd..25c9a11 100644 --- a/src/main/java/com/example/egobook_be/domain/letters/enums/LettersErrorCode.java +++ b/src/main/java/com/example/egobook_be/domain/letters/enums/LettersErrorCode.java @@ -20,6 +20,9 @@ public enum LettersErrorCode implements BaseErrorCode { INVALID_REPORT_REASON(HttpStatus.BAD_REQUEST, "PLAZA400_INVALID_REPORT_REASON", "잘못된 신고 사유입니다."), ALREADY_REPORTED(HttpStatus.BAD_REQUEST, "PLAZA400_ALREADY_REPORTED", "이미 신고한 답장입니다."), NOT_FRIEND(HttpStatus.BAD_REQUEST, "PLAZA400_NOT_FRIEND", "친구 관계가 아닙니다."), + INSUFFICIENT_INK(HttpStatus.BAD_REQUEST, "PLAZA400_INSUFFICIENT_INK", "잉크가 부족합니다."), + + @@ -34,6 +37,7 @@ public enum LettersErrorCode implements BaseErrorCode { USER_NOT_FOUND(HttpStatus.NOT_FOUND,"PLAZA404_USER_NOT_FOUND", "유저를 찾을 수 없어요."), + // 409 ALREADY_REPLIED(HttpStatus.CONFLICT, "PLAZA409_ALREADY_REPLIED", "이미 답장한 편지예요"), ALREADY_GAVE_UP(HttpStatus.CONFLICT, "PLAZA409_ALREADY_GAVE_UP", "24시간이 지나 답장할 수 없어요"), diff --git a/src/main/java/com/example/egobook_be/domain/letters/enums/PlazaLetterColor.java b/src/main/java/com/example/egobook_be/domain/letters/enums/PlazaLetterColor.java index aa4bcc1..39e64f7 100644 --- a/src/main/java/com/example/egobook_be/domain/letters/enums/PlazaLetterColor.java +++ b/src/main/java/com/example/egobook_be/domain/letters/enums/PlazaLetterColor.java @@ -16,5 +16,6 @@ public enum PlazaLetterColor { public int getPrice() { return price; } + } diff --git a/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterService.java b/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterService.java index 44304e9..f9a1e2a 100644 --- a/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterService.java +++ b/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterService.java @@ -36,6 +36,7 @@ import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Value; import java.time.*; @@ -66,6 +67,10 @@ public class PlazaLetterService { private final NotificationService notificationService; + @Value("${spring.cloud.aws.cloudfront.domain}") + private String cloudfrontDomain; + + @Transactional(readOnly = true) public InboxNextResponse getNextArrivedLetter(Long userId) { return plazaLetterRepository @@ -118,8 +123,35 @@ public CreateLetterResponse createLetter(Long userId, CreateLetterRequest reques } } + // ========================= + // 편지지 색상/잉크 결제 로직 + // - null 또는 WHITE => WHITE로 확정, price=0, 차감 없음 + // - 유료 색상 => 잉크 부족 체크 + 차감 + 로그 + // ========================= + // 1. 색상 안전 처리 + PlazaLetterColor color = + (request.getBackgroundColor() == null) + ? PlazaLetterColor.WHITE + : request.getBackgroundColor(); + +// 2. 가격 조회 + int price = color.getPrice(); // WHITE면 0 + +// 3. 잉크 차감 (유료일 때만) + if (price > 0) { + user.usingInk(price); // 부족하면 여기서 예외 발생 + + InkLog inkLog = InkLog.builder() + .user(user) + .amount(-price) + .reason(InkLogType.LETTER_COLOR_PURCHASE) + .build(); + + inkLogRepository.save(inkLog); + } + + PlazaLetterThread thread = plazaLetterThreadRepository.save(PlazaLetterThread.createNow()); - String bg = request.getBackgroundColor() == null ? "WHITE" : request.getBackgroundColor().name(); String fromLabel = "익명"; if (request.getMode() == PlazaLetterMode.FRIEND) { @@ -162,11 +194,11 @@ public CreateLetterResponse createLetter(Long userId, CreateLetterRequest reques .mode(request.getMode()) .fromLabel(fromLabel) .content(request.getText()) - .backgroundColor(PlazaLetterColor.valueOf(bg)) - .status(status) // ARRIVED or WAITING + .backgroundColor(color) + .status(status) .createdAt(now) - .arrivedAt(arrivedAt) // WAITING이면 null - .replyDeadlineAt(replyDeadlineAt) // WAITING이면 null + .arrivedAt(arrivedAt) + .replyDeadlineAt(replyDeadlineAt) .build(); // ================================================= @@ -216,6 +248,14 @@ public CreateLetterResponse createLetter(Long userId, CreateLetterRequest reques letter.getSenderId(), saved.getLetterId(), e); } + + String imageUrl = null; + if (saved.getBackgroundColor() != PlazaLetterColor.WHITE) { + String fileName = saved.getBackgroundColor().name().substring(0, 1) + + saved.getBackgroundColor().name().substring(1).toLowerCase(); + imageUrl = cloudfrontDomain + "/letter/" + fileName + ".png"; + } + return CreateLetterResponse.builder() .letterId(saved.getLetterId()) .threadId(saved.getThreadId()) @@ -223,6 +263,7 @@ public CreateLetterResponse createLetter(Long userId, CreateLetterRequest reques .mode(saved.getMode()) .fromLabel(saved.getFromLabel()) .backgroundColor(saved.getBackgroundColor().name()) + .backgroundImageUrl(imageUrl) .createdAt(saved.getCreatedAt()) .build(); } @@ -269,19 +310,30 @@ private PlazaLetterColor resolveBackgroundColor(String requested) { } + private void validateCreateLetterRequest(CreateLetterRequest request) { - if (request == null || request.getMode() == null) { + if (request == null) { + throw new CustomException(LettersErrorCode.INVALID_MODE); + } + + if (request.getMode() == null) { throw new CustomException(LettersErrorCode.INVALID_MODE); } + String text = request.getText(); if (text == null || text.isBlank() || text.length() > LETTER_TEXT_LIMIT) { throw new CustomException(LettersErrorCode.LETTER_TEXT_LIMIT); } + if (request.getMode() == PlazaLetterMode.FRIEND && request.getToFriendId() == null) { throw new CustomException(LettersErrorCode.FRIEND_ID_REQUIRED); } + + // backgroundColor는 null 허용. + // null/WHITE면 이후 createLetter에서 WHITE로 보정 + price=0 처리함. } + @Transactional public DeleteThreadResponse deleteThread(Long userId, Long threadId) { // 스레드 존재 체크 diff --git a/src/main/java/com/example/egobook_be/domain/user/entity/InkLogType.java b/src/main/java/com/example/egobook_be/domain/user/entity/InkLogType.java index 9203837..a10636f 100644 --- a/src/main/java/com/example/egobook_be/domain/user/entity/InkLogType.java +++ b/src/main/java/com/example/egobook_be/domain/user/entity/InkLogType.java @@ -11,5 +11,6 @@ public enum InkLogType { DAILY_MISSION_REWARD, WEEKLY_MISSION_REWARD, WATCH_AD, - WEEKLY_UNLOCK + WEEKLY_UNLOCK, + LETTER_COLOR_PURCHASE } \ No newline at end of file diff --git a/src/main/java/com/example/egobook_be/domain/user/entity/User.java b/src/main/java/com/example/egobook_be/domain/user/entity/User.java index 74b4c0e..ef33acb 100644 --- a/src/main/java/com/example/egobook_be/domain/user/entity/User.java +++ b/src/main/java/com/example/egobook_be/domain/user/entity/User.java @@ -1,10 +1,12 @@ package com.example.egobook_be.domain.user.entity; import com.example.egobook_be.domain.ego_room.enums.CounselTone; +import com.example.egobook_be.domain.letters.enums.LettersErrorCode; import com.example.egobook_be.domain.shop.entity.UserItem; import com.example.egobook_be.domain.user.enums.RoleType; import com.example.egobook_be.domain.user.enums.UserStatus; import com.example.egobook_be.global.entity.BaseTimeEntity; +import com.example.egobook_be.global.exception.CustomException; import jakarta.persistence.*; import lombok.*; @@ -173,6 +175,16 @@ public void useInk(int price){ this.ink -= price; } + public void usingInk(int price){ + if (price <= 0) return; + + if (this.ink == null || this.ink < price) { + throw new CustomException(LettersErrorCode.INSUFFICIENT_INK); + } + + this.ink -= price; + } + public void updateNotificationEnabled() { this.notificationEnabled = !this.notificationEnabled; }