Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 메모 관련 기능 #357

Merged
merged 43 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1a6a40a
feature: init memo
Chan531 Nov 27, 2024
96ef175
feature: init memo service
Chan531 Nov 27, 2024
b98b1ec
feature: init memo entity
Chan531 Nov 27, 2024
f3b6b8c
feature: init memo api
Chan531 Nov 27, 2024
8da9af3
feature: init memo repository
Chan531 Nov 27, 2024
8fb24c1
feature: init memo adapter
Chan531 Nov 27, 2024
9516021
feature: memo entity constructor
Chan531 Nov 27, 2024
25d539e
feature: memo entity to domain
Chan531 Nov 27, 2024
80e044a
feature: save memo
Chan531 Nov 27, 2024
e70f88e
feature: memo create request
Chan531 Nov 27, 2024
6241bcb
feature: memo create response
Chan531 Nov 27, 2024
29021ac
feature: add memo create message
Chan531 Nov 27, 2024
ca7f4c3
feature: create memo
Chan531 Nov 27, 2024
a6c1fb2
feature: create memo api
Chan531 Nov 27, 2024
019e5f3
fix: add transaction
Chan531 Nov 27, 2024
cd2e07f
feature: find memo
Chan531 Nov 27, 2024
86cdd8e
feature: update content
Chan531 Nov 27, 2024
47b36f9
feature: update content
Chan531 Nov 27, 2024
83fe586
feature: memo update request
Chan531 Nov 27, 2024
af74384
feature: find memo
Chan531 Nov 27, 2024
87d40ad
feature: update memo
Chan531 Nov 27, 2024
2e95e70
feature: add memo update message
Chan531 Nov 27, 2024
9f2660b
feature: update memo
Chan531 Nov 27, 2024
fbde455
feature: update memo api
Chan531 Nov 27, 2024
3aeb643
chore: add final keyword
Chan531 Nov 27, 2024
f9543d7
style: modify code format
Chan531 Nov 27, 2024
1708bf9
feature: delete memo
Chan531 Nov 27, 2024
04b8866
feature: delete memo
Chan531 Nov 27, 2024
dbb5d76
feature: add memo delete message
Chan531 Nov 27, 2024
a6a837f
feature: delete memo
Chan531 Nov 27, 2024
a442d43
feature: delete memo api
Chan531 Nov 27, 2024
7024289
feature: add member id
Chan531 Nov 29, 2024
a4da56d
feature: add member id
Chan531 Nov 29, 2024
9646322
feature: find by member id and createdat
Chan531 Nov 29, 2024
0979f80
feature: find by memberid and createdat
Chan531 Nov 29, 2024
8725f94
feature: check routine achieved date
Chan531 Nov 29, 2024
153a859
refactor: check memo exist before delete
Chan531 Nov 29, 2024
a6cc647
chore: edit dto example
Chan531 Nov 30, 2024
e6b362b
chore: change id to memoid
Chan531 Nov 30, 2024
d755abb
chore: edit method name
Chan531 Nov 30, 2024
ce66cf8
refactor: update memo delete
Chan531 Nov 30, 2024
cd1897f
refactor: update validate achieved date
Chan531 Nov 30, 2024
4cec824
refactor: edit parameter
Chan531 Nov 30, 2024
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
62 changes: 62 additions & 0 deletions src/main/java/com/soptie/server/api/controller/MemoApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.soptie.server.api.controller;

import java.security.Principal;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.soptie.server.api.controller.dto.request.memo.CreateMemoRequest;
import com.soptie.server.api.controller.dto.request.memo.ModifyMemoRequest;
import com.soptie.server.api.controller.dto.response.SuccessResponse;
import com.soptie.server.api.controller.dto.response.memo.CreateMemoResponse;
import com.soptie.server.api.controller.generic.SuccessMessage;
import com.soptie.server.domain.memo.MemoService;

