Skip to content

Commit

Permalink
[BE] Feat/#586 핀 수정 시 변경 이력 저장 이벤트 구현 (#591)
Browse files Browse the repository at this point in the history
* refactor: 테스트 환경 콘솔 로그 출력 설정 추가

- additivity-false는 기본 콘솔 로그와의 중복 출력 방지를 위함

* test: 핀 수정 인수 테스트 추가

* feat: 핀 수정 시 수정 이력 저장 구현 및 테스트

* refactor: 사용하지 않는 변수 삭제

* feat: 새 핀 추가시에도 핀 이력 저장

* feat: 핀 수정 이력이 참조하는 엔티티 삭제 시 soft delete 하도록 이벤트 구현

- 회원 차단 시, 토픽 삭제 시, 핀 삭제 시 soft delete

* feat: 핀 수정 이력은 삭제 개념이 없는 것으로 복구 (이전 커밋 revert)

* test: 핀 수정 이력 이벤트 테스트 실패 수정

* test: 빠른 테스트 조건문 확인을 위해 assertAll softly로 변경

* refactor: hard delete 메서드 Query 작성해서 Modifying 적용

* rename: PinUpdateHistory 에서 PinHistory로 네이밍 변경

- 핀의 정보 변경 이력을 관리하는데 update라는 수식어가 불필요함

* test: 테스트 displayName 핀 정보 이력 관련 용어 통일

* fix: 핀 변경 이력 엔티티 컬럼 수정

- pin의 FK 외에도 변경 내용을 함께 저장해야 함

* refactor: 불필요한 공백 제거

* test: 일시 검증 메서드 수정, 핀 변경 일시 검증 추가

* feat: 핀 변경 이력 엔티티에 핀 변경 일시 컬럼 추가

- pinHistory의 createdAt과 저장 시 pin 의 updatedAt은 미세하게 다르므로 정확한 일시를 기록

* fix: PinHistoryCommandService 트랜잭션 어노테이션 추가

* fix: 핀 변경 이력 롤백 테스트 예외 수정, 실패 테스트 disabled 처리

* fix: 이벤트를 통한 롤백 테스트 통합 테스트로 이동

- DataJpaTest에서는 내부 트랜잭션의 롤백을 검증할 수 없음

* test: 새로운 테스트에 테스트 컨테이너 적용
  • Loading branch information
