Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<RsData<MemoResponseDto>> 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<RsData<MemoResponseDto>> 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<RsData<MemoResponseDto>> deleteMemo(
@AuthenticationPrincipal CustomUserDetails user,
@PathVariable Long memoId
) {
Long userId = user.getUserId();
MemoResponseDto response = memoService.deleteMemo(userId, memoId);
return ResponseEntity.ok(RsData.success("메모가 삭제되었습니다.", response));
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/back/domain/study/memo/dto/MemoRequestDto.java
Original file line number Diff line number Diff line change
@@ -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;

}
24 changes: 24 additions & 0 deletions src/main/java/com/back/domain/study/memo/dto/MemoResponseDto.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/back/domain/study/memo/entity/Memo.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Memo, Long> {
// 사용자의 날짜별 메모 조회
Optional<Memo> findByUserIdAndDate(Long userId, LocalDate date);

}
81 changes: 81 additions & 0 deletions src/main/java/com/back/domain/study/memo/service/MemoService.java
Original file line number Diff line number Diff line change
@@ -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<Memo> 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;
}

}
3 changes: 3 additions & 0 deletions src/main/java/com/back/global/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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", "알림에 대한 접근 권한이 없습니다."),
Expand Down