From 48f8b396041628dcc9ee1e8361c17a9192f66193 Mon Sep 17 00:00:00 2001 From: KSH0326 Date: Sat, 4 Oct 2025 22:33:00 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EB=A9=94=EB=AA=A8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/memo/controller/MemoController.java | 61 ++++++++++++++ .../domain/study/memo/dto/MemoRequestDto.java | 15 ++++ .../study/memo/dto/MemoResponseDto.java | 24 ++++++ .../back/domain/study/memo/entity/Memo.java | 38 +++++++++ .../study/memo/repository/MemoRepository.java | 15 ++++ .../study/memo/service/MemoService.java | 81 +++++++++++++++++++ .../com/back/global/exception/ErrorCode.java | 3 + 7 files changed, 237 insertions(+) create mode 100644 src/main/java/com/back/domain/study/memo/controller/MemoController.java create mode 100644 src/main/java/com/back/domain/study/memo/dto/MemoRequestDto.java create mode 100644 src/main/java/com/back/domain/study/memo/dto/MemoResponseDto.java create mode 100644 src/main/java/com/back/domain/study/memo/entity/Memo.java create mode 100644 src/main/java/com/back/domain/study/memo/repository/MemoRepository.java create mode 100644 src/main/java/com/back/domain/study/memo/service/MemoService.java diff --git a/src/main/java/com/back/domain/study/memo/controller/MemoController.java b/src/main/java/com/back/domain/study/memo/controller/MemoController.java new file mode 100644 index 00000000..5a0a6eae --- /dev/null +++ b/src/main/java/com/back/domain/study/memo/controller/MemoController.java @@ -0,0 +1,61 @@ +package com.back.domain.study.memo.controller; + + +import com.back.domain.study.memo.dto.MemoRequestDto; +import com.back.domain.study.memo.dto.MemoResponseDto; +import com.back.domain.study.memo.service.MemoService; +import com.back.global.common.dto.RsData; +import com.back.global.security.user.CustomUserDetails; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/memos") +public class MemoController { + private final MemoService memoService; + + // ==================== 생성 및 수정 =================== + // 메모 생성 및 수정 + @PostMapping + public ResponseEntity> createOrUpdateMemo( + @AuthenticationPrincipal CustomUserDetails user, + @Valid @RequestBody MemoRequestDto request + ) { + Long userId = user.getUserId(); + MemoResponseDto response = memoService.createOrUpdateMemo(userId, request); + return ResponseEntity.ok(RsData.success("메모가 저장되었습니다.", response)); + } + + // ==================== 조회 =================== + // 날짜별 메모 조회 + @GetMapping + public ResponseEntity> getMemoByDate( + @AuthenticationPrincipal CustomUserDetails user, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date + ) { + Long userId = user.getUserId(); + MemoResponseDto response = memoService.getMemoByDate(userId, date); + return ResponseEntity.ok(RsData.success("메모를 조회했습니다.", response)); + } + + // ==================== 삭제 =================== + // 메모 삭제 + @DeleteMapping("/{memoId}") + public ResponseEntity> deleteMemo( + @AuthenticationPrincipal CustomUserDetails user, + @PathVariable Long memoId + ) { + Long userId = user.getUserId(); + memoService.deleteMemo(userId, memoId); + MemoResponseDto response = memoService.deleteMemo(userId, memoId); + return ResponseEntity.ok(RsData.success("메모가 삭제되었습니다.", response)); + } +} diff --git a/src/main/java/com/back/domain/study/memo/dto/MemoRequestDto.java b/src/main/java/com/back/domain/study/memo/dto/MemoRequestDto.java new file mode 100644 index 00000000..8b5318bb --- /dev/null +++ b/src/main/java/com/back/domain/study/memo/dto/MemoRequestDto.java @@ -0,0 +1,15 @@ +package com.back.domain.study.memo.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Getter +@NoArgsConstructor +public class MemoRequestDto { + private LocalDate date; + + private String description; + +} diff --git a/src/main/java/com/back/domain/study/memo/dto/MemoResponseDto.java b/src/main/java/com/back/domain/study/memo/dto/MemoResponseDto.java new file mode 100644 index 00000000..422152a9 --- /dev/null +++ b/src/main/java/com/back/domain/study/memo/dto/MemoResponseDto.java @@ -0,0 +1,24 @@ +package com.back.domain.study.memo.dto; + +import com.back.domain.study.memo.entity.Memo; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Getter +@NoArgsConstructor +public class MemoResponseDto { + private Long id; + private LocalDate date; + private String description; + + // 엔티티 -> DTO 변환 + public static MemoResponseDto from(Memo memo) { + MemoResponseDto dto = new MemoResponseDto(); + dto.id = memo.getId(); + dto.date = memo.getDate(); + dto.description = memo.getDescription(); + return dto; + } +} diff --git a/src/main/java/com/back/domain/study/memo/entity/Memo.java b/src/main/java/com/back/domain/study/memo/entity/Memo.java new file mode 100644 index 00000000..427d3ad6 --- /dev/null +++ b/src/main/java/com/back/domain/study/memo/entity/Memo.java @@ -0,0 +1,38 @@ +package com.back.domain.study.memo.entity; + +import com.back.domain.user.entity.User; +import com.back.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Entity +@Getter +@NoArgsConstructor +public class Memo extends BaseEntity { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(nullable = false) + private LocalDate date; + + @Column(columnDefinition = "TEXT") + private String description; + + // setter 사용 안하고 메서드를 이용 + // 생성 + public static Memo create(User user, LocalDate date, String description) { + Memo memo = new Memo(); + memo.user = user; + memo.date = date; + memo.description = description; + return memo; + } + // 수정 + public void update(String description) { + this.description = description; + } +} diff --git a/src/main/java/com/back/domain/study/memo/repository/MemoRepository.java b/src/main/java/com/back/domain/study/memo/repository/MemoRepository.java new file mode 100644 index 00000000..43132dbd --- /dev/null +++ b/src/main/java/com/back/domain/study/memo/repository/MemoRepository.java @@ -0,0 +1,15 @@ +package com.back.domain.study.memo.repository; + +import com.back.domain.study.memo.entity.Memo; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.Optional; + +@Repository +public interface MemoRepository extends JpaRepository { + // 사용자의 날짜별 메모 조회 + Optional findByUserIdAndDate(Long userId, LocalDate date); + +} diff --git a/src/main/java/com/back/domain/study/memo/service/MemoService.java b/src/main/java/com/back/domain/study/memo/service/MemoService.java new file mode 100644 index 00000000..55b8c4bf --- /dev/null +++ b/src/main/java/com/back/domain/study/memo/service/MemoService.java @@ -0,0 +1,81 @@ +package com.back.domain.study.memo.service; + +import com.back.domain.study.memo.dto.MemoRequestDto; +import com.back.domain.study.memo.dto.MemoResponseDto; +import com.back.domain.study.memo.entity.Memo; +import com.back.domain.study.memo.repository.MemoRepository; +import com.back.domain.user.entity.User; +import com.back.domain.user.repository.UserRepository; +import com.back.global.exception.CustomException; +import com.back.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemoService { + private final MemoRepository memoRepository; + private final UserRepository userRepository; + + // ==================== 생성 및 수정 =================== + // 같은 날짜에 메모가 있으면 수정, 없으면 생성 + @Transactional + public MemoResponseDto createOrUpdateMemo(Long userId, MemoRequestDto request) { + // 사용자 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + + // 같은 날짜의 메모가 있는지 확인 + Optional existingMemo = memoRepository.findByUserIdAndDate(userId, request.getDate()); + + Memo memo; + if (existingMemo.isPresent()) { + // 기존 메모 수정 + memo = existingMemo.get(); + memo.update(request.getDescription()); + } else { + // 새 메모 생성 + memo = Memo.create(user, request.getDate(), request.getDescription()); + memo = memoRepository.save(memo); + } + + return MemoResponseDto.from(memo); + } + + // ==================== 조회 =================== + public MemoResponseDto getMemoByDate(Long userId, LocalDate date) { + // 사용자 존재 확인 + if (!userRepository.existsById(userId)) { + throw new CustomException(ErrorCode.USER_NOT_FOUND); + } + + // 메모를 조회해서 해당 날짜에 없으면 null + return memoRepository.findByUserIdAndDate(userId, date) + .map(MemoResponseDto::from) + .orElse(null); + } + + // ==================== 삭제 =================== + @Transactional + public MemoResponseDto deleteMemo(Long userId, Long memoId) { + Memo memo = memoRepository.findById(memoId) + .orElseThrow(() -> new CustomException(ErrorCode.MEMO_NOT_FOUND)); + + // 권한 확인 + if (!memo.getUser().getId().equals(userId)) { + throw new CustomException(ErrorCode.FORBIDDEN); + } + + MemoResponseDto memoDto = MemoResponseDto.from(memo); + + memoRepository.delete(memo); + // 삭제된 메모 정보 반환 + return memoDto; + } + +} diff --git a/src/main/java/com/back/global/exception/ErrorCode.java b/src/main/java/com/back/global/exception/ErrorCode.java index 67266f4a..e416d463 100644 --- a/src/main/java/com/back/global/exception/ErrorCode.java +++ b/src/main/java/com/back/global/exception/ErrorCode.java @@ -53,6 +53,9 @@ public enum ErrorCode { TODO_NOT_FOUND(HttpStatus.NOT_FOUND, "TODO_001", "존재하지 않는 할 일입니다."), TODO_FORBIDDEN(HttpStatus.FORBIDDEN, "TODO_002", "할 일에 대한 접근 권한이 없습니다."), + // ======================== 알림 관련 ======================== + MEMO_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMO_001", "존재하지 않는 메모입니다."), + // ======================== 알림 관련 ======================== NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTIFICATION_001", "존재하지 않는 알림입니다."), NOTIFICATION_FORBIDDEN(HttpStatus.FORBIDDEN, "NOTIFICATION_002", "알림에 대한 접근 권한이 없습니다."), From e6048730bb9a1fa6f8af30f867ae4290a21bf192 Mon Sep 17 00:00:00 2001 From: KSH0326 Date: Sat, 4 Oct 2025 22:35:30 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=EC=82=AD=EC=A0=9C=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=202=EB=B2=88=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/back/domain/study/memo/controller/MemoController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/back/domain/study/memo/controller/MemoController.java b/src/main/java/com/back/domain/study/memo/controller/MemoController.java index 5a0a6eae..5e35181c 100644 --- a/src/main/java/com/back/domain/study/memo/controller/MemoController.java +++ b/src/main/java/com/back/domain/study/memo/controller/MemoController.java @@ -54,7 +54,6 @@ public ResponseEntity> deleteMemo( @PathVariable Long memoId ) { Long userId = user.getUserId(); - memoService.deleteMemo(userId, memoId); MemoResponseDto response = memoService.deleteMemo(userId, memoId); return ResponseEntity.ok(RsData.success("메모가 삭제되었습니다.", response)); }