mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor: dry validation rules
This commit is contained in:
@@ -28,23 +28,14 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
|
||||||
const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/;
|
|
||||||
|
|
||||||
const domain = z.object({
|
import { domain } from "@/server/db/validations";
|
||||||
host: z.string().regex(hostnameRegex, { message: "Invalid hostname" }),
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
path: z.string().min(1),
|
import { flushSync } from "react-dom";
|
||||||
port: z
|
import type z from "zod";
|
||||||
.number()
|
|
||||||
.min(1, { message: "Port must be at least 1" })
|
|
||||||
.max(65535, { message: "Port must be 65535 or below" }),
|
|
||||||
https: z.boolean(),
|
|
||||||
certificateType: z.enum(["letsencrypt", "none"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
type Domain = z.infer<typeof domain>;
|
type Domain = z.infer<typeof domain>;
|
||||||
|
|
||||||
@@ -74,16 +65,7 @@ export const AddDomain = ({
|
|||||||
? api.domain.update.useMutation()
|
? api.domain.update.useMutation()
|
||||||
: api.domain.create.useMutation();
|
: api.domain.create.useMutation();
|
||||||
|
|
||||||
const defaultValues: Domain = {
|
|
||||||
host: "",
|
|
||||||
https: false,
|
|
||||||
path: "/",
|
|
||||||
port: 3000,
|
|
||||||
certificateType: "none",
|
|
||||||
};
|
|
||||||
|
|
||||||
const form = useForm<Domain>({
|
const form = useForm<Domain>({
|
||||||
defaultValues,
|
|
||||||
resolver: zodResolver(domain),
|
resolver: zodResolver(domain),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,8 +73,9 @@ export const AddDomain = ({
|
|||||||
if (data) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
...data,
|
...data,
|
||||||
path: data.path || defaultValues.path,
|
/* Convert null to undefined */
|
||||||
port: data.port || defaultValues.port,
|
path: data.path || undefined,
|
||||||
|
port: data.port || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form, form.reset, data]);
|
}, [form, form.reset, data]);
|
||||||
@@ -120,11 +103,19 @@ export const AddDomain = ({
|
|||||||
applicationId,
|
applicationId,
|
||||||
});
|
});
|
||||||
await utils.application.readTraefikConfig.invalidate({ applicationId });
|
await utils.application.readTraefikConfig.invalidate({ applicationId });
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset form if it was a new domain
|
||||||
|
Flushsync is needed for a bug witht he react-hook-form reset method
|
||||||
|
https://github.com/orgs/react-hook-form/discussions/7589#discussioncomment-10060621
|
||||||
|
*/
|
||||||
|
if (!domainId) {
|
||||||
|
flushSync(() => form.reset());
|
||||||
|
}
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error(dictionary.error);
|
toast.error(dictionary.error);
|
||||||
setIsOpen(false);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
@@ -239,6 +230,7 @@ export const AddDomain = ({
|
|||||||
<FormDescription>
|
<FormDescription>
|
||||||
Automatically provision SSL Certificate.
|
Automatically provision SSL Certificate.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { domain } from "@/server/db/validations";
|
||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { boolean, integer, pgTable, serial, text } from "drizzle-orm/pg-core";
|
import { boolean, integer, pgTable, serial, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
|
||||||
import { applications } from "./application";
|
import { applications } from "./application";
|
||||||
import { certificateType } from "./shared";
|
import { certificateType } from "./shared";
|
||||||
|
|
||||||
@@ -31,27 +31,17 @@ export const domainsRelations = relations(domains, ({ one }) => ({
|
|||||||
references: [applications.applicationId],
|
references: [applications.applicationId],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/;
|
|
||||||
const createSchema = createInsertSchema(domains, {
|
|
||||||
domainId: z.string().min(1),
|
|
||||||
host: z.string().min(1),
|
|
||||||
path: z.string().min(1),
|
|
||||||
port: z.number(),
|
|
||||||
https: z.boolean(),
|
|
||||||
applicationId: z.string(),
|
|
||||||
certificateType: z.enum(["letsencrypt", "none"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiCreateDomain = createSchema
|
const createSchema = createInsertSchema(domains, domain._def.schema.shape);
|
||||||
.pick({
|
|
||||||
host: true,
|
export const apiCreateDomain = createSchema.pick({
|
||||||
path: true,
|
host: true,
|
||||||
port: true,
|
path: true,
|
||||||
https: true,
|
port: true,
|
||||||
applicationId: true,
|
https: true,
|
||||||
certificateType: true,
|
applicationId: true,
|
||||||
})
|
certificateType: true,
|
||||||
.required();
|
});
|
||||||
|
|
||||||
export const apiFindDomain = createSchema
|
export const apiFindDomain = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
@@ -59,19 +49,16 @@ export const apiFindDomain = createSchema
|
|||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiFindDomainByApplication = createSchema
|
export const apiFindDomainByApplication = createSchema.pick({
|
||||||
.pick({
|
applicationId: true,
|
||||||
applicationId: true,
|
});
|
||||||
})
|
|
||||||
.required();
|
|
||||||
|
|
||||||
export const apiUpdateDomain = createSchema
|
export const apiUpdateDomain = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
domainId: true,
|
|
||||||
host: true,
|
host: true,
|
||||||
path: true,
|
path: true,
|
||||||
port: true,
|
port: true,
|
||||||
https: true,
|
https: true,
|
||||||
certificateType: true,
|
certificateType: true,
|
||||||
})
|
})
|
||||||
.required();
|
.merge(createSchema.pick({ domainId: true }).required());
|
||||||
|
|||||||
25
server/db/validations/index.ts
Normal file
25
server/db/validations/index.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const domain = z
|
||||||
|
.object({
|
||||||
|
host: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/, {
|
||||||
|
message: "Invalid hostname",
|
||||||
|
}),
|
||||||
|
path: z.string().min(1).optional(),
|
||||||
|
port: z
|
||||||
|
.number()
|
||||||
|
.min(1, { message: "Port must be at least 1" })
|
||||||
|
.max(65535, { message: "Port must be 65535 or below" })
|
||||||
|
.optional(),
|
||||||
|
https: z.boolean().optional(),
|
||||||
|
certificateType: z.enum(["letsencrypt", "none"]).optional(),
|
||||||
|
})
|
||||||
|
.superRefine((input, ctx) => {
|
||||||
|
if (input.https && !input.certificateType) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["certificateType"],
|
||||||
|
message: "Required",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user