Skip to content

Commit 6a02d4c

Browse files
committed
Merge remote-tracking branch 'upstream/main' into uiRefactor
2 parents 797708c + 622c5f7 commit 6a02d4c

26 files changed

+3029
-871
lines changed

apps/basket/package.json

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
11
{
2-
"name": "basket",
2+
"name": "@databuddy/basket",
33
"version": "1.0.50",
4-
"packageManager": "bun@1.2.12",
4+
"packageManager": "bun@1.3.0",
55
"scripts": {
6-
"dev": "bun --watch run src/index.ts --port 3002",
7-
"start": "bun run src/index.ts --port 3002",
6+
"dev": "bun --watch run src/index.ts",
7+
"start": "bun run src/index.ts",
88
"test": "bun test",
99
"test:watch": "bun test --watch",
1010
"test:routes": "bun test src/routes/*.test.ts",
1111
"test:utils": "bun test src/utils/*.test.ts",
12-
"test:coverage": "bun test --coverage"
12+
"test:kafka": "bun test src/lib/kafka.test.ts src/lib/producer.test.ts",
13+
"test:coverage": "bun test --coverage",
14+
"test:stress": "bun test src/lib/kafka-stress.test.ts",
15+
"stress:light": "bun run src/lib/kafka-stress-cli.ts 1000 10 100 1",
16+
"stress:medium": "bun run src/lib/kafka-stress-cli.ts 5000 10 250 1",
17+
"stress:heavy": "bun run src/lib/kafka-stress-cli.ts 10000 10 500 1",
18+
"stress:extreme": "bun run src/lib/kafka-stress-cli.ts 25000 10 1000 1",
19+
"stress:burst": "bun run src/lib/kafka-stress-cli.ts 50000 1 1000 1",
20+
"stress:sustained": "bun run src/lib/kafka-stress-cli.ts 10000 30 500 1",
21+
"stress:concurrent": "bun run src/lib/kafka-stress-cli.ts 5000 10 250 5",
22+
"stress:monitor": "bun run src/lib/kafka-monitor.ts",
23+
"stress:run": "bun run src/lib/run-stress-test.ts"
1324
},
1425
"dependencies": {
1526
"@clickhouse/client": "catalog:",
1627
"@clickhouse/client-web": "catalog:",
1728
"@databuddy/auth": "workspace:*",
29+
"@databuddy/db": "workspace:*",
1830
"@databuddy/redis": "workspace:*",
1931
"@databuddy/shared": "workspace:*",
20-
"@databuddy/db": "workspace:*",
2132
"@databuddy/validation": "workspace:*",
2233
"@elysiajs/cors": "^1.3.3",
2334
"@elysiajs/server-timing": "^1.3.0",
@@ -29,6 +40,7 @@
2940
"autumn-js": "catalog:",
3041
"dayjs": "catalog:",
3142
"elysia": "catalog:",
43+
"kafkajs": "^2.2.4",
3244
"pino": "catalog:",
3345
"stripe": "catalog:",
3446
"tldts": "^7.0.7",

apps/basket/src/index.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Elysia } from 'elysia';
44
import { logger } from './lib/logger';
5+
import { disconnectProducer } from './lib/producer';
56
import basketRouter from './routes/basket';
67
import emailRouter from './routes/email';
78
import stripeRouter from './routes/stripe';
@@ -38,7 +39,26 @@ const app = new Elysia()
3839
.use(emailRouter)
3940
.get('/health', () => ({ status: 'ok', version: '1.0.0' }));
4041

42+
const port = process.env.PORT || 4000;
43+
44+
await new Promise(resolve => setTimeout(resolve, 400));
45+
46+
console.log(`Starting basket service on port ${port}`);
47+
console.log(`Basket service running on http://localhost:${port}`);
48+
49+
process.on('SIGINT', async () => {
50+
console.log('Received SIGINT, shutting down...');
51+
await disconnectProducer();
52+
process.exit(0);
53+
});
54+
55+
process.on('SIGTERM', async () => {
56+
console.log('Received SIGTERM, shutting down...');
57+
await disconnectProducer();
58+
process.exit(0);
59+
});
60+
4161
export default {
42-
port: process.env.PORT || 4000,
4362
fetch: app.fetch,
63+
port: parseInt(port.toString()),
4464
};
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { randomUUID } from 'node:crypto';
2+
import {
3+
type AnalyticsEvent,
4+
type BlockedTraffic,
5+
clickHouse,
6+
} from '@databuddy/db';
7+
import { extractIpFromRequest, getGeo } from '../utils/ip-geo';
8+
import { parseUserAgent } from '../utils/user-agent';
9+
import { sanitizeString, VALIDATION_LIMITS } from '../utils/validation';
10+
11+
/**
12+
* Log blocked traffic for security and monitoring purposes
13+
*/
14+
export async function logBlockedTraffic(
15+
request: Request,
16+
body: any,
17+
_query: any,
18+
blockReason: string,
19+
blockCategory: string,
20+
botName?: string,
21+
clientId?: string
22+
): Promise<void> {
23+
try {
24+
const ip = extractIpFromRequest(request);
25+
const userAgent =
26+
sanitizeString(
27+
request.headers.get('user-agent'),
28+
VALIDATION_LIMITS.STRING_MAX_LENGTH
29+
) || '';
30+
31+
const { anonymizedIP, country, region, city } = await getGeo(ip);
32+
const { browserName, browserVersion, osName, osVersion, deviceType } =
33+
parseUserAgent(userAgent);
34+
35+
const now = Date.now();
36+
37+
const blockedEvent: BlockedTraffic = {
38+
id: randomUUID(),
39+
client_id: clientId || '',
40+
timestamp: now,
41+
42+
path: sanitizeString(body?.path, VALIDATION_LIMITS.STRING_MAX_LENGTH),
43+
url: sanitizeString(
44+
body?.url || body?.href,
45+
VALIDATION_LIMITS.STRING_MAX_LENGTH
46+
),
47+
referrer: sanitizeString(
48+
body?.referrer || request.headers.get('referer'),
49+
VALIDATION_LIMITS.STRING_MAX_LENGTH
50+
),
51+
method: 'POST',
52+
origin: sanitizeString(
53+
request.headers.get('origin'),
54+
VALIDATION_LIMITS.STRING_MAX_LENGTH
55+
),
56+
57+
ip: anonymizedIP || ip,
58+
user_agent: userAgent || '',
59+
accept_header: sanitizeString(
60+
request.headers.get('accept'),
61+
VALIDATION_LIMITS.STRING_MAX_LENGTH
62+
),
63+
language: sanitizeString(
64+
request.headers.get('accept-language'),
65+
VALIDATION_LIMITS.STRING_MAX_LENGTH
66+
),
67+
68+
block_reason: blockReason,
69+
block_category: blockCategory,
70+
bot_name: botName || '',
71+
72+
country: country || '',
73+
region: region || '',
74+
city: city || '',
75+
browser_name: browserName || '',
76+
browser_version: browserVersion || '',
77+
os_name: osName || '',
78+
os_version: osVersion || '',
79+
device_type: deviceType || '',
80+
81+
payload_size:
82+
blockReason === 'payload_too_large'
83+
? JSON.stringify(body || {}).length
84+
: undefined,
85+
86+
created_at: now,
87+
};
88+
89+
clickHouse
90+
.insert({
91+
table: 'analytics.blocked_traffic',
92+
values: [blockedEvent],
93+
format: 'JSONEachRow',
94+
})
95+
.then(() => {
96+
// Successfully logged blocked traffic
97+
})
98+
.catch((err) => {
99+
console.error('Failed to log blocked traffic', { error: err as Error });
100+
throw err;
101+
});
102+
} catch (error) {
103+
console.error('Failed to log blocked traffic', { error: error as Error });
104+
throw error;
105+
}
106+
}
107+

0 commit comments

Comments
 (0)