Skip to content

Commit 3ed5691

Browse files
committed
cleanup, validation package
1 parent c414d2c commit 3ed5691

33 files changed

+1013
-1178
lines changed

apps/api/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Elysia } from 'elysia';
55
import { assistant } from './routes/assistant';
66
import { health } from './routes/health';
77
import { query } from './routes/query';
8+
import { logger } from '@databuddy/shared';
89

910
const app = new Elysia()
1011
.use(

apps/basket/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"@clickhouse/client-web": "^1.11.1",
1212
"@databuddy/auth": "../../packages/auth",
1313
"@databuddy/redis": "../../packages/redis",
14+
"@databuddy/validation": "../../packages/validation",
1415
"@elysiajs/cors": "^1.3.3",
1516
"@elysiajs/server-timing": "^1.3.0",
1617
"@logtail/edge": "^0.5.4",
Lines changed: 5 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,5 @@
1-
import { z } from 'zod/v4';
2-
3-
const resolutionRegex = /^(\d{2,5})x(\d{2,5})$/;
4-
const resolutionSchema = z
5-
.string()
6-
.regex(resolutionRegex, "Must be in the format 'WIDTHxHEIGHT'")
7-
.refine((val) => {
8-
const match = val.match(resolutionRegex);
9-
if (!match) {
10-
return false;
11-
}
12-
const width = Number(match[1]);
13-
const height = Number(match[2]);
14-
return width >= 240 && width <= 10_000 && height >= 240 && height <= 10_000;
15-
}, 'Width/height out of range (240-10000)');
16-
17-
const languageSchema = z
18-
.string()
19-
.regex(/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/, 'Invalid language tag');
20-
21-
const connectionTypeSchema = z
22-
.enum([
23-
'bluetooth',
24-
'cellular',
25-
'ethernet',
26-
'none',
27-
'wifi',
28-
'wimax',
29-
'other',
30-
'unknown',
31-
'slow-2g',
32-
'2g',
33-
'3g',
34-
'4g',
35-
])
36-
.nullable()
37-
.optional();
38-
39-
const MAX_FUTURE_MS = 60 * 60 * 1000; // 1 hour
40-
const MIN_TIMESTAMP = 946_684_800_000; // year 2000
41-
42-
export const analyticsEventSchema = z.object({
43-
eventId: z.string().max(512),
44-
name: z.string().min(1).max(128),
45-
anonymousId: z.string().nullable().optional(),
46-
sessionId: z.string().nullable().optional(),
47-
timestamp: z
48-
.number()
49-
.int()
50-
.gte(MIN_TIMESTAMP)
51-
.nullable()
52-
.optional()
53-
.refine((val) => val == null || val <= Date.now() + MAX_FUTURE_MS, {
54-
message: 'Timestamp too far in the future (max 1 hour ahead)',
55-
}),
56-
sessionStartTime: z
57-
.number()
58-
.int()
59-
.gte(MIN_TIMESTAMP)
60-
.nullable()
61-
.optional()
62-
.refine((val) => val == null || val <= Date.now() + MAX_FUTURE_MS, {
63-
message: 'Session start time too far in the future (max 1 hour ahead)',
64-
}),
65-
referrer: z
66-
.union([
67-
z.url({ protocol: /^https?$/, hostname: z.regexes.domain }),
68-
z.literal('direct'),
69-
])
70-
.nullable()
71-
.optional(),
72-
path: z.union([
73-
z.url({ protocol: /^https?$/, hostname: z.regexes.domain }),
74-
z
75-
.string()
76-
.regex(/^https?:\/\/localhost(:\d+)?\//), // Temporary, probably should remove tho.
77-
]),
78-
title: z.string().max(512).nullable().optional(),
79-
screen_resolution: resolutionSchema.nullable().optional(),
80-
viewport_size: resolutionSchema.nullable().optional(),
81-
language: languageSchema.nullable().optional(),
82-
timezone: z.string().max(64).nullable().optional(),
83-
connection_type: connectionTypeSchema,
84-
rtt: z.number().int().max(10_000).nullable().optional(),
85-
downlink: z.number().max(10_000).nullable().optional(),
86-
time_on_page: z.number().int().max(86_400).nullable().optional(),
87-
scroll_depth: z.number().max(100).nullable().optional(),
88-
interaction_count: z.number().int().max(10_000).nullable().optional(),
89-
exit_intent: z.number().int().max(1).nullable().optional(),
90-
page_count: z.number().int().max(1000).nullable().optional(),
91-
is_bounce: z.number().int().max(1).nullable().optional(),
92-
has_exit_intent: z.boolean().nullable().optional(),
93-
page_size: z.number().int().max(100_000_000).nullable().optional(),
94-
utm_source: z.string().max(128).nullable().optional(),
95-
utm_medium: z.string().max(128).nullable().optional(),
96-
utm_campaign: z.string().max(128).nullable().optional(),
97-
utm_term: z.string().max(128).nullable().optional(),
98-
utm_content: z.string().max(128).nullable().optional(),
99-
load_time: z.number().max(60_000).nullable().optional(),
100-
dom_ready_time: z.number().max(60_000).nullable().optional(),
101-
dom_interactive: z.number().max(60_000).nullable().optional(),
102-
ttfb: z.number().max(60_000).nullable().optional(),
103-
connection_time: z.number().max(60_000).nullable().optional(),
104-
request_time: z.number().max(60_000).nullable().optional(),
105-
render_time: z.number().max(60_000).nullable().optional(),
106-
redirect_time: z.number().max(60_000).nullable().optional(),
107-
domain_lookup_time: z.number().max(60_000).nullable().optional(),
108-
fcp: z.number().max(60_000).nullable().optional(),
109-
lcp: z.number().max(60_000).nullable().optional(),
110-
cls: z.number().max(10).nullable().optional(),
111-
fid: z.number().max(10_000).nullable().optional(),
112-
inp: z.number().max(10_000).nullable().optional(),
113-
href: z.string().max(2048).nullable().optional(),
114-
text: z.string().max(2048).nullable().optional(),
115-
value: z.string().max(2048).nullable().optional(),
116-
});
117-
118-
export const errorEventSchema = z.object({
119-
payload: z.object({
120-
eventId: z.string().max(128).nullable().optional(),
121-
anonymousId: z.string().max(128).nullable().optional(),
122-
sessionId: z.string().max(128).nullable().optional(),
123-
timestamp: z
124-
.number()
125-
.int()
126-
.gte(MIN_TIMESTAMP)
127-
.nullable()
128-
.optional()
129-
.refine((val) => val == null || val <= Date.now() + MAX_FUTURE_MS, {
130-
message: 'Timestamp too far in the future (max 1 hour ahead)',
131-
}),
132-
path: z.string().max(2048),
133-
message: z.string().max(2048),
134-
filename: z.string().max(512).nullable().optional(),
135-
lineno: z.number().int().max(100_000).nullable().optional(),
136-
colno: z.number().int().max(100_000).nullable().optional(),
137-
stack: z.string().max(4096).nullable().optional(),
138-
errorType: z.string().max(128).nullable().optional(),
139-
}),
140-
});
141-
142-
export const webVitalsEventSchema = z.object({
143-
payload: z.object({
144-
eventId: z.string().max(128).nullable().optional(),
145-
anonymousId: z.string().max(128).nullable().optional(),
146-
sessionId: z.string().max(128).nullable().optional(),
147-
timestamp: z
148-
.number()
149-
.int()
150-
.gte(MIN_TIMESTAMP)
151-
.nullable()
152-
.optional()
153-
.refine((val) => val == null || val <= Date.now() + MAX_FUTURE_MS, {
154-
message: 'Timestamp too far in the future (max 1 hour ahead)',
155-
}),
156-
path: z.string().max(2048),
157-
fcp: z.number().max(60_000).nullable().optional(),
158-
lcp: z.number().max(60_000).nullable().optional(),
159-
cls: z.number().max(10).nullable().optional(),
160-
fid: z.number().max(10_000).nullable().optional(),
161-
inp: z.number().max(10_000).nullable().optional(),
162-
}),
163-
});
1+
export {
2+
analyticsEventSchema,
3+
errorEventSchema,
4+
webVitalsEventSchema,
5+
} from '@databuddy/validation';

0 commit comments

Comments
 (0)