traeon/backend/src/plugins/tracker.js
caadiq d9ba70de16 feat: delivery-tracker 셀프호스팅 + API 라우트 구현
- delivery-tracker Docker 이미지 빌드 및 컨테이너 추가
- GraphQL 클라이언트 플러그인 (tracker.js)
- parcels CRUD API (등록/조회/수정/삭제/새로고침)
- carriers 목록 API
- 택배사 ID에서 kr. 접두사 제거, logo_url 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:57:42 +09:00

101 lines
2.6 KiB
JavaScript

import fp from 'fastify-plugin';
const TRACKER_URL = process.env.TRACKER_URL || 'http://delivery-tracker:4000/';
// delivery-tracker의 carrier ID는 kr. 접두사 필요
const CARRIER_ID_MAP = {
cjlogistics: 'kr.cjlogistics',
hanjin: 'kr.hanjin',
lotte: 'kr.lotte',
epost: 'kr.epost',
logen: 'kr.logen',
};
const TRACK_QUERY = `
query Track($carrierId: ID!, $trackingNumber: String!) {
track(carrierId: $carrierId, trackingNumber: $trackingNumber) {
trackingNumber
lastEvent {
status { code name }
time
location { name }
description
}
events(last: 100) {
edges {
node {
status { code name }
time
location { name }
description
}
}
}
}
}
`;
async function trackerPlugin(fastify) {
const tracker = {
async track(carrierId, trackingNumber) {
const graphqlCarrierId = CARRIER_ID_MAP[carrierId] || `kr.${carrierId}`;
const res = await fetch(TRACKER_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: TRACK_QUERY,
variables: {
carrierId: graphqlCarrierId,
trackingNumber,
},
}),
});
const json = await res.json();
if (json.errors) {
const msg = json.errors[0]?.message || 'tracking failed';
throw new Error(msg);
}
const trackInfo = json.data?.track;
if (!trackInfo) {
return null;
}
// 이벤트 정리
const events = (trackInfo.events?.edges || []).map((edge) => {
const node = edge.node;
return {
status: node.status?.code || 'UNKNOWN',
statusName: node.status?.name || '',
description: node.description || '',
location: node.location?.name || '',
time: node.time || null,
};
});
// 마지막 이벤트
const lastEvent = trackInfo.lastEvent
? {
status: trackInfo.lastEvent.status?.code || 'UNKNOWN',
statusName: trackInfo.lastEvent.status?.name || '',
description: trackInfo.lastEvent.description || '',
location: trackInfo.lastEvent.location?.name || '',
time: trackInfo.lastEvent.time || null,
}
: null;
return {
trackingNumber: trackInfo.trackingNumber,
lastEvent,
events,
};
},
};
fastify.decorate('tracker', tracker);
}
export default fp(trackerPlugin, { name: 'tracker' });