Initial project setup: Add contracts, API definitions, tests, and documentation
- Add Foundry project configuration (foundry.toml, foundry.lock) - Add Solidity contracts (TokenFactory138, BridgeVault138, ComplianceRegistry, etc.) - Add API definitions (OpenAPI, GraphQL, gRPC, AsyncAPI) - Add comprehensive test suite (unit, integration, fuzz, invariants) - Add API services (REST, GraphQL, orchestrator, packet service) - Add documentation (ISO20022 mapping, runbooks, adapter guides) - Add development tools (RBC tool, Swagger UI, mock server) - Update OpenZeppelin submodules to v5.0.0
This commit is contained in:
29
api/services/webhook-service/src/index.ts
Normal file
29
api/services/webhook-service/src/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Webhook Service
|
||||
* Converts event bus topics into HTTPS callbacks with retry logic
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import { webhookRouter } from './routes/webhooks';
|
||||
import { eventBusClient } from '@emoney/events';
|
||||
import { webhookDeliveryService } from './services/delivery';
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
// Webhook management API
|
||||
app.use('/v1/webhooks', webhookRouter);
|
||||
|
||||
// Subscribe to event bus and deliver webhooks
|
||||
eventBusClient.on('published', async ({ topic, event }) => {
|
||||
await webhookDeliveryService.deliverToSubscribers(topic, event);
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Webhook service listening on port ${PORT}`);
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
70
api/services/webhook-service/src/routes/webhooks.ts
Normal file
70
api/services/webhook-service/src/routes/webhooks.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Webhook management routes
|
||||
*/
|
||||
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { webhookService } from '../services/webhook-service';
|
||||
|
||||
export const webhookRouter = Router();
|
||||
|
||||
// Create webhook
|
||||
webhookRouter.post('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const webhook = await webhookService.createWebhook(req.body);
|
||||
res.status(201).json(webhook);
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Update webhook
|
||||
webhookRouter.patch('/:id', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const webhook = await webhookService.updateWebhook(req.params.id, req.body);
|
||||
res.json(webhook);
|
||||
} catch (error: any) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Test webhook
|
||||
webhookRouter.post('/:id/test', async (req: Request, res: Response) => {
|
||||
try {
|
||||
await webhookService.testWebhook(req.params.id);
|
||||
res.json({ success: true });
|
||||
} catch (error: any) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Replay webhooks
|
||||
webhookRouter.post('/:id/replay', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { since } = req.query;
|
||||
const count = await webhookService.replayWebhooks(req.params.id, since as string);
|
||||
res.json({ replayed: count });
|
||||
} catch (error: any) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Get webhook
|
||||
webhookRouter.get('/:id', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const webhook = await webhookService.getWebhook(req.params.id);
|
||||
res.json(webhook);
|
||||
} catch (error: any) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// List webhooks
|
||||
webhookRouter.get('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const webhooks = await webhookService.listWebhooks();
|
||||
res.json({ items: webhooks });
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
77
api/services/webhook-service/src/services/delivery.ts
Normal file
77
api/services/webhook-service/src/services/delivery.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Webhook delivery service with retry logic and DLQ
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import crypto from 'crypto';
|
||||
|
||||
export interface DeliveryAttempt {
|
||||
webhookId: string;
|
||||
url: string;
|
||||
event: any;
|
||||
attempt: number;
|
||||
status: 'pending' | 'success' | 'failed';
|
||||
error?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export const webhookDeliveryService = {
|
||||
async deliverToSubscribers(topic: string, event: any): Promise<void> {
|
||||
// TODO: Get all webhooks subscribed to this topic
|
||||
// TODO: For each webhook, deliver with retry logic
|
||||
},
|
||||
|
||||
async deliver(webhookId: string, url: string, event: any, secret?: string): Promise<void> {
|
||||
const payload = JSON.stringify(event);
|
||||
const signature = secret ? this.signPayload(payload, secret) : undefined;
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'eMoney-Webhook/1.0',
|
||||
};
|
||||
|
||||
if (signature) {
|
||||
headers['X-Webhook-Signature'] = signature;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post(url, payload, {
|
||||
headers,
|
||||
timeout: 10000,
|
||||
});
|
||||
} catch (error: any) {
|
||||
// TODO: Retry with exponential backoff
|
||||
// TODO: Move to DLQ after max retries
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
signPayload(payload: string, secret: string): string {
|
||||
return crypto
|
||||
.createHmac('sha256', secret)
|
||||
.update(payload)
|
||||
.digest('hex');
|
||||
},
|
||||
|
||||
async retryWithBackoff(
|
||||
webhookId: string,
|
||||
url: string,
|
||||
event: any,
|
||||
maxRetries: number = 3
|
||||
): Promise<void> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
await this.deliver(webhookId, url, event);
|
||||
return;
|
||||
} catch (error) {
|
||||
if (attempt === maxRetries) {
|
||||
// TODO: Move to dead letter queue
|
||||
throw error;
|
||||
}
|
||||
// Exponential backoff: 1s, 2s, 4s
|
||||
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1000));
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
45
api/services/webhook-service/src/services/webhook-service.ts
Normal file
45
api/services/webhook-service/src/services/webhook-service.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Webhook service - manages webhook registrations
|
||||
*/
|
||||
|
||||
export interface Webhook {
|
||||
id: string;
|
||||
url: string;
|
||||
events: string[];
|
||||
secret?: string;
|
||||
enabled: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export const webhookService = {
|
||||
async createWebhook(data: Partial<Webhook>): Promise<Webhook> {
|
||||
// TODO: Store webhook in database
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
async updateWebhook(id: string, data: Partial<Webhook>): Promise<Webhook> {
|
||||
// TODO: Update webhook in database
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
async getWebhook(id: string): Promise<Webhook> {
|
||||
// TODO: Retrieve webhook from database
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
async listWebhooks(): Promise<Webhook[]> {
|
||||
// TODO: List all webhooks
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
async testWebhook(id: string): Promise<void> {
|
||||
// TODO: Send test event to webhook
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
async replayWebhooks(id: string, since?: string): Promise<number> {
|
||||
// TODO: Replay events since timestamp
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user