fix(deps): upgrade zod to v4 and fix breaking changes (#3461)
* fix(deps): update dependency drizzle-zod to ^0.8.2 * chore: update zod to v4 import * fix: path is no longer available in transform context * fix: AnyZodObject does no longer exist * fix: auth env.ts using wrong createEnv and remove unused file env-validation.ts * fix: required_error no longer exists on z.string * fix: zod error map is deprecated and replaced with config * fix: default requires callback now * fix: migrate zod resolver for mantine * fix: remove unused form translation file * fix: wrong enum type * fix: record now requires two arguments * fix: add-confirm-password-refinement type issues * fix: add missing first record argument for entityStateSchema * fix: migrate superrefine to check * fix(deps): upgrade zod-form-data to v3 * fix: migrate superRefine to check for mediaUploadSchema * fix: authProvidersSchema default is array * fix: use stringbool instead of custom implementation * fix: record requires first argument * fix: migrate superRefine to check for certificate router * fix: confirm pasword refinement is overwriting types * fix: email optional not working * fix: migrate intersection to object converter * fix: safe parse return value rename * fix: easier access for min and max number value * fix: migrate superRefine to check for oldmarr import file * fix: inference of enum shape for old-import board-size wrong * fix: errors renamed to issues * chore: address pull request feedback * fix: zod form requires object * fix: inference for use-zod-form not working * fix: remove unnecessary convertion * fix(deps): upgrade trpc-to-openapi to v3 * fix: build error * fix: migrate missing zod imports to v4 * fix: migrate zod records to v4 * fix: missing core package dependency in api module * fix: unable to convert custom zod schema to openapi schema * fix(deps): upgrade zod to v4 * chore(renovate): enable zod dependency updates * test: add simple unit test for convertIntersectionToZodObject --------- Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const appHrefSchema = z
|
||||
.string()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import {
|
||||
backgroundImageAttachments,
|
||||
|
||||
@@ -1,48 +1,44 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createCustomErrorParams } from "./form/i18n";
|
||||
|
||||
export const certificateValidFileNameSchema = z.string().regex(/^[\w\-. ]+$/);
|
||||
|
||||
export const superRefineCertificateFile = (value: File | null, context: z.RefinementCtx) => {
|
||||
if (!value) {
|
||||
return context.addIssue({
|
||||
code: "invalid_type",
|
||||
expected: "object",
|
||||
received: "null",
|
||||
});
|
||||
}
|
||||
|
||||
const result = certificateValidFileNameSchema.safeParse(value.name);
|
||||
export const checkCertificateFile: z.core.CheckFn<File> = (context) => {
|
||||
const result = certificateValidFileNameSchema.safeParse(context.value.name);
|
||||
if (!result.success) {
|
||||
return context.addIssue({
|
||||
context.issues.push({
|
||||
code: "custom",
|
||||
params: createCustomErrorParams({
|
||||
key: "invalidFileName",
|
||||
params: {},
|
||||
}),
|
||||
input: context.value.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value.name.endsWith(".crt") && !value.name.endsWith(".pem")) {
|
||||
return context.addIssue({
|
||||
if (!context.value.name.endsWith(".crt") && !context.value.name.endsWith(".pem")) {
|
||||
context.issues.push({
|
||||
code: "custom",
|
||||
params: createCustomErrorParams({
|
||||
key: "invalidFileType",
|
||||
params: { expected: ".crt" },
|
||||
}),
|
||||
input: context.value.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.size > 1024 * 1024) {
|
||||
return context.addIssue({
|
||||
if (context.value.size > 1024 * 1024) {
|
||||
context.issues.push({
|
||||
code: "custom",
|
||||
params: createCustomErrorParams({
|
||||
key: "fileTooLarge",
|
||||
params: { maxSize: "1 MB" },
|
||||
}),
|
||||
input: context.value.size,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const paginatedSchema = z.object({
|
||||
search: z.string().optional(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
type CouldBeReadonlyArray<T> = T[] | readonly T[];
|
||||
|
||||
@@ -6,6 +6,6 @@ export const zodEnumFromArray = <T extends string>(array: CouldBeReadonlyArray<T
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
z.enum([array[0]!, ...array.slice(1)]);
|
||||
|
||||
export const zodUnionFromArray = <T extends z.ZodTypeAny>(array: CouldBeReadonlyArray<T>) =>
|
||||
export const zodUnionFromArray = <T extends z.ZodType>(array: CouldBeReadonlyArray<T>) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
z.union([array[0]!, array[1]!, ...array.slice(2)]);
|
||||
|
||||
114
packages/validation/src/form/i18n.spec.ts
Normal file
114
packages/validation/src/form/i18n.spec.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
|
||||
import { createCustomErrorParams, zodErrorMap } from "./i18n";
|
||||
|
||||
const expectError = (error: z.core.$ZodIssue, key: string) => {
|
||||
expect(error.message).toContain(key);
|
||||
};
|
||||
|
||||
describe("i18n", () => {
|
||||
const t = ((key: string) => {
|
||||
return `${key}`;
|
||||
}) as TranslationFunction;
|
||||
z.config({
|
||||
customError: zodErrorMap(t),
|
||||
});
|
||||
|
||||
test("should return required error for string when passing null", () => {
|
||||
const schema = z.string();
|
||||
const result = schema.safeParse(null);
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "required");
|
||||
});
|
||||
test("should return required error for empty string", () => {
|
||||
const schema = z.string().nonempty();
|
||||
const result = schema.safeParse("");
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "required");
|
||||
});
|
||||
test("should return invalid email error", () => {
|
||||
const schema = z.string().email();
|
||||
const result = schema.safeParse("invalid-email");
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "invalidEmail");
|
||||
});
|
||||
test("should return startsWith error", () => {
|
||||
const schema = z.string().startsWith("test");
|
||||
const result = schema.safeParse("invalid");
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "startsWith");
|
||||
});
|
||||
test("should return endsWith error", () => {
|
||||
const schema = z.string().endsWith("test");
|
||||
const result = schema.safeParse("invalid");
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "endsWith");
|
||||
});
|
||||
test("should return includes error", () => {
|
||||
const schema = z.string().includes("test");
|
||||
const result = schema.safeParse("invalid");
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "includes");
|
||||
});
|
||||
test("should return tooSmall error for string", () => {
|
||||
const schema = z.string().min(5);
|
||||
const result = schema.safeParse("test");
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "tooSmall.string");
|
||||
});
|
||||
test("should return tooSmall error for number", () => {
|
||||
const schema = z.number().min(5);
|
||||
const result = schema.safeParse(3);
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "tooSmall.number");
|
||||
});
|
||||
test("should return tooBig error for string", () => {
|
||||
const schema = z.string().max(5);
|
||||
const result = schema.safeParse("too long");
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "tooBig.string");
|
||||
});
|
||||
test("should return tooBig error for number", () => {
|
||||
const schema = z.number().max(5);
|
||||
const result = schema.safeParse(10);
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "tooBig.number");
|
||||
});
|
||||
test("should return custom error", () => {
|
||||
const schema = z.string().refine((val) => val === "valid", {
|
||||
params: createCustomErrorParams({
|
||||
key: "boardAlreadyExists",
|
||||
params: {},
|
||||
}),
|
||||
});
|
||||
|
||||
const result = schema.safeParse("invalid");
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectError(result.error.issues[0]!, "boardAlreadyExists");
|
||||
});
|
||||
});
|
||||
@@ -1,132 +1,139 @@
|
||||
import type { ErrorMapCtx, z, ZodTooBigIssue, ZodTooSmallIssue } from "zod";
|
||||
import { ZodIssueCode } from "zod";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import type { TranslationFunction, TranslationObject } from "@homarr/translation";
|
||||
import type { ScopedTranslationFunction, TranslationFunction, TranslationObject } from "@homarr/translation";
|
||||
|
||||
export const zodErrorMap = <TFunction extends TranslationFunction>(t: TFunction) => {
|
||||
return (issue: z.ZodIssueOptionalMessage, ctx: ErrorMapCtx) => {
|
||||
const error = handleZodError(issue, ctx);
|
||||
if ("message" in error && error.message) {
|
||||
return {
|
||||
message: error.message,
|
||||
};
|
||||
export const zodErrorMap = (t: TranslationFunction): z.core.$ZodErrorMap<z.core.$ZodIssue> => {
|
||||
return (issue) => {
|
||||
const error = handleError(issue);
|
||||
if (typeof error === "string") {
|
||||
return error;
|
||||
}
|
||||
return {
|
||||
// use never to make ts happy
|
||||
message: t(error.key ? `common.zod.${error.key}` : "common.zod.errors.default", (error.params ?? {}) as never),
|
||||
};
|
||||
return t(`common.zod.errors.${error.key}`, (error.params ?? {}) as never);
|
||||
};
|
||||
};
|
||||
|
||||
const handleStringError = (issue: z.ZodInvalidStringIssue) => {
|
||||
if (issue.validation === "email") {
|
||||
type ValidTranslationKeys = Parameters<ScopedTranslationFunction<"common.zod.errors">>[0];
|
||||
|
||||
type HandlerReturnValue =
|
||||
| string
|
||||
| {
|
||||
key: ValidTranslationKeys;
|
||||
params?: Record<string, string | number>;
|
||||
};
|
||||
|
||||
const handleError = (issue: z.core.$ZodRawIssue): HandlerReturnValue => {
|
||||
if (issue.code === "too_big") return handleTooBigError(issue);
|
||||
if (issue.code === "too_small") return handleTooSmallError(issue);
|
||||
if (issue.code === "invalid_format") return handleInvalidFormatError(issue);
|
||||
if (issue.code === "invalid_type" && issue.expected === "string" && issue.input === null) {
|
||||
return {
|
||||
key: "errors.string.invalidEmail",
|
||||
} as const;
|
||||
key: "required",
|
||||
};
|
||||
}
|
||||
if (issue.code === "custom" && issue.params?.i18n) {
|
||||
const i18n = issue.params.i18n as { key: CustomErrorKey; params?: Record<string, string | number> };
|
||||
return {
|
||||
key: `custom.${i18n.key}`,
|
||||
params: i18n.params,
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof issue.validation === "object") {
|
||||
if ("startsWith" in issue.validation) {
|
||||
return {
|
||||
key: "errors.string.startsWith",
|
||||
params: {
|
||||
startsWith: issue.validation.startsWith,
|
||||
},
|
||||
} as const;
|
||||
} else if ("endsWith" in issue.validation) {
|
||||
return {
|
||||
key: "errors.string.endsWith",
|
||||
params: {
|
||||
endsWith: issue.validation.endsWith,
|
||||
},
|
||||
} as const;
|
||||
return (
|
||||
issue.message ?? {
|
||||
key: "default",
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleTooBigError = (
|
||||
issue: Pick<z.core.$ZodIssueTooBig, "origin" | "maximum"> & { message?: string },
|
||||
): HandlerReturnValue => {
|
||||
if (issue.origin !== "string" && issue.origin !== "number") {
|
||||
return (
|
||||
issue.message ?? {
|
||||
key: "default",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const origin = issue.origin as "string" | "number";
|
||||
|
||||
return {
|
||||
key: `tooBig.${origin}`,
|
||||
params: {
|
||||
maximum: issue.maximum.toString(),
|
||||
count: issue.maximum.toString(),
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
const handleTooSmallError = (
|
||||
issue: Pick<z.core.$ZodIssueTooSmall, "origin" | "minimum"> & { message?: string },
|
||||
): HandlerReturnValue => {
|
||||
if (issue.origin !== "string" && issue.origin !== "number") {
|
||||
return (
|
||||
issue.message ?? {
|
||||
key: "default",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const origin = issue.origin as "string" | "number";
|
||||
if (origin === "string" && issue.minimum === 1) {
|
||||
return {
|
||||
key: "errors.string.includes",
|
||||
key: "required",
|
||||
} as const;
|
||||
}
|
||||
return {
|
||||
key: `tooSmall.${origin}`,
|
||||
params: {
|
||||
minimum: issue.minimum.toString(),
|
||||
count: issue.minimum.toString(),
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
const handleInvalidFormatError = (
|
||||
issue: Pick<z.core.$ZodIssueInvalidStringFormat, "format"> & { message?: string },
|
||||
): HandlerReturnValue => {
|
||||
if (issue.format === "includes" && "includes" in issue && typeof issue.includes === "string") {
|
||||
return {
|
||||
key: "string.includes",
|
||||
params: {
|
||||
includes: issue.validation.includes,
|
||||
includes: issue.includes,
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
|
||||
return {
|
||||
message: issue.message,
|
||||
};
|
||||
};
|
||||
|
||||
const handleTooSmallError = (issue: ZodTooSmallIssue) => {
|
||||
if (issue.type !== "string" && issue.type !== "number") {
|
||||
if (issue.format === "ends_with" && "suffix" in issue && typeof issue.suffix === "string") {
|
||||
return {
|
||||
message: issue.message,
|
||||
};
|
||||
}
|
||||
|
||||
if (issue.type === "string" && issue.minimum === 1) {
|
||||
return {
|
||||
key: "errors.required",
|
||||
key: "string.endsWith",
|
||||
params: {
|
||||
endsWith: issue.suffix,
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
|
||||
return {
|
||||
key: `errors.tooSmall.${issue.type}`,
|
||||
params: {
|
||||
minimum: issue.minimum,
|
||||
count: issue.minimum,
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
const handleTooBigError = (issue: ZodTooBigIssue) => {
|
||||
if (issue.type !== "string" && issue.type !== "number") {
|
||||
if (issue.format === "starts_with" && "prefix" in issue && typeof issue.prefix === "string") {
|
||||
return {
|
||||
message: issue.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
key: `errors.tooBig.${issue.type}`,
|
||||
params: {
|
||||
maximum: issue.maximum,
|
||||
count: issue.maximum,
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
const handleZodError = (issue: z.ZodIssueOptionalMessage, ctx: ErrorMapCtx) => {
|
||||
if (ctx.defaultError === "Required") {
|
||||
return {
|
||||
key: "errors.required",
|
||||
params: {},
|
||||
key: "string.startsWith",
|
||||
params: {
|
||||
startsWith: issue.prefix,
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
|
||||
if (issue.code === ZodIssueCode.invalid_string) {
|
||||
return handleStringError(issue);
|
||||
}
|
||||
if (issue.code === ZodIssueCode.too_small) {
|
||||
return handleTooSmallError(issue);
|
||||
}
|
||||
if (issue.code === ZodIssueCode.too_big) {
|
||||
return handleTooBigError(issue);
|
||||
}
|
||||
if (issue.code === ZodIssueCode.invalid_type && (ctx.data === "" || issue.received === "null")) {
|
||||
if (issue.format === "email") {
|
||||
return {
|
||||
key: "errors.required",
|
||||
params: {},
|
||||
} as const;
|
||||
}
|
||||
if (issue.code === ZodIssueCode.custom && issue.params?.i18n) {
|
||||
const { i18n } = issue.params as CustomErrorParams<CustomErrorKey>;
|
||||
return {
|
||||
key: `errors.custom.${i18n.key}`,
|
||||
params: i18n.params,
|
||||
key: "string.invalidEmail",
|
||||
} as const;
|
||||
}
|
||||
|
||||
return {
|
||||
message: issue.message,
|
||||
};
|
||||
return (
|
||||
issue.message ?? {
|
||||
key: "default",
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
type CustomErrorKey = keyof TranslationObject["common"]["zod"]["errors"]["custom"];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { everyoneGroup, groupPermissionKeys } from "@homarr/definitions";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const iconsFindSchema = z.object({
|
||||
searchText: z.string().optional(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { integrationKinds, integrationPermissions, integrationSecretKinds } from "@homarr/definitions";
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { z } from "zod";
|
||||
import { zfd } from "zod-form-data";
|
||||
|
||||
import { createCustomErrorParams } from "./form/i18n";
|
||||
@@ -6,36 +5,30 @@ import { createCustomErrorParams } from "./form/i18n";
|
||||
export const supportedMediaUploadFormats = ["image/png", "image/jpeg", "image/webp", "image/gif", "image/svg+xml"];
|
||||
|
||||
export const mediaUploadSchema = zfd.formData({
|
||||
file: zfd.file().superRefine((value: File | null, context: z.RefinementCtx) => {
|
||||
if (!value) {
|
||||
return context.addIssue({
|
||||
code: "invalid_type",
|
||||
expected: "object",
|
||||
received: "null",
|
||||
});
|
||||
}
|
||||
|
||||
if (!supportedMediaUploadFormats.includes(value.type)) {
|
||||
return context.addIssue({
|
||||
file: zfd.file().check((context) => {
|
||||
if (!supportedMediaUploadFormats.includes(context.value.type)) {
|
||||
context.issues.push({
|
||||
code: "custom",
|
||||
params: createCustomErrorParams({
|
||||
key: "invalidFileType",
|
||||
params: { expected: `one of ${supportedMediaUploadFormats.join(", ")}` },
|
||||
}),
|
||||
input: context.value.type,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.size > 1024 * 1024 * 32) {
|
||||
if (context.value.size > 1024 * 1024 * 32) {
|
||||
// Don't forget to update the limit in nginx.conf (client_max_body_size)
|
||||
return context.addIssue({
|
||||
context.issues.push({
|
||||
code: "custom",
|
||||
params: createCustomErrorParams({
|
||||
key: "fileTooLarge",
|
||||
params: { maxSize: "32 MB" },
|
||||
}),
|
||||
input: context.value.size,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const createSavePermissionsSchema = <const TPermissionSchema extends z.ZodEnum<[string, ...string[]]>>(
|
||||
export const createSavePermissionsSchema = <const TPermissionSchema extends z.core.$ZodEnum>(
|
||||
permissionSchema: TPermissionSchema,
|
||||
) => {
|
||||
return z.object({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ZodTypeAny } from "zod";
|
||||
import { z } from "zod";
|
||||
import type { ZodTypeAny } from "zod/v4";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { SearchEngineType } from "@homarr/definitions";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const settingsInitSchema = z.object({
|
||||
analytics: z.object({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { integrationKinds, widgetKinds } from "@homarr/definitions";
|
||||
|
||||
@@ -40,7 +40,7 @@ export const sharedItemSchema = z.object({
|
||||
export const commonItemSchema = z
|
||||
.object({
|
||||
kind: zodEnumFromArray(widgetKinds),
|
||||
options: z.record(z.unknown()),
|
||||
options: z.record(z.string(), z.unknown()),
|
||||
})
|
||||
.and(sharedItemSchema);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { colorSchemes } from "@homarr/definitions";
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
@@ -38,9 +38,10 @@ export const userPasswordSchema = z
|
||||
},
|
||||
);
|
||||
|
||||
const addConfirmPasswordRefinement = <TObj extends { password: string; confirmPassword: string }>(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
schema: z.ZodObject<any, "strip", z.ZodTypeAny, TObj>,
|
||||
const addConfirmPasswordRefinement = <
|
||||
TSchema extends z.ZodObject<{ password: z.core.$ZodString; confirmPassword: z.core.$ZodString }, z.core.$strip>,
|
||||
>(
|
||||
schema: TSchema,
|
||||
) => {
|
||||
return schema.refine((data) => data.password === data.confirmPassword, {
|
||||
path: ["confirmPassword"],
|
||||
@@ -55,7 +56,7 @@ export const userBaseCreateSchema = z.object({
|
||||
username: usernameSchema,
|
||||
password: userPasswordSchema,
|
||||
confirmPassword: z.string(),
|
||||
email: z.string().email().or(z.string().length(0).optional()),
|
||||
email: z.string().email().or(z.string().length(0)).optional(),
|
||||
groupIds: z.array(z.string()),
|
||||
});
|
||||
|
||||
@@ -121,7 +122,15 @@ export const userChangeColorSchemeSchema = z.object({
|
||||
});
|
||||
|
||||
export const userFirstDayOfWeekSchema = z.object({
|
||||
firstDayOfWeek: z.custom<DayOfWeek>((value) => z.number().min(0).max(6).safeParse(value).success),
|
||||
firstDayOfWeek: z
|
||||
.custom<DayOfWeek>((value) => z.number().min(0).max(6).safeParse(value).success)
|
||||
.meta({
|
||||
override: {
|
||||
type: "integer",
|
||||
minimum: 0,
|
||||
maximum: 6,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export const userPingIconsEnabledSchema = z.object({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const mediaRequestOptionsSchema = z.object({
|
||||
mediaId: z.number(),
|
||||
|
||||
Reference in New Issue
Block a user