yoondgu authored Oct 18, 2023
1 parent 11320bb commit 0405080
Show file tree
Hide file tree
Showing 15 changed files with 393 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public AdminCommandService(
public void blockMember(Long memberId) {
Member member = findMemberById(memberId);
member.updateStatus(Status.BLOCKED);
memberRepository.flush();

deleteAllRelatedMember(member);
}
Expand All @@ -63,11 +64,8 @@ private void deleteAllRelatedMember(Member member) {
Long memberId = member.getId();

permissionRepository.deleteAllByMemberId(memberId);
permissionRepository.flush();
atlasRepository.deleteAllByMemberId(memberId);
atlasRepository.flush();
bookmarkRepository.deleteAllByMemberId(memberId);
bookmarkRepository.flush();
pinImageRepository.deleteAllByPinIds(pinIds);
pinRepository.deleteAllByMemberId(memberId);
topicRepository.deleteAllByMemberId(memberId);
Expand All @@ -90,11 +88,8 @@ public void deleteTopic(Long topicId) {
List<Long> pinIds = extractPinIdsByTopic(topic);

permissionRepository.deleteAllByTopicId(topicId);
permissionRepository.flush();
atlasRepository.deleteAllByTopicId(topicId);
atlasRepository.flush();
bookmarkRepository.deleteAllByTopicId(topicId);
bookmarkRepository.flush();
pinImageRepository.deleteAllByPinIds(pinIds);
pinRepository.deleteAllByTopicId(topicId);
topicRepository.deleteById(topicId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.mapbefine.mapbefine.atlas.domain;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
Expand All @@ -10,7 +12,11 @@ public interface AtlasRepository extends JpaRepository<Atlas, Long> {

void deleteByMemberIdAndTopicId(Long memberId, Long topicId);

@Modifying(clearAutomatically = true)
@Query("delete from Atlas a where a.member.id = :memberId")
void deleteAllByMemberId(Long memberId);

@Modifying(clearAutomatically = true)
@Query("delete from Atlas a where a.topic.id = :topicId")
void deleteAllByTopicId(Long topicId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {

Optional<Bookmark> findByMemberIdAndTopicId(Long memberId, Long topicId);

boolean existsByMemberIdAndTopicId(Long memberId, Long topicId);

@Modifying(clearAutomatically = true)
@Query("delete from Bookmark b where b.member.id = :memberId")
void deleteAllByMemberId(Long memberId);

@Modifying(clearAutomatically = true)
@Query("delete from Bookmark b where b.topic.id = :topicId")
void deleteAllByTopicId(Long topicId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.mapbefine.mapbefine.history.application;

import com.mapbefine.mapbefine.history.domain.PinHistory;
import com.mapbefine.mapbefine.history.domain.PinHistoryRepository;
import com.mapbefine.mapbefine.pin.domain.Pin;
import com.mapbefine.mapbefine.pin.event.PinUpdateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Transactional
@Service
public class PinHistoryCommandService {

private final PinHistoryRepository pinHistoryRepository;

public PinHistoryCommandService(PinHistoryRepository pinHistoryRepository) {
this.pinHistoryRepository = pinHistoryRepository;
}

@EventListener
public void saveHistory(PinUpdateEvent event) {
Pin pin = event.pin();
pinHistoryRepository.save(new PinHistory(pin, event.member()));

log.debug("pin history saved for update pin id =: {}", pin.getId());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.mapbefine.mapbefine.history.domain;

import static lombok.AccessLevel.PROTECTED;

import com.mapbefine.mapbefine.common.entity.BaseTimeEntity;
import com.mapbefine.mapbefine.member.domain.Member;
import com.mapbefine.mapbefine.pin.domain.Pin;
import com.mapbefine.mapbefine.pin.domain.PinInfo;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@EntityListeners(AuditingEntityListener.class)
@Entity
@NoArgsConstructor(access = PROTECTED)
@Getter
public class PinHistory extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
@JoinColumn(name = "pin_id", nullable = false)
private Pin pin;

@ManyToOne
@JoinColumn(name = "member_id", nullable = false)
private Member member;

@Embedded
private PinInfo pinInfo;

@Column(name = "pin_updated_at", nullable = false)
private LocalDateTime pinUpdatedAt;

public PinHistory(Pin pin, Member member) {
this.pin = pin;
PinInfo history = pin.getPinInfo();
this.pinInfo = PinInfo.of(history.getName(), history.getDescription());
this.pinUpdatedAt = pin.getUpdatedAt();
this.member = member;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.mapbefine.mapbefine.history.domain;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PinHistoryRepository extends JpaRepository<PinHistory, Long> {
List<PinHistory> findAllByPinId(Long pinId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

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 PermissionRepository extends JpaRepository<Permission, Long> {

List<Permission> findAllByTopicId(Long topicId);

boolean existsByTopicIdAndMemberId(Long topicId, Long memberId);

@Modifying(clearAutomatically = true)
@Query("delete from Permission p where p.member.id = :memberId")
void deleteAllByMemberId(Long memberId);

@Modifying(clearAutomatically = true)
@Query("delete from Permission p where p.topic.id = :topicId")
void deleteAllByTopicId(Long topicId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.mapbefine.mapbefine.pin.dto.request.PinCreateRequest;
import com.mapbefine.mapbefine.pin.dto.request.PinImageCreateRequest;
import com.mapbefine.mapbefine.pin.dto.request.PinUpdateRequest;
import com.mapbefine.mapbefine.pin.event.PinUpdateEvent;
import com.mapbefine.mapbefine.pin.exception.PinException.PinBadRequestException;
import com.mapbefine.mapbefine.pin.exception.PinException.PinForbiddenException;
import com.mapbefine.mapbefine.topic.domain.Topic;
Expand All @@ -30,16 +31,20 @@
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Transactional
@Service
public class PinCommandService {

private static final double DUPLICATE_LOCATION_DISTANCE_METERS = 10.0;

private final ApplicationEventPublisher eventPublisher;
private final PinRepository pinRepository;
private final LocationRepository locationRepository;
private final TopicRepository topicRepository;
Expand All @@ -48,13 +53,15 @@ public class PinCommandService {
private final ImageService imageService;

public PinCommandService(
ApplicationEventPublisher eventPublisher,
PinRepository pinRepository,
LocationRepository locationRepository,
TopicRepository topicRepository,
MemberRepository memberRepository,
PinImageRepository pinImageRepository,
ImageService imageService
) {
this.eventPublisher = eventPublisher;
this.pinRepository = pinRepository;
this.locationRepository = locationRepository;
this.topicRepository = topicRepository;
Expand All @@ -81,8 +88,8 @@ public long save(
);

addPinImagesToPin(images, pin);

pinRepository.save(pin);
eventPublisher.publishEvent(new PinUpdateEvent(pin, member));

return pin.getId();
}
Expand Down Expand Up @@ -139,10 +146,12 @@ public void update(
Long pinId,
PinUpdateRequest request
) {
Member member = findMember(authMember.getMemberId());
Pin pin = findPin(pinId);
validatePinCreateOrUpdate(authMember, pin.getTopic());

pin.updatePinInfo(request.name(), request.description());
eventPublisher.publishEvent(new PinUpdateEvent(pin, member));
}

private Pin findPin(Long pinId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ public double getLongitude() {
return location.getLongitude();
}

public String getDescription() {
return pinInfo.getDescription();
}

public String getRoadBaseAddress() {
Address address = location.getAddress();
return address.getRoadBaseAddress();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.mapbefine.mapbefine.pin.event;

import com.mapbefine.mapbefine.member.domain.Member;
import com.mapbefine.mapbefine.pin.domain.Pin;

public record PinUpdateEvent(Pin pin, Member member) {
}
16 changes: 8 additions & 8 deletions backend/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@
<property name="LOG_PATTERN_COLORED"
value="%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] ${PID} %highlight(%-5level) %cyan(%logger) - %msg%n"/>

<springProfile name="default">
<include resource="logback/console-appender.xml"/>
<include resource="logback/console-appender.xml"/>

<root level="INFO">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>

<springProfile name="test">
<logger name="com.mapbefine.mapbefine" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</root>
</logger>
</springProfile>

<springProfile name="dev">
<include resource="logback/console-appender.xml"/>
<include resource="logback/file-hibernate-appender.xml"/>
<include resource="logback/file-debug-appender.xml"/>
<include resource="logback/file-warn-appender.xml"/>
<include resource="logback/file-error-appender.xml"/>

<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_WARN"/>
<appender-ref ref="FILE_ERROR"/>
</root>
Expand All @@ -38,14 +40,12 @@
</springProfile>

<springProfile name="prod">
<include resource="logback/console-appender.xml"/>
<include resource="logback/file-info-appender.xml"/>
<include resource="logback/file-warn-appender.xml"/>
<include resource="logback/file-error-appender.xml"/>
<include resource="logback/slack-error-appender.xml"/>

<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_WARN"/>
<appender-ref ref="FILE_ERROR"/>
<appender-ref ref="SLACK_ERROR"/>
Expand Down
Loading

0 comments on commit 0405080

Please sign in to comment.