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
34 changes: 34 additions & 0 deletions src/docs/asciidoc/post-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,40 @@ include::{snippetsDir}/updatePost/11/http-response.adoc[]

include::{snippetsDir}/updatePost/12/http-response.adoc[]

---

=== **4. 게시글 삭제**

유저픽 게시글 삭제 api 입니다. +
`Soft Delete` 전략을 사용해 `isDeleted` , `deletedAt` 필드를 업데이트함으로써 실제 데이터를 물리적으로 삭제하지 않고 삭제된 것으로 간주하여 처리했습니다. +
스케쥴러를 통해 매일 오전 6시에 전체 게시글을 조회하면서 삭제 처리된지 30일이 지난 데이터는 `Hard Delete` 를 수행합니다.

==== Request
include::{snippetsDir}/deletePost/1/http-request.adoc[]

==== Request Path Parameters
include::{snippetsDir}/deletePost/1/path-parameters.adoc[]

==== 성공 Response
include::{snippetsDir}/deletePost/1/http-response.adoc[]

==== Response Body Fields
include::{snippetsDir}/deletePost/1/response-fields.adoc[]

==== 실패 Response
실패 1. 인증되지 않은 유저일 경우

include::{snippetsDir}/deletePost/2/http-response.adoc[]

실패 2. 존재하지 않는 게시글 ID일 경우

include::{snippetsDir}/deletePost/3/http-response.adoc[]

실패 3. 게시글을 삭제할 권한이 없을 경우 (해당 게시글의 작성자가 아닐 경우)

include::{snippetsDir}/deletePost/4/http-response.adoc[]





Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.ftm.server.adapter.in.web.post.controller;

import com.ftm.server.application.command.post.DeletePostCommand;
import com.ftm.server.application.port.in.post.DeletePostUseCase;
import com.ftm.server.common.response.ApiResponse;
import com.ftm.server.common.response.enums.SuccessResponseCode;
import com.ftm.server.infrastructure.security.UserPrincipal;
import lombok.RequiredArgsConstructor;
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.RestController;

