Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
a56f004
refactor(config): 사용하지 않는 import문 제거 DP-358
projectmiluju Sep 9, 2025
fac433f
feat(error): 알림 수락/거절 허용 에러코드 생성 DP-358
projectmiluju Sep 9, 2025
e72ef9a
feat(error): 유효하지 않은 알림 타입 에러코드 생성 DP-358
projectmiluju Sep 10, 2025
35cf13d
feat(notification): 프로젝트/스터디 신청 시 알림 생성 DP-358
projectmiluju Sep 10, 2025
0f268a5
feat(error): 알림 승인 거절 관련 에러코드 추가 DP-358
projectmiluju Sep 10, 2025
c928c16
feat(repository): 프로젝트/스터디 조회 메서드 추가 DP-358
projectmiluju Sep 10, 2025
840abee
feat(repository): 사용자 ID 목록으로 모든 사용자 평판 조회 메서드 추가 DP-358
projectmiluju Sep 10, 2025
87fee19
feat(notification): 알림 엔티티 추가 및 필드 정의 DP-358
projectmiluju Sep 10, 2025
3e515d8
feat(notification): 알림 리포지토리 추가 및 조회 메서드 정의 DP-358
projectmiluju Sep 10, 2025
1616e02
feat(notification): 알림 응답 DTO 추가 및 변환 메서드 정의 DP-358
projectmiluju Sep 10, 2025
dc9ad71
feat(notification): 알림 타입 정의 DP-358
projectmiluju Sep 10, 2025
a33ab0e
feat(notification): 프로젝트/스터디 참여 요청 이벤트 구현 DP-358
projectmiluju Sep 10, 2025
f13d606
feat(notification): 프로젝트·스터디 참여 승인/거절 이벤트 추가 DP-358
projectmiluju Sep 10, 2025
3141d3b
feat(notification): 프로젝트/스터디 참여 승인 및 거절 이벤트 리스너 추가 DP-358
projectmiluju Sep 10, 2025
c65a97d
feat(notification): 알림 페이로드 DTO 추가 DP-358
projectmiluju Sep 10, 2025
b4a8867
feat(notification): 알림 메시지 헬퍼 클래스 추가 및 온도 정보 포함 기능 구현 DP-358
projectmiluju Sep 10, 2025
b0853bd
feat(notification): 알림 요청 승인 및 거절 기능 구현 DP-358
projectmiluju Sep 10, 2025
8a109c6
feat(notification): 알림 목록 조회 및 읽음 처리 기능 추가 DP-358
projectmiluju Sep 10, 2025
55d0ee3
feat(notification): 알림 액션 처리 컨트롤러 추가 DP-358
projectmiluju Sep 10, 2025
3bdca14
feat(notification): 알림 처리 서비스 및 푸시 기능 추가 DP-358
projectmiluju Sep 10, 2025
1f45cbe
feat(notification): 알림 액션 메시지 클래스 추가 DP-358
projectmiluju Sep 10, 2025
0ad9f16
feat(notification): 스터디·프로젝트 신청 승인/거절 이벤트 처리 DP-358
projectmiluju Sep 10, 2025
8d195bf
feat(notification): 알림 페이로드/응답을 '행위자(actor)' 기준으로 리팩터링 DP-358
projectmiluju Sep 10, 2025
2e7eb7e
feat(notification): 알림 목록 조회 시 '행위자(actor)' 정보 추가 및 리팩토링 DP-358
projectmiluju Sep 10, 2025
cca1071
feat(notification): 알림 메시지에 '행위자(actor)' 정보 추가 및 리팩토링 DP-358
projectmiluju Sep 10, 2025
9bb00f2
feat(notification): 알림 메시지에 '행위자(actor)' 정보 추가 및 리팩토링 DP-358
projectmiluju Sep 10, 2025
35dba0e
feat(notification): 'isRead' -> 'IsRead' DP-358
projectmiluju Sep 10, 2025
c78c660
feat(notification): DM 요청 관련 ErrorCode 적용 DP-358
projectmiluju Sep 10, 2025
713d690
feat(notification): DM 요청 생성 API 추가 DP-358
projectmiluju Sep 10, 2025
d69e50f
feat(notification): DM 요청 상태 관리 및 엔티티 추가 DP-358
projectmiluju Sep 10, 2025
652e04e
feat(notification): DM 요청 상태 관리 및 엔티티 추가 DP-358
projectmiluju Sep 10, 2025
c964412
feat(notification): DM 요청 생성 서비스 및 DTO 추가 DP-358
projectmiluju Sep 10, 2025
043ab93
feat(notification): DM 요청 생성 이벤트 및 리스너 추가 DP-358
projectmiluju Sep 10, 2025
d32e7d0
feat(notification): DM 요청에 대한 actor ID 처리 추가 DP-358
projectmiluju Sep 10, 2025
0c80ea4
feat(notification): 주석 정리 DP-358
projectmiluju Sep 10, 2025
2eefcc5
feat(notification): DM 요청 승인/거절 및 개인 채팅 방 생성 구현 DP-358
projectmiluju Sep 10, 2025
89ccc82
feat(notification): DM 요청 수락 및 거절 로직 추가 DP-358
projectmiluju Sep 10, 2025
976da02
feat(notification): DM_REJECTED,DM_APPROVED 알림 타입 추가 DP-358
projectmiluju Sep 10, 2025
770dfef
feat(notification): DM 요청 승인/거절 처리 및 알림 전송 로직 추가 DP-358
projectmiluju Sep 10, 2025
263bede
feat(notification): DM 요청 승인/거절 시 이벤트 발행 로직 추가 DP-358
projectmiluju Sep 10, 2025
be55ddc
feat(notification): WebSocket message broker 에 queue 추가 DP-358
projectmiluju Sep 11, 2025
3f5963c
Merge remote-tracking branch 'origin/develop' into feature/DP-358-not…
projectmiluju Sep 11, 2025
7b19da3
Merge branch 'develop' into feature/DP-358-notification
projectmiluju Sep 11, 2025
3d4c2ad
Merge branch 'develop' into feature/DP-358-notification
projectmiluju Sep 12, 2025
1c549b5
fix(notification): DM approval, rejection user ID handling 추가 DP-358
projectmiluju Sep 12, 2025
411144d
fix(notification): DM 알림 로직에서 applicant와 requester 사용자 ID가 뒤바뀐 문제 수정 …
projectmiluju Sep 12, 2025
33f8761
fix(notification): IsProcessed 추가 DP-358
projectmiluju Sep 12, 2025
bdf2d80
Merge branch 'develop' into feature/DP-358-notification
projectmiluju Sep 12, 2025
ee24f7d
Merge branch 'develop' into feature/DP-358-notification
projectmiluju Sep 12, 2025
ccea782
fix(ReputationQueryService): add 강원특별자치도 to 강원 region list DP-358
projectmiluju Sep 12, 2025
1da01fc
feat(chat): 개인 채팅방 관리를 위한 ChatRoomRepository 및 ChatRoomService 추가 DP-358
projectmiluju Sep 12, 2025
44b7c5f
feat(ProfileSearchService): ChatRoomService 연동하여 개인 채팅방 ID 조회 기능 추가 D…
projectmiluju Sep 12, 2025
52752fc
refactor(StudyRecruitmentService): 스터디 지원시 2번 저장되던 현상 수정 DP-358
projectmiluju Sep 12, 2025
bff161b
feat: ChatRoomService 연동하여 개인 채팅방 ID 조회 기능 추가 DP-358
projectmiluju Sep 12, 2025
1fde302
feat(chat): 프라이빗 채팅방 생성 구현 및 DM 요청 처리 개선 DP-358
projectmiluju Sep 12, 2025
4955c91
feat(DmRequest): DM 요청 시 프라이빗 채팅방 중복 생성 방지 DP-358
projectmiluju Sep 12, 2025
697289e
feat(StudyRecruitmentQueryService): ChatRoomService 연동하여 개인 채팅방 ID 조회…
projectmiluju Sep 12, 2025
9a515d6
feat(StudyRecruitmentEditService): 개인 채팅방 ID 조회 기능 추가 DP-358
projectmiluju Sep 12, 2025
945c516
feat(ProjectRecruitmentQueryService): 개인 채팅방 ID 조회 기능 추가 DP-358
projectmiluju Sep 12, 2025
75e8d8f
feat(ProjectRecruitmentEditService): ChatRoomService 연동으로 개인 채팅방 ID 조…
projectmiluju Sep 12, 2025
81acb2f
feat(PlayerProfileService, ProfileQueryService): ChatRoomService 연동하여…
projectmiluju Sep 12, 2025
80266b0
feat(reputation): ChatRoomService 연동하여 랭킹 응답에 개인 채팅방 ID 조회 추가 DP-358
projectmiluju Sep 12, 2025
0ea6fb5
feat(DmRequestRepository): 특정 상태로 양방향 DM 요청 존재 여부 조회 메서드 추가 DP-358
projectmiluju Sep 12, 2025
7abfd3a
feat(DmRequestCommandService): DM 요청 상태와 채팅방 존재 여부 확인 메서드 추가 DP-358
projectmiluju Sep 12, 2025
8ab953b
feat(PlayerProfileService): 프로필 업데이트에 DM 요청 상태 확인 로직 연동 DP-358
projectmiluju Sep 12, 2025
73cf600
feat(ProfileQueryService): DM 요청 상태 확인 로직 추가하여 프로필 응답 개선 DP-358
projectmiluju Sep 12, 2025
ef6a549
feat(ProfileSearchService): 플레이어 검색 응답에 DM 요청 상태 반영 DP-358
projectmiluju Sep 12, 2025
f795289
feat(ProjectRecruitment): 프로젝트 모집 응답에 DM 요청 상태 반영 DP-358
projectmiluju Sep 12, 2025
167a4dc
feat(StudyRecruitment): 스터디 모집 응답에 DM 요청 상태 반영 DP-358
projectmiluju Sep 12, 2025
0aaf22b
feat(Team): 팀 확정자/지원자 응답에 DM 요청 상태 반영 DP-358
projectmiluju Sep 12, 2025
ab4f3af
feat(ReputationQuery): DM 요청 상태 반영 및 응답 개선 DP-358
projectmiluju Sep 12, 2025
116f545
feat(TeamMemberRepository): 팀 ID로 활성 팀원 목록 조회 메서드 추가 DP-358
projectmiluju Sep 12, 2025
1633621
feat(notification): 팀원 하차/추방 알림 리스너 추가 DP-358
projectmiluju Sep 12, 2025
29fad92
feat(notification): actor ID 해석 로직에 TEAM_MEMBER_VIOLATION 추가 DP-358
projectmiluju Sep 12, 2025
e5e2383
feat(notification): 팀원 하차/추방 시 TeamMemberExitEvent 발행 DP-358
projectmiluju Sep 12, 2025
22beb3f
feat(BadgeTierRuleRepository): 배지 타입과 누적 횟수 기준으로 최상위 티어 룰 조회 메서드 추가 D…
projectmiluju Sep 12, 2025
2d94afd
feat(notification): 업적 달성 알림을 위한 BadgeAchievementEvent 및 리스너 추가 DP-358
projectmiluju Sep 12, 2025
ed04bc3
feat(badge): 배지 카운트 증가 시 이벤트 발행 로직 추가 DP-358
projectmiluju Sep 12, 2025
681031d
Merge pull request #212 from DeepDirect/feature/DP-358-notification
projectmiluju Sep 13, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ Optional<BadgeTierRule> findTopByBadgeTypeAndRequiredCntLessThanEqualOrderByRequ
BadgeType badgeType,
int requiredCnt
);

