#!/usr/bin/env node
/**
* テストデータを流し込むスクリプト
* サーバーが起動している状態で実行してください
*/
import * as readline from 'readline';
// コマンドライン引数の解析
function parseArgs() {
const args = process.argv.slice(2);
let patternKey: string | undefined;
let port = 3000;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--port' || arg === '-p') {
const portStr = args[i + 1];
if (portStr && !isNaN(parseInt(portStr, 10))) {
port = parseInt(portStr, 10);
i++; // 次の引数をスキップ
}
} else if (!arg.startsWith('-')) {
// 数値のみの場合はポート番号として扱う
const num = parseInt(arg, 10);
if (!isNaN(num) && num >= 1000 && num <= 65535) {
port = num;
} else if (!patternKey) {
// 数値以外の場合はパターン名として扱う
patternKey = arg;
}
}
}
return { patternKey, port };
}
const { patternKey: PATTERN_KEY, port: PORT } = parseArgs();
const API_BASE = process.env.API_BASE || `http://localhost:${PORT}/api`;
interface Node {
id: string;
label: string;
x?: number;
y?: number;
color?: string;
solution?: string;
filePath?: string;
lineNumber?: number;
code?: string;
group?: string;
}
interface Edge {
from: string;
to: string;
label?: string;
}
interface Group {
id: string;
label: string;
color?: string;
}
interface Diagram {
nodes: Node[];
edges: Edge[];
groups?: Group[];
}
async function setDiagram(diagram: Diagram): Promise<void> {
try {
const response = await fetch(`${API_BASE}/diagram`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(diagram),
});
if (!response.ok) {
throw new Error(`Failed to set diagram: ${response.statusText}`);
}
const data = await response.json();
console.log(`✓ ダイアグラムを設定しました: ${data.nodes}個のノード、${data.edges}個のエッジ`);
} catch (error) {
console.error(`✗ ダイアグラム設定失敗:`, error);
throw error;
}
}
// ダイアグラムパターン1: Webアプリケーションアーキテクチャ
const webAppArchitecture: Diagram = {
nodes: [
{
id: 'client',
label: 'ブラウザ',
color: '#64748b',
memo: '## クライアント\n\nユーザーのWebブラウザ。\n\n### 対応ブラウザ\n- Chrome\n- Firefox\n- Safari\n- Edge'
},
{ id: 'cdn', label: 'CDN', color: '#0ea5e9' },
{
id: 'lb',
label: 'ロードバランサー',
color: '#8b5cf6',
memo: '## ロードバランサー\n\nトラフィックを複数のWebサーバーに分散します。\n\n```nginx\nupstream backend {\n server web1:8080;\n server web2:8080;\n}\n```'
},
{ id: 'web1', label: 'Webサーバー1', color: '#2563eb' },
{ id: 'web2', label: 'Webサーバー2', color: '#2563eb' },
{
id: 'cache',
label: 'Redis Cache',
color: '#dc2626',
memo: '### Redis設定\n\n```bash\nredis-cli CONFIG SET maxmemory 2gb\nredis-cli CONFIG SET maxmemory-policy allkeys-lru\n```'
},
{
id: 'app',
label: 'APIサーバー',
color: '#059669',
memo: '## APIサーバー\n\nビジネスロジックを処理するバックエンドサーバー。\n\n### 主な機能\n- ユーザー認証\n- データ処理\n- 外部API連携'
},
{ id: 'db-primary', label: 'DB (Primary)', color: '#7c3aed' },
{ id: 'db-replica', label: 'DB (Replica)', color: '#9333ea' },
{ id: 'queue', label: 'メッセージキュー', color: '#ea580c' },
{
id: 'worker',
label: 'バックグラウンドワーカー',
color: '#d97706',
memo: '## バックグラウンドワーカー\n\n非同期タスクを処理します。\n\n- メール送信\n- レポート生成\n- データ集計'
},
],
edges: [
{ from: 'client', to: 'cdn', label: 'HTTPS' },
{ from: 'client', to: 'lb', label: 'HTTPS' },
{ from: 'lb', to: 'web1', label: 'HTTP' },
{ from: 'lb', to: 'web2', label: 'HTTP' },
{ from: 'web1', to: 'cache', label: 'Cache' },
{ from: 'web2', to: 'cache', label: 'Cache' },
{ from: 'web1', to: 'app', label: 'REST API' },
{ from: 'web2', to: 'app', label: 'REST API' },
{ from: 'app', to: 'db-primary', label: 'SQL (Write)' },
{ from: 'app', to: 'db-replica', label: 'SQL (Read)' },
{ from: 'db-primary', to: 'db-replica', label: 'Replication' },
{ from: 'app', to: 'queue', label: 'Publish' },
{ from: 'queue', to: 'worker', label: 'Subscribe' },
{ from: 'worker', to: 'db-primary', label: 'SQL' },
],
};
// ダイアグラムパターン2: マイクロサービスアーキテクチャ
const microservicesArchitecture: Diagram = {
groups: [
{ id: 'core', label: 'コアサービス', color: '#dbeafe' },
{ id: 'business', label: 'ビジネスロジック', color: '#dcfce7' },
{ id: 'infrastructure', label: 'インフラ', color: '#fef3c7' },
],
nodes: [
{
id: 'gateway',
label: 'API Gateway',
color: '#0891b2',
group: 'infrastructure',
memo: '## API Gateway\n\nすべてのクライアントリクエストの入口。\n\n### 機能\n- ルーティング\n- 認証・認可\n- レート制限\n- リクエスト/レスポンス変換'
},
{
id: 'auth',
label: '認証サービス',
color: '#dc2626',
group: 'core',
memo: '## 認証サービス\n\nJWT発行と検証を担当。\n\n```typescript\nconst token = jwt.sign({ userId }, SECRET, { expiresIn: "24h" });\n```'
},
{ id: 'user', label: 'ユーザーサービス', color: '#2563eb', group: 'core' },
{
id: 'order',
label: '注文サービス',
color: '#059669',
group: 'business',
memo: '## 注文サービス\n\n注文処理のコアロジック。\n\n1. 注文検証\n2. 在庫確認\n3. 決済処理\n4. 注文確定'
},
{ id: 'payment', label: '決済サービス', color: '#7c3aed', group: 'business' },
{ id: 'inventory', label: '在庫サービス', color: '#d97706', group: 'business' },
{ id: 'notification', label: '通知サービス', color: '#ea580c', group: 'core' },
{
id: 'eventbus',
label: 'イベントバス',
color: '#4b5563',
group: 'infrastructure',
memo: '## イベントバス\n\nサービス間の非同期通信を実現。\n\n### 使用技術\n- Apache Kafka\n- RabbitMQ\n- AWS EventBridge'
},
{ id: 'service-mesh', label: 'サービスメッシュ', color: '#6366f1', group: 'infrastructure' },
],
edges: [
{ from: 'gateway', to: 'auth', label: 'JWT検証' },
{ from: 'gateway', to: 'user', label: 'REST' },
{ from: 'gateway', to: 'order', label: 'REST' },
{ from: 'order', to: 'payment', label: 'gRPC' },
{ from: 'order', to: 'inventory', label: 'gRPC' },
{ from: 'payment', to: 'eventbus', label: 'Publish' },
{ from: 'order', to: 'eventbus', label: 'Publish' },
{ from: 'eventbus', to: 'notification', label: 'Subscribe' },
{ from: 'service-mesh', to: 'user', label: 'トラフィック管理' },
{ from: 'service-mesh', to: 'order', label: 'トラフィック管理' },
{ from: 'service-mesh', to: 'payment', label: 'トラフィック管理' },
],
};
// ダイアグラムパターン3: データパイプライン
const dataPipeline: Diagram = {
nodes: [
{ id: 'source1', label: 'データソース1 (API)' },
{ id: 'source2', label: 'データソース2 (DB)' },
{ id: 'source3', label: 'データソース3 (ファイル)' },
{ id: 'ingestion', label: 'データ取込' },
{ id: 'raw', label: 'Raw Data Lake' },
{ id: 'transform', label: 'ETL処理' },
{ id: 'warehouse', label: 'Data Warehouse' },
{ id: 'ml', label: '機械学習モデル' },
{ id: 'analytics', label: '分析ツール' },
{ id: 'dashboard', label: 'ダッシュボード' },
],
edges: [
{ from: 'source1', to: 'ingestion', label: 'REST API' },
{ from: 'source2', to: 'ingestion', label: 'CDC' },
{ from: 'source3', to: 'ingestion', label: 'S3' },
{ from: 'ingestion', to: 'raw', label: 'Batch' },
{ from: 'raw', to: 'transform', label: 'Spark Job' },
{ from: 'transform', to: 'warehouse', label: 'Load' },
{ from: 'warehouse', to: 'ml', label: 'Training Data' },
{ from: 'warehouse', to: 'analytics', label: 'Query' },
{ from: 'analytics', to: 'dashboard', label: 'Visualization' },
{ from: 'ml', to: 'dashboard', label: '予測結果' },
],
};
// ダイアグラムパターン4: CI/CDパイプライン
const cicdPipeline: Diagram = {
nodes: [
{ id: 'git', label: 'Git Repository' },
{ id: 'ci', label: 'CI Server' },
{ id: 'build', label: 'ビルド' },
{ id: 'test', label: 'テスト実行' },
{ id: 'scan', label: 'セキュリティスキャン' },
{ id: 'artifact', label: 'アーティファクトリポジトリ' },
{ id: 'staging', label: 'ステージング環境' },
{ id: 'approval', label: '承認プロセス' },
{ id: 'production', label: '本番環境' },
{ id: 'monitoring', label: 'モニタリング' },
],
edges: [
{ from: 'git', to: 'ci', label: 'Webhook' },
{ from: 'ci', to: 'build', label: 'Trigger' },
{ from: 'build', to: 'test', label: 'Artifact' },
{ from: 'test', to: 'scan', label: 'Success' },
{ from: 'scan', to: 'artifact', label: 'Upload' },
{ from: 'artifact', to: 'staging', label: 'Deploy' },
{ from: 'staging', to: 'approval', label: 'テスト完了' },
{ from: 'approval', to: 'production', label: 'Deploy' },
{ from: 'production', to: 'monitoring', label: 'Metrics' },
{ from: 'monitoring', to: 'git', label: 'Alert → Issue' },
],
};
// ダイアグラムパターン5: クラウドインフラストラクチャ
const cloudInfrastructure: Diagram = {
nodes: [
{ id: 'internet', label: 'インターネット' },
{ id: 'cloudflare', label: 'Cloudflare' },
{ id: 'alb', label: 'ALB' },
{ id: 'ecs1', label: 'ECSタスク1' },
{ id: 'ecs2', label: 'ECSタスク2' },
{ id: 's3', label: 'S3バケット' },
{ id: 'rds', label: 'RDS (PostgreSQL)' },
{ id: 'elasticache', label: 'ElastiCache' },
{ id: 'lambda', label: 'Lambda関数' },
{ id: 'sqs', label: 'SQSキュー' },
{ id: 'cloudwatch', label: 'CloudWatch' },
],
edges: [
{ from: 'internet', to: 'cloudflare', label: 'HTTPS' },
{ from: 'cloudflare', to: 'alb', label: 'HTTPS' },
{ from: 'alb', to: 'ecs1', label: 'HTTP' },
{ from: 'alb', to: 'ecs2', label: 'HTTP' },
{ from: 'ecs1', to: 'rds', label: 'SQL' },
{ from: 'ecs2', to: 'rds', label: 'SQL' },
{ from: 'ecs1', to: 'elasticache', label: 'Redis' },
{ from: 'ecs1', to: 's3', label: 'S3 API' },
{ from: 'ecs1', to: 'sqs', label: 'SendMessage' },
{ from: 'sqs', to: 'lambda', label: 'Trigger' },
{ from: 'lambda', to: 'rds', label: 'SQL' },
{ from: 'cloudwatch', to: 'ecs1', label: 'ログ収集' },
{ from: 'cloudwatch', to: 'lambda', label: 'ログ収集' },
],
};
// ダイアグラムパターン6: シンプルな3層アーキテクチャ
const simpleThreeTier: Diagram = {
nodes: [
{
id: 'browser',
label: 'Webブラウザ',
color: '#64748b',
},
{
id: 'frontend',
label: 'フロントエンド (React)',
color: '#2563eb',
solution: 'MyApp',
filePath: 'src/frontend/App.tsx',
lineNumber: 15,
code: `function App() {
const [data, setData] = useState([]);
useEffect(() => {
fetch("/api/data")
.then(res => res.json())
.then(setData);
}, []);
return <Dashboard data={data} />;
}`
},
{
id: 'backend',
label: 'バックエンド (Node.js)',
color: '#059669',
solution: 'MyApp',
filePath: 'src/backend/api.ts',
lineNumber: 42,
code: `app.get('/api/data', async (req, res) => {
const data = await db.query(
'SELECT * FROM users'
);
res.json(data);
});`
},
{
id: 'database',
label: 'データベース (MySQL)',
color: '#dc2626',
solution: 'MyApp',
filePath: 'db/schema.sql',
lineNumber: 1,
code: `CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
},
],
edges: [
{ from: 'browser', to: 'frontend', label: 'HTTPS' },
{ from: 'frontend', to: 'backend', label: 'REST API' },
{ from: 'backend', to: 'database', label: 'SQL' },
],
};
// ダイアグラムパターン7: Forest(複数の独立したtree)
const forestExample: Diagram = {
nodes: [
// Tree 1: ユーザー管理システム
{ id: 'user-api', label: 'User API', color: '#2563eb' },
{ id: 'user-db', label: 'User DB', color: '#1e40af' },
{ id: 'auth', label: '認証', color: '#3b82f6' },
// Tree 2: 商品管理システム
{ id: 'product-api', label: 'Product API', color: '#059669' },
{ id: 'product-db', label: 'Product DB', color: '#047857' },
{ id: 'inventory', label: '在庫管理', color: '#10b981' },
{ id: 'catalog', label: 'カタログ', color: '#34d399' },
// Tree 3: 注文システム
{ id: 'order-api', label: 'Order API', color: '#dc2626' },
{ id: 'order-db', label: 'Order DB', color: '#b91c1c' },
{ id: 'payment', label: '決済', color: '#ef4444' },
// Tree 4: 通知システム
{ id: 'notification', label: '通知サービス', color: '#7c3aed' },
{ id: 'email', label: 'Email', color: '#6d28d9' },
{ id: 'sms', label: 'SMS', color: '#8b5cf6' },
],
edges: [
// Tree 1の接続
{ from: 'user-api', to: 'user-db', label: 'Query' },
{ from: 'user-api', to: 'auth', label: 'Verify' },
// Tree 2の接続
{ from: 'product-api', to: 'product-db', label: 'Query' },
{ from: 'product-api', to: 'inventory', label: 'Check' },
{ from: 'product-api', to: 'catalog', label: 'List' },
// Tree 3の接続
{ from: 'order-api', to: 'order-db', label: 'Store' },
{ from: 'order-api', to: 'payment', label: 'Process' },
// Tree 4の接続
{ from: 'notification', to: 'email', label: 'Send' },
{ from: 'notification', to: 'sms', label: 'Send' },
],
};
// 全パターンの定義
const patterns: Record<string, { name: string; diagram: Diagram }> = {
'webapp': { name: 'Webアプリケーションアーキテクチャ', diagram: webAppArchitecture },
'microservices': { name: 'マイクロサービスアーキテクチャ', diagram: microservicesArchitecture },
'datapipeline': { name: 'データパイプライン', diagram: dataPipeline },
'cicd': { name: 'CI/CDパイプライン', diagram: cicdPipeline },
'cloud': { name: 'クラウドインフラストラクチャ', diagram: cloudInfrastructure },
'simple': { name: 'シンプルな3層アーキテクチャ', diagram: simpleThreeTier },
'forest': { name: 'Forest(複数の独立したTree)', diagram: forestExample },
};
// Enterキーの入力を待つ関数
function waitForEnter(message: string): Promise<void> {
return new Promise((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(message, () => {
rl.close();
resolve();
});
});
}
async function main() {
console.log('=== ネットワークダイアグラム テストデータ投入 ===\n');
console.log(`接続先: ${API_BASE}\n`);
// サーバーが起動しているか確認
try {
const healthResponse = await fetch(`${API_BASE}/health`);
if (!healthResponse.ok) {
throw new Error('Server is not responding');
}
console.log('✓ サーバー接続確認\n');
} catch (error) {
console.error('✗ サーバーに接続できません。先にサーバーを起動してください。');
console.error(' 起動コマンド: npm start\n');
process.exit(1);
}
// 特定のパターンが指定された場合は、そのパターンのみ表示
if (PATTERN_KEY && patterns[PATTERN_KEY]) {
const pattern = patterns[PATTERN_KEY];
console.log(`--- ${pattern.name} ---\n`);
await setDiagram(pattern.diagram);
console.log('\n=== 完了 ===');
console.log(`\nWebインターフェースで確認: http://localhost:${PORT}`);
console.log(`APIで確認: curl http://localhost:${PORT}/api/diagram\n`);
return;
}
// パターンが指定されていない場合は対話モード
if (!PATTERN_KEY) {
console.log('対話モード: Enterキーで次のパターンに切り替わります');
console.log('終了するには Ctrl+C を押してください\n');
const patternEntries = Object.entries(patterns);
let currentIndex = 0;
while (true) {
const [key, pattern] = patternEntries[currentIndex];
console.log(`\n[${ currentIndex + 1}/${patternEntries.length}] --- ${pattern.name} ---\n`);
await setDiagram(pattern.diagram);
currentIndex = (currentIndex + 1) % patternEntries.length;
if (currentIndex === 0) {
await waitForEnter('\nEnterキーで最初から繰り返す (Ctrl+Cで終了): ');
} else {
await waitForEnter('\nEnterキーで次のパターンへ (Ctrl+Cで終了): ');
}
}
}
// 不明なパターンの場合
console.error(`✗ 不明なパターン: ${PATTERN_KEY}\n`);
console.log('利用可能なパターン:');
Object.entries(patterns).forEach(([key, pattern]) => {
console.log(` ${key.padEnd(15)} - ${pattern.name}`);
});
console.log('\n使用例:');
console.log(' npm run test-data # 対話モード(Enterで切り替え)');
console.log(' npm run test-data webapp # Webアプリケーション');
console.log(' npm run test-data microservices # マイクロサービス');
console.log(' npm run test-data simple # シンプルな3層');
console.log(' npm run test-data microservices 3001 # ポート3001を指定(簡潔形式)');
console.log(' npm run test-data -- simple --port 3001 # ポート3001を指定(明示形式)');
console.log(' npm run test-data -- webapp -p 3002 # ポート3002を指定(短縮形)');
process.exit(1);
}
main().catch(error => {
console.error('エラーが発生しました:', error);
process.exit(1);
});