Skip to content

Commit 7cdbadb

Browse files
committed
feat: store city location
1 parent 12bed03 commit 7cdbadb

File tree

3 files changed

+34
-31
lines changed

3 files changed

+34
-31
lines changed

apps/basket/src/routes/basket.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ async function insertTrackEvent(
268268
return;
269269
}
270270

271-
const { anonymizedIP, country, region } = await getGeo(ip);
271+
const { anonymizedIP, country, region, city } = await getGeo(ip);
272272
const {
273273
browserName,
274274
browserVersion,
@@ -322,7 +322,7 @@ async function insertTrackEvent(
322322
device_model: deviceModel || "",
323323
country: country || "",
324324
region: region || "",
325-
city: "",
325+
city: city || "",
326326

327327
screen_resolution: trackData.screen_resolution,
328328
viewport_size: trackData.viewport_size,
@@ -425,7 +425,7 @@ async function logBlockedTraffic(
425425
VALIDATION_LIMITS.STRING_MAX_LENGTH,
426426
) || "";
427427

428-
const { anonymizedIP, country, region } = await getGeo(ip);
428+
const { anonymizedIP, country, region, city } = await getGeo(ip);
429429
const { browserName, browserVersion, osName, osVersion, deviceType } =
430430
parseUserAgent(userAgent);
431431

@@ -468,6 +468,7 @@ async function logBlockedTraffic(
468468

469469
country: country || "",
470470
region: region || "",
471+
city: city || "",
471472
browser_name: browserName || "",
472473
browser_version: browserVersion || "",
473474
os_name: osName || "",

apps/basket/src/utils/ip-geo.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import type { City } from "@maxmind/geoip2-node";
21
import { Reader, AddressNotFoundError, BadMethodCallError } from "@maxmind/geoip2-node";
3-
import { readFile } from "node:fs/promises";
4-
import path from "node:path";
2+
import type { City } from "@maxmind/geoip2-node";
53
import { createHash } from 'node:crypto';
64

75
interface GeoIPReader extends Reader {
@@ -92,7 +90,7 @@ function isValidIp(ip: string): boolean {
9290

9391
export async function getGeoLocation(ip: string) {
9492
if (!ip || ignore.includes(ip) || !isValidIp(ip)) {
95-
return { country: undefined, region: undefined };
93+
return { country: undefined, region: undefined, city: undefined };
9694
}
9795

9896
// Lazy load database on first use
@@ -101,39 +99,41 @@ export async function getGeoLocation(ip: string) {
10199
await loadDatabase();
102100
} catch (error) {
103101
console.error("Failed to load database for IP lookup:", error);
104-
return { country: undefined, region: undefined };
102+
return { country: undefined, region: undefined, city: undefined };
105103
}
106104
}
107105

108106
if (!reader) {
109-
return { country: undefined, region: undefined };
107+
return { country: undefined, region: undefined, city: undefined };
110108
}
111109

112110
try {
113111
const response = reader.city(ip);
114112

115-
// Extract region data
113+
// Extract region and city data
116114
const region = response.subdivisions?.[0]?.names?.en;
115+
const city = response.city?.names?.en;
117116

118117
return {
119118
country: response.country?.names?.en,
120119
region: region,
120+
city: city,
121121
};
122122
} catch (error) {
123123
// Handle AddressNotFoundError specifically (IP not in database)
124124
if (error instanceof AddressNotFoundError) {
125-
return { country: undefined, region: undefined };
125+
return { country: undefined, region: undefined, city: undefined };
126126
}
127127

128128
// Handle BadMethodCallError (wrong database type)
129129
if (error instanceof BadMethodCallError) {
130130
console.error("Database type mismatch - using city() method with ipinfo database");
131-
return { country: undefined, region: undefined };
131+
return { country: undefined, region: undefined, city: undefined };
132132
}
133133

134134
// Handle other errors
135135
console.error("Error looking up IP:", ip, error);
136-
return { country: undefined, region: undefined };
136+
return { country: undefined, region: undefined, city: undefined };
137137
}
138138
}
139139

@@ -173,6 +173,7 @@ export async function getGeo(ip: string) {
173173
anonymizedIP: anonymizeIp(ip),
174174
country: geo.country,
175175
region: geo.region,
176+
city: geo.city,
176177
};
177178
}
178179

packages/db/src/clickhouse/schema.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ export interface BlockedTraffic {
431431
bot_name?: string;
432432
country?: string;
433433
region?: string;
434+
city?: string;
434435
browser_name?: string;
435436
browser_version?: string;
436437
os_name?: string;
@@ -449,19 +450,19 @@ export interface AnalyticsEvent {
449450
anonymous_id: string;
450451
time: number;
451452
session_id: string;
452-
453+
453454
// New fields
454455
event_type?: 'track' | 'error' | 'web_vitals';
455456
event_id?: string;
456457
session_start_time?: number;
457458
timestamp?: number;
458-
459+
459460
// Page context
460461
referrer?: string;
461462
url: string;
462463
path: string;
463464
title?: string;
464-
465+
465466
// Server enrichment
466467
ip: string;
467468
user_agent: string;
@@ -475,18 +476,18 @@ export interface AnalyticsEvent {
475476
country?: string;
476477
region?: string;
477478
city?: string;
478-
479+
479480
// User context
480481
screen_resolution?: string;
481482
viewport_size?: string;
482483
language?: string;
483484
timezone?: string;
484-
485+
485486
// Connection info
486487
connection_type?: string;
487488
rtt?: number;
488489
downlink?: number;
489-
490+
490491
// Engagement metrics
491492
time_on_page?: number;
492493
scroll_depth?: number;
@@ -496,14 +497,14 @@ export interface AnalyticsEvent {
496497
is_bounce: number;
497498
has_exit_intent?: number;
498499
page_size?: number;
499-
500+
500501
// UTM parameters
501502
utm_source?: string;
502503
utm_medium?: string;
503504
utm_campaign?: string;
504505
utm_term?: string;
505506
utm_content?: string;
506-
507+
507508
// Performance metrics
508509
load_time?: number;
509510
dom_ready_time?: number;
@@ -514,32 +515,32 @@ export interface AnalyticsEvent {
514515
render_time?: number;
515516
redirect_time?: number;
516517
domain_lookup_time?: number;
517-
518+
518519
// Web Vitals
519520
fcp?: number;
520521
lcp?: number;
521522
cls?: number;
522523
fid?: number;
523524
inp?: number;
524-
525+
525526
// Link tracking
526527
href?: string;
527528
text?: string;
528-
529+
529530
// Custom event value
530531
value?: string;
531-
532+
532533
// Error tracking
533534
error_message?: string;
534535
error_filename?: string;
535536
error_lineno?: number;
536537
error_colno?: number;
537538
error_stack?: string;
538539
error_type?: string;
539-
540+
540541
// Legacy properties
541542
properties: string;
542-
543+
543544
// Metadata
544545
created_at: number;
545546
}
@@ -550,13 +551,13 @@ export interface AnalyticsEvent {
550551
export async function initClickHouseSchema() {
551552
try {
552553
console.info('Initializing ClickHouse schema...');
553-
554+
554555
// Create the analytics database
555556
await clickHouse.command({
556557
query: CREATE_DATABASE,
557558
});
558559
console.info(`Created database: ${ANALYTICS_DATABASE}`);
559-
560+
560561
// Create tables
561562
const tables = [
562563
{ name: 'events', query: CREATE_EVENTS_TABLE },
@@ -567,14 +568,14 @@ export async function initClickHouseSchema() {
567568
{ name: 'stripe_refunds', query: CREATE_STRIPE_REFUNDS_TABLE },
568569
{ name: 'blocked_traffic', query: CREATE_BLOCKED_TRAFFIC_TABLE },
569570
];
570-
571+
571572
for (const table of tables) {
572573
await clickHouse.command({
573574
query: table.query,
574575
});
575576
console.info(`Created table: ${ANALYTICS_DATABASE}.${table.name}`);
576577
}
577-
578+
578579
console.info('ClickHouse schema initialization completed successfully');
579580
return {
580581
success: true,

0 commit comments

Comments
 (0)