Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.example.RealMatch.chat.presentation.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;

import com.example.RealMatch.chat.presentation.conversion.MessageCursor;
import com.example.RealMatch.chat.presentation.conversion.RoomCursor;

/**
* cursor 요청 파라미터를 RoomCursor/MessageCursor로 바인딩하는 설정
*/
@Configuration
public class ChatCursorConverterConfig {

@Bean
public Converter<String, RoomCursor> roomCursorConverter() {
return RoomCursor::decode;
}

@Bean
public Converter<String, MessageCursor> messageCursorConverter() {
return MessageCursor::decode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.example.RealMatch.chat.presentation.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.example.RealMatch.chat.presentation.controller.fixture.ChatFixtureFactory;
import com.example.RealMatch.chat.presentation.conversion.MessageCursor;
import com.example.RealMatch.chat.presentation.conversion.RoomCursor;
import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomFilterStatus;
import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomSort;
import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomTab;
import com.example.RealMatch.chat.presentation.dto.request.ChatAttachmentUploadRequest;
import com.example.RealMatch.chat.presentation.dto.request.ChatRoomCreateRequest;
import com.example.RealMatch.chat.presentation.dto.response.ChatAttachmentUploadResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatMessageListResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatRoomCreateResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatRoomDetailResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatRoomListResponse;
import com.example.RealMatch.chat.presentation.swagger.ChatSwagger;
import com.example.RealMatch.global.config.jwt.CustomUserDetails;
import com.example.RealMatch.global.presentation.CustomResponse;

import jakarta.validation.Valid;

@RestController
@RequestMapping("/api/chat")
public class ChatController implements ChatSwagger {

@PostMapping("/rooms")
public CustomResponse<ChatRoomCreateResponse> createOrGetRoom(
@AuthenticationPrincipal CustomUserDetails user,
@Valid @RequestBody ChatRoomCreateRequest request
) {
return CustomResponse.ok(ChatFixtureFactory.sampleRoomCreateResponse());
}

@GetMapping("/rooms")
public CustomResponse<ChatRoomListResponse> getRoomList(
@AuthenticationPrincipal CustomUserDetails user,
@RequestParam(required = false) ChatRoomTab tab,
@RequestParam(name = "status", required = false) ChatRoomFilterStatus filterStatus,
@RequestParam(required = false) ChatRoomSort sort,
@RequestParam(name = "cursor", required = false) RoomCursor roomCursor,
@RequestParam(defaultValue = "20") int size
) {
return CustomResponse.ok(ChatFixtureFactory.sampleRoomListResponse());
}

@GetMapping("/rooms/{roomId}")
public CustomResponse<ChatRoomDetailResponse> getRoomDetail(
@AuthenticationPrincipal CustomUserDetails user,
@PathVariable Long roomId
) {
return CustomResponse.ok(ChatFixtureFactory.sampleRoomDetailResponse(roomId));
}

@GetMapping("/rooms/{roomId}/messages")
public CustomResponse<ChatMessageListResponse> getMessages(
@AuthenticationPrincipal CustomUserDetails user,
@PathVariable Long roomId,
@RequestParam(name = "cursor", required = false) MessageCursor messageCursor,
@RequestParam(defaultValue = "20") int size
) {
return CustomResponse.ok(ChatFixtureFactory.sampleMessageListResponse(roomId));
}

@PostMapping("/attachments")
public CustomResponse<ChatAttachmentUploadResponse> uploadAttachment(
@AuthenticationPrincipal CustomUserDetails user,
@Valid @RequestPart("request") ChatAttachmentUploadRequest request,
@RequestPart("file") MultipartFile file
) {
return CustomResponse.ok(ChatFixtureFactory.sampleAttachmentUploadResponse(request, file));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
package com.example.RealMatch.chat.presentation.controller.fixture;

import java.time.LocalDateTime;
import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import com.example.RealMatch.chat.presentation.conversion.MessageCursor;
import com.example.RealMatch.chat.presentation.conversion.RoomCursor;
import com.example.RealMatch.chat.presentation.dto.enums.ChatAttachmentStatus;
import com.example.RealMatch.chat.presentation.dto.enums.ChatAttachmentType;
import com.example.RealMatch.chat.presentation.dto.enums.ChatMessageType;
import com.example.RealMatch.chat.presentation.dto.enums.ChatProposalDecisionStatus;
import com.example.RealMatch.chat.presentation.dto.enums.ChatProposalDirection;
import com.example.RealMatch.chat.presentation.dto.enums.ChatProposalStatus;
import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomTab;
import com.example.RealMatch.chat.presentation.dto.enums.ChatSenderType;
import com.example.RealMatch.chat.presentation.dto.enums.ChatSystemMessageKind;
import com.example.RealMatch.chat.presentation.dto.request.ChatAttachmentUploadRequest;
import com.example.RealMatch.chat.presentation.dto.response.ChatAttachmentInfoResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatAttachmentUploadResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatMatchedCampaignPayloadResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatMessageListResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatMessageResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatProposalActionButtonResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatProposalActionButtonsResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatProposalCardPayloadResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatProposalStatusNoticePayloadResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatRoomCardResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatRoomCreateResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatRoomDetailResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatRoomListResponse;
import com.example.RealMatch.chat.presentation.dto.response.ChatSystemMessageResponse;

/**
* 채팅 API 테스트를 위한 Fixture 응답 DTO를 생성하는 팩토리 (서비스 로직 완성하면 삭제할 것임)
*/
public final class ChatFixtureFactory {

private ChatFixtureFactory() {
}

public static ChatRoomCreateResponse sampleRoomCreateResponse() {
return new ChatRoomCreateResponse(
3001L,
"direct:101:202",
ChatProposalDirection.NONE,
LocalDateTime.of(2025, 1, 1, 0, 0)
);
}

public static ChatRoomListResponse sampleRoomListResponse() {
ChatRoomCardResponse card = new ChatRoomCardResponse(
3001L,
202L,
"라운드랩",
"https://yt3.googleusercontent.com/ytc/AIdro_lLlKeDBBNPBO1FW7jkxvXpJyyM6CU2AR7NMx2GIjFFxQ=s900-c-k-c0x00ffffff-no-rj",
ChatProposalStatus.REVIEWING,
"안녕하세요!",
ChatMessageType.TEXT,
LocalDateTime.of(2025, 1, 1, 10, 0),
3,
ChatRoomTab.SENT
);
return new ChatRoomListResponse(
5L,
2L,
7L,
List.of(card),
RoomCursor.of(LocalDateTime.of(2025, 1, 1, 9, 59, 59), 2999L),
true
);
}

public static ChatRoomDetailResponse sampleRoomDetailResponse(Long roomId) {
return new ChatRoomDetailResponse(
roomId,
202L,
"라운드랩",
"https://yt3.googleusercontent.com/ytc/AIdro_lLlKeDBBNPBO1FW7jkxvXpJyyM6CU2AR7NMx2GIjFFxQ=s900-c-k-c0x00ffffff-no-rj",
List.of("청정자극", "저자극", "심플한 감성"),
ChatProposalStatus.REVIEWING,
ChatProposalStatus.REVIEWING.labelOrNull()
);
}

public static ChatMessageListResponse sampleMessageListResponse(Long roomId) {
return new ChatMessageListResponse(
List.of(
sampleMatchedCampaignMessage(roomId),
sampleProposalStatusNoticeMessage(roomId),
sampleSystemMessage(roomId),
sampleFileMessage(roomId),
sampleImageMessage(roomId),
sampleTextMessage(roomId)
),
MessageCursor.of(6999L),
true
);
}

private static ChatMessageResponse sampleTextMessage(Long roomId) {
return new ChatMessageResponse(
7001L,
roomId,
202L,
ChatSenderType.USER,
ChatMessageType.TEXT,
"안녕하세요!",
null,
null,
LocalDateTime.of(2025, 1, 1, 10, 2),
"11111111-1111-1111-1111-111111111111"
);
}

private static ChatMessageResponse sampleImageMessage(Long roomId) {
ChatAttachmentInfoResponse attachment = new ChatAttachmentInfoResponse(
9001L,
ChatAttachmentType.IMAGE,
"image/png",
"photo.png",
204800L,
"https://img.cjnews.cj.net/wp-content/uploads/2020/11/CJ%EC%98%AC%EB%A6%AC%EB%B8%8C%EC%98%81-%EC%98%AC%EB%A6%AC%EB%B8%8C%EC%98%81-%EC%83%88-BI-%EB%A1%9C%EA%B3%A0.jpg",
ChatAttachmentStatus.READY
);
return new ChatMessageResponse(
7002L,
roomId,
202L,
ChatSenderType.USER,
ChatMessageType.IMAGE,
null,
attachment,
null,
LocalDateTime.of(2025, 1, 1, 10, 3),
"22222222-2222-2222-2222-222222222222"
);
}

private static ChatMessageResponse sampleFileMessage(Long roomId) {
ChatAttachmentInfoResponse attachment = new ChatAttachmentInfoResponse(
9002L,
ChatAttachmentType.FILE,
"application/pdf",
"proposal.pdf",
102400L,
"https://cdn.example.com/attachments/9002.pdf",
ChatAttachmentStatus.READY
);
return new ChatMessageResponse(
7003L,
roomId,
202L,
ChatSenderType.USER,
ChatMessageType.FILE,
null,
attachment,
null,
LocalDateTime.of(2025, 1, 1, 10, 4),
"33333333-3333-3333-3333-333333333333"
);
}

private static ChatMessageResponse sampleSystemMessage(Long roomId) {
ChatSystemMessageResponse systemMessage = new ChatSystemMessageResponse(
1,
ChatSystemMessageKind.PROPOSAL_CARD,
new ChatProposalCardPayloadResponse(
5001L,
4001L,
"캠페인 A",
"캠페인 요약 문구",
ChatProposalDecisionStatus.PENDING,
ChatProposalDirection.BRAND_TO_CREATOR,
new ChatProposalActionButtonsResponse(
new ChatProposalActionButtonResponse(
"제안 수락하기",
true
),
new ChatProposalActionButtonResponse(
"거절하기",
true
)
),
null
)
);
return new ChatMessageResponse(
7004L,
roomId,
null,
ChatSenderType.SYSTEM,
ChatMessageType.SYSTEM,
null,
null,
systemMessage,
LocalDateTime.of(2025, 1, 1, 10, 5),
null
);
}

private static ChatMessageResponse sampleProposalStatusNoticeMessage(Long roomId) {
ChatSystemMessageResponse systemMessage = new ChatSystemMessageResponse(
1,
ChatSystemMessageKind.PROPOSAL_STATUS_NOTICE,
new ChatProposalStatusNoticePayloadResponse(
5001L,
202L,
LocalDateTime.of(2025, 1, 1, 10, 6)
)
);
return new ChatMessageResponse(
7005L,
roomId,
null,
ChatSenderType.SYSTEM,
ChatMessageType.SYSTEM,
null,
null,
systemMessage,
LocalDateTime.of(2025, 1, 1, 10, 6),
null
);
}

private static ChatMessageResponse sampleMatchedCampaignMessage(Long roomId) {
ChatSystemMessageResponse systemMessage = new ChatSystemMessageResponse(
1,
ChatSystemMessageKind.MATCHED_CAMPAIGN_CARD,
new ChatMatchedCampaignPayloadResponse(
4001L,
"캠페인 A",
150000L,
"KRW",
"ORDER-20250101-0001",
"캠페인이 매칭되었습니다. 협업을 시작해 주세요."
)
);
return new ChatMessageResponse(
7006L,
roomId,
null,
ChatSenderType.SYSTEM,
ChatMessageType.SYSTEM,
null,
null,
systemMessage,
LocalDateTime.of(2025, 1, 1, 10, 7),
null
);
}

public static ChatAttachmentUploadResponse sampleAttachmentUploadResponse(
ChatAttachmentUploadRequest request,
MultipartFile file
) {
return new ChatAttachmentUploadResponse(
9001L,
request.attachmentType(),
file.getContentType(),
file.getOriginalFilename(),
file.getSize(),
"https://cdn.example.com/attachments/9001",
ChatAttachmentStatus.UPLOADED,
LocalDateTime.of(2025, 1, 1, 10, 10)
);
}
}
Loading