201 lines
6.6 KiB
TypeScript
201 lines
6.6 KiB
TypeScript
/**
|
|
* Mock Service Server — port 5100
|
|
*
|
|
* Simulates external/internal enterprise systems for the reference implementation.
|
|
* MCPlet handlers call these endpoints; replacing with real services requires only
|
|
* updating the mockServiceUrl in reference.yaml.
|
|
*/
|
|
import http from 'node:http';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const dataDir = path.join(__dirname, 'data');
|
|
const PORT = 5100;
|
|
|
|
// ---- Data loaders ----
|
|
|
|
function loadJson<T>(filename: string): T {
|
|
return JSON.parse(fs.readFileSync(path.join(dataDir, filename), 'utf-8')) as T;
|
|
}
|
|
|
|
interface Customer {
|
|
customerId: string;
|
|
name: string;
|
|
email: string;
|
|
cancelTendency: string;
|
|
cancelTendencyReason: string;
|
|
reservations: string[];
|
|
}
|
|
|
|
interface Reservation {
|
|
reservationId: string;
|
|
customerId: string;
|
|
customerName: string;
|
|
date: string;
|
|
time: string;
|
|
partySize: number;
|
|
status: string;
|
|
notes: string;
|
|
}
|
|
|
|
interface SentEmail {
|
|
to: string;
|
|
subject: string;
|
|
body: string;
|
|
sentAt: string;
|
|
}
|
|
|
|
interface SentPost {
|
|
platform: string;
|
|
content: string;
|
|
postedAt: string;
|
|
}
|
|
|
|
// In-memory log for mock sends
|
|
const sentEmails: SentEmail[] = [];
|
|
const sentPosts: SentPost[] = [];
|
|
|
|
// ---- Route handlers ----
|
|
|
|
function handleCrmCustomers(url: URL, res: http.ServerResponse): void {
|
|
const filter = url.searchParams.get('filter');
|
|
const customers = loadJson<Customer[]>('customers.json');
|
|
const result = filter === 'rain_cancel_tendency'
|
|
? customers.filter((c) => c.cancelTendency === 'high')
|
|
: customers;
|
|
sendJson(res, 200, { customers: result, total: result.length });
|
|
}
|
|
|
|
function handleCrmReservations(_url: URL, res: http.ServerResponse): void {
|
|
// Fixed mock: return all reservations regardless of date query
|
|
const reservations = loadJson<Reservation[]>('reservations.json');
|
|
sendJson(res, 200, { reservations, total: reservations.length });
|
|
}
|
|
|
|
function handleErpInventory(url: URL, res: http.ServerResponse): void {
|
|
const item = url.searchParams.get('item');
|
|
const data = loadJson<{ items: Array<{ category: string }> }>('inventory.json');
|
|
const items = item
|
|
? data.items.filter((i) => i.category === item || i.category.includes(item))
|
|
: data.items;
|
|
sendJson(res, 200, { items, total: items.length });
|
|
}
|
|
|
|
function handleWeatherForecast(_url: URL, res: http.ServerResponse): void {
|
|
// Fixed mock: always return rainy forecast regardless of date query
|
|
const data = loadJson<{ location: string; forecasts: Array<{ date: string }> }>('weather.json');
|
|
sendJson(res, 200, { location: data.location, forecasts: data.forecasts });
|
|
}
|
|
|
|
function handleSiteStats(_url: URL, res: http.ServerResponse): void {
|
|
sendJson(res, 200, {
|
|
site: 'EPARK',
|
|
period: 'last_7_days',
|
|
pageViews: 12480,
|
|
uniqueVisitors: 3920,
|
|
reservationPageViews: 2150,
|
|
conversionRate: 0.18,
|
|
updatedAt: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
async function handleEmailSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
const body = await parseJsonBody<{ to: string; subject: string; body: string }>(req);
|
|
const entry: SentEmail = { ...body, sentAt: new Date().toISOString() };
|
|
sentEmails.push(entry);
|
|
console.log(`[mock-email] Sent to ${body.to}: ${body.subject}`);
|
|
sendJson(res, 200, { ok: true, messageId: `mock-${Date.now()}`, sentAt: entry.sentAt });
|
|
}
|
|
|
|
async function handleSnsPost(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
const body = await parseJsonBody<{ platform: string; content: string }>(req);
|
|
const entry: SentPost = { ...body, postedAt: new Date().toISOString() };
|
|
sentPosts.push(entry);
|
|
console.log(`[mock-sns] Posted to ${body.platform}: ${body.content.slice(0, 50)}...`);
|
|
sendJson(res, 200, { ok: true, postId: `mock-${Date.now()}`, postedAt: entry.postedAt });
|
|
}
|
|
|
|
function handleSentEmails(_url: URL, res: http.ServerResponse): void {
|
|
sendJson(res, 200, { sentEmails, total: sentEmails.length });
|
|
}
|
|
|
|
function handleSentPosts(_url: URL, res: http.ServerResponse): void {
|
|
sendJson(res, 200, { sentPosts, total: sentPosts.length });
|
|
}
|
|
|
|
// ---- Router ----
|
|
|
|
const server = http.createServer((req, res) => {
|
|
const url = new URL(req.url ?? '/', `http://localhost:${PORT}`);
|
|
const method = req.method ?? 'GET';
|
|
|
|
try {
|
|
if (method === 'GET' && url.pathname === '/crm/customers') {
|
|
handleCrmCustomers(url, res); return;
|
|
}
|
|
if (method === 'GET' && url.pathname === '/crm/reservations') {
|
|
handleCrmReservations(url, res); return;
|
|
}
|
|
if (method === 'GET' && url.pathname === '/erp/inventory') {
|
|
handleErpInventory(url, res); return;
|
|
}
|
|
if (method === 'GET' && url.pathname === '/weather/forecast') {
|
|
handleWeatherForecast(url, res); return;
|
|
}
|
|
if (method === 'GET' && url.pathname === '/site/stats') {
|
|
handleSiteStats(url, res); return;
|
|
}
|
|
if (method === 'POST' && url.pathname === '/email/send') {
|
|
void handleEmailSend(req, res); return;
|
|
}
|
|
if (method === 'POST' && url.pathname === '/sns/post') {
|
|
void handleSnsPost(req, res); return;
|
|
}
|
|
if (method === 'GET' && url.pathname === '/debug/emails') {
|
|
handleSentEmails(url, res); return;
|
|
}
|
|
if (method === 'GET' && url.pathname === '/debug/posts') {
|
|
handleSentPosts(url, res); return;
|
|
}
|
|
|
|
sendJson(res, 404, { error: `Not found: ${method} ${url.pathname}` });
|
|
} catch (err) {
|
|
sendJson(res, 500, { error: (err as Error).message });
|
|
}
|
|
});
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`[mock-services] Listening on http://localhost:${PORT}`);
|
|
console.log('[mock-services] Endpoints:');
|
|
console.log(' GET /crm/customers?filter=rain_cancel_tendency');
|
|
console.log(' GET /crm/reservations?date=YYYY-MM-DD');
|
|
console.log(' GET /erp/inventory?item=dessert');
|
|
console.log(' GET /weather/forecast?date=YYYY-MM-DD');
|
|
console.log(' GET /site/stats');
|
|
console.log(' POST /email/send');
|
|
console.log(' POST /sns/post');
|
|
console.log(' GET /debug/emails (sent email log)');
|
|
console.log(' GET /debug/posts (sent SNS post log)');
|
|
});
|
|
|
|
// ---- Helpers ----
|
|
|
|
function sendJson(res: http.ServerResponse, status: number, body: unknown): void {
|
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify(body, null, 2));
|
|
}
|
|
|
|
function parseJsonBody<T>(req: http.IncomingMessage): Promise<T> {
|
|
return new Promise((resolve, reject) => {
|
|
let data = '';
|
|
req.on('data', (chunk: Buffer) => { data += chunk.toString(); });
|
|
req.on('end', () => {
|
|
try { resolve(JSON.parse(data) as T); }
|
|
catch { reject(new Error('Invalid JSON')); }
|
|
});
|
|
req.on('error', reject);
|
|
});
|
|
}
|