Skip to content

Commit 809477b

Browse files
committed
format
1 parent 66cb63b commit 809477b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1219
-630
lines changed

apps/api/src/lib/export/data-fetcher.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,55 @@
11
// Data fetching logic for exports
22

33
import { chQuery } from '@databuddy/db';
4-
import type {
5-
SanitizedEvent,
6-
SanitizedError,
4+
import {
5+
buildDateFilter,
6+
getErrorsQuery,
7+
getEventsQuery,
8+
getWebVitalsQuery,
9+
} from './queries';
10+
import type {
11+
ExportRequest,
12+
SanitizedError,
13+
SanitizedEvent,
714
SanitizedWebVitals,
8-
ExportRequest
915
} from './types';
10-
import {
11-
buildDateFilter,
12-
getEventsQuery,
13-
getErrorsQuery,
14-
getWebVitalsQuery
15-
} from './queries';
1616

1717
export interface ExportData {
1818
events: SanitizedEvent[];
1919
errors: SanitizedError[];
2020
webVitals: SanitizedWebVitals[];
2121
}
2222

23-
export async function fetchExportData(request: ExportRequest): Promise<ExportData> {
24-
const { website_id: websiteId, start_date: startDate, end_date: endDate } = request;
25-
23+
export async function fetchExportData(
24+
request: ExportRequest
25+
): Promise<ExportData> {
26+
const {
27+
website_id: websiteId,
28+
start_date: startDate,
29+
end_date: endDate,
30+
} = request;
31+
2632
// Build secure date filter with parameters
27-
const { filter: dateFilter, params: dateParams } = buildDateFilter(startDate, endDate);
28-
33+
const { filter: dateFilter, params: dateParams } = buildDateFilter(
34+
startDate,
35+
endDate
36+
);
37+
2938
// Prepare queries
3039
const eventsQuery = getEventsQuery(dateFilter);
3140
const errorsQuery = getErrorsQuery(dateFilter);
3241
const webVitalsQuery = getWebVitalsQuery(dateFilter);
33-
42+
3443
// Combine parameters: websiteId + date parameters
3544
const queryParams = { websiteId, ...dateParams };
36-
45+
3746
// Execute queries in parallel with secure parameters
3847
const [events, errors, webVitals] = await Promise.all([
3948
chQuery<SanitizedEvent>(eventsQuery, queryParams),
4049
chQuery<SanitizedError>(errorsQuery, queryParams),
4150
chQuery<SanitizedWebVitals>(webVitalsQuery, queryParams),
4251
]);
43-
52+
4453
return {
4554
events,
4655
errors,

apps/api/src/lib/export/file-generator.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
// File generation and ZIP creation
22

33
import JSZip from 'jszip';
4-
import type {
5-
ExportFile,
6-
ExportFormat,
7-
ExportMetadata,
8-
ExportRequest
9-
} from './types';
104
import type { ExportData } from './data-fetcher';
115
import { formatData, getFileExtension } from './formatters';
6+
import type {
7+
ExportFile,
8+
ExportFormat,
9+
ExportMetadata,
10+
ExportRequest,
11+
} from './types';
1212

1313
export function generateExportFiles(
14-
data: ExportData,
14+
data: ExportData,
1515
format: ExportFormat
1616
): ExportFile[] {
1717
const extension = getFileExtension(format);
18-
18+
1919
return [
2020
{
2121
name: `events.${extension}`,
@@ -33,7 +33,7 @@ export function generateExportFiles(
3333
}
3434

3535
export function generateMetadataFile(
36-
request: ExportRequest,
36+
request: ExportRequest,
3737
data: ExportData
3838
): ExportFile {
3939
const metadata: ExportMetadata = {
@@ -59,11 +59,11 @@ export function generateMetadataFile(
5959

6060
export async function createZipBuffer(files: ExportFile[]): Promise<Buffer> {
6161
const zip = new JSZip();
62-
62+
6363
for (const file of files) {
6464
zip.file(file.name, file.content);
6565
}
66-
66+
6767
return await zip.generateAsync({ type: 'nodebuffer' });
6868
}
6969

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import type { ExportFormat } from './types';
44

5-
export function convertToCSV<T extends Record<string, unknown>>(data: T[]): string {
5+
export function convertToCSV<T extends Record<string, unknown>>(
6+
data: T[]
7+
): string {
68
if (data.length === 0) return '';
79

810
const headers = Object.keys(data[0] || {}).join(',');
@@ -31,7 +33,9 @@ export function convertToCSV<T extends Record<string, unknown>>(data: T[]): stri
3133
return `${headers}\n${rows}`;
3234
}
3335

34-
export function convertToTXT<T extends Record<string, unknown>>(data: T[]): string {
36+
export function convertToTXT<T extends Record<string, unknown>>(
37+
data: T[]
38+
): string {
3539
if (data.length === 0) return '';
3640

3741
const headers = Object.keys(data[0] || {}).join('\t');
@@ -53,20 +57,20 @@ export function convertToTXT<T extends Record<string, unknown>>(data: T[]): stri
5357
}
5458

5559
export function convertToProto<T extends Record<string, unknown>>(
56-
data: T[],
60+
data: T[],
5761
typeName: string
5862
): string {
5963
if (data.length === 0) return '';
6064

6165
let protoContent = `# Protocol Buffer Text Format\n# Type: ${typeName}\n\n`;
62-
66+
6367
for (const [index, row] of data.entries()) {
6468
protoContent += `${typeName} {\n`;
65-
69+
6670
for (const [key, value] of Object.entries(row)) {
6771
if (value !== null && value !== undefined) {
6872
const fieldName = key.toLowerCase().replace(/[^a-z0-9_]/g, '_');
69-
73+
7074
if (typeof value === 'string') {
7175
// Escape quotes in string values
7276
const escapedValue = value.replace(/"/g, '\\"').replace(/\n/g, '\\n');
@@ -77,24 +81,26 @@ export function convertToProto<T extends Record<string, unknown>>(
7781
protoContent += ` ${fieldName}: ${value}\n`;
7882
} else {
7983
// Convert other types to string
80-
const stringValue = String(value).replace(/"/g, '\\"').replace(/\n/g, '\\n');
84+
const stringValue = String(value)
85+
.replace(/"/g, '\\"')
86+
.replace(/\n/g, '\\n');
8187
protoContent += ` ${fieldName}: "${stringValue}"\n`;
8288
}
8389
}
8490
}
85-
91+
8692
protoContent += '}\n';
8793
if (index < data.length - 1) {
8894
protoContent += '\n';
8995
}
9096
}
91-
97+
9298
return protoContent;
9399
}
94100

95101
export function formatData<T extends Record<string, unknown>>(
96-
data: T[],
97-
format: ExportFormat,
102+
data: T[],
103+
format: ExportFormat,
98104
typeName: string
99105
): string {
100106
switch (format) {

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

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// Main export orchestrator
22

33
import { logger } from '../logger';
4-
import type { ExportRequest } from './types';
54
import { fetchExportData } from './data-fetcher';
6-
import {
7-
generateExportFiles,
8-
generateMetadataFile,
9-
createZipBuffer,
10-
generateExportFilename
5+
import {
6+
createZipBuffer,
7+
generateExportFilename,
8+
generateExportFiles,
9+
generateMetadataFile,
1110
} from './file-generator';
11+
import type { ExportRequest } from './types';
1212

1313
export interface ExportResult {
1414
buffer: Buffer;
@@ -21,9 +21,11 @@ export interface ExportResult {
2121
};
2222
}
2323

24-
export async function processExport(request: ExportRequest): Promise<ExportResult> {
24+
export async function processExport(
25+
request: ExportRequest
26+
): Promise<ExportResult> {
2527
const { website_id: websiteId, format = 'json' } = request;
26-
28+
2729
logger.info('Starting data export', {
2830
websiteId,
2931
startDate: request.start_date,
@@ -33,7 +35,7 @@ export async function processExport(request: ExportRequest): Promise<ExportResul
3335

3436
// Fetch data from ClickHouse
3537
const data = await fetchExportData(request);
36-
38+
3739
logger.info('Data export queries completed', {
3840
websiteId,
3941
eventsCount: data.events.length,
@@ -50,7 +52,8 @@ export async function processExport(request: ExportRequest): Promise<ExportResul
5052
const buffer = await createZipBuffer(allFiles);
5153
const filename = generateExportFilename(websiteId);
5254

53-
const totalRecords = data.events.length + data.errors.length + data.webVitals.length;
55+
const totalRecords =
56+
data.events.length + data.errors.length + data.webVitals.length;
5457

5558
logger.info('Data export completed successfully', {
5659
websiteId,
@@ -72,10 +75,10 @@ export async function processExport(request: ExportRequest): Promise<ExportResul
7275
}
7376

7477
// Re-export types for convenience
75-
export type {
76-
ExportRequest,
77-
ExportFormat,
78-
SanitizedEvent,
79-
SanitizedError,
80-
SanitizedWebVitals
78+
export type {
79+
ExportFormat,
80+
ExportRequest,
81+
SanitizedError,
82+
SanitizedEvent,
83+
SanitizedWebVitals,
8184
} from './types';

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
* Builds a secure date filter using parameterized queries
55
* Returns both the filter string and parameters to prevent SQL injection
66
*/
7-
export function buildDateFilter(startDate?: string, endDate?: string): {
7+
export function buildDateFilter(
8+
startDate?: string,
9+
endDate?: string
10+
): {
811
filter: string;
912
params: Record<string, string>;
1013
} {

apps/api/src/query/builders/pages.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ export const PagesBuilders: Record<string, SimpleQueryConfig> = {
1818
orderBy: 'pageviews DESC',
1919
limit: 100,
2020
timeField: 'time',
21-
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
21+
allowedFilters: [
22+
'path',
23+
'country',
24+
'device_type',
25+
'browser_name',
26+
'os_name',
27+
'referrer',
28+
'utm_source',
29+
'utm_medium',
30+
'utm_campaign',
31+
],
2232
customizable: true,
2333
meta: {
2434
title: 'Top Pages',
@@ -68,7 +78,17 @@ export const PagesBuilders: Record<string, SimpleQueryConfig> = {
6878
},
6979

7080
entry_pages: {
71-
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
81+
allowedFilters: [
82+
'path',
83+
'country',
84+
'device_type',
85+
'browser_name',
86+
'os_name',
87+
'referrer',
88+
'utm_source',
89+
'utm_medium',
90+
'utm_campaign',
91+
],
7292
customizable: true,
7393
customSql: (
7494
websiteId: string,
@@ -124,7 +144,17 @@ export const PagesBuilders: Record<string, SimpleQueryConfig> = {
124144
},
125145

126146
exit_pages: {
127-
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
147+
allowedFilters: [
148+
'path',
149+
'country',
150+
'device_type',
151+
'browser_name',
152+
'os_name',
153+
'referrer',
154+
'utm_source',
155+
'utm_medium',
156+
'utm_campaign',
157+
],
128158
customizable: true,
129159
customSql: (
130160
websiteId: string,
@@ -204,7 +234,17 @@ export const PagesBuilders: Record<string, SimpleQueryConfig> = {
204234
orderBy: 'pageviews DESC',
205235
limit: 100,
206236
timeField: 'time',
207-
allowedFilters: ['path', 'country', 'device_type', 'browser_name', 'os_name', 'referrer', 'utm_source', 'utm_medium', 'utm_campaign'],
237+
allowedFilters: [
238+
'path',
239+
'country',
240+
'device_type',
241+
'browser_name',
242+
'os_name',
243+
'referrer',
244+
'utm_source',
245+
'utm_medium',
246+
'utm_campaign',
247+
],
208248
customizable: true,
209249
},
210250
};

0 commit comments

Comments
 (0)