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] 회원 데일리 루틴 삭제 #37

Merged
merged 10 commits into from
Jan 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ public ResponseEntity<Response> entityNotFoundException(EntityNotFoundException
log.error(exception.getMessage());
return ResponseEntity.status(NOT_FOUND).body(fail(exception.getMessage()));
}

@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<Response> illegalStateException(IllegalStateException exception) {
log.error(exception.getMessage());
return ResponseEntity.status(BAD_REQUEST).body(fail(exception.getMessage()));
}
}
12 changes: 7 additions & 5 deletions src/main/java/com/soptie/server/member/message/ErrorMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
@RequiredArgsConstructor
@Getter
public enum ErrorMessage {
EMPTY_MEMBER("존재하지 않는 회원입니다."),
DUPLICATE_USERNAME("이미 존재하는 닉네임입니다."),
INVALID_MEMBER("유효하지 않은 회원입니다."),
INVALID_USERNAME("유효하지 않은 닉네임입니다.");
EMPTY_MEMBER("존재하지 않는 회원입니다."),
DUPLICATE_USERNAME("이미 존재하는 닉네임입니다."),
INVALID_MEMBER("유효하지 않은 회원입니다."),
INVALID_USERNAME("유효하지 않은 닉네임입니다."),
INACCESSIBLE_ROUTINE("회원의 루틴이 아닙니다."),
;

private final String message;
private final String meesage;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import static com.soptie.server.memberRoutine.message.ResponseMessage.*;

import java.net.URI;
import java.nio.file.AccessDeniedException;
import java.security.Principal;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
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;
Expand Down Expand Up @@ -44,4 +47,11 @@ private URI getURI() {
.buildAndExpand()
.toUri();
}

@DeleteMapping("/routine/{routineId}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"/routine/{routineId}" 말고 "/{routineId}"는 좀 그럴려나요??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자원의 표현성으로 의논 완료!

public ResponseEntity<Response> deleteMemberDailyRoutine(Principal principal, @PathVariable Long routineId) {
val memberId = Long.parseLong(principal.getName());
memberDailyRoutineService.deleteMemberDailyRoutine(memberId, routineId);
return ResponseEntity.ok(success(SUCCESS_DELETE_ROUTINE.getMessage()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,15 @@ public class CompletedMemberDailyRoutine {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "routine_id")
private DailyRoutine routine;

public CompletedMemberDailyRoutine(MemberDailyRoutine routine) {
this.achieveCount = routine.getAchieveCount();
setMember(routine);
this.routine = routine.getRoutine();
}

private void setMember(MemberDailyRoutine routine) {
routine.getMember().getDailyRoutines().remove(routine);
this.member = routine.getMember();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@Getter
public enum ResponseMessage {
SUCCESS_CREATE_ROUTINE("데일리 루틴 추가 성공"),
SUCCESS_DELETE_ROUTINE("데일리 루틴 삭제 성공"),
;

private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.soptie.server.memberRoutine.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.soptie.server.memberRoutine.entity.daily.CompletedMemberDailyRoutine;

public interface CompletedMemberDailyRoutineRepository extends JpaRepository<CompletedMemberDailyRoutine, Long> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@

public interface MemberDailyRoutineService {
MemberDailyRoutineResponse createMemberDailyRoutine(long memberId, MemberDailyRoutineRequest request);
void deleteMemberDailyRoutine(long memberId, Long routineId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import com.soptie.server.member.repository.MemberRepository;
import com.soptie.server.memberRoutine.dto.MemberDailyRoutineRequest;
import com.soptie.server.memberRoutine.dto.MemberDailyRoutineResponse;
import com.soptie.server.memberRoutine.entity.daily.CompletedMemberDailyRoutine;
import com.soptie.server.memberRoutine.entity.daily.MemberDailyRoutine;
import com.soptie.server.memberRoutine.repository.CompletedMemberDailyRoutineRepository;
import com.soptie.server.memberRoutine.repository.MemberDailyRoutineRepository;
import com.soptie.server.routine.entity.daily.DailyRoutine;
import com.soptie.server.routine.repository.daily.routine.DailyRoutineRepository;
Expand All @@ -26,6 +28,7 @@ public class MemberDailyRoutineServiceImpl implements MemberDailyRoutineService
private final MemberDailyRoutineRepository memberDailyRoutineRepository;
private final MemberRepository memberRepository;
private final DailyRoutineRepository dailyRoutineRepository;
private final CompletedMemberDailyRoutineRepository completedMemberDailyRoutineRepository;

@Override
@Transactional
Expand All @@ -37,13 +40,43 @@ public MemberDailyRoutineResponse createMemberDailyRoutine(long memberId, Member
return MemberDailyRoutineResponse.of(savedMemberRoutine.getId());
}

private DailyRoutine findRoutine(Long id) {
return dailyRoutineRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(INVALID_ROUTINE.getMessage()));
}

@Override
@Transactional
public void deleteMemberDailyRoutine(long memberId, Long routineId) {
val member = findMember(memberId);
val routine = findMemberRoutine(routineId);
checkRoutineForMember(member, routine);
deleteMemberRoutine(routine);
}

private Member findMember(Long id) {
return memberRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(INVALID_MEMBER.getMessage()));
.orElseThrow(() -> new EntityNotFoundException(INVALID_MEMBER.getMeesage()));
}

private DailyRoutine findRoutine(Long id) {
return dailyRoutineRepository.findById(id)
private MemberDailyRoutine findMemberRoutine(Long id) {
return memberDailyRoutineRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(INVALID_ROUTINE.getMessage()));
}

private void checkRoutineForMember(Member member, MemberDailyRoutine routine) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

체크와 동시에 예외도 터뜨리고 있는데 이럴 시, 메서드명에 예외도 터뜨림을 알려줘야 할까요....??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

논의 완료!

if (!member.getDailyRoutines().contains(routine)) {
throw new IllegalStateException(INACCESSIBLE_ROUTINE.getMeesage());
}
}

private void deleteMemberRoutine(MemberDailyRoutine routine) {
moveCompletedRoutine(routine);
memberDailyRoutineRepository.delete(routine);
}

private void moveCompletedRoutine(MemberDailyRoutine routine) {
val completedRoutine = new CompletedMemberDailyRoutine(routine);
completedMemberDailyRoutineRepository.save(completedRoutine);
}
}
126 changes: 126 additions & 0 deletions src/main/resources/static/docs/open-api-3.0.1.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,53 @@
}
}
},
"/api/v1/routines/daily/member" : {
"post" : {
"tags" : [ "MEMBER DAILY ROUTINE" ],
"summary" : "회원 데일리 루틴 추가 성공",
"description" : "회원 데일리 루틴 추가 성공",
"operationId" : "post-routine-docs",
"requestBody" : {
"content" : {
"application/json;charset=UTF-8" : {
"schema" : {
"$ref" : "#/components/schemas/api-v1-routines-daily-member1721287602"
},
"examples" : {
"post-routine-docs" : {
"value" : "{\n \"routineId\" : 1\n}"
}
}
}
}
},
"responses" : {
"201" : {
"description" : "201",
"headers" : {
"Location" : {
"description" : "Redirect URI",
"schema" : {
"type" : "string"
}
}
},
"content" : {
"application/json;charset=UTF-8" : {
"schema" : {
"$ref" : "#/components/schemas/api-v1-routines-daily-member1327124516"
},
"examples" : {
"post-routine-docs" : {
"value" : "{\n \"success\" : true,\n \"message\" : \"루틴 추가 성공\",\n \"data\" : {\n \"routineId\" : 1\n }\n}"
}
}
}
}
}
}
}
},
"/api/v1/routines/daily/themes" : {
"get" : {
"tags" : [ "DAILY ROUTINE" ],
Expand Down Expand Up @@ -127,6 +174,40 @@
}
}
}
},
"/api/v1/routines/daily/member/routine/{routineId}" : {
"delete" : {
"tags" : [ "MEMBER DAILY ROUTINE" ],
"summary" : "회원 데일리 루틴 삭제 성공",
"description" : "회원 데일리 루틴 삭제 성공",
"operationId" : "delete-routine-docs",
"parameters" : [ {
"name" : "routineId",
"in" : "path",
"description" : "루틴 id",
"required" : true,
"schema" : {
"type" : "string"
}
} ],
"responses" : {
"200" : {
"description" : "200",
"content" : {
"application/json;charset=UTF-8" : {
"schema" : {
"$ref" : "#/components/schemas/api-v1-routines-daily-member-routine-routineId594740350"
},
"examples" : {
"delete-routine-docs" : {
"value" : "{\n \"success\" : true,\n \"message\" : \"루틴 삭제 성공\",\n \"data\" : null\n}"
}
}
}
}
}
}
}
}
},
"components" : {
Expand Down Expand Up @@ -167,6 +248,15 @@
}
}
},
"api-v1-routines-daily-member1721287602" : {
"type" : "object",
"properties" : {
"routineId" : {
"type" : "number",
"description" : "추가할 루틴 id"
}
}
},
"api-v1-routines-daily-themes1975236624" : {
"type" : "object",
"properties" : {
Expand Down Expand Up @@ -234,6 +324,42 @@
}
}
},
"api-v1-routines-daily-member-routine-routineId594740350" : {
"type" : "object",
"properties" : {
"success" : {
"type" : "boolean",
"description" : "응답 성공 여부"
},
"message" : {
"type" : "string",
"description" : "응답 메시지"
}
}
},
"api-v1-routines-daily-member1327124516" : {
"type" : "object",
"properties" : {
"data" : {
"type" : "object",
"properties" : {
"routineId" : {
"type" : "number",
"description" : "생성한 루틴 id"
}
},
"description" : "응답 데이터"
},
"success" : {
"type" : "boolean",
"description" : "응답 성공 여부"
},
"message" : {
"type" : "string",
"description" : "응답 메시지"
}
}
},
"api-v1-test486549215" : {
"type" : "object"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.soptie.server.memberRoutine.controller;

import static com.epages.restdocs.apispec.ResourceDocumentation.*;
import static com.soptie.server.common.dto.Response.*;
import static org.mockito.Mockito.*;
import static org.springframework.http.MediaType.*;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
Expand Down Expand Up @@ -46,7 +47,7 @@ void success_createMemberDailyRoutine() throws Exception {
MemberDailyRoutineResponse savedMemberRoutine = MemberDailyRoutineFixture.createMemberDailyRoutineResponseDTO();
ResponseEntity<Response> response = ResponseEntity
.created(URI.create("redirect_uri"))
.body(Response.success("루틴 추가 성공", savedMemberRoutine));
.body(success("루틴 추가 성공", savedMemberRoutine));

// when
when(controller.createMemberDailyRoutine(principal, request)).thenReturn(response);
Expand Down Expand Up @@ -82,4 +83,40 @@ void success_createMemberDailyRoutine() throws Exception {
))
.andExpect(status().isCreated());
}

@Test
@DisplayName("회원 데일리 루틴 삭제 성공")
void success_deleteMemberDailyRoutine() throws Exception {
// given
Long routineId = 1L;
ResponseEntity<Response> response = ResponseEntity.ok(success("루틴 삭제 성공"));

// when
when(controller.deleteMemberDailyRoutine(principal, routineId)).thenReturn(response);

// then
mockMvc.perform(delete(DEFAULT_URL + "/routine/{routineId}", routineId)
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON)
.principal(principal))
.andDo(
document("delete-routine-docs",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
resource(ResourceSnippetParameters.builder()
.tag(TAG)
.description("회원 데일리 루틴 삭제 성공")
.pathParameters(
parameterWithName("routineId").description("루틴 id")
)
.responseFields(
fieldWithPath("success").type(BOOLEAN).description("응답 성공 여부"),
fieldWithPath("message").type(STRING).description("응답 메시지"),
fieldWithPath("data").type(NULL).description("응답 데이터")
)
.build()
)
))
.andExpect(status().isOk());
}
}