@RestController
@RequiredArgsConstructor
public class DeletePostController {

private final DeletePostUseCase deletePostUseCase;

@DeleteMapping("/api/posts/{postId}")
public ResponseEntity<ApiResponse<Void>> deletePost(
@PathVariable Long postId, @AuthenticationPrincipal UserPrincipal userPrincipal) {

deletePostUseCase.execute(DeletePostCommand.of(postId, userPrincipal.getId()));
return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(SuccessResponseCode.OK));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class PostDomainPersistenceAdapter
UpdatePostPort,
UpdatePostProductPort,
UpdatePostProductImagePort,
DeletePostPort,
DeletePostImagePort,
DeletePostProductPort,
DeletePostProductImagePort {
Expand Down Expand Up @@ -139,6 +140,13 @@ public Optional<Post> loadPost(FindByIdQuery query) {
return postRepository.findById(query.getId()).map(postMapper::toDomainEntity);
}

@Override
public List<Post> loadPostsByDeleteOption(FindPostByDeleteOptionQuery query) {
return postRepository.findAllByDeletedBefore(query).stream()
.map(postMapper::toDomainEntity)
.toList();
}

@Override
public List<PostImage> loadPostImagesByPostId(FindByPostIdQuery query) {
PostJpaEntity postJpaEntity =
Expand All @@ -151,6 +159,13 @@ public List<PostImage> loadPostImagesByPostId(FindByPostIdQuery query) {
.toList();
}

@Override
public List<PostImage> loadPostImagesByPostIds(FindByIdsQuery query) {
return postImageRepository.findAllByPostIdIn(query.getIds()).stream()
.map(postImageMapper::toDomainEntity)
.toList();
}

@Override
public List<PostProduct> loadPostProductsByPostId(FindByPostIdQuery query) {
PostJpaEntity postJpaEntity =
Expand All @@ -170,10 +185,17 @@ public List<PostProduct> loadPostProductsByIds(FindByIdsQuery query) {
.toList();
}

@Override
public List<PostProduct> loadPostProductsByPostIds(FindByIdsQuery query) {
return postProductRepository.findAllByPostIdIn(query.getIds()).stream()
.map(postProductMapper::toDomainEntity)
.toList();
}

@Override
public List<PostProductImage> loadPostProductImagesByPostProductIds(FindByIdsQuery query) {
List<PostProductImageJpaEntity> postProductImageJpaEntities =
postProductImageRepository.findByPostProductIds(query);
postProductImageRepository.findAllByPostProductIdIn(query.getIds());

return postProductImageJpaEntities.stream()
.map(postProductImageMapper::toDomainEntity)
Expand Down Expand Up @@ -238,23 +260,31 @@ public void updatePostProductImages(List<PostProductImage> postProductImages) {
}
}

@Override
public void deletePostsByIds(List<Long> postIds) {
postRepository.deleteAllByIdInBatch(postIds);
}

@Override
public void deletePostImages(List<PostImage> postImages) {
List<Long> ids = postImages.stream().map(PostImage::getId).toList();

postImageRepository.deleteAllById(ids);
postImageRepository.deleteAllByIdInBatch(ids);
}

@Override
public void deletePostProducts(List<PostProduct> postProducts) {
List<Long> ids = postProducts.stream().map(PostProduct::getId).toList();
postProductRepository.deleteAllByIdInBatch(ids);
}

postProductRepository.deleteAllById(ids);
@Override
public void deletePostProductsByIds(List<Long> postProductIds) {
postProductRepository.deleteAllByIdInBatch(postProductIds);
}

@Override
public void deletePostProductImages(List<PostProductImage> postProductImages) {
List<Long> ids = postProductImages.stream().map(PostProductImage::getId).toList();

postProductImageRepository.deleteAllById(ids);
postProductImageRepository.deleteAllByIdInBatch(ids);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ftm.server.adapter.out.persistence.repository;

import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
import com.ftm.server.application.query.FindPostByDeleteOptionQuery;
import java.util.List;

public interface PostCustomRepository {

List<PostJpaEntity> findAllByDeletedBefore(FindPostByDeleteOptionQuery query);
}
Copy link
Member

Choose a reason for hiding this comment

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

이거 저도 한번 알아봐야겠네요..!!

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ftm.server.adapter.out.persistence.repository;

import static com.ftm.server.adapter.out.persistence.model.QPostJpaEntity.postJpaEntity;

import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
import com.ftm.server.application.query.FindPostByDeleteOptionQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.LocalTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class PostCustomRepositoryImpl implements PostCustomRepository {

private final JPAQueryFactory queryFactory;

@Override
public List<PostJpaEntity> findAllByDeletedBefore(FindPostByDeleteOptionQuery query) {
return queryFactory
.selectFrom(postJpaEntity)
.where(
postJpaEntity.isDeleted.eq(query.getIsDeleted()),
postJpaEntity.deletedAt.loe(query.getDeletedAt().atTime(LocalTime.MAX)))
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@

import com.ftm.server.adapter.out.persistence.model.PostImageJpaEntity;
import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
import io.lettuce.core.dynamic.annotation.Param;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface PostImageRepository extends JpaRepository<PostImageJpaEntity, Long> {

List<PostImageJpaEntity> findAllByPost(PostJpaEntity post);

List<PostImageJpaEntity> findAllByPostIdIn(List<Long> postIds);

@Modifying
@Query("DELETE FROM PostImageJpaEntity pi WHERE pi.id IN (:postImageIds)")
void deleteAllByIdInBatch(@Param("postImageIds") List<Long> postImageIds);
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
package com.ftm.server.adapter.out.persistence.repository;

import com.ftm.server.adapter.out.persistence.model.PostProductImageJpaEntity;
import io.lettuce.core.dynamic.annotation.Param;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface PostProductImageRepository
extends JpaRepository<PostProductImageJpaEntity, Long>, PostProductImageCustomRepository {}
public interface PostProductImageRepository extends JpaRepository<PostProductImageJpaEntity, Long> {

List<PostProductImageJpaEntity> findAllByPostProductIdIn(List<Long> postProductIds);

@Modifying
@Query("DELETE FROM PostProductImageJpaEntity ppi WHERE ppi.id IN (:postProductImageIds)")
void deleteAllByIdInBatch(@Param("postProductImageIds") List<Long> postProductImageIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@

import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
import com.ftm.server.adapter.out.persistence.model.PostProductJpaEntity;
import io.lettuce.core.dynamic.annotation.Param;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface PostProductRepository extends JpaRepository<PostProductJpaEntity, Long> {

List<PostProductJpaEntity> findAllByPost(PostJpaEntity post);

List<PostProductJpaEntity> findAllByPostIdIn(List<Long> postIds);

@Modifying
@Query("DELETE FROM PostProductJpaEntity pp WHERE pp.id IN (:postProductIds)")
void deleteAllByIdInBatch(@Param("postProductIds") List<Long> postProductIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface PostRepository extends JpaRepository<PostJpaEntity, Long> {
public interface PostRepository extends JpaRepository<PostJpaEntity, Long>, PostCustomRepository {

List<PostJpaEntity> findByUserId(Long userId);

@Modifying
@Query("DELETE FROM PostJpaEntity p WHERE p.id IN (:postIds)")
void deleteAllByIdInBatch(@Param("postIds") List<Long> postIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.ftm.server.adapter.out.scheduler;

import com.ftm.server.application.port.in.post.PostHardDeleteUseCase;
import com.ftm.server.common.annotation.Adapter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;

@Slf4j
@Adapter
@RequiredArgsConstructor
public class PostHardDeleteScheduler {

private final PostHardDeleteUseCase postHardDeleteUseCase;

// 매일 오전 3시 hard delete 진행
@Scheduled(cron = "0 0 3 * * *", zone = "Asia/Seoul")
public void run() {
log.info(
"Posts Hard Delete started at {}",
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
postHardDeleteUseCase.execute();
log.info("Posts Hard Deleted Finish.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@

import com.ftm.server.application.port.in.user.UserHardDeleteUseCase;
import com.ftm.server.common.annotation.Adapter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;

@Slf4j
@Adapter
@RequiredArgsConstructor
public class UserHardDeleteScheduler {

private final UserHardDeleteUseCase userHardDeleteUseCase;

// 매일 새벽 3시 hard delete 진행
@Scheduled(cron = "0 0 3 * * *")
@Scheduled(cron = "0 0 3 * * *", zone = "Asia/Seoul")
public void run() {
log.info(
"Users Hard Delete started at {}",
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
userHardDeleteUseCase.execute();
log.info("Users Hard Deleted Finish.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ftm.server.application.command.post;

import lombok.Getter;

@Getter
public class DeletePostCommand {

private final Long postId;
private final Long userId;

private DeletePostCommand(Long postId, Long userId) {
this.postId = postId;
this.userId = userId;
}

public static DeletePostCommand of(Long postId, Long userId) {
return new DeletePostCommand(postId, userId);
}
}
Loading
Loading