diff --git a/src/main/java/com/cmc/mercury/domain/memo/controller/MemoController.java b/src/main/java/com/cmc/mercury/domain/memo/controller/MemoController.java new file mode 100644 index 0000000..0956b7f --- /dev/null +++ b/src/main/java/com/cmc/mercury/domain/memo/controller/MemoController.java @@ -0,0 +1,59 @@ +package com.cmc.mercury.domain.memo.controller; + +import com.cmc.mercury.domain.memo.dto.MemoCreateRequest; +import com.cmc.mercury.domain.memo.dto.MemoResponse; +import com.cmc.mercury.domain.memo.dto.MemoUpdateRequest; +import com.cmc.mercury.domain.memo.service.MemoService; +import com.cmc.mercury.global.response.SuccessResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; + +@RestController +@RequestMapping("/records/{recordId}/memos") +@RequiredArgsConstructor +@Tag(name = "MemoController", description = "메모 관련 API") +public class MemoController { + + private final MemoService memoService; + + @PostMapping + @Operation(summary = "메모 추가", description = "메모를 생성합니다.") + public SuccessResponse createMemo( + @RequestParam("userId") Long testUserId, + @PathVariable Long recordId, + @RequestBody @Valid MemoCreateRequest request + ) { + return SuccessResponse.created( + memoService.createMemo(testUserId, recordId, request) + ); + } + + @PatchMapping("/{memoId}") + @Operation(summary = "메모 수정", description = "특정 메모를 수정합니다.") + public SuccessResponse updateMemo( + @RequestParam("userId") Long testUserId, + @PathVariable Long recordId, + @PathVariable Long memoId, + @RequestBody MemoUpdateRequest request + ) { + return SuccessResponse.ok( + memoService.updateMemo(testUserId, recordId, memoId, request) + ); + } + + @DeleteMapping("/{memoId}") + @Operation(summary = "메모 삭제", description = "특정 메모를 삭제합니다.") + public SuccessResponse deleteMemo( + @RequestParam("userId") Long testUserId, + @PathVariable Long recordId, + @PathVariable Long memoId + ) { + memoService.deleteMemo(testUserId, recordId, memoId); + return SuccessResponse.ok(new HashMap<>()); + } +} diff --git a/src/main/java/com/cmc/mercury/domain/memo/service/MemoService.java b/src/main/java/com/cmc/mercury/domain/memo/service/MemoService.java new file mode 100644 index 0000000..e33baef --- /dev/null +++ b/src/main/java/com/cmc/mercury/domain/memo/service/MemoService.java @@ -0,0 +1,100 @@ +package com.cmc.mercury.domain.memo.service; + +import com.cmc.mercury.domain.memo.dto.MemoCreateRequest; +import com.cmc.mercury.domain.memo.dto.MemoResponse; +import com.cmc.mercury.domain.memo.dto.MemoUpdateRequest; +import com.cmc.mercury.domain.memo.entity.Memo; +import com.cmc.mercury.domain.memo.repository.MemoRepository; +import com.cmc.mercury.domain.record.entity.Record; +import com.cmc.mercury.domain.record.entity.RecordDetail; +import com.cmc.mercury.domain.record.repository.RecordRepository; +import com.cmc.mercury.global.exception.CustomException; +import com.cmc.mercury.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class MemoService { + + private final MemoRepository memoRepository; + private final RecordRepository recordRepository; + + @Transactional + public MemoResponse createMemo(Long testUserId, Long recordId, MemoCreateRequest request) { + + // Record와 RecordDetail을 함께 조회 (Record가 없으면 RecordDetail도 없음) + Record record = recordRepository.findByIdAndUser_TestUserId(recordId, testUserId) + .orElseThrow(() -> new CustomException(ErrorCode.RECORD_NOT_FOUND)); + + RecordDetail recordDetail = record.getRecordDetail(); + + // Memo 생성 (아직 연관관계 설정 전) + Memo memo = Memo.builder() + .content(request.content()) + .gauge(request.gauge()) + .build(); + + // 연관관계 설정 + recordDetail.addMemo(memo); + + // 메모의 gauge로 RecordDetail의 updatedGauge 업데이트 + recordDetail.updateGauge(request.gauge()); + + Memo savedMemo = memoRepository.save(memo); + + // save()로 인해 즉시 updatedAt 설정됨 => flush 필요없음 + // 메모의 updatedAt으로 record와 recordDetail 업데이트 + record.updateLastModifiedDateWithDetail(savedMemo.getUpdatedAt()); + + return MemoResponse.from(savedMemo, recordId); + + } + + @Transactional + public MemoResponse updateMemo(Long testUserId, Long recordId, Long memoId, MemoUpdateRequest request) { + + Memo memo = validateAndGetMemo(testUserId, recordId, memoId); + + memo.updateContent(request.content()); + + // db에 updatedAt이 바로 반영되어 응답하도록 + memoRepository.flush(); + + Record record = memo.getRecordDetail().getRecord(); + record.updateLastModifiedDateWithDetail(memo.getUpdatedAt()); + + return MemoResponse.from(memo, recordId); + } + + @Transactional + public void deleteMemo(Long testUserId, Long recordId, Long memoId) { + + Memo memo = validateAndGetMemo(testUserId, recordId, memoId); + + memo.getRecordDetail().getMemos().remove(memo); + + memoRepository.delete(memo); + } + + private Memo validateAndGetMemo(Long testUserId, Long recordId, Long memoId) { + + // Record와 RecordDetail을 함께 조회 (Record가 없으면 RecordDetail도 없음) + Record record = recordRepository.findByIdAndUser_TestUserId(recordId, testUserId) + .orElseThrow(() -> new CustomException(ErrorCode.RECORD_NOT_FOUND)); + + RecordDetail recordDetail = record.getRecordDetail(); + + Memo memo = memoRepository.findById(memoId) + .orElseThrow(() -> new CustomException(ErrorCode.MEMO_NOT_FOUND)); + + if (!memo.getRecordDetail().getId().equals(recordDetail.getId())) { + throw new CustomException(ErrorCode.MEMO_NOT_BELONG_TO_RECORD); + } + + return memo; + } +}