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
Expand Up @@ -7,12 +7,14 @@
import com.sofa.linkiving.domain.link.dto.request.LinkTitleUpdateReq;
import com.sofa.linkiving.domain.link.dto.request.LinkUpdateReq;
import com.sofa.linkiving.domain.link.dto.request.MetaScrapeReq;
import com.sofa.linkiving.domain.link.dto.request.SummaryUpdateReq;
import com.sofa.linkiving.domain.link.dto.response.LinkCardsRes;
import com.sofa.linkiving.domain.link.dto.response.LinkDetailRes;
import com.sofa.linkiving.domain.link.dto.response.LinkDuplicateCheckRes;
import com.sofa.linkiving.domain.link.dto.response.LinkRes;
import com.sofa.linkiving.domain.link.dto.response.MetaScrapeRes;
import com.sofa.linkiving.domain.link.dto.response.RecreateSummaryResponse;
import com.sofa.linkiving.domain.link.dto.response.SummaryRes;
import com.sofa.linkiving.domain.link.enums.Format;
import com.sofa.linkiving.domain.member.entity.Member;
import com.sofa.linkiving.global.common.BaseResponse;
Expand Down Expand Up @@ -98,4 +100,11 @@ BaseResponse<RecreateSummaryResponse> recreateSummary(
@Valid Format format,
Member member
);

