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..5e35181c --- /dev/null +++ b/src/main/java/com/back/domain/study/memo/controller/MemoController.java @@ -0,0 +1,60 @@ +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(); + 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 2e347ce9..fb119246 100644 --- a/src/main/java/com/back/global/exception/ErrorCode.java +++ b/src/main/java/com/back/global/exception/ErrorCode.java @@ -56,6 +56,9 @@ public enum ErrorCode { // ======================== 학습 기록 관련 ======================== DURATION_MISMATCH(HttpStatus.BAD_REQUEST, "RECORD_001", "받은 duration과 계산된 duration이 5초 이상 차이납니다."), + // ======================== 알림 관련 ======================== + MEMO_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMO_001", "존재하지 않는 메모입니다."), + // ======================== 알림 관련 ======================== NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTIFICATION_001", "존재하지 않는 알림입니다."), NOTIFICATION_FORBIDDEN(HttpStatus.FORBIDDEN, "NOTIFICATION_002", "알림에 대한 접근 권한이 없습니다."),