Skip to content

Commit 8da6a1d

Browse files
committed
fix: export data
1 parent a99a116 commit 8da6a1d

File tree

5 files changed

+92
-73
lines changed

5 files changed

+92
-73
lines changed
Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Data fetching logic for exports
22

33
import { chQuery } from "@databuddy/db";
4+
import { logger } from "../logger";
45
import {
56
buildDateFilter,
67
getErrorsQuery,
@@ -14,11 +15,11 @@ import type {
1415
SanitizedWebVitals,
1516
} from "./types";
1617

17-
export interface ExportData {
18+
export type ExportData = {
1819
events: SanitizedEvent[];
1920
errors: SanitizedError[];
2021
webVitals: SanitizedWebVitals[];
21-
}
22+
};
2223

2324
export async function fetchExportData(
2425
request: ExportRequest
@@ -29,30 +30,71 @@ export async function fetchExportData(
2930
end_date: endDate,
3031
} = request;
3132

32-
// Build secure date filter with parameters
33-
const { filter: dateFilter, params: dateParams } = buildDateFilter(
34-
startDate,
35-
endDate
36-
);
37-
38-
// Prepare queries
39-
const eventsQuery = getEventsQuery(dateFilter);
40-
const errorsQuery = getErrorsQuery(dateFilter);
41-
const webVitalsQuery = getWebVitalsQuery(dateFilter);
42-
43-
// Combine parameters: websiteId + date parameters
44-
const queryParams = { websiteId, ...dateParams };
45-
46-
// Execute queries in parallel with secure parameters
47-
const [events, errors, webVitals] = await Promise.all([
48-
chQuery<SanitizedEvent>(eventsQuery, queryParams),
49-
chQuery<SanitizedError>(errorsQuery, queryParams),
50-
chQuery<SanitizedWebVitals>(webVitalsQuery, queryParams),
51-
]);
52-
53-
return {
54-
events,
55-
errors,
56-
webVitals,
57-
};
33+
try {
34+
// Build secure date filter with parameters
35+
const { filter: dateFilter, params: dateParams } = buildDateFilter(
36+
startDate,
37+
endDate
38+
);
39+
40+
// Prepare queries
41+
const eventsQuery = getEventsQuery(dateFilter);
42+
const errorsQuery = getErrorsQuery(dateFilter);
43+
const webVitalsQuery = getWebVitalsQuery(dateFilter);
44+
45+
// Combine parameters: websiteId + date parameters
46+
const queryParams = { websiteId, ...dateParams };
47+
48+
// Execute queries in parallel with secure parameters
49+
const [events, errors, webVitals] = await Promise.all([
50+
chQuery<SanitizedEvent>(eventsQuery, queryParams).catch((error) => {
51+
logger.error({
52+
message: "Failed to fetch events for export",
53+
websiteId,
54+
error: error instanceof Error ? error.message : String(error),
55+
});
56+
throw new Error(
57+
`Failed to fetch events: ${error instanceof Error ? error.message : String(error)}`
58+
);
59+
}),
60+
chQuery<SanitizedError>(errorsQuery, queryParams).catch((error) => {
61+
logger.error({
62+
message: "Failed to fetch errors for export",
63+
websiteId,
64+
error: error instanceof Error ? error.message : String(error),
65+
});
66+
throw new Error(
67+
`Failed to fetch errors: ${error instanceof Error ? error.message : String(error)}`
68+
);
69+
}),
70+
chQuery<SanitizedWebVitals>(webVitalsQuery, queryParams).catch(
71+
(error) => {
72+
logger.error({
73+
message: "Failed to fetch web vitals for export",
74+
websiteId,
75+
error: error instanceof Error ? error.message : String(error),
76+
});
77+
throw new Error(
78+
`Failed to fetch web vitals: ${error instanceof Error ? error.message : String(error)}`
79+
);
80+
}
81+
),
82+
]);
83+
84+
return {
85+
events,
86+
errors,
87+
webVitals,
88+
};
89+
} catch (error) {
90+
logger.error({
91+
message: "Export data fetch failed",
92+
websiteId,
93+
startDate,
94+
endDate,
95+
error: error instanceof Error ? error.message : String(error),
96+
stack: error instanceof Error ? error.stack : undefined,
97+
});
98+
throw error;
99+
}
58100
}

apps/api/src/lib/export/queries.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,7 @@ export function getEventsQuery(dateFilter: string): string {
6060
time_on_page,
6161
scroll_depth,
6262
interaction_count,
63-
exit_intent,
6463
page_count,
65-
is_bounce,
66-
page_size,
6764
utm_source,
6865
utm_medium,
6966
utm_campaign,
@@ -72,12 +69,6 @@ export function getEventsQuery(dateFilter: string): string {
7269
load_time,
7370
dom_ready_time,
7471
ttfb,
75-
connection_time,
76-
request_time,
77-
render_time,
78-
fcp,
79-
lcp,
80-
cls,
8172
properties,
8273
toString(created_at) as created_at
8374
FROM analytics.events
@@ -88,8 +79,8 @@ export function getEventsQuery(dateFilter: string): string {
8879
}
8980

9081
export function getErrorsQuery(dateFilter: string): string {
91-
// Replace 'time' with 'timestamp' for errors table
92-
const errorDateFilter = dateFilter.replace(/time/g, "timestamp");
82+
// Replace 'time' with 'timestamp' for errors table (only in the filter conditions)
83+
const errorDateFilter = dateFilter.replace(/\btime\b/g, "timestamp");
9384
return `
9485
SELECT
9586
id,
@@ -121,8 +112,8 @@ export function getErrorsQuery(dateFilter: string): string {
121112
}
122113

123114
export function getWebVitalsQuery(dateFilter: string): string {
124-
// Replace 'time' with 'timestamp' for web_vitals table
125-
const vitalsDateFilter = dateFilter.replace(/time/g, "timestamp");
115+
// Replace 'time' with 'timestamp' for web_vitals table (only in the filter conditions)
116+
const vitalsDateFilter = dateFilter.replace(/\btime\b/g, "timestamp");
126117
return `
127118
SELECT
128119
id,

apps/api/src/lib/export/types.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
export type ExportFormat = "csv" | "json" | "txt" | "proto";
44

5-
export interface ExportRequest {
5+
export type ExportRequest = {
66
website_id: string;
77
start_date?: string;
88
end_date?: string;
99
format?: ExportFormat;
10-
}
10+
};
1111

12-
export interface ExportFile {
12+
export type ExportFile = {
1313
name: string;
1414
content: string;
15-
}
15+
};
1616

17-
export interface ExportMetadata {
17+
export type ExportMetadata = {
1818
export_date: string;
1919
website_id: string;
2020
date_range: {
@@ -27,7 +27,7 @@ export interface ExportMetadata {
2727
errors: number;
2828
web_vitals: number;
2929
};
30-
}
30+
};
3131

3232
// Sanitized data interfaces (excluding sensitive fields like IP, user_agent)
3333
export interface SanitizedEvent extends Record<string, unknown> {
@@ -60,10 +60,7 @@ export interface SanitizedEvent extends Record<string, unknown> {
6060
time_on_page?: number;
6161
scroll_depth?: number;
6262
interaction_count?: number;
63-
exit_intent: number;
6463
page_count: number;
65-
is_bounce: number;
66-
page_size?: number;
6764
utm_source?: string;
6865
utm_medium?: string;
6966
utm_campaign?: string;
@@ -72,12 +69,6 @@ export interface SanitizedEvent extends Record<string, unknown> {
7269
load_time?: number;
7370
dom_ready_time?: number;
7471
ttfb?: number;
75-
connection_time?: number;
76-
request_time?: number;
77-
render_time?: number;
78-
fcp?: number;
79-
lcp?: number;
80-
cls?: number;
8172
properties: string;
8273
created_at: string;
8374
}

apps/api/src/routes/export.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,9 @@ import { Elysia, t } from "elysia";
77
import { type ExportRequest, processExport } from "../lib/export";
88
import { logger } from "../lib/logger";
99

10-
import { createRateLimitMiddleware } from "../middleware/rate-limit";
1110

1211
dayjs.extend(utc);
1312

14-
const _exportRateLimit = createRateLimitMiddleware({
15-
type: "expensive",
16-
identifier: "export",
17-
customConfig: {
18-
requests: 10,
19-
window: "1m",
20-
},
21-
skipAuth: false,
22-
});
2313

2414
const getWebsiteById = cacheable(
2515
async (id: string) => {
@@ -90,7 +80,6 @@ async function authorizeWebsiteAccess(
9080
}
9181

9282
export const exportRoute = new Elysia({ prefix: "/v1/export" })
93-
// .use(exportRateLimit)
9483
.post(
9584
"/data",
9685
async ({ body, request }) => {

apps/dashboard/app/(main)/websites/[id]/settings/export/page.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
"use client";
22

3-
import { CheckIcon, DownloadIcon } from "@phosphor-icons/react";
3+
import {
4+
CheckIcon,
5+
DownloadIcon,
6+
FileCodeIcon,
7+
TableIcon,
8+
FileTextIcon,
9+
} from "@phosphor-icons/react";
410
import dayjs from "dayjs";
511
import { useParams } from "next/navigation";
612
import { useCallback, useMemo, useState } from "react";
@@ -36,19 +42,19 @@ export default function ExportPage() {
3642
value: "json" as const,
3743
label: "JSON",
3844
description: "Structured data for developers",
39-
icon: "📄",
45+
icon: FileCodeIcon,
4046
},
4147
{
4248
value: "csv" as const,
4349
label: "CSV",
4450
description: "Works with spreadsheets",
45-
icon: "📊",
51+
icon: TableIcon,
4652
},
4753
{
4854
value: "txt" as const,
4955
label: "TXT",
5056
description: "Plain text export",
51-
icon: "📝",
57+
icon: FileTextIcon,
5258
},
5359
],
5460
[]
@@ -129,8 +135,8 @@ export default function ExportPage() {
129135
onClick={() => setSelectedFormat(format.value)}
130136
type="button"
131137
>
132-
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-muted text-lg">
133-
{format.icon}
138+
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-muted">
139+
<format.icon className="h-5 w-5" />
134140
</div>
135141
<div className="min-w-0 flex-1">
136142
<div className="mb-1 flex items-center gap-2">

0 commit comments

Comments
 (0)