@Operation(summary = "새로운 요약 선택", description = "신규 요약으로 요약 내용을 수정합니다.")
BaseResponse<SummaryRes> updateSummary(
Long id,
@Valid SummaryUpdateReq request,
Member member
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
import com.sofa.linkiving.domain.link.dto.request.LinkTitleUpdateReq;
import com.sofa.linkiving.domain.link.dto.request.LinkUpdateReq;
import com.sofa.linkiving.domain.link.dto.request.MetaScrapeReq;
import com.sofa.linkiving.domain.link.dto.request.SummaryUpdateReq;
import com.sofa.linkiving.domain.link.dto.response.LinkCardsRes;
import com.sofa.linkiving.domain.link.dto.response.LinkDetailRes;
import com.sofa.linkiving.domain.link.dto.response.LinkDuplicateCheckRes;
import com.sofa.linkiving.domain.link.dto.response.LinkRes;
import com.sofa.linkiving.domain.link.dto.response.MetaScrapeRes;
import com.sofa.linkiving.domain.link.dto.response.RecreateSummaryResponse;
import com.sofa.linkiving.domain.link.dto.response.SummaryRes;
import com.sofa.linkiving.domain.link.enums.Format;
import com.sofa.linkiving.domain.link.facade.LinkFacade;
import com.sofa.linkiving.domain.member.entity.Member;
Expand Down Expand Up @@ -152,4 +154,15 @@ public BaseResponse<RecreateSummaryResponse> recreateSummary(
RecreateSummaryResponse response = linkFacade.recreateSummary(member, id, format);
return BaseResponse.success(response, "요약 재성성 완료");
}

@Override
@PatchMapping("/{id}/summary")
public BaseResponse<SummaryRes> updateSummary(
@PathVariable Long id,
@RequestBody SummaryUpdateReq request,
@AuthMember Member member
) {
SummaryRes response = linkFacade.updateSummary(id, member, request.summary(), request.format());
return BaseResponse.success(response, "요약 수정 완료");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.sofa.linkiving.domain.link.dto.request;

import com.sofa.linkiving.domain.link.enums.Format;

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

public record SummaryUpdateReq(
@NotNull(message = "요약 내용은 필수입니다.")
@Schema(description = "요약 내용", example = "새롭게 선택한 요약 내용")
String summary,
@NotNull(message = "요약 포맷 정보는 필수입니다.")
@Schema(description = "요약 포맷 정보 (CONCISE, DETAILED)", example = "CONCISE")
Format format
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,4 @@ public static LinkDetailRes of(Link link, Summary summary) {
);
}

public record SummaryRes(
@Schema(description = "요약 ID")
Long id,
@Schema(description = "요약 내용", example = "이 링크는 예시 링크입니다.")
String content
) {
public static SummaryRes from(Summary summary) {
if (summary == null) {
return null;
}
return new SummaryRes(summary.getId(), summary.getContent());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.sofa.linkiving.domain.link.dto.response;

import com.sofa.linkiving.domain.link.entity.Summary;

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

public record SummaryRes(
@Schema(description = "요약 ID")
Long id,
@Schema(description = "요약 내용", example = "이 링크는 예시 링크입니다.")
String content
) {
public static SummaryRes from(Summary summary) {
if (summary == null) {
return null;
}
return new SummaryRes(summary.getId(), summary.getContent());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
import com.sofa.linkiving.domain.link.dto.response.LinkRes;
import com.sofa.linkiving.domain.link.dto.response.MetaScrapeRes;
import com.sofa.linkiving.domain.link.dto.response.RecreateSummaryResponse;
import com.sofa.linkiving.domain.link.dto.response.SummaryRes;
import com.sofa.linkiving.domain.link.entity.Link;
import com.sofa.linkiving.domain.link.entity.Summary;
import com.sofa.linkiving.domain.link.enums.Format;
import com.sofa.linkiving.domain.link.service.LinkService;
import com.sofa.linkiving.domain.link.service.SummaryService;
Expand Down Expand Up @@ -82,7 +84,7 @@ public RecreateSummaryResponse recreateSummary(Member member, Long linkId, Forma
String url = linkService.getLink(linkId, member).getUrl();

String existingSummary = summaryService.getSummary(linkId).getContent();
String newSummary = summaryService.createSummary(linkId, url, format);
String newSummary = summaryService.initialSummary(linkId, url, format);

String comparison = summaryService.comparisonSummary(existingSummary, newSummary);

Expand All @@ -98,4 +100,11 @@ public MetaScrapeRes scrapeMetadata(String url) {
OgTagDto ogTag = ogTagCrawler.crawl(url);
return MetaScrapeRes.from(ogTag);
}

public SummaryRes updateSummary(Long id, Member member, String content, Format format) {
Link link = linkService.getLink(id, member);
Summary summary = summaryService.createSummary(link, format, content);
summaryService.selectSummary(link.getId(), summary.getId());
return SummaryRes.from(summary);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import org.springframework.stereotype.Service;

import com.sofa.linkiving.domain.link.entity.Link;
import com.sofa.linkiving.domain.link.entity.Summary;
import com.sofa.linkiving.domain.link.enums.Format;
import com.sofa.linkiving.domain.link.error.LinkErrorCode;
import com.sofa.linkiving.domain.link.repository.SummaryRepository;
import com.sofa.linkiving.global.error.exception.BusinessException;
Expand All @@ -24,5 +27,15 @@ public void selectSummary(Long linkId, Long summaryId) {
throw new BusinessException(LinkErrorCode.SUMMARY_NOT_FOUND);
}
}

public Summary save(Link link, Format format, String content) {
return summaryRepository.save(
Summary.builder()
.link(link)
.format(format)
.content(content)
.build()
);
}
}

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.link.ai.AiSummaryClient;
import com.sofa.linkiving.domain.link.entity.Link;
import com.sofa.linkiving.domain.link.entity.Summary;
import com.sofa.linkiving.domain.link.enums.Format;

Expand All @@ -12,9 +13,10 @@
@RequiredArgsConstructor
public class SummaryService {
private final SummaryQueryService summaryQueryService;
private final SummaryCommandService summaryCommandService;
private final AiSummaryClient aiSummaryClient;

public String createSummary(Long linkId, String url, Format format) {
public String initialSummary(Long linkId, String url, Format format) {
return aiSummaryClient.generateSummary(linkId, url, format);
}

Expand All @@ -25,4 +27,12 @@ public String comparisonSummary(String existingSummary, String newSummary) {
public Summary getSummary(Long linkId) {
return summaryQueryService.getSummary(linkId);
}

public Summary createSummary(Link link, Format format, String content) {
return summaryCommandService.save(link, format, content);
}

public void selectSummary(Long linkId, Long summaryId) {
summaryCommandService.selectSummary(linkId, summaryId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ void shouldReturnRecreateSummaryResponseWhenRecreateSummary() {
given(summaryService.getSummary(linkId)).willReturn(mockSummary);

// 3. SummaryService (새 요약 생성 및 비교)
given(summaryService.createSummary(linkId, url, format)).willReturn(newSummaryBody);
given(summaryService.initialSummary(linkId, url, format)).willReturn(newSummaryBody);
given(summaryService.comparisonSummary(existingSummaryBody, newSummaryBody)).willReturn(comparisonBody);

// when
Expand All @@ -130,7 +130,7 @@ void shouldReturnRecreateSummaryResponseWhenRecreateSummary() {

// verify
verify(summaryService).getSummary(linkId);
verify(summaryService).createSummary(linkId, url, format);
verify(summaryService).initialSummary(linkId, url, format);
verify(summaryService).comparisonSummary(existingSummaryBody, newSummaryBody);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.sofa.linkiving.domain.link.dto.request.LinkTitleUpdateReq;
import com.sofa.linkiving.domain.link.dto.request.LinkUpdateReq;
import com.sofa.linkiving.domain.link.dto.request.MetaScrapeReq;
import com.sofa.linkiving.domain.link.dto.request.SummaryUpdateReq;
import com.sofa.linkiving.domain.link.entity.Link;
import com.sofa.linkiving.domain.link.entity.Summary;
import com.sofa.linkiving.domain.link.enums.Format;
Expand Down Expand Up @@ -724,4 +725,29 @@ void shouldFailWhenMetaScrapeUrlIsMissing() throws Exception {
)
.andExpect(status().isBadRequest());
}

@Test
@DisplayName("updateSummary API: PATCH 요청 시 요약 정보를 수정하고 200 OK를 반환한다")
void updateSummaryApi_ShouldReturn200Ok() throws Exception {
// given
Link savedLink = linkRepository.save(Link.builder()
.member(testMember)
.url("https://example.com/article")
.title("테스트 링크")
.build());
Long linkId = savedLink.getId();

SummaryUpdateReq request = new SummaryUpdateReq("수정된 요약 텍스트", Format.DETAILED);

// when & then
mockMvc.perform(patch(BASE_URL + "/{id}/summary", linkId)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request))
.with(csrf())
.with(user(testUserDetails))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("요약 수정 완료"))
.andExpect(jsonPath("$.data.content").value("수정된 요약 텍스트"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.*;

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

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -13,6 +14,7 @@

import com.sofa.linkiving.domain.link.entity.Link;
import com.sofa.linkiving.domain.link.entity.Summary;
import com.sofa.linkiving.domain.link.enums.Format;
import com.sofa.linkiving.domain.member.entity.Member;

@DataJpaTest
Expand Down Expand Up @@ -119,4 +121,65 @@ void shouldReturnEmptyWhenLinkListIsEmpty() {
// then
assertThat(result).isEmpty();
}

@Test
@DisplayName("clearSelectedByLinkId 및 selectByIdAndLinkId 실행 시 selected 요약이 단 1개만 존재한다")
void shouldEnsureOnlyOneSummaryIsSelected() {
// given
Member member = Member.builder()
.email("test@test.com")
.password("pw")
.build();
em.persist(member);
Link link = Link.builder()
.member(member)
.url("http://test.com")
.title("t1")
.build();
em.persist(link);

// 1. 과거 요약 1 (기존 선택됨)
Summary oldSummary1 = Summary.builder()
.link(link)
.content("과거 요약 1")
.format(Format.CONCISE)
.selected(true)
.build();

// 2. 과거 요약 2 (선택 안됨)
Summary oldSummary2 = Summary.builder()
.link(link)
.content("과거 요약 2")
.format(Format.DETAILED)
.selected(false)
.build();

// 3. 방금 새로 수정한 요약 (아직 선택 안됨)
Summary newSummary = Summary.builder()
.link(link)
.content("새로 수정한 요약")
.format(Format.DETAILED).selected(false)
.build();

em.persist(oldSummary1);
em.persist(oldSummary2);
em.persist(newSummary);

// when - Service 계층에서 수행하는 두 가지 쿼리를 순서대로 실행

summaryRepository.clearSelectedByLinkId(link.getId());
summaryRepository.selectByIdAndLinkId(newSummary.getId(), link.getId());

// then
Optional<Summary> selectedSummary = summaryRepository.findByLinkIdAndSelectedTrue(link.getId());
assertThat(selectedSummary).isPresent();
assertThat(selectedSummary.get().getId()).isEqualTo(newSummary.getId());

long selectedCount = summaryRepository.findAll().stream()
.filter(s -> s.getLink().getId().equals(link.getId()))
.filter(Summary::isSelected)
.count();

assertThat(selectedCount).isEqualTo(1L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public class SummaryServiceTest {
private AiSummaryClient aiSummaryClient;

@Test
@DisplayName("createSummary 호출 시 AiSummaryClient에게 위임한다")
void shouldCallGenerateSummaryWhenCreateSummary() {
@DisplayName("generateSummary 호출 시 SummaryClient에게 위임한다")
void shouldCallGenerateSummaryWhenInitialSummary() {
// given
Long linkId = 1L;
String url = "https://example.com";
Expand All @@ -37,7 +37,7 @@ void shouldCallGenerateSummaryWhenCreateSummary() {
given(aiSummaryClient.generateSummary(linkId, url, format)).willReturn(expectedResult);

// when
String result = summaryService.createSummary(linkId, url, format);
String result = summaryService.initialSummary(linkId, url, format);

// then
assertThat(result).isEqualTo(expectedResult);
Expand Down