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
2 changes: 1 addition & 1 deletion config
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,23 @@
import com.assu.server.domain.student.repository.StudentRepository;
import com.assu.server.domain.student.service.StudentService;
import com.assu.server.global.apiPayload.code.status.ErrorStatus;
import com.assu.server.infra.discord.DiscordNotifier;
import com.assu.server.infra.s3.AmazonS3Manager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;
import java.util.Optional;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
Expand All @@ -64,6 +69,7 @@ public class SignUpServiceImpl implements SignUpService {
private final StudentService studentService;
private final PhoneAuthService phoneAuthService;
private final CommonAuthRepository commonAuthRepository;
private final DiscordNotifier discordNotifier;

private RealmAuthAdapter pickAdapter(AuthRealm realm) {
return realmAuthAdapters.stream()
Expand Down Expand Up @@ -122,7 +128,11 @@ public SignUpResponseDTO signupSsuStudent(StudentTokenSignUpRequestDTO req) {
member.setProfile(student);

// 6) 가입 시점 사용 가능 제휴 동기화 (자정 배치와 별개로 즉시 반영)
studentService.syncUserPapersForStudent(student.getId());
try {
studentService.syncUserPapersForStudent(student.getId());
} catch (Exception e) {
log.error("[SignUp] UserPaper 동기화 실패 studentId={}", student.getId(), e);
}

// 7) JWT 토큰 발급
TokensDTO tokens = jwtUtil.issueTokens(
Expand Down Expand Up @@ -159,7 +169,11 @@ private SignUpResponseDTO restoreWithdrawnStudent(
studentRepository.save(student);

// 탈퇴 기간 중 변동된 제휴를 반영한다
studentService.syncUserPapersForStudent(student.getId());
try {
studentService.syncUserPapersForStudent(student.getId());
} catch (Exception e) {
log.error("[SignUp] UserPaper 동기화 실패 studentId={}", student.getId(), e);
}

TokensDTO tokens = jwtUtil.issueTokens(
member.getId(),
Expand Down Expand Up @@ -227,6 +241,8 @@ public SignUpResponseDTO signupPartner(PartnerSignUpRequestDTO req, MultipartFil

linkStore(partner, info.name(), address, info.detailAddress(), lat, lng, point);

runAfterCommit(() -> discordNotifier.send("🔔 새 제휴업체 가입 승인 요청이 있습니다. 백오피스에서 확인해 주세요."));

return SignUpResponseDTO.from(member, null);
}

Expand Down Expand Up @@ -411,6 +427,8 @@ public SignUpResponseDTO signupAdmin(AdminSignUpRequestDTO req, MultipartFile si
.build());
member.setProfile(admin);

runAfterCommit(() -> discordNotifier.send("🔔 새 학생회 가입 승인 요청이 있습니다. 백오피스에서 확인해 주세요."));

return SignUpResponseDTO.from(member, null);
}

Expand Down Expand Up @@ -485,4 +503,17 @@ private Point toPoint(Double lat, Double lng) {
private String pickDisplayAddress(String road, String jibun) {
return (road != null && !road.isBlank()) ? road : jibun;
}

private void runAfterCommit(Runnable task) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
task.run();
return;
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
task.run();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
import com.assu.server.domain.store.entity.Store;
import com.assu.server.domain.store.repository.StoreRepository;
import com.assu.server.global.apiPayload.code.status.ErrorStatus;
import com.assu.server.infra.discord.DiscordNotifier;
import com.assu.server.infra.s3.AmazonS3Manager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
Expand All @@ -44,6 +47,7 @@ public class BackofficeMemberServiceImpl implements BackofficeMemberService {
private final WithdrawalService withdrawalService;
private final AmazonS3Manager amazonS3Manager;
private final ProfileImageService profileImageService;
private final DiscordNotifier discordNotifier;

@Override
@Transactional(readOnly = true)
Expand Down Expand Up @@ -93,6 +97,9 @@ public BackofficeMemberSummaryDTO approveMember(Long memberId) {
admin.setSignVerifiedAt(now);
}

String roleLabel = member.getRole() == UserRole.PARTNER ? "제휴업체" : "학생회";
runAfterCommit(() -> discordNotifier.send("✅ " + roleLabel + " 회원가입이 승인되었습니다."));

return BackofficeMemberSummaryDTO.from(member);
}

Expand All @@ -103,6 +110,10 @@ public BackofficeMemberSummaryDTO rejectMember(Long memberId) {
assertPendingApproval(member);

member.setIsActivated(ActivationStatus.INACTIVE);

String roleLabel = member.getRole() == UserRole.PARTNER ? "제휴업체" : "학생회";
runAfterCommit(() -> discordNotifier.send("❌ " + roleLabel + " 회원가입이 거절되었습니다."));

return BackofficeMemberSummaryDTO.from(member);
}

Expand Down Expand Up @@ -310,6 +321,19 @@ private void assertApprovable(Member member) {
}
}

private void runAfterCommit(Runnable task) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
task.run();
return;
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
task.run();
}
});
}

private void activatePartnerStore(Partner partner) {
Store store = storeRepository.findByPartner(partner)
.orElseThrow(() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
import com.assu.server.domain.suggestion.repository.SuggestionRepository;
import com.assu.server.global.apiPayload.code.status.ErrorStatus;
import com.assu.server.global.exception.GeneralException;
import com.assu.server.infra.discord.DiscordNotifier;

import lombok.RequiredArgsConstructor;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Service
@RequiredArgsConstructor
Expand All @@ -36,6 +39,7 @@ public class BackofficeReportServiceImpl implements BackofficeReportService {
private final SuggestionRepository suggestionRepository;
private final ReportRepository reportRepository;
private final ApplicationEventPublisher eventPublisher;
private final DiscordNotifier discordNotifier;

@Override
@Transactional(readOnly = true)
Expand Down Expand Up @@ -68,6 +72,13 @@ public BackofficeReportResponseDTO updateReportStatus(Long reportId, BackofficeR

reportRepository.save(report);

String statusLabel = switch (nextStatus) {
case PENDING -> "대기";
case PROCESSED -> "처리 완료";
case REJECTED -> "기각";
};
runAfterCommit(() -> discordNotifier.send("📋 신고 상태가 변경되었습니다: " + statusLabel));

return BackofficeReportResponseDTO.from(report);
}

Expand All @@ -81,6 +92,7 @@ public BackofficeReportDTO.SoftDeleteResponseDTO softDeleteReview(Long reviewId)
}

processRelatedReports(ReportTargetType.REVIEW, reviewId);
runAfterCommit(() -> discordNotifier.send("🗑️ 신고된 콘텐츠가 삭제 처리되었습니다."));

return BackofficeReportDTO.SoftDeleteResponseDTO.of(reviewId);
}
Expand All @@ -95,6 +107,7 @@ public BackofficeReportDTO.SoftDeleteResponseDTO softDeleteSuggestion(Long sugge
}

processRelatedReports(ReportTargetType.SUGGESTION, suggestionId);
runAfterCommit(() -> discordNotifier.send("🗑️ 신고된 콘텐츠가 삭제 처리되었습니다."));

return BackofficeReportDTO.SoftDeleteResponseDTO.of(suggestionId);
}
Expand All @@ -111,6 +124,7 @@ public BackofficeReportDTO.RejectReportResponseDTO rejectReport(Long reportId) {
report.updateStatus(ReportStatus.REJECTED);
eventPublisher.publishEvent(new ReportProcessedEvent(
report.getId(), report.getTargetType(), report.getTargetId(), ReportStatus.REJECTED));
runAfterCommit(() -> discordNotifier.send("✅ 신고가 기각되었습니다."));

return BackofficeReportDTO.RejectReportResponseDTO.of(reportId);
}
Expand All @@ -133,6 +147,19 @@ public List<BackofficeReportDTO.ReportListItemDTO> getReports(boolean pending, b
);
}

