diff --git a/src/main/java/com/vigilante/retriever/adapter/web/openapi/constant/ExampleKeyConstant.java b/src/main/java/com/vigilante/retriever/adapter/web/openapi/constant/ExampleKeyConstant.java index 86c24d6..b06fc05 100644 --- a/src/main/java/com/vigilante/retriever/adapter/web/openapi/constant/ExampleKeyConstant.java +++ b/src/main/java/com/vigilante/retriever/adapter/web/openapi/constant/ExampleKeyConstant.java @@ -46,6 +46,8 @@ public final class ExampleKeyConstant { public static final String DRUG_GET_BY_ARGOT_200 = "Drug__Get__By__Argot__200"; public static final String DRUG_GET_BY_ID_404 = "Drug__Get__By__Id__404"; public static final String DRUG_GRAPH_FIND_ALL_200 = "Drug__Graph__Find__All__200"; + public static final String DRUG_GET_TRACE_200 = "Drug__Get__Trace__200"; + public static final String DRUG_GET_TRACE_404 = "Drug__Get__Trace__404"; // Argot public static final String ARGOT_FIND_ALL_200 = "Argot__Find__All__200"; diff --git a/src/main/java/com/vigilante/retriever/infrastructure/config/Neo4jConfig.java b/src/main/java/com/vigilante/retriever/infrastructure/config/Neo4jConfig.java index a0b886b..a160e2a 100644 --- a/src/main/java/com/vigilante/retriever/infrastructure/config/Neo4jConfig.java +++ b/src/main/java/com/vigilante/retriever/infrastructure/config/Neo4jConfig.java @@ -1,21 +1,11 @@ package com.vigilante.retriever.infrastructure.config; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.ArrayList; -import java.util.List; - import org.neo4j.driver.Driver; -import org.neo4j.driver.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.convert.converter.Converter; import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.Neo4jTemplate; -import org.springframework.data.neo4j.core.convert.Neo4jConversions; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; -import org.springframework.lang.Nullable; @Configuration @EnableNeo4jRepositories(basePackages = {"com.vigilante.retriever.v1.*.adapter.out.persistence.neo4j.repository"}) diff --git a/src/main/java/com/vigilante/retriever/v1/argot/adapter/in/web/mapper/ArgotWebMapper.java b/src/main/java/com/vigilante/retriever/v1/argot/adapter/in/web/mapper/ArgotWebMapper.java index 1849436..68a39fb 100644 --- a/src/main/java/com/vigilante/retriever/v1/argot/adapter/in/web/mapper/ArgotWebMapper.java +++ b/src/main/java/com/vigilante/retriever/v1/argot/adapter/in/web/mapper/ArgotWebMapper.java @@ -64,4 +64,10 @@ public ArgotTraceResponse toTraceResponse(ArgotTraceVO vo) { .refersToDrugs(vo.refersToDrugs()) .build(); } + + public Set toTraceResponseSet(Set GraphViews) { + return GraphViews.stream() + .map(graphView -> toTraceResponse(ArgotTraceVO.create(graphView))) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/DrugGraphApi.java b/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/DrugGraphApi.java index 37c61d5..09bf221 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/DrugGraphApi.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/DrugGraphApi.java @@ -6,12 +6,14 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import com.vigilante.retriever.adapter.web.dto.response.CommonResponse; import com.vigilante.retriever.adapter.web.openapi.annotation.ApiErrorExample; import com.vigilante.retriever.adapter.web.openapi.annotation.ApiSuccessExample; import com.vigilante.retriever.v1.drug.adapter.in.web.dto.response.DrugGraphInfoResponse; +import com.vigilante.retriever.v1.drug.adapter.in.web.dto.response.DrugTraceResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -25,4 +27,13 @@ public interface DrugGraphApi { @ApiSuccessExample({@ApiSuccessExample.Success(code = "200", exampleKey = DRUG_GRAPH_FIND_ALL_200)}) @ApiErrorExample(include = {"401", "500"}) ResponseEntity>> findAll(); + + @GetMapping("/{drugBankId}") + @Operation(summary = "마약 그래프 상세 추적 조회", description = "지정한 마약에 대해 참조하는 은어와 판매 채널 정보를 포함한 추적 정보를 조회합니다.") + @ApiSuccessExample({@ApiSuccessExample.Success(code = "200", exampleKey = DRUG_GET_TRACE_200)}) + @ApiErrorExample( + include = {"401", "500"}, + custom = @ApiErrorExample.ErrorSpec(code = "404", exampleKey = DRUG_GET_TRACE_404) + ) + ResponseEntity getDrugTrace(@PathVariable String drugBankId); } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/controller/DrugGraphController.java b/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/controller/DrugGraphController.java index dbd2fe3..d238da4 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/controller/DrugGraphController.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/controller/DrugGraphController.java @@ -8,6 +8,7 @@ import com.vigilante.retriever.adapter.web.dto.response.CommonResponse; import com.vigilante.retriever.v1.drug.adapter.in.web.DrugGraphApi; import com.vigilante.retriever.v1.drug.adapter.in.web.dto.response.DrugGraphInfoResponse; +import com.vigilante.retriever.v1.drug.adapter.in.web.dto.response.DrugTraceResponse; import com.vigilante.retriever.v1.drug.adapter.in.web.mapper.DrugWebMapper; import com.vigilante.retriever.v1.drug.domain.port.in.GetDrugGraphUseCase; @@ -26,4 +27,13 @@ public ResponseEntity>> findAll() { return CommonResponse.retrieved(response); } + + @Override + public ResponseEntity getDrugTrace(String drugBankId) { + DrugTraceResponse response = drugWebMapper.toTraceResponse( + getDrugGraphUseCase.findDrugWithAllRelationships(drugBankId) + ); + + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/dto/response/DrugTraceResponse.java b/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/dto/response/DrugTraceResponse.java new file mode 100644 index 0000000..8e3c634 --- /dev/null +++ b/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/dto/response/DrugTraceResponse.java @@ -0,0 +1,17 @@ +package com.vigilante.retriever.v1.drug.adapter.in.web.dto.response; + +import java.util.Set; + +import com.vigilante.retriever.v1.argot.adapter.in.web.dto.response.ArgotTraceResponse; + +import lombok.Builder; + +@Builder +public record DrugTraceResponse( + String drugBankId, + String name, + String englishName, + String drugType, + Set referredByArgots +) { +} diff --git a/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/mapper/DrugWebMapper.java b/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/mapper/DrugWebMapper.java index 5fa2a86..8b1c7a0 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/mapper/DrugWebMapper.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/adapter/in/web/mapper/DrugWebMapper.java @@ -2,16 +2,24 @@ import java.util.List; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Component; +import com.vigilante.retriever.v1.argot.adapter.in.web.mapper.ArgotWebMapper; import com.vigilante.retriever.v1.drug.adapter.in.web.dto.response.DrugGraphInfoResponse; import com.vigilante.retriever.v1.drug.adapter.in.web.dto.response.DrugInfoResponse; +import com.vigilante.retriever.v1.drug.adapter.in.web.dto.response.DrugTraceResponse; import com.vigilante.retriever.v1.drug.domain.entity.DrugEntity; import com.vigilante.retriever.v1.drug.domain.graphview.DrugGraphView; +import lombok.RequiredArgsConstructor; + @Component +@RequiredArgsConstructor public class DrugWebMapper { + private final ObjectProvider argotWebMapperProvider; + public DrugInfoResponse toResponse(DrugEntity entity) { return DrugInfoResponse.builder() .id(entity.id()) @@ -56,4 +64,15 @@ public List toGraphResponseList(List graph .map(this::toGraphResponse) .toList(); } + + public DrugTraceResponse toTraceResponse(DrugGraphView graphView) { + ArgotWebMapper argotWebMapper = argotWebMapperProvider.getObject(); + return DrugTraceResponse.builder() + .drugBankId(graphView.drugBankId()) + .name(graphView.name()) + .englishName(graphView.englishName()) + .drugType(graphView.drugType()) + .referredByArgots(argotWebMapper.toTraceResponseSet(graphView.referredByArgots())) + .build(); + } } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/mapper/DrugNeo4jMapper.java b/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/mapper/DrugNeo4jMapper.java index b7020ad..016b5be 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/mapper/DrugNeo4jMapper.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/mapper/DrugNeo4jMapper.java @@ -1,12 +1,20 @@ package com.vigilante.retriever.v1.drug.adapter.out.mapper; +import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import org.mapstruct.ReportingPolicy; import org.springframework.context.annotation.Primary; import com.vigilante.retriever.infrastructure.common.mapper.GenericNeo4jMapper; +import com.vigilante.retriever.v1.argot.domain.graphview.ArgotGraphView; +import com.vigilante.retriever.v1.channel.domain.graphview.ChannelGraphView; import com.vigilante.retriever.v1.drug.adapter.out.persistence.neo4j.node.DrugNode; import com.vigilante.retriever.v1.drug.domain.graphview.DrugGraphView; @@ -22,8 +30,100 @@ public interface DrugNeo4jMapper extends GenericNeo4jMapper shallowReferredByArgots = + node.getReferredByArgots() + .stream() + .map(argot -> { + // refersDrugs를 1 depth만 매핑 + Set drugViews = + (argot.getRefersDrugs() != null && !argot.getRefersDrugs().isEmpty()) ? + argot.getRefersDrugs() + .stream() + .map(drug -> DrugGraphView.builder() + .drugBankId(drug.getDrugBankId()) + .name(drug.getName()) + .englishName(drug.getEnglishName()) + .drugType(drug.getDrugType()) + .referredByArgots(Collections.emptySet()) // 2 depth는 빈 Set + .build()) + .collect(Collectors.toSet()) : Collections.emptySet(); + + // soldByChannels를 1 depth만 매핑 + Set shallowChannels = + (argot.getSoldByChannels() != null && !argot.getSoldByChannels().isEmpty()) ? + argot.getSoldByChannels() + .stream() + .map(channel -> { + // sellsArgots를 1 depth 표시 (내부 관계는 빈 Set) + Set channelArgots = + (channel.getSellsArgots() != null && !channel.getSellsArgots().isEmpty()) ? + channel.getSellsArgots() + .stream() + .map(a -> ArgotGraphView.builder() + .name(a.getName()) + .description(a.getDescription()) + .refersDrugs(Collections.emptySet()) // 2 depth는 빈 Set + .soldByChannels(Collections.emptySet()) // 2 depth는 빈 Set + .build()) + .collect(Collectors.toSet()) : Collections.emptySet(); + + // promotedByPosts를 1 depth 표시 (내부 관계는 빈 Set) + Set channelPosts = + (channel.getPromotedByPosts() != null && !channel.getPromotedByPosts() + .isEmpty()) ? + channel.getPromotedByPosts() + .stream() + .map( + post -> com.vigilante.retriever.v1.post.domain.graphview.PostGraphView.builder() + .postId(post.getPostId()) + .title(post.getTitle()) + .link(post.getLink()) + .domain(post.getDomain()) + .content(post.getContent()) + .cluster(post.getCluster()) + .discoveredAt(post.getDiscoveredAt()) + .updatedAt(post.getUpdatedAt()) + .isDeleted(post.isDeleted()) + .promotesChannels(Collections.emptySet()) // 2 depth는 빈 Set + .similarPosts(Collections.emptySet()) // 2 depth는 빈 Set + .build()) + .collect(Collectors.toSet()) : Collections.emptySet(); + + return ChannelGraphView.builder() + .channelId(channel.getChannelId()) + .title(channel.getTitle()) + .username(channel.getUsername()) + .status(channel.getStatus()) + .sellsArgots(channelArgots) // 1 depth 표시 + .promotedByPosts(channelPosts) // 1 depth 표시 + .build(); + }) + .collect(Collectors.toSet()) : Collections.emptySet(); + + return ArgotGraphView.builder() + .name(argot.getName()) + .description(argot.getDescription()) + .refersDrugs(drugViews) // 1 depth 표시 + .soldByChannels(shallowChannels) // 1 depth 표시 + .build(); + }) + .collect(Collectors.toSet()); + builder.referredByArgots(shallowReferredByArgots); + } else { + builder.referredByArgots(Collections.emptySet()); + } + } + @Override List getNodeList(List graphViewList); diff --git a/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/adapter/DrugGraphQueryAdapter.java b/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/adapter/DrugGraphQueryAdapter.java index 7642e9a..1d6f5cb 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/adapter/DrugGraphQueryAdapter.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/adapter/DrugGraphQueryAdapter.java @@ -1,6 +1,7 @@ package com.vigilante.retriever.v1.drug.adapter.out.persistence.neo4j.adapter; import java.util.List; +import java.util.Optional; import org.springframework.stereotype.Component; @@ -24,4 +25,9 @@ public List findAll() { List allDrug = drugNeo4jRepository.findAll(); return drugGraphMapper.getGraphViewList(allDrug); } + + @Override + public Optional findDrugWithAllRelationships(String drugBankId) { + return drugNeo4jRepository.findDrugWithAllRelationships(drugBankId).map(drugGraphMapper::toGraphView); + } } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/node/DrugNode.java b/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/node/DrugNode.java index ff4a86d..b5f0522 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/node/DrugNode.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/node/DrugNode.java @@ -1,8 +1,15 @@ package com.vigilante.retriever.v1.drug.adapter.out.persistence.neo4j.node; +import java.util.HashSet; +import java.util.Set; + import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; +import org.springframework.data.neo4j.core.schema.Relationship; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.vigilante.retriever.v1.argot.adapter.out.persistence.neo4j.node.ArgotNode; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -28,4 +35,8 @@ public class DrugNode { @Property("drug_type") private String drugType; + + @Relationship(type = "REFERS_TO", direction = Relationship.Direction.INCOMING) + @JsonBackReference + private Set referredByArgots = new HashSet<>(); } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/repository/DrugNeo4jRepository.java b/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/repository/DrugNeo4jRepository.java index 1d004a3..be5f191 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/repository/DrugNeo4jRepository.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/adapter/out/persistence/neo4j/repository/DrugNeo4jRepository.java @@ -1,10 +1,24 @@ package com.vigilante.retriever.v1.drug.adapter.out.persistence.neo4j.repository; +import java.util.Optional; + import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.vigilante.retriever.v1.drug.adapter.out.persistence.neo4j.node.DrugNode; @Repository public interface DrugNeo4jRepository extends Neo4jRepository { + + @Query(""" + MATCH (d:Drug {drugbank_id: $drugBankId}) + OPTIONAL MATCH (a:Argot)-[rf:REFERS_TO]->(d) + OPTIONAL MATCH (c:Channel)-[sell:SELLS]->(a) + OPTIONAL MATCH (p:Post)-[pr:PROMOTES]->(c) + OPTIONAL MATCH (p)-[sim:SIMILAR_TO]->(sp:Post) + RETURN d, collect(a), collect(rf), collect(c), collect(sell), collect(p), collect(pr), collect(sim), collect(sp) + """) + Optional findDrugWithAllRelationships(@Param("drugBankId") String drugBankId); } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/application/query/DrugNeo4jQuery.java b/src/main/java/com/vigilante/retriever/v1/drug/application/query/DrugNeo4jQuery.java index e132212..e380ad0 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/application/query/DrugNeo4jQuery.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/application/query/DrugNeo4jQuery.java @@ -3,6 +3,8 @@ import java.util.List; import com.vigilante.retriever.common.domain.annotation.QueryService; +import com.vigilante.retriever.common.domain.exception.NotFoundException; +import com.vigilante.retriever.v1.drug.domain.code.DrugErrorCode; import com.vigilante.retriever.v1.drug.domain.graphview.DrugGraphView; import com.vigilante.retriever.v1.drug.domain.port.out.DrugNeo4jPort; @@ -17,4 +19,9 @@ public class DrugNeo4jQuery { public List findAll() { return drugNeo4jPort.findAll(); } + + public DrugGraphView findDrugWithAllRelationships(String drugBankId) { + return drugNeo4jPort.findDrugWithAllRelationships(drugBankId) + .orElseThrow(() -> new NotFoundException(DrugErrorCode.DRUG_NOT_FOUND)); + } } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/application/service/GetDrugGraphService.java b/src/main/java/com/vigilante/retriever/v1/drug/application/service/GetDrugGraphService.java index f3b5cc1..4eeca63 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/application/service/GetDrugGraphService.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/application/service/GetDrugGraphService.java @@ -20,4 +20,9 @@ public class GetDrugGraphService implements GetDrugGraphUseCase { public List findAll() { return drugNeo4jQuery.findAll(); } + + @Override + public DrugGraphView findDrugWithAllRelationships(String drugBankId) { + return drugNeo4jQuery.findDrugWithAllRelationships(drugBankId); + } } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/domain/code/DrugErrorCode.java b/src/main/java/com/vigilante/retriever/v1/drug/domain/code/DrugErrorCode.java index 62aed1d..3cfac67 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/domain/code/DrugErrorCode.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/domain/code/DrugErrorCode.java @@ -8,7 +8,7 @@ @Getter @AllArgsConstructor public enum DrugErrorCode implements BaseCode { - DRUG_NOT_FOUND("DRUG-4041", "해당하는 챗봇을 찾을 수 없습니다."); + DRUG_NOT_FOUND("DRUG-4041", "해당하는 마약을 찾을 수 없습니다."); private final String code; private final String message; diff --git a/src/main/java/com/vigilante/retriever/v1/drug/domain/graphview/DrugGraphView.java b/src/main/java/com/vigilante/retriever/v1/drug/domain/graphview/DrugGraphView.java index cfb7af2..5d240ca 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/domain/graphview/DrugGraphView.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/domain/graphview/DrugGraphView.java @@ -1,5 +1,9 @@ package com.vigilante.retriever.v1.drug.domain.graphview; +import java.util.Set; + +import com.vigilante.retriever.v1.argot.domain.graphview.ArgotGraphView; + import lombok.Builder; @Builder @@ -7,6 +11,8 @@ public record DrugGraphView( String drugBankId, String name, String englishName, - String drugType + String drugType, + Set referredByArgots ) { + } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/domain/port/in/GetDrugGraphUseCase.java b/src/main/java/com/vigilante/retriever/v1/drug/domain/port/in/GetDrugGraphUseCase.java index 2107161..4b3578b 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/domain/port/in/GetDrugGraphUseCase.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/domain/port/in/GetDrugGraphUseCase.java @@ -6,4 +6,6 @@ public interface GetDrugGraphUseCase { List findAll(); + + DrugGraphView findDrugWithAllRelationships(String drugBankId); } diff --git a/src/main/java/com/vigilante/retriever/v1/drug/domain/port/out/DrugNeo4jPort.java b/src/main/java/com/vigilante/retriever/v1/drug/domain/port/out/DrugNeo4jPort.java index 0b63f4c..228489f 100644 --- a/src/main/java/com/vigilante/retriever/v1/drug/domain/port/out/DrugNeo4jPort.java +++ b/src/main/java/com/vigilante/retriever/v1/drug/domain/port/out/DrugNeo4jPort.java @@ -1,10 +1,15 @@ package com.vigilante.retriever.v1.drug.domain.port.out; import java.util.List; +import java.util.Optional; + +import org.springframework.data.repository.query.Param; import com.vigilante.retriever.v1.drug.domain.graphview.DrugGraphView; public interface DrugNeo4jPort { List findAll(); + + Optional findDrugWithAllRelationships(@Param("drugBankId") String drugBankId); } diff --git a/src/main/resources/openapi/examples/drug/get-trace-200.json b/src/main/resources/openapi/examples/drug/get-trace-200.json new file mode 100644 index 0000000..1a539e0 --- /dev/null +++ b/src/main/resources/openapi/examples/drug/get-trace-200.json @@ -0,0 +1,42 @@ +{ + "success": true, + "code": "200", + "message": "조회가 완료되었습니다", + "data": { + "drugBankId": "drug-001", + "name": "아편", + "englishName": "Opium", + "drugType": "opioid", + "referredByArgots": [ + { + "name": "감미료", + "description": "비공식적 은어: 달콤한 향을 의미", + "soldByChannels": [ + { + "id": 1234567890, + "title": "Example Channel", + "username": "example_user", + "status": "active", + "promotingPosts": [ + { + "postId": "mock0000000000000000000001", + "title": "[공지] 거래 관련 안내 - 예시 제목", + "link": "https://example.com/post/1", + "domain": "example.com", + "content": "이 게시물은 예시(mock) 내용입니다. 실제 데이터가 아님을 확인하세요.", + "cluster": 1, + "discoveredAt": "2025-01-01T00:00:00.000000", + "updatedAt": "2025-01-01T00:00:00.000000", + "isDeleted": false, + "similarPosts": [] + } + ] + } + ], + "refersToDrugs": [] + } + ] + }, + "timestamp": "2025-01-01T00:00:00.000000" +} + diff --git a/src/main/resources/openapi/examples/drug/get-trace-404.json b/src/main/resources/openapi/examples/drug/get-trace-404.json new file mode 100644 index 0000000..cdf4786 --- /dev/null +++ b/src/main/resources/openapi/examples/drug/get-trace-404.json @@ -0,0 +1,7 @@ +{ + "success": false, + "code": 404, + "message": "해당 마약을 찾을 수 없습니다.", + "timestamp": "2025-01-01T00:00:00.000000" +} +