Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.

Commit 44514ab

Browse files
committed
add services
1 parent a1d1f76 commit 44514ab

14 files changed

Lines changed: 7705 additions & 6 deletions

File tree

docker-compose.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
version: '3.8'
2+
3+
services:
4+
nats:
5+
image: nats:2.10-alpine
6+
container_name: nats-server
7+
ports:
8+
- "4222:4222" # Client connections
9+
- "8222:8222" # HTTP monitoring
10+
- "6222:6222" # Cluster routing
11+
command: ["-js", "-m", "8222"] # Enable JetStream and monitoring
12+
healthcheck:
13+
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8222/healthz"]
14+
interval: 5s
15+
timeout: 3s
16+
retries: 5

services/fraud-detection-service/package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@
44
"description": "This service monitors transactions to detect potentially fraudulent activities",
55
"main": "index.js",
66
"license": "MIT",
7+
"scripts": {
8+
"start": "ts-node src/index.ts",
9+
"generate": "codegen generate"
10+
},
711
"dependencies": {
8-
"typescript": "^5.9.3"
12+
"typescript": "^5.9.3",
13+
"nats": "^2.29.3"
914
},
1015
"devDependencies": {
11-
"ts-node": "^10.9.2"
16+
"@the-codegen-project/cli": "^0.55.1",
17+
"ts-node": "^10.9.2",
18+
"@types/node": "^22.13.10"
1219
}
1320
}
Lines changed: 175 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,175 @@
1-
console.log('Fraud Detection Service');
1+
import { connect, NatsConnection, Subscription, JSONCodec } from 'nats';
2+
3+
// Type definitions based on AsyncAPI spec
4+
interface FraudAlert {
5+
alertId: string;
6+
transactionId: string;
7+
alertTime: string;
8+
severity: string;
9+
details: string;
10+
}
11+
12+
interface TransactionEvaluated {
13+
transactionId: string;
14+
evaluationTime: string;
15+
isFraudulent: boolean;
16+
riskScore: number;
17+
}
18+
19+
interface TransactionReview {
20+
transactionId: string;
21+
reviewTime: string;
22+
reviewOutcome: string;
23+
reviewerId: string;
24+
}
25+
26+
// Channel addresses from AsyncAPI spec
27+
const CHANNELS = {
28+
FRAUD_ALERT: 'fraud.alert',
29+
TRANSACTION_EVALUATED: 'transaction.evaluated',
30+
TRANSACTION_REVIEW: 'transaction.review',
31+
};
32+
33+
class FraudDetectionService {
34+
private nc: NatsConnection | null = null;
35+
private subscriptions: Subscription[] = [];
36+
private jc = JSONCodec();
37+
38+
async connect(natsUrl: string = 'nats://localhost:4222'): Promise<void> {
39+
console.log(`Connecting to NATS at ${natsUrl}...`);
40+
this.nc = await connect({ servers: natsUrl });
41+
console.log(`Connected to NATS server: ${this.nc.getServer()}`);
42+
}
43+
44+
async disconnect(): Promise<void> {
45+
for (const sub of this.subscriptions) {
46+
sub.unsubscribe();
47+
}
48+
if (this.nc) {
49+
await this.nc.drain();
50+
console.log('Disconnected from NATS');
51+
}
52+
}
53+
54+
// Operation: sendFraudAlert
55+
async sendFraudAlert(alert: FraudAlert): Promise<void> {
56+
if (!this.nc) throw new Error('Not connected to NATS');
57+
58+
console.log(`Publishing fraud alert for transaction ${alert.transactionId}`);
59+
this.nc.publish(CHANNELS.FRAUD_ALERT, this.jc.encode(alert));
60+
}
61+
62+
// Operation: receiveTransactionEvaluated
63+
async subscribeToTransactionEvaluated(
64+
handler: (data: TransactionEvaluated) => Promise<void>
65+
): Promise<void> {
66+
if (!this.nc) throw new Error('Not connected to NATS');
67+
68+
console.log(`Subscribing to ${CHANNELS.TRANSACTION_EVALUATED}...`);
69+
const sub = this.nc.subscribe(CHANNELS.TRANSACTION_EVALUATED);
70+
this.subscriptions.push(sub);
71+
72+
(async () => {
73+
for await (const msg of sub) {
74+
try {
75+
const data = this.jc.decode(msg.data) as TransactionEvaluated;
76+
console.log(`Received transaction evaluation for ${data.transactionId}`);
77+
await handler(data);
78+
} catch (err) {
79+
console.error('Error processing transaction evaluated message:', err);
80+
}
81+
}
82+
})();
83+
}
84+
85+
// Operation: receiveTransactionReview
86+
async subscribeToTransactionReview(
87+
handler: (data: TransactionReview) => Promise<void>
88+
): Promise<void> {
89+
if (!this.nc) throw new Error('Not connected to NATS');
90+
91+
console.log(`Subscribing to ${CHANNELS.TRANSACTION_REVIEW}...`);
92+
const sub = this.nc.subscribe(CHANNELS.TRANSACTION_REVIEW);
93+
this.subscriptions.push(sub);
94+
95+
(async () => {
96+
for await (const msg of sub) {
97+
try {
98+
const data = this.jc.decode(msg.data) as TransactionReview;
99+
console.log(`Received transaction review for ${data.transactionId}`);
100+
await handler(data);
101+
} catch (err) {
102+
console.error('Error processing transaction review message:', err);
103+
}
104+
}
105+
})();
106+
}
107+
}
108+
109+
// Main application logic
110+
async function main() {
111+
const service = new FraudDetectionService();
112+
113+
const natsUrl = process.env.NATS_URL || 'nats://localhost:4222';
114+
115+
try {
116+
await service.connect(natsUrl);
117+
118+
// Handle transaction evaluations
119+
await service.subscribeToTransactionEvaluated(async (data) => {
120+
console.log('Processing transaction evaluation:', JSON.stringify(data, null, 2));
121+
122+
// Business logic: If transaction is flagged as fraudulent, send an alert
123+
if (data.isFraudulent || data.riskScore > 0.7) {
124+
const alert: FraudAlert = {
125+
alertId: `ALERT-${Date.now()}`,
126+
transactionId: data.transactionId,
127+
alertTime: new Date().toISOString(),
128+
severity: data.riskScore > 0.9 ? 'CRITICAL' : data.riskScore > 0.7 ? 'HIGH' : 'MEDIUM',
129+
details: `Transaction flagged with risk score: ${data.riskScore}`,
130+
};
131+
await service.sendFraudAlert(alert);
132+
}
133+
});
134+
135+
// Handle manual transaction reviews
136+
await service.subscribeToTransactionReview(async (data) => {
137+
console.log('Processing transaction review:', JSON.stringify(data, null, 2));
138+
139+
// Business logic: Log review outcomes
140+
if (data.reviewOutcome === 'Declined') {
141+
console.log(`Transaction ${data.transactionId} was declined by reviewer ${data.reviewerId}`);
142+
} else if (data.reviewOutcome === 'Escalated') {
143+
const alert: FraudAlert = {
144+
alertId: `ALERT-ESC-${Date.now()}`,
145+
transactionId: data.transactionId,
146+
alertTime: new Date().toISOString(),
147+
severity: 'HIGH',
148+
details: `Transaction escalated by reviewer ${data.reviewerId}`,
149+
};
150+
await service.sendFraudAlert(alert);
151+
}
152+
});
153+
154+
console.log('Fraud Detection Service is running. Press Ctrl+C to exit.');
155+
156+
// Keep the service running
157+
process.on('SIGINT', async () => {
158+
console.log('\nShutting down...');
159+
await service.disconnect();
160+
process.exit(0);
161+
});
162+
163+
process.on('SIGTERM', async () => {
164+
console.log('\nShutting down...');
165+
await service.disconnect();
166+
process.exit(0);
167+
});
168+
169+
} catch (err) {
170+
console.error('Failed to start Fraud Detection Service:', err);
171+
process.exit(1);
172+
}
173+
}
174+
175+
main();

0 commit comments

Comments
 (0)