private void runAfterCommit(Runnable task) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
task.run();
return;
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
task.run();
}
});
}

private void processRelatedReports(ReportTargetType targetType, Long targetId) {
List<Report> reports = reportRepository.findAllByTargetTypeAndTargetId(targetType, targetId);
reports.forEach(report -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
import com.assu.server.global.apiPayload.code.status.ErrorStatus;
import com.assu.server.global.exception.DatabaseException;
import com.assu.server.global.exception.GeneralException;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.List;
import java.util.Optional;
Expand All @@ -21,6 +24,7 @@
public class DeviceTokenServiceImpl implements DeviceTokenService {
private final DeviceTokenRepository deviceTokenRepository;
private final MemberRepository memberRepository;
private final MeterRegistry meterRegistry;

@Override
public Long register(String token, Long memberId) {
Expand Down Expand Up @@ -52,6 +56,7 @@ public Long register(String token, Long memberId) {
.build();

deviceTokenRepository.save(newToken);
incrementAfterCommit(meterRegistry.counter("device.token.registered"));
return newToken.getId();
}

Expand All @@ -63,11 +68,25 @@ public void unregister(Long tokenId, Long memberId) {
throw new DatabaseException(ErrorStatus.DEVICE_TOKEN_NOT_OWNED);
}
deviceToken.setActive(false);
incrementAfterCommit(meterRegistry.counter("device.token.deleted"));
}, () -> {
throw new DatabaseException(ErrorStatus.DEVICE_TOKEN_NOT_FOUND);
});
}

private void incrementAfterCommit(Counter counter) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
counter.increment();
return;
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
counter.increment();
}
});
}

@Override
public void deactivateTokens(List<String> invalidTokens) {
List<DeviceToken> invalidEntities = deviceTokenRepository.findAllByTokenIn(invalidTokens);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@
import com.assu.server.global.apiPayload.code.status.ErrorStatus;
import com.assu.server.global.exception.DatabaseException;
import com.assu.server.global.exception.GeneralException;
import com.assu.server.infra.discord.DiscordNotifier;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Service
@Transactional
@RequiredArgsConstructor
public class BackofficeInquiryServiceImpl implements BackofficeInquiryService {

private final InquiryRepository inquiryRepository;
private final DiscordNotifier discordNotifier;

@Override
@Transactional(readOnly = true)
Expand Down Expand Up @@ -67,5 +71,19 @@ public void answer(Long inquiryId, String answerText) {
}

inquiry.answer(answerText);
runAfterCommit(() -> discordNotifier.send("💬 문의 답변이 등록되었습니다."));
}

private void runAfterCommit(Runnable task) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
task.run();
return;
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
task.run();
}
});
}
}
Loading
Loading