import lombok.RequiredArgsConstructor;
import lombok.val;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v3/memos")
public class MemoApi {

private final MemoService memoService;

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public SuccessResponse<CreateMemoResponse> createMemo(
final Principal principal,
@RequestBody final CreateMemoRequest request
) {
val memberId = Long.parseLong(principal.getName());
val response = memoService.create(memberId, request);
return SuccessResponse.success(SuccessMessage.CREATE_MEMO.getMessage(), response);
}

@PatchMapping("/{memoId}")
@ResponseStatus(HttpStatus.OK)
public SuccessResponse<?> modifyMemo(
final Principal principal,
@PathVariable final long memoId,
@RequestBody final ModifyMemoRequest request
) {
val memberId = Long.parseLong(principal.getName());
memoService.modify(memberId, memoId, request);
return SuccessResponse.success(SuccessMessage.UPDATE_MEMO.getMessage());
}

@DeleteMapping("/{memoId}")
@ResponseStatus(HttpStatus.OK)
public SuccessResponse<?> deleteMemo(final Principal principal, @PathVariable final long memoId) {
val memberId = Long.parseLong(principal.getName());
memoService.delete(memberId, memoId);
return SuccessResponse.success(SuccessMessage.DELETE_MEMO.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.soptie.server.api.controller.dto.request.memo;

import java.time.LocalDate;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

public record CreateMemoRequest(
@Schema(description = "달성 날짜", example = "2024-11-30")
@NotNull LocalDate achievedDate,
@Schema(description = "메모 내용", example = "좋은 루틴이구만.")
@NotNull String content
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.soptie.server.api.controller.dto.request.memo;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

public record ModifyMemoRequest(
@Schema(description = "수정할 메모 내용", example = "대단한 루틴이구만.")
@NotNull String content
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.soptie.server.api.controller.dto.response.memo;

import com.soptie.server.domain.memo.Memo;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.Builder;

@Builder(access = AccessLevel.PRIVATE)
public record CreateMemoResponse(
@Schema(description = "추가한 메모 id", example = "1")
long memoId
) {

public static CreateMemoResponse from(Memo memo) {
return CreateMemoResponse.builder()
.memoId(memo.getId())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public enum SuccessMessage {
/* maker */
SUCCESS_GET_MAKER_THEME("메이커 테마 조회 성공"),

/* memo */
CREATE_MEMO("메모 생성 성공"),
UPDATE_MEMO("메모 수정 성공"),
DELETE_MEMO("메모 삭제 성공"),

/* version */
GET_VERSION("버전 조회 성공");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void achieveMemberMission(long memberId, long missionId) {
memberMissionAdapter.update(memberMission);
memberMissionAdapter.flush();
memberMissionAdapter.delete(memberMission);
missionHistoryAdapter.save(memberMission.getId());
missionHistoryAdapter.save(memberMission);
}

public Optional<GetMemberMissionResponse> getMemberMission(long memberId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,16 @@ public AchieveMemberRoutineResponse achieveMemberRoutine(long memberId, long mem

memberRoutine.achieve();
memberRoutineAdapter.update(memberRoutine);
updateHistory(memberRoutineId, isAchievedToday);
updateHistory(memberRoutineId, isAchievedToday, memberId);

return AchieveMemberRoutineResponse.of(memberRoutine, !isAchievedToday);
}

private void updateHistory(long memberRoutineId, boolean isAchievedToday) {
private void updateHistory(long memberRoutineId, boolean isAchievedToday, long memberId) {
if (isAchievedToday) {
routineHistoryAdapter.deleteByRoutineIdAndCreatedAt(memberRoutineId, LocalDate.now());
} else {
routineHistoryAdapter.save(memberRoutineId);
routineHistoryAdapter.save(memberRoutineId, memberId);
}
}

Expand Down
38 changes: 38 additions & 0 deletions src/main/java/com/soptie/server/domain/memo/Memo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.soptie.server.domain.memo;

import java.time.LocalDate;

import com.soptie.server.common.exception.ExceptionCode;
import com.soptie.server.common.exception.SoftieException;
import com.soptie.server.domain.member.Member;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class Memo {
private Long id;
private LocalDate achievedDate;
private String content;
private long memberId;

public Memo(final LocalDate achievedDate, final String content, final Member member) {
this.achievedDate = achievedDate;
this.content = content;
this.memberId = member.getId();
}

public void updateContent(final String content) {
this.content = content;
}

public void validateMember(final long memberId) {
if (this.memberId != memberId) {
throw new SoftieException(ExceptionCode.UNAUTHORIZED, "접근할 수 없는 메모입니다.");
}
}
}
64 changes: 64 additions & 0 deletions src/main/java/com/soptie/server/domain/memo/MemoService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.soptie.server.domain.memo;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.soptie.server.api.controller.dto.request.memo.CreateMemoRequest;
import com.soptie.server.api.controller.dto.request.memo.ModifyMemoRequest;
import com.soptie.server.api.controller.dto.response.memo.CreateMemoResponse;
import com.soptie.server.common.exception.ExceptionCode;
import com.soptie.server.common.exception.SoftieException;
import com.soptie.server.persistence.adapter.MemberAdapter;
import com.soptie.server.persistence.adapter.MemoAdapter;
import com.soptie.server.persistence.adapter.mission.MissionHistoryAdapter;
import com.soptie.server.persistence.adapter.routine.RoutineHistoryAdapter;

import lombok.RequiredArgsConstructor;
import lombok.val;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemoService {

private final MemberAdapter memberAdapter;
private final MemoAdapter memoAdapter;
private final MissionHistoryAdapter missionHistoryAdapter;
private final RoutineHistoryAdapter routineHistoryAdapter;

@Transactional
public CreateMemoResponse create(final long memberId, final CreateMemoRequest request) {
val member = memberAdapter.findById(memberId);
validateAchievedDate(memberId, request);
val memo = memoAdapter.save(request.achievedDate(), request.content(), member);
return CreateMemoResponse.from(memo);
}

@Transactional
public void modify(final long memberId, final long memoId, final ModifyMemoRequest request) {
val memo = memoAdapter.findByIdAndMemberId(memoId, memberId);
memo.updateContent(request.content());
memoAdapter.update(memo);
}

@Transactional
public void delete(final long memberId, final long memoId) {
val memo = memoAdapter.findById(memoId);
memo.validateMember(memberId);
memoAdapter.delete(memo);
}

private void validateAchievedDate(final long memberId, final CreateMemoRequest request) {
if (hasNoRoutineHistory(memberId, request) && hasNoMissionHistory(memberId, request)) {
throw new SoftieException(ExceptionCode.BAD_REQUEST, "해당 날짜에 루틴 및 미션 달성 내역이 존재하지 않습니다.");
}
}

private boolean hasNoRoutineHistory(final long memberId, final CreateMemoRequest request) {
return !routineHistoryAdapter.isExistByMemberIdAndCreatedAt(memberId, request.achievedDate());
}

private boolean hasNoMissionHistory(final long memberId, final CreateMemoRequest request) {
return !missionHistoryAdapter.isExistByMemberIdAndCreatedAt(memberId, request.achievedDate());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.soptie.server.persistence.adapter;

import java.time.LocalDate;

import com.soptie.server.common.exception.ExceptionCode;
import com.soptie.server.common.exception.SoftieException;
import com.soptie.server.common.support.RepositoryAdapter;
import com.soptie.server.domain.member.Member;
import com.soptie.server.domain.memo.Memo;
import com.soptie.server.persistence.entity.MemoEntity;
import com.soptie.server.persistence.repository.MemoRepository;

import lombok.RequiredArgsConstructor;
import lombok.val;

@RepositoryAdapter
@RequiredArgsConstructor
public class MemoAdapter {

private final MemoRepository memoRepository;

public Memo save(final LocalDate achievedDate, final String content, final Member member) {
return memoRepository.save(new MemoEntity(achievedDate, content, member)).toDomain();
}

public void update(final Memo memo) {
val memoEntity = find(memo.getId());
memoEntity.update(memo);
}

public void delete(final Memo memo) {
memoRepository.deleteById(memo.getId());
}

public Memo findByIdAndMemberId(final long memoId, final long memberId) {
return memoRepository.findByIdAndMemberId(memoId, memberId)
.orElseThrow(() -> new SoftieException(
ExceptionCode.NOT_FOUND,
"Member ID: " + memberId + " Memo ID: " + memoId)).toDomain();
}

public Memo findById(final long memoId) {
return memoRepository.findById(memoId)
.orElseThrow(() -> new SoftieException(
ExceptionCode.NOT_FOUND,
" Memo ID: " + memoId)).toDomain();
}

private MemoEntity find(final long id) {
return memoRepository.findById(id)
.orElseThrow(() -> new SoftieException(ExceptionCode.NOT_FOUND, "Memo ID: " + id));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.soptie.server.persistence.adapter.mission;

import java.time.LocalDate;

import org.springframework.stereotype.Component;

import com.soptie.server.domain.membermission.MemberMission;
import com.soptie.server.persistence.entity.mission.MissionHistoryEntity;
import com.soptie.server.persistence.repository.mission.MissionHistoryRepository;

Expand All @@ -12,11 +15,15 @@
public class MissionHistoryAdapter {
private final MissionHistoryRepository historyRepository;

public void save(long memberMissionId) {
historyRepository.save(new MissionHistoryEntity(memberMissionId));
public void save(final MemberMission memberMission) {
historyRepository.save(new MissionHistoryEntity(memberMission.getMissionId(), memberMission.getMemberId()));
}

public void deleteById(long historyId) {
historyRepository.deleteById(historyId);
}

public boolean isExistByMemberIdAndCreatedAt(final long memberId, final LocalDate date) {
return historyRepository.findByMemberIdAndCreatedAt(memberId, date).isPresent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
public class RoutineHistoryAdapter {
private final RoutineHistoryRepository historyRepository;

public void save(long memberRoutineId) {
historyRepository.save(new RoutineHistoryEntity(memberRoutineId));
public void save(long memberRoutineId, long memberId) {
historyRepository.save(new RoutineHistoryEntity(memberRoutineId, memberId));
}

public void deleteByRoutineIdAndCreatedAt(long memberRoutineId, LocalDate date) {
Expand All @@ -25,4 +25,8 @@ public void deleteByRoutineIdAndCreatedAt(long memberRoutineId, LocalDate date)
public void deleteById(long id) {
historyRepository.deleteById(id);
}

public boolean isExistByMemberIdAndCreatedAt(final long memberId, final LocalDate date) {
return historyRepository.findByMemberIdAndCreatedAt(memberId, date).isPresent();
}
}
Loading