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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.sofa.linkiving.domain.chat.controller;

import com.sofa.linkiving.domain.chat.dto.request.AddFeedbackReq;
import com.sofa.linkiving.domain.chat.dto.response.AddFeedbackRes;
import com.sofa.linkiving.domain.member.entity.Member;
import com.sofa.linkiving.global.common.BaseResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "Feedback", description = "피드백 관리 API")
public interface FeedbackApi {
@Operation(summary = "피드백 추가", description = "메세지에 피드백을 추가하고 생성된 피드백 ID를 반환합니다.")
BaseResponse<AddFeedbackRes> createFeedback(Long messageId, AddFeedbackReq createFeedbackReq, Member member);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.sofa.linkiving.domain.chat.controller;

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.RestController;

import com.sofa.linkiving.domain.chat.dto.request.AddFeedbackReq;
import com.sofa.linkiving.domain.chat.dto.response.AddFeedbackRes;
import com.sofa.linkiving.domain.chat.facade.FeedbackFacade;
import com.sofa.linkiving.domain.member.entity.Member;
import com.sofa.linkiving.global.common.BaseResponse;
import com.sofa.linkiving.security.annotation.AuthMember;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/v1/messages")
@RequiredArgsConstructor
public class FeedbackController implements FeedbackApi {
private final FeedbackFacade feedbackFacade;

@Override
@PostMapping("/{messageId}/feedback")
public BaseResponse<AddFeedbackRes> createFeedback(@PathVariable Long messageId,
@Valid @RequestBody AddFeedbackReq req, @AuthMember Member member) {
AddFeedbackRes res = feedbackFacade.createFeedback(member, messageId, req.sentiment(), req.text());
return BaseResponse.success(res, "피드백이 등록되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.sofa.linkiving.domain.chat.dto.request;

import com.sofa.linkiving.domain.chat.enums.Sentiment;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record AddFeedbackReq(
@NotNull(message = "피드백 상태는 필수입니다.") // 필수 값 체크
@Schema(description = "피드백 상태 (LIKE, DISLIKE)", example = "LIKE")
Sentiment sentiment,
@Schema(description = "피드백 내용 (선택)", example = "도움이 되었습니다.")
@Size(max = 20, message = "피드백 내용은 20자를 넘을 수 없습니다.")
String text
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.sofa.linkiving.domain.chat.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

public record AddFeedbackRes(
@Schema(description = "피드백 ID")
Long id
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.sofa.linkiving.domain.chat.error;

import org.springframework.http.HttpStatus;

import com.sofa.linkiving.global.error.code.ErrorCode;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum MessageErrorCode implements ErrorCode {
MESSAGE_NOT_FOUND(HttpStatus.NOT_FOUND, "MS-001", "메세지를 찾을 수 없습니다.");

private final HttpStatus status;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.sofa.linkiving.domain.chat.facade;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.sofa.linkiving.domain.chat.dto.response.AddFeedbackRes;
import com.sofa.linkiving.domain.chat.entity.Message;
import com.sofa.linkiving.domain.chat.enums.Sentiment;
import com.sofa.linkiving.domain.chat.service.FeedbackService;
import com.sofa.linkiving.domain.chat.service.MessageService;
import com.sofa.linkiving.domain.member.entity.Member;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional
public class FeedbackFacade {
private final FeedbackService feedbackService;
private final MessageService messageService;

public AddFeedbackRes createFeedback(Member member, Long messageId, Sentiment sentiment, String text) {

Message message = messageService.get(messageId, member);
Long id = feedbackService.create(message, sentiment, text);
return new AddFeedbackRes(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sofa.linkiving.domain.chat.repository;

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

import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -11,9 +12,13 @@

import com.sofa.linkiving.domain.chat.entity.Chat;
import com.sofa.linkiving.domain.chat.entity.Message;
import com.sofa.linkiving.domain.member.entity.Member;

@Repository
public interface MessageRepository extends JpaRepository<Message, Long> {
@Query("SELECT m FROM Message m JOIN m.chat c WHERE m.id = :id AND c.member = :member")
Optional<Message> findByIdAndMember(@Param("id") Long id, @Param("member") Member member);

@Modifying(clearAutomatically = true)
@Query("DELETE FROM Message m WHERE m.chat = :chat")
void deleteAllByChat(Chat chat);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.stereotype.Service;

import com.sofa.linkiving.domain.chat.entity.Chat;
import com.sofa.linkiving.domain.chat.entity.Feedback;
import com.sofa.linkiving.domain.chat.repository.FeedbackRepository;

import lombok.RequiredArgsConstructor;
Expand All @@ -12,6 +13,10 @@
public class FeedbackCommandService {
private final FeedbackRepository feedbackRepository;

public Feedback save(Feedback feedback) {
return feedbackRepository.save(feedback);
}

public void deleteFeedbacksByChat(Chat chat) {
feedbackRepository.deleteAllByChat(chat);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import org.springframework.stereotype.Service;

import com.sofa.linkiving.domain.chat.entity.Chat;
import com.sofa.linkiving.domain.chat.entity.Feedback;
import com.sofa.linkiving.domain.chat.entity.Message;
import com.sofa.linkiving.domain.chat.enums.Sentiment;

import lombok.RequiredArgsConstructor;

Expand All @@ -12,6 +15,15 @@ public class FeedbackService {
private final FeedbackQueryService feedbackQueryService;
private final FeedbackCommandService feedbackCommandService;

public Long create(Message message, Sentiment sentiment, String text) {
Feedback feedback = Feedback.builder()
.message(message)
.sentiment(sentiment)
.text(text)
.build();
return feedbackCommandService.save(feedback).getId();
}

public void deleteAll(Chat chat) {
feedbackCommandService.deleteFeedbacksByChat(chat);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

import com.sofa.linkiving.domain.chat.entity.Chat;
import com.sofa.linkiving.domain.chat.entity.Message;
import com.sofa.linkiving.domain.chat.error.MessageErrorCode;
import com.sofa.linkiving.domain.chat.repository.MessageRepository;
import com.sofa.linkiving.domain.member.entity.Member;
import com.sofa.linkiving.global.error.exception.BusinessException;

import lombok.RequiredArgsConstructor;

Expand All @@ -18,6 +21,12 @@
public class MessageQueryService {
private final MessageRepository messageRepository;

public Message findByIdAndMember(Long messageId, Member member) {
return messageRepository.findByIdAndMember(messageId, member).orElseThrow(
() -> new BusinessException(MessageErrorCode.MESSAGE_NOT_FOUND)
);
}

public Slice<Message> findAllByChatAndCursor(Chat chat, Long lastId, int size) {
PageRequest pageRequest = PageRequest.of(0, size + 1);
List<Message> messages = messageRepository.findAllByChatAndCursor(chat, lastId, pageRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.sofa.linkiving.domain.link.entity.Link;
import com.sofa.linkiving.domain.link.entity.Summary;
import com.sofa.linkiving.domain.link.service.SummaryQueryService;
import com.sofa.linkiving.domain.member.entity.Member;

import lombok.RequiredArgsConstructor;
import reactor.core.Disposable;
Expand Down Expand Up @@ -95,6 +96,10 @@ private void saveMessage(Chat chat, Type type, String content) {
messageCommandService.saveMessage(message);
}

public Message get(Long messageId, Member member) {
return messageQueryService.findByIdAndMember(messageId, member);
}

public void deleteAll(Chat chat) {
messageCommandService.deleteAllByChat(chat);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.sofa.linkiving.domain.chat.facade;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.sofa.linkiving.domain.chat.dto.response.AddFeedbackRes;
import com.sofa.linkiving.domain.chat.entity.Message;
import com.sofa.linkiving.domain.chat.enums.Sentiment;
import com.sofa.linkiving.domain.chat.service.FeedbackService;
import com.sofa.linkiving.domain.chat.service.MessageService;
import com.sofa.linkiving.domain.member.entity.Member;

@ExtendWith(MockitoExtension.class)
public class FeedbackFacadeTest {
@InjectMocks
private FeedbackFacade feedbackFacade;

@Mock
private FeedbackService feedbackService;

@Mock
private MessageService messageService;

@Test
@DisplayName("피드백 생성 요청 시 Message 조회 후 Feedback을 생성하고 결과를 반환함")
void shouldCreateFeedbackAndReturnRes() {
// given
Long messageId = 1L;
Long feedbackId = 100L;
Sentiment sentiment = Sentiment.LIKE;
String text = "도움이 되었어요";

Message message = mock(Message.class);
Member member = mock(Member.class);

given(messageService.get(messageId, member)).willReturn(message);
given(feedbackService.create(message, sentiment, text)).willReturn(feedbackId);

// when
AddFeedbackRes result = feedbackFacade.createFeedback(member, messageId, sentiment, text);

// then
assertThat(result).isNotNull();
assertThat(result.id()).isEqualTo(feedbackId);

verify(messageService).get(messageId, member);
verify(feedbackService).create(message, sentiment, text);
}
}
Loading