Optional<BadgeTierRule> findTopByBadgeTypeAndRequiredCntLessThanEqualOrderByRequiredCntDesc(
BadgeType badgeType, Integer totalCnt
);
}
157 changes: 57 additions & 100 deletions src/main/java/goorm/ddok/badge/service/BadgeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,159 +9,129 @@
import goorm.ddok.global.dto.AbandonBadgeDto;
import goorm.ddok.global.dto.BadgeDto;
import goorm.ddok.member.domain.User;
import goorm.ddok.notification.event.BadgeAchievementEvent;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.List;
import java.util.*;

@Service
@RequiredArgsConstructor
public class BadgeService {
private final UserBadgeRepository userBadgeRepository;
private final BadgeTierRuleRepository badgeTierRuleRepository;
private final ApplicationEventPublisher publisher;

/** 티어 규칙이 있는 배지 세트 */
private static final Set<BadgeType> TIERED_BADGES =
EnumSet.of(BadgeType.complete, BadgeType.leader_complete, BadgeType.login);

/**
* 배지 카운트 증가
*
* 특정 유저의 배지(totalCnt)를 +1 증가시킨다.
* 해당 배지가 존재하지 않으면 새로 생성한 후 카운트를 1로 설정한다.
* 배지 카운트 증가 (+ 이벤트 발행)
*
* @param user 배지를 증가시킬 사용자
* @param badgeType 증가시킬 배지 타입
* @return 갱신된 UserBadge 엔티티
* - 특정 유저의 배지(totalCnt)를 +1 증가
* - 배지 레코드가 없으면 생성 후 +1 (신규 획득으로 간주)
* - 티어 배지(complete/leader_complete/login)에 대해:
* - 신규 생성 시: NEW_BADGE 이벤트 발행 (이때 newTier 포함)
* - 기존 보유 시: 티어 변경 감지되면 TIER_UP 이벤트 발행
*/
@Transactional
public UserBadge increaseBadge(User user, BadgeType badgeType) {
UserBadge userBadge = userBadgeRepository.findByUserAndBadgeType(user, badgeType)
.orElseGet(() -> UserBadge.create(user, badgeType));
var existing = userBadgeRepository.findByUserAndBadgeType(user, badgeType);
UserBadge userBadge = existing.orElseGet(() -> UserBadge.create(user, badgeType));

final boolean isNewRow = (userBadge.getId() == null);
final int prevCnt = userBadge.getTotalCnt() == null ? 0 : userBadge.getTotalCnt();
final BadgeTier prevTier = (TIERED_BADGES.contains(badgeType))
? resolveTier(badgeType, prevCnt)
: null;

// 카운트 증가 및 저장
userBadge.increaseCount();
UserBadge saved = userBadgeRepository.save(userBadge);

// 이벤트 발행 (티어 배지에만)
if (TIERED_BADGES.contains(badgeType)) {
final int newCnt = saved.getTotalCnt();
final BadgeTier newTier = resolveTier(badgeType, newCnt);

if (isNewRow) {
// 신규 생성 시에는 무조건 NEW_BADGE(complete의 bronze=0 규칙도 신규로 처리)
publisher.publishEvent(new BadgeAchievementEvent(
user.getId(), badgeType, null, newTier, newCnt, BadgeAchievementEvent.Reason.NEW_BADGE
));
} else if (newTier != prevTier) {
// 기존 보유 상태에서 티어 상승
publisher.publishEvent(new BadgeAchievementEvent(
user.getId(), badgeType, prevTier, newTier, newCnt, BadgeAchievementEvent.Reason.TIER_UP
));
}
}

return userBadgeRepository.save(userBadge);
return saved;
}

/**
* 착한 배지 조회
*
* complete, leader_complete, login 배지들을 조회하고,
* 각 배지의 누적 횟수(totalCnt)에 따라 현재 티어(bronze/silver/gold)를 계산하여 반환한다.
*
* @param user 배지를 조회할 사용자
* @return 착한 배지 리스트 (BadgeDto)
*/
/** 착한 배지 조회 */
@Transactional(readOnly = true)
public List<BadgeDto> getGoodBadges(User user) {
List<UserBadge> badges = userBadgeRepository.findByUserAndBadgeTypeIn(
user,
List.of(BadgeType.complete, BadgeType.leader_complete, BadgeType.login)
);

return badges.stream()
.map(this::toGoodBadgeDto)
.toList();
return badges.stream().map(this::toGoodBadgeDto).toList();
}

/**
* 나쁜 배지 조회
*
* abandon(탈주) 배지 조회.
* 누적 횟수가 0보다 크면 획득한 것으로 간주한다.
*
* @param user 배지를 조회할 사용자
* @return 나쁜 배지 정보 (AbandonBadgeDto)
*/
/** 나쁜 배지 조회 (abandon) */
@Transactional(readOnly = true)
public AbandonBadgeDto getAbandonBadge(User user) {
return userBadgeRepository.findByUserAndBadgeType(user, BadgeType.abandon)
.map(badge -> AbandonBadgeDto.builder()
.IsGranted(badge.getTotalCnt() > 0)
.count(badge.getTotalCnt())
.build())
.orElse(AbandonBadgeDto.builder()
.IsGranted(false)
.count(0)
.build());
.orElse(AbandonBadgeDto.builder().IsGranted(false).count(0).build());
}

/**
* 프로젝트/스터디 중도 하차 배지 부여
*
* - 중도 하차 시 abandon 배지를 +1 증가.
*
* @param user 중도 하차한 사용자
*/
/** 프로젝트/스터디 중도 하차 배지 부여 (abandon +1) */
@Transactional
public void grantAbandonBadge(User user) {
increaseBadge(user, BadgeType.abandon);
}


/**
* 착한 배지 변환
*
* UserBadge 엔티티를 BadgeDto로 변환하면서,
* totalCnt 기준으로 티어를 계산하여 함께 반환한다.
*
* @param userBadge 변환할 유저 배지 엔티티
* @return BadgeDto (배지 타입 + 티어)
*/
/** 착한 배지 DTO 변환 (+ 현재 티어 계산) */
private BadgeDto toGoodBadgeDto(UserBadge userBadge) {
BadgeTier tier = calculateTier(userBadge.getBadgeType(), userBadge.getTotalCnt());
BadgeTier tier = resolveTier(userBadge.getBadgeType(), userBadge.getTotalCnt());
return BadgeDto.builder()
.type(userBadge.getBadgeType())
.tier(tier)
.build();
}

/**
* 누적 카운트에 따른 티어 계산
*
* BadgeTierRule 테이블에서 totalCnt 이하의 가장 큰 requiredCnt를 찾아
* 해당 룰의 티어(bronze/silver/gold)를 반환한다.
* 조건에 맞는 룰이 없으면 bronze로 기본 설정된다.
*
* @param type 배지 타입
* @param totalCnt 누적 카운트
* @return 계산된 배지 티어
*/
private BadgeTier calculateTier(BadgeType type, int totalCnt) {
/** 누적 카운트 기준 티어 계산 (룰 없으면 bronze 기본) */
private BadgeTier resolveTier(BadgeType type, int totalCnt) {
return badgeTierRuleRepository
.findTopByBadgeTypeAndRequiredCntLessThanEqualOrderByRequiredCntDesc(type, totalCnt)
.map(BadgeTierRule::getTier)
.orElse(BadgeTier.bronze);
}

/**
* 로그인 배지 부여 (1일 1회 제한)
*
* @param user 로그인한 사용자
*/
/** 로그인 배지 (1일 1회) */
@Transactional
public void grantLoginBadge(User user) {
// 오늘 이미 로그인 배지 반영했는지 확인
boolean alreadyGrantedToday = userBadgeRepository.findByUserAndBadgeType(user, BadgeType.login)
.map(badge -> badge.getUpdatedAt() != null &&
badge.getUpdatedAt().isAfter(Instant.now().truncatedTo(ChronoUnit.DAYS)))
.orElse(false);

if (!alreadyGrantedToday) {
increaseBadge(user, BadgeType.login);
}
}

/**
* 프로젝트/스터디 종료 배지 부여
*
* 프로젝트 또는 스터디가 성공적으로 종료될 때 호출.
* - 멤버: complete 배지 증가
* - 리더: leader_complete 배지 추가 증가
*
* @param user 배지를 부여할 사용자
* @param isLeader 리더 여부
*/
/** 프로젝트/스터디 종료 배지 부여 */
@Transactional
public void grantCompleteBadge(User user, boolean isLeader) {
if (isLeader) {
Expand All @@ -171,33 +141,20 @@ public void grantCompleteBadge(User user, boolean isLeader) {
}
}

/**
* 대표 착한 배지 조회
*
* complete, leader_complete, login 배지 중
* 1순위 totalCnt (내림차순, max)
* 2순위 updateAt (가장 최신)
*
* @param user 대표 배지를 조회할 사용자
* @return 대표 배지 (없으면 null)
*/
/** 대표 착한 배지 조회 */
@Transactional(readOnly = true)
public BadgeDto getRepresentativeGoodBadge(User user) {
List<UserBadge> badges = userBadgeRepository.findByUserAndBadgeTypeIn(
user,
List.of(BadgeType.complete, BadgeType.leader_complete, BadgeType.login)
);

return badges.stream()
.max(Comparator.comparingInt(UserBadge::getTotalCnt)
.thenComparing(UserBadge::getUpdatedAt, Comparator.nullsLast(Comparator.naturalOrder())))
.map(b -> BadgeDto.builder()
.type(b.getBadgeType())
.tier(calculateTier(b.getBadgeType(), b.getTotalCnt()))
.tier(resolveTier(b.getBadgeType(), b.getTotalCnt()))
.build())
.orElse(null);
}



}
54 changes: 54 additions & 0 deletions src/main/java/goorm/ddok/chat/controller/ChatController.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
package goorm.ddok.chat.controller;

import goorm.ddok.chat.dto.request.ChatMessageRequest;
import goorm.ddok.chat.dto.request.DmRequestDto;
import goorm.ddok.chat.dto.request.LastReadMessageRequest;
import goorm.ddok.chat.dto.response.*;
import goorm.ddok.chat.service.ChatMessageService;
import goorm.ddok.chat.service.ChatRoomManagementService;
import goorm.ddok.chat.service.ChatRoomQueryService;
import goorm.ddok.chat.service.DmRequestCommandService;
import goorm.ddok.global.exception.ErrorCode;
import goorm.ddok.global.exception.GlobalException;
import goorm.ddok.global.response.ApiResponseDto;
import goorm.ddok.global.security.auth.CustomUserDetails;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

Expand All @@ -34,6 +40,7 @@ public class ChatController {

private final ChatMessageService chatMessageService;
private final ChatRoomQueryService chatRoomQueryService;
private final DmRequestCommandService dmRequestCommandService;

@GetMapping("/private")
@Operation(summary = "개인 채팅 목록 조회", description = "사용자의 1:1 개인 채팅 목록을 조회합니다.")
Expand Down Expand Up @@ -202,4 +209,51 @@ public ResponseEntity<ApiResponseDto<ChatReadResponse>> sendReadMessage(

return ResponseEntity.ok(ApiResponseDto.of(200, "메세지 읽음 처리 완료", response));
}

@Operation(
summary = "플레이어 DM 요청 만들기",
description = "특정 사용자에게 DM 요청을 생성합니다.",
security = @SecurityRequirement(name = "Authorization"),
parameters = {
@Parameter(
name = "Authorization", in = ParameterIn.HEADER, required = true,
description = "Bearer {accessToken}",
examples = @ExampleObject(value = "Bearer eyJhbGciOi...")
),
@Parameter(name = "userId", in = ParameterIn.PATH, required = true, description = "DM을 받는 사용자 ID")
}
)
@ApiResponse(
responseCode = "201",
description = "DM 요청 생성 성공",
content = @Content(schema = @Schema(implementation = ApiResponseDto.class),
examples = @ExampleObject(value = """
{
"status": 201,
"message": "플레이어 DM 요청이 생성되었습니다.",
"data": {
"dmRequestId": 301,
"fromUserId": 10,
"toUserId": 1,
"status": "PENDING",
"chatRoomId": null,
"dmRequestPending": true,
"createdAt": "2025-08-20T01:55:00Z"
}
}
"""))
)
@PostMapping("/{userId}/dm-requests")
public ResponseEntity<ApiResponseDto<DmRequestCreateResponse>> createDmRequest(
@PathVariable Long userId,
@AuthenticationPrincipal CustomUserDetails loginUser
) {
DmRequestDto dto = dmRequestCommandService.create(userId, loginUser);

return ResponseEntity.ok(ApiResponseDto.of(
201,
"플레이어 DM 요청이 생성되었습니다.",
DmRequestCreateResponse.from(dto)
));
}
}
Loading
Loading