Skip to content

Commit

Permalink
[STMT-273] 활동 삭제 API 구현 (#131)
Browse files Browse the repository at this point in the history
* ✨ [STMT-273] 관리자 혹은 작성자 검증 로직 구현

* ✨ [STMT-273] 활동 이미지 파일 폴더로 삭제 기능 구현

* ✨ [STMT-273] 활동 이미지 파일 데이터 삭제 구현

* ✨ [STMT-273] 활동 참가자 데이터 삭제 구현

* ✨ [STMT-273] 활동 삭제 메서드 구현

* ✨ [STMT-273] 활동 삭제 api 구현

* ✅ [STMT-273] test 전 setup 데이터에 활동 이미지 더미 데이터 추가

* ✅ [STMT-273] 스터디 활동 삭제 api 테스트 케이스 작성

* ✅ [STMT-273] 관리자가 아닌 작성자가 삭제하는 테스트 케이스 추가

* ✅ [STMT-273] 스터디 멤버가 아닌 경우 테스트 케이스 추가

* 📝 [STMT-273] 활동 삭제 api 명세서 작성
  • Loading branch information
05AM authored Jun 17, 2024
1 parent 838459a commit 63e0289
Show file tree
Hide file tree
Showing 27 changed files with 435 additions and 32 deletions.
25 changes: 25 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -609,3 +609,28 @@ include::{snippets}/get-activity-participants/fail/not-found-study/response-fiel
.존재하지 않는 활동을 전달한 경우
include::{snippets}/get-activity-participants/fail/not-found-activity/response-body.adoc[]
include::{snippets}/get-activity-participants/fail/not-found-activity/response-fields.adoc[]


=== 스터디 활동 삭제

스터디 활동을 삭제하는 API입니다.

==== POST /api/v1/studies/{studyId}/activities/{activityId}

===== 요청
include::{snippets}/delete-activity/success/http-request.adoc[]
include::{snippets}/delete-activity/success/path-parameters.adoc[]
include::{snippets}/delete-activity/success/request-headers.adoc[]

===== 응답 성공 (200)
include::{snippets}/delete-activity/success/response-body.adoc[]
include::{snippets}/delete-activity/success/response-fields.adoc[]

===== 응답 실패 (403)
.삭제를 시도하는 멤버가 일반 멤버인 경우
include::{snippets}/delete-activity/fail/forbidden/response-body.adoc[]
include::{snippets}/delete-activity/fail/forbidden/response-fields.adoc[]

.스터디의 멤버가 아닌 경우
include::{snippets}/delete-activity/fail/not-joined-member/response-body.adoc[]
include::{snippets}/delete-activity/fail/not-joined-member/response-fields.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.stumeet.server.activity.adapter.in;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.stumeet.server.activity.application.port.in.ActivityDeleteUseCase;
import com.stumeet.server.activity.application.port.in.command.ActivityDeleteCommand;
import com.stumeet.server.common.annotation.WebAdapter;
import com.stumeet.server.common.auth.model.LoginMember;
import com.stumeet.server.common.model.ApiResponse;
import com.stumeet.server.common.response.SuccessCode;

import lombok.RequiredArgsConstructor;

@WebAdapter
@RequestMapping("/api/v1")
@RequiredArgsConstructor
public class ActivityDeleteApi {

private final ActivityDeleteUseCase activityDeleteUseCase;

@DeleteMapping("/studies/{studyId}/activities/{activityId}")
public ResponseEntity<ApiResponse<Void>> delete(
@AuthenticationPrincipal LoginMember member,
@PathVariable Long studyId,
@PathVariable Long activityId
) {
ActivityDeleteCommand command = ActivityDeleteCommand.builder()
.memberId(member.getId())
.studyId(studyId)
.activityId(activityId)
.build();
activityDeleteUseCase.delete(command);

return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(SuccessCode.DELETE_SUCCESS));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.stumeet.server.activity.adapter.out.mapper.ActivityImagePersistenceMapper;
import com.stumeet.server.activity.adapter.out.model.ActivityImageJpaEntity;
import com.stumeet.server.activity.application.port.out.ActivityImageCommandPort;
import com.stumeet.server.activity.application.port.out.ActivityImageCreatePort;
import com.stumeet.server.activity.application.port.out.ActivityImageQueryPort;
import com.stumeet.server.activity.domain.model.ActivityImage;
Expand All @@ -12,7 +13,7 @@

@PersistenceAdapter
@RequiredArgsConstructor
public class ActivityImagePersistenceAdapter implements ActivityImageCreatePort, ActivityImageQueryPort {
public class ActivityImagePersistenceAdapter implements ActivityImageCreatePort, ActivityImageQueryPort, ActivityImageCommandPort {

private final JpaActivityImageRepository jpaActivityImageRepository;
private final ActivityImagePersistenceMapper activityImagePersistenceMapper;
Expand All @@ -30,4 +31,9 @@ public List<ActivityImage> findAllByActivityId(Long activityId) {
.map(activityImagePersistenceMapper::toDomain)
.toList();
}

@Override
public void deleteAllByActivityId(Long activityId) {
jpaActivityImageRepository.deleteAllByActivityId(activityId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.stumeet.server.activity.adapter.out.mapper.ActivityParticipantPersistenceMapper;
import com.stumeet.server.activity.adapter.out.model.ActivityParticipantJpaEntity;
import com.stumeet.server.activity.application.port.out.ActivityParticipantCommandPort;
import com.stumeet.server.activity.application.port.out.ActivityParticipantCreatePort;
import com.stumeet.server.activity.application.port.out.ActivityParticipantQueryPort;
import com.stumeet.server.activity.domain.model.ActivityParticipant;
Expand All @@ -12,7 +13,7 @@

@PersistenceAdapter
@RequiredArgsConstructor
public class ActivityParticipantPersistenceAdapter implements ActivityParticipantCreatePort, ActivityParticipantQueryPort {
public class ActivityParticipantPersistenceAdapter implements ActivityParticipantCreatePort, ActivityParticipantQueryPort, ActivityParticipantCommandPort {

private final JpaActivityParticipantRepository jpaActivityParticipantRepository;
private final ActivityParticipantPersistenceMapper activityParticipantPersistenceMapper;
Expand All @@ -32,4 +33,8 @@ public List<ActivityParticipant> findAllByActivityId(Long activityId) {
.toList();
}

@Override
public void deleteByActivityId(Long activityId) {
jpaActivityParticipantRepository.deleteAllByActivityId(activityId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import com.stumeet.server.activity.adapter.in.response.ActivityListBriefResponse;
import com.stumeet.server.activity.adapter.out.mapper.ActivityPersistenceMapper;
import com.stumeet.server.activity.adapter.out.model.ActivityJpaEntity;
import com.stumeet.server.activity.application.port.out.ActivityAuthorValidationPort;
import com.stumeet.server.activity.application.port.out.ActivityCreatePort;
import com.stumeet.server.activity.application.port.out.ActivityDeletePort;
import com.stumeet.server.activity.application.port.out.ActivityQueryPort;
import com.stumeet.server.activity.domain.exception.NotExistsActivityException;
import com.stumeet.server.activity.domain.model.Activity;
Expand All @@ -20,7 +22,7 @@

@PersistenceAdapter
@RequiredArgsConstructor
public class ActivityPersistenceAdapter implements ActivityCreatePort, ActivityQueryPort {
public class ActivityPersistenceAdapter implements ActivityCreatePort, ActivityQueryPort, ActivityAuthorValidationPort, ActivityDeletePort {

private final JpaActivityRepository jpaActivityRepository;
private final ActivityPersistenceMapper activityPersistenceMapper;
Expand Down Expand Up @@ -59,4 +61,15 @@ public List<ActivityListBriefResponse> getBriefsByCondition(Boolean isNotice, Lo
ActivityCategory category, LocalDateTime startDate, LocalDateTime endDate) {
return jpaActivityRepository.findBriefsByCondition(isNotice, memberId, studyId, category, startDate, endDate);
}

@Override
public boolean isNotActivityAuthor(Long memberId, Long activityId) {
return jpaActivityRepository.findByIdAndAuthorId(activityId, memberId)
.isEmpty();
}

@Override
public void deleteById(Long activityId) {
jpaActivityRepository.deleteById(activityId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@

public interface JpaActivityImageRepository extends JpaRepository<ActivityImageJpaEntity, Long> {
List<ActivityImageJpaEntity> findAllByActivityId(Long activityId);

void deleteAllByActivityId(Long activityId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@

public interface JpaActivityParticipantRepository extends JpaRepository<ActivityParticipantJpaEntity, Long> {
List<ActivityParticipantJpaEntity> findAllByActivityId(Long activityId);
void deleteAllByActivityId(Long activityId);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.stumeet.server.activity.adapter.out.persistence;

import java.util.Optional;

import com.stumeet.server.activity.adapter.out.model.ActivityJpaEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;

public interface JpaActivityRepository extends JpaRepository<ActivityJpaEntity, Long>, JpaActivityRepositoryCustom {

Optional<ActivityJpaEntity> findByIdAndAuthorId(@Param("id") Long activityId, @Param("authorId") Long memberId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.stumeet.server.activity.application.port.in;

public interface ActivityAuthorityValidationUseCase {

void checkDeleteAuthority(Long studyId, Long memberId, Long activityId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.stumeet.server.activity.application.port.in;

import com.stumeet.server.activity.application.port.in.command.ActivityDeleteCommand;

public interface ActivityDeleteUseCase {

void delete(ActivityDeleteCommand command);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.stumeet.server.activity.application.port.in.command;

import lombok.Builder;

@Builder
public record ActivityDeleteCommand(
Long memberId,
Long studyId,
Long activityId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.stumeet.server.activity.application.port.out;

public interface ActivityAuthorValidationPort {

boolean isNotActivityAuthor(Long memberId, Long activityId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.stumeet.server.activity.application.port.out;

public interface ActivityDeletePort {

void deleteById(Long activityId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.stumeet.server.activity.application.port.out;

public interface ActivityImageCommandPort {

void deleteAllByActivityId(Long activityId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.stumeet.server.activity.application.port.out;

public interface ActivityParticipantCommandPort {

void deleteByActivityId(Long activityId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.stumeet.server.activity.application.service;

import com.stumeet.server.activity.application.port.in.ActivityAuthorityValidationUseCase;
import com.stumeet.server.activity.application.port.out.ActivityAuthorValidationPort;
import com.stumeet.server.activity.domain.exception.ActivityManagementAccessDeniedException;
import com.stumeet.server.common.annotation.UseCase;
import com.stumeet.server.studymember.application.port.out.StudyMemberValidationPort;

import lombok.RequiredArgsConstructor;

@UseCase
@RequiredArgsConstructor
public class ActivityAuthorityValidationService implements ActivityAuthorityValidationUseCase {

private final StudyMemberValidationPort studyMemberValidationPort;
private final ActivityAuthorValidationPort activityAuthorValidationPort;

@Override
public void checkDeleteAuthority(Long studyId, Long memberId, Long activityId) {
if (studyMemberValidationPort.isNotAdmin(studyId, memberId) && activityAuthorValidationPort.isNotActivityAuthor(memberId, activityId)) {
throw new ActivityManagementAccessDeniedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.stumeet.server.activity.application.service;

import org.springframework.transaction.annotation.Transactional;

import com.stumeet.server.activity.application.port.in.ActivityAuthorityValidationUseCase;
import com.stumeet.server.activity.application.port.in.ActivityDeleteUseCase;
import com.stumeet.server.activity.application.port.in.command.ActivityDeleteCommand;
import com.stumeet.server.activity.application.port.out.ActivityDeletePort;
import com.stumeet.server.activity.application.port.out.ActivityParticipantCommandPort;
import com.stumeet.server.common.annotation.UseCase;
import com.stumeet.server.study.application.port.in.ActivityImageDeleteUseCase;
import com.stumeet.server.study.application.port.in.ActivityParticipantDeleteUseCase;
import com.stumeet.server.study.application.port.in.StudyValidationUseCase;
import com.stumeet.server.studymember.application.port.in.StudyMemberValidationUseCase;

import lombok.RequiredArgsConstructor;

@UseCase
@RequiredArgsConstructor
@Transactional
public class ActivityDeleteService implements ActivityDeleteUseCase {

private final StudyMemberValidationUseCase studyMemberValidationUseCase;
private final StudyValidationUseCase studyValidationUseCase;
private final ActivityAuthorityValidationUseCase activityAuthorityValidationUseCase;

private final ActivityImageDeleteUseCase activityImageDeleteUseCase;
private final ActivityParticipantDeleteUseCase activityParticipantDeleteUseCase;

private final ActivityDeletePort activityDeletePort;

@Override
public void delete(ActivityDeleteCommand command) {
studyValidationUseCase.checkById(command.studyId());
studyMemberValidationUseCase.checkStudyJoinMember(command.studyId(), command.memberId());
activityAuthorityValidationUseCase.checkDeleteAuthority(command.studyId(), command.memberId(), command.activityId());

activityImageDeleteUseCase.deleteByActivityId(command.studyId(), command.activityId());
activityParticipantDeleteUseCase.deleteByActivityId(command.activityId());
activityDeletePort.deleteById(command.activityId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.stumeet.server.activity.domain.exception;

import com.stumeet.server.common.exception.model.InvalidStateException;
import com.stumeet.server.common.response.ErrorCode;

public class ActivityManagementAccessDeniedException extends InvalidStateException {
public ActivityManagementAccessDeniedException() {
super(ErrorCode.ACTIVITY_MANAGEMENT_ACCESS_DENIED_EXCEPTION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public enum ErrorCode {
INVALID_ACTIVITY_CATEGORY_EXCEPTION(HttpStatus.BAD_REQUEST, "유효하지 않은 활동 카테고리입니다."),
START_DATE_NOT_YET_EXCEPTION(HttpStatus.BAD_REQUEST, "시작일 전에 스터디를 완료할 수 없습니다."),


/*
401 - UNAUTHORIZED
*/
Expand All @@ -42,6 +43,7 @@ public enum ErrorCode {
JWT_TOKEN_PARSING_EXCEPTION(HttpStatus.UNAUTHORIZED, "JWT 토큰 파싱에 실패했습니다."),
NOT_EXIST_OAUTH_PROVIDER(HttpStatus.UNAUTHORIZED, "존재하지 않는 OAuth 제공자입니다."),


/*
403 - FORBIDDEN
*/
Expand All @@ -50,6 +52,7 @@ public enum ErrorCode {
NOT_STUDY_ADMIN_EXCEPTION(HttpStatus.FORBIDDEN, "스터디 관리자가 아닙니다."),
ALREADY_STUDY_JOIN_MEMBER_EXCEPTION(HttpStatus.FORBIDDEN, "스터디에 이미 가입한 사용자입니다."),
NOT_EXIST_ACTIVITY_STATUS_EXCEPTION(HttpStatus.FORBIDDEN, "존재하지 않는 활동 상태입니다."),
ACTIVITY_MANAGEMENT_ACCESS_DENIED_EXCEPTION(HttpStatus.FORBIDDEN, "활동 관리 권한이 없습니다."),

/*
404 - NOT FOUND
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ public interface FileDeleteUseCase {
void deleteStudyRelatedImage(Long studyId);

void deleteUserRelatedImage(Long userId);

void deleteActivityRelatedImage(Long studyId, Long activityId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
@RequiredArgsConstructor
public class FileDeleteService implements FileDeleteUseCase {

private final String STUDY_PREFIX = "study/%d/";
private final String USER_PREFIX = "user/%d/";
private final String STUDY_PREFIX = "study/%d/";
private final String ACTIVITY_PREFIX = "study/%d/activity/%d";


private final FileCommandPort fileCommandPort;
Expand All @@ -25,4 +26,9 @@ public void deleteStudyRelatedImage(Long studyId) {
public void deleteUserRelatedImage(Long userId) {
fileCommandPort.deleteFolder(String.format(USER_PREFIX, userId));
}

@Override
public void deleteActivityRelatedImage(Long studyId, Long activityId) {
fileCommandPort.deleteFolder(String.format(ACTIVITY_PREFIX, studyId, activityId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.stumeet.server.study.application.port.in;

public interface ActivityImageDeleteUseCase {

void deleteByActivityId(Long studyId, Long activityId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.stumeet.server.study.application.port.in;

public interface ActivityParticipantDeleteUseCase {

void deleteByActivityId(Long activityId);
}
Loading

0 comments on commit 63e0289

Please sign in to comment.