Files
stats/packages/validation/src/index.ts
2024-10-16 10:18:16 +02:00

271 lines
6.5 KiB
TypeScript

import { z } from 'zod';
import {
chartTypes,
intervals,
lineTypes,
metrics,
operators,
timeWindows,
} from '@openpanel/constants';
export function objectToZodEnums<K extends string>(
obj: Record<K, any>,
): [K, ...K[]] {
const [firstKey, ...otherKeys] = Object.keys(obj) as K[];
return [firstKey!, ...otherKeys];
}
export const mapKeys = objectToZodEnums;
export const zChartEventFilter = z.object({
id: z.string().optional(),
name: z.string(),
operator: z.enum(objectToZodEnums(operators)),
value: z.array(z.string().or(z.number()).or(z.boolean()).or(z.null())),
});
export const zChartEvent = z.object({
id: z.string().optional(),
name: z.string(),
displayName: z.string().optional(),
property: z.string().optional(),
segment: z.enum([
'event',
'user',
'session',
'user_average',
'one_event_per_user',
'property_sum',
'property_average',
]),
filters: z.array(zChartEventFilter).default([]),
});
export const zChartBreakdown = z.object({
id: z.string().optional(),
name: z.string(),
});
export const zChartEvents = z.array(zChartEvent);
export const zChartBreakdowns = z.array(zChartBreakdown);
export const zChartType = z.enum(objectToZodEnums(chartTypes));
export const zLineType = z.enum(objectToZodEnums(lineTypes));
export const zTimeInterval = z.enum(objectToZodEnums(intervals));
export const zMetric = z.enum(objectToZodEnums(metrics));
export const zRange = z.enum(objectToZodEnums(timeWindows));
export const zChartInput = z.object({
chartType: zChartType.default('linear'),
interval: zTimeInterval.default('day'),
events: zChartEvents,
breakdowns: zChartBreakdowns.default([]),
range: zRange.default('30d'),
previous: z.boolean().default(false),
formula: z.string().optional(),
metric: zMetric.default('sum'),
projectId: z.string(),
startDate: z.string().nullish(),
endDate: z.string().nullish(),
limit: z.number().optional(),
offset: z.number().optional(),
});
export const zCriteria = z.enum(['on_or_after', 'on']);
export const zReportInput = zChartInput.extend({
name: z.string(),
lineType: zLineType,
unit: z.string().optional(),
criteria: zCriteria.optional(),
});
export const zInviteUser = z.object({
email: z.string().email(),
organizationSlug: z.string(),
role: z.enum(['org:admin', 'org:member']),
access: z.array(z.string()),
});
export const zShareOverview = z.object({
organizationSlug: z.string(),
projectId: z.string(),
password: z.string().nullable(),
public: z.boolean(),
});
export const zCreateReference = z.object({
title: z.string(),
description: z.string().nullish(),
projectId: z.string(),
datetime: z.string(),
});
export const zOnboardingProject = z
.object({
organization: z.string().optional(),
organizationSlug: z.string().optional(),
project: z.string().min(3),
domain: z.string().url().or(z.literal('').or(z.null())),
website: z.boolean(),
app: z.boolean(),
backend: z.boolean(),
})
.superRefine((data, ctx) => {
if (!data.organization && !data.organizationSlug) {
ctx.addIssue({
code: 'custom',
message: 'Organization is required',
path: ['organization'],
});
ctx.addIssue({
code: 'custom',
message: 'Organization is required',
path: ['organizationSlug'],
});
}
if (data.website && !data.domain) {
ctx.addIssue({
code: 'custom',
message: 'Domain is required for website tracking',
path: ['domain'],
});
}
if (
data.website === false &&
data.app === false &&
data.backend === false
) {
for (const key of ['app', 'backend', 'website']) {
ctx.addIssue({
code: 'custom',
message: 'At least one type must be selected',
path: [key],
});
}
}
});
export const zSlackAuthResponse = z.object({
ok: z.literal(true),
app_id: z.string(),
authed_user: z.object({
id: z.string(),
}),
scope: z.string(),
token_type: z.literal('bot'),
access_token: z.string(),
bot_user_id: z.string(),
team: z.object({
id: z.string(),
name: z.string(),
}),
incoming_webhook: z.object({
channel: z.string(),
channel_id: z.string(),
configuration_url: z.string().url(),
url: z.string().url(),
}),
});
export const zSlackConfig = z
.object({
type: z.literal('slack'),
})
.merge(zSlackAuthResponse);
export type ISlackConfig = z.infer<typeof zSlackConfig>;
export const zWebhookConfig = z.object({
type: z.literal('webhook'),
url: z.string().url(),
headers: z.record(z.string()),
payload: z.record(z.string(), z.unknown()),
});
export type IWebhookConfig = z.infer<typeof zWebhookConfig>;
export const zDiscordConfig = z.object({
type: z.literal('discord'),
url: z.string().url(),
});
export type IDiscordConfig = z.infer<typeof zDiscordConfig>;
export const zAppConfig = z.object({
type: z.literal('app'),
});
export type IAppConfig = z.infer<typeof zAppConfig>;
export const zEmailConfig = z.object({
type: z.literal('email'),
});
export type IEmailConfig = z.infer<typeof zEmailConfig>;
export type IIntegrationConfig =
| ISlackConfig
| IDiscordConfig
| IWebhookConfig
| IAppConfig
| IEmailConfig;
const zCreateIntegration = z.object({
id: z.string().optional(),
name: z.string().min(1),
organizationId: z.string().min(1),
projectId: z.string().min(1),
});
export const zCreateSlackIntegration = zCreateIntegration;
export const zCreateWebhookIntegration = zCreateIntegration.merge(
z.object({
config: zWebhookConfig,
}),
);
export const zCreateDiscordIntegration = zCreateIntegration.merge(
z.object({
config: zDiscordConfig,
}),
);
export const zNotificationRuleEventConfig = z.object({
type: z.literal('events'),
events: z.array(zChartEvent),
});
export type INotificationRuleEventConfig = z.infer<
typeof zNotificationRuleEventConfig
>;
export const zNotificationRuleFunnelConfig = z.object({
type: z.literal('funnel'),
events: z.array(zChartEvent).min(1),
});
export type INotificationRuleFunnelConfig = z.infer<
typeof zNotificationRuleFunnelConfig
>;
export const zNotificationRuleConfig = z.discriminatedUnion('type', [
zNotificationRuleEventConfig,
zNotificationRuleFunnelConfig,
]);
export type INotificationRuleConfig = z.infer<typeof zNotificationRuleConfig>;
export const zCreateNotificationRule = z.object({
id: z.string().optional(),
name: z.string().min(1),
config: zNotificationRuleConfig,
integrations: z.array(z.string()),
sendToApp: z.boolean(),
sendToEmail: z.boolean(),
projectId: z.string(),
});