Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions apps/api/src/routes/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface AuthContext {
user: { id: string; name: string; email: string } | null;
isAuthenticated: boolean;
authMethod: "api_key" | "session" | "none";
};
}

const AUTH_FAILED = new Response(
JSON.stringify({
Expand Down Expand Up @@ -67,9 +67,9 @@ function getAccessibleWebsites(authCtx: AuthContext) {
? eq(websites.organizationId, authCtx.apiKey.organizationId)
: authCtx.apiKey.userId
? and(
eq(websites.userId, authCtx.apiKey.userId),
isNull(websites.organizationId)
)
eq(websites.userId, authCtx.apiKey.userId),
isNull(websites.organizationId)
)
: eq(websites.id, "");
return db
.select(select)
Expand Down Expand Up @@ -116,12 +116,12 @@ function getTimeUnit(
type ParamInput =
| string
| {
name: string;
start_date?: string;
end_date?: string;
granularity?: string;
id?: string;
};
name: string;
start_date?: string;
end_date?: string;
granularity?: string;
id?: string;
};

function parseParam(p: ParamInput) {
if (typeof p === "string") {
Expand Down Expand Up @@ -270,7 +270,7 @@ interface QueryResult {
success: boolean;
data: Record<string, unknown>[];
error?: string;
};
}

async function runDynamicQuery(
req: DynamicQueryRequestType,
Expand Down
157 changes: 78 additions & 79 deletions apps/basket/src/utils/pixel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,113 +6,112 @@ const FLOAT_REGEX = /^-?\d*\.\d+$/;

// 1x1 transparent GIF pixel (base64)
const TRANSPARENT_PIXEL = Buffer.from(
"R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
"base64"
"R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
"base64"
);

/**
* Returns a 1x1 transparent GIF response
*/
export function createPixelResponse(): Response {
return new Response(TRANSPARENT_PIXEL, {
status: 200,
headers: {
"Content-Type": "image/gif",
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0",
},
});
return new Response(TRANSPARENT_PIXEL, {
status: 200,
headers: {
"Content-Type": "image/gif",
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0",
},
});
}

/**
* Parses string values to appropriate types
*/
function parseValue(value: string): string | number | boolean {
if (INTEGER_REGEX.test(value)) {
return Number.parseInt(value, 10);
}
if (FLOAT_REGEX.test(value)) {
return Number.parseFloat(value);
}
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
return value;
if (INTEGER_REGEX.test(value)) {
return Number.parseInt(value, 10);
}
if (FLOAT_REGEX.test(value)) {
return Number.parseFloat(value);
}
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
return value;
}

/**
* Converts pixel query parameters back into event data structure
* Handles nested keys like "key[subkey]" and JSON-stringified properties
*/
export function parsePixelQuery(query: Record<string, string>): {
eventData: Record<string, unknown>;
eventType: string;
eventData: Record<string, unknown>;
eventType: string;
} {
const result: Record<string, unknown> = {};
const result: Record<string, unknown> = {};

for (const [key, value] of Object.entries(query)) {
// Skip SDK metadata
if (key === "sdk_name" || key === "sdk_version" || key === "client_id") {
continue;
}
for (const [key, value] of Object.entries(query)) {
// Skip SDK metadata
if (key === "sdk_name" || key === "sdk_version" || key === "client_id") {
continue;
}

// Handle JSON-stringified properties
if (key === "properties") {
try {
result.properties = JSON.parse(value);
} catch {
result.properties = {};
}
continue;
}
// Handle JSON-stringified properties
if (key === "properties") {
try {
result.properties = JSON.parse(value);
} catch {
result.properties = {};
}
continue;
}

const match = key.match(NESTED_KEY_REGEX);
if (!match) {
result[key] = parseValue(value);
continue;
}
const match = key.match(NESTED_KEY_REGEX);
if (!match) {
result[key] = parseValue(value);
continue;
}

const baseKey = match[1];
const nestedPath = match[2];
const baseKey = match[1];
const nestedPath = match[2];

if (!nestedPath) {
result[baseKey] = parseValue(value);
continue;
}
if (!nestedPath) {
result[baseKey] = parseValue(value);
continue;
}

// Extract nested keys from brackets
const nestedKeys =
nestedPath.match(BRACKET_EXTRACT_REGEX)?.map((k) => k.slice(1, -1)) || [];
// Extract nested keys from brackets
const nestedKeys =
nestedPath.match(BRACKET_EXTRACT_REGEX)?.map((k) => k.slice(1, -1)) || [];

if (nestedKeys.length === 0) {
result[baseKey] = parseValue(value);
continue;
}
if (nestedKeys.length === 0) {
result[baseKey] = parseValue(value);
continue;
}

// Build nested structure
if (!result[baseKey]) {
result[baseKey] = {};
}
// Build nested structure
if (!result[baseKey]) {
result[baseKey] = {};
}

let current = result[baseKey] as Record<string, unknown>;
const lastIndex = nestedKeys.length - 1;
for (let i = 0; i < lastIndex; i++) {
const nestedKey = nestedKeys[i];
if (!current[nestedKey]) {
current[nestedKey] = {};
}
current = current[nestedKey] as Record<string, unknown>;
}
current[nestedKeys[lastIndex]] = parseValue(value);
}
let current = result[baseKey] as Record<string, unknown>;
const lastIndex = nestedKeys.length - 1;
for (let i = 0; i < lastIndex; i++) {
const nestedKey = nestedKeys[i];
if (!current[nestedKey]) {
current[nestedKey] = {};
}
current = current[nestedKey] as Record<string, unknown>;
}
current[nestedKeys[lastIndex]] = parseValue(value);
}

return {
eventData: result,
eventType: (result.type as string) || "track",
};
return {
eventData: result,
eventType: (result.type as string) || "track",
};
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ export interface DateRange {
end_date: string;
granularity?: "hourly" | "daily";
timezone?: string;
};
}

export interface BaseTabProps {
websiteId: string;
dateRange: DateRange;
};
}

export type WebsiteData = ReturnType<typeof useWebsite>["data"];

Expand All @@ -30,7 +30,7 @@ export interface MetricPoint {
sessions?: number;
bounce_rate?: number;
[key: string]: string | number | undefined;
};
}

export interface TrackingOptions {
disabled: boolean;
Expand All @@ -54,12 +54,12 @@ export interface TrackingOptions {
enableBatching: boolean;
batchSize: number;
batchTimeout: number;
};
}

export interface TrackingOptionConfig {
key: keyof TrackingOptions;
title: string;
description: string;
data: string[];
inverted?: boolean;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,47 +20,31 @@ export const getErrorTypeIcon = (type: string) => {
return <CodeIcon className="size-3.5 text-primary" weight="duotone" />;
}
if (lowerType.includes("network")) {
return (
<NetworkIcon className="size-3.5 text-primary" weight="duotone" />
);
return <NetworkIcon className="size-3.5 text-primary" weight="duotone" />;
}
if (lowerType.includes("script")) {
return (
<FileCodeIcon className="size-3.5 text-primary" weight="duotone" />
);
return <FileCodeIcon className="size-3.5 text-primary" weight="duotone" />;
}
if (lowerType.includes("syntax")) {
return (
<TerminalIcon className="size-3.5 text-primary" weight="duotone" />
);
return <TerminalIcon className="size-3.5 text-primary" weight="duotone" />;
}
return <BugIcon className="size-3.5 text-primary" weight="duotone" />;
};

// Get device icon
export const getDeviceIcon = (deviceType: string) => {
if (!deviceType) {
return (
<MonitorIcon className="size-3.5 text-chart-2" weight="duotone" />
);
return <MonitorIcon className="size-3.5 text-chart-2" weight="duotone" />;
}

switch (deviceType.toLowerCase()) {
case "mobile":
return (
<PhoneIcon className="size-3.5 text-chart-2" weight="duotone" />
);
return <PhoneIcon className="size-3.5 text-chart-2" weight="duotone" />;
case "tablet":
return (
<TableIcon className="size-3.5 text-chart-2" weight="duotone" />
);
return <TableIcon className="size-3.5 text-chart-2" weight="duotone" />;
case "desktop":
return (
<LaptopIcon className="size-3.5 text-chart-2" weight="duotone" />
);
return <LaptopIcon className="size-3.5 text-chart-2" weight="duotone" />;
default:
return (
<MonitorIcon className="size-3.5 text-chart-2" weight="duotone" />
);
return <MonitorIcon className="size-3.5 text-chart-2" weight="duotone" />;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,15 @@ export const RecentErrorsTable = ({ recentErrors }: Props) => {
}

return (
<div className="flex items-center gap-1.5">
<CountryFlag
country={countryCode || countryName || ""}
size={16}
/>
<span className="max-w-[80px] truncate text-sm">
{countryName}
</span>
</div>
<div className="flex items-center gap-1.5">
<CountryFlag
country={countryCode || countryName || ""}
size={16}
/>
<span className="max-w-[80px] truncate text-sm">
{countryName}
</span>
</div>
);
},
},
Expand All @@ -246,10 +246,7 @@ export const RecentErrorsTable = ({ recentErrors }: Props) => {
<Tooltip skipProvider>
<TooltipTrigger asChild>
<div className="flex items-center gap-1.5 text-muted-foreground">
<ClockIcon
className="size-3.5 shrink-0"
weight="duotone"
/>
<ClockIcon className="size-3.5 shrink-0" weight="duotone" />
<span className="whitespace-nowrap text-sm">{relative}</span>
</div>
</TooltipTrigger>
Expand Down
Loading
Loading