113 lines
4.0 KiB
TypeScript
113 lines
4.0 KiB
TypeScript
import { z } from "zod/v4";
|
|
|
|
import {
|
|
colorSchemes,
|
|
integrationKinds,
|
|
integrationSecretKinds,
|
|
onboardingLayoutPresets,
|
|
widgetKinds,
|
|
} from "@homarr/definitions";
|
|
import { supportedLanguages } from "@homarr/translation/languages";
|
|
|
|
import { boardNameSchema } from "./board";
|
|
import { zodEnumFromArray } from "./enums";
|
|
|
|
const hexColorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/);
|
|
const serviceAddressSchema = z.string().trim().min(1).max(2048);
|
|
const uniqueIdsSchema = (maxLength = 255) =>
|
|
z
|
|
.array(z.string().trim().min(1).max(maxLength))
|
|
.max(128)
|
|
.refine((ids) => new Set(ids).size === ids.length, { message: "IDs must be unique" });
|
|
const sourceIdSchema = z.string().trim().min(1).max(512);
|
|
|
|
export const onboardingCreateIntegrationSchema = z.object({
|
|
name: z.string().trim().min(1).max(127),
|
|
url: serviceAddressSchema,
|
|
sourceId: sourceIdSchema.optional(),
|
|
iconUrl: z.string().trim().min(1).max(2048).nullable().optional(),
|
|
description: z.string().trim().max(512).nullable().optional(),
|
|
pingUrl: serviceAddressSchema.nullable().optional(),
|
|
});
|
|
|
|
export const onboardingIntegrationDraftSchema = onboardingCreateIntegrationSchema.extend({
|
|
sourceId: sourceIdSchema,
|
|
kind: zodEnumFromArray(integrationKinds),
|
|
secrets: z.array(
|
|
z.object({
|
|
kind: zodEnumFromArray(integrationSecretKinds),
|
|
value: z.string().nonempty(),
|
|
}),
|
|
),
|
|
});
|
|
|
|
export const onboardingDiscoveredAppSchema = z.object({
|
|
sourceId: sourceIdSchema.optional(),
|
|
name: z.string().trim().min(1).max(127),
|
|
href: serviceAddressSchema.nullable(),
|
|
pingUrl: serviceAddressSchema.nullable().optional(),
|
|
iconUrl: z.string().trim().min(1).nullable(),
|
|
description: z.string().trim().max(512).nullable().optional(),
|
|
});
|
|
|
|
export const onboardingAppDraftSchema = onboardingDiscoveredAppSchema.extend({
|
|
sourceId: sourceIdSchema,
|
|
href: serviceAddressSchema,
|
|
});
|
|
|
|
const integrationDraftsSchema = z
|
|
.array(onboardingIntegrationDraftSchema)
|
|
.max(128)
|
|
.refine((drafts) => new Set(drafts.map((draft) => draft.sourceId)).size === drafts.length, {
|
|
message: "Integration source IDs must be unique",
|
|
});
|
|
const appDraftsSchema = z
|
|
.array(onboardingAppDraftSchema)
|
|
.max(128)
|
|
.refine((drafts) => new Set(drafts.map((draft) => draft.sourceId)).size === drafts.length, {
|
|
message: "App source IDs must be unique",
|
|
});
|
|
|
|
export const onboardingCompleteSetupSchema = z
|
|
.object({
|
|
server: z.object({
|
|
defaultLocale: z.enum(supportedLanguages),
|
|
defaultColorScheme: z.enum(colorSchemes),
|
|
analyticsEnabled: z.boolean().optional(),
|
|
}),
|
|
board: z.object({
|
|
id: z.string().trim().min(1).max(255).optional(),
|
|
name: boardNameSchema,
|
|
primaryColor: hexColorSchema,
|
|
secondaryColor: hexColorSchema,
|
|
itemRadius: z.enum(["xs", "sm", "md", "lg", "xl"]),
|
|
layoutPreset: z.enum(onboardingLayoutPresets).default("balanced"),
|
|
columnCount: z.number().int().min(8).max(24).optional(),
|
|
leftSidebar: z.boolean().default(false),
|
|
rightSidebar: z.boolean().default(false),
|
|
}),
|
|
selectedIntegrationIds: uniqueIdsSchema().default([]),
|
|
selectedAppIds: uniqueIdsSchema().default([]),
|
|
integrations: integrationDraftsSchema.default([]),
|
|
apps: appDraftsSchema.default([]),
|
|
selectedDockerSourceIds: uniqueIdsSchema(512).default([]),
|
|
selectedWidgetKinds: z
|
|
.array(z.enum(widgetKinds))
|
|
.max(widgetKinds.length)
|
|
.refine((kinds) => new Set(kinds).size === kinds.length, { message: "Widget kinds must be unique" })
|
|
.default([]),
|
|
})
|
|
.superRefine((input, ctx) => {
|
|
const integrationSourceIds = new Set(input.integrations.map((integration) => integration.sourceId));
|
|
for (const [index, app] of input.apps.entries()) {
|
|
if (!integrationSourceIds.has(app.sourceId)) continue;
|
|
ctx.addIssue({
|
|
code: "custom",
|
|
message: "A source ID cannot be used by both an integration and an app",
|
|
path: ["apps", index, "sourceId"],
|
|
});
|
|
}
|
|
});
|
|
|
|
export type OnboardingCompleteSetupInput = z.infer<typeof onboardingCompleteSetupSchema>;
|