fix zod schemas

This commit is contained in:
Milo Schwartz
2025-01-29 00:03:10 -05:00
parent 0e04e82b88
commit 20f659db89
7 changed files with 158 additions and 122 deletions

View File

@@ -59,38 +59,39 @@ import {
SelectTrigger,
SelectValue
} from "@app/components/ui/select";
import { subdomainSchema } from "@server/schemas/subdomainSchema";
const createResourceFormSchema = z
.object({
subdomain: z
.union([
z
.string()
.regex(
/^(?!:\/\/)([a-zA-Z0-9-_]+\.)*[a-zA-Z0-9-_]+$/,
"Invalid subdomain format"
)
.min(1, "Subdomain must be at least 1 character long")
.transform((val) => val.toLowerCase()),
z.string().optional()
])
.optional(),
subdomain: z.string().optional(),
name: z.string().min(1).max(255),
siteId: z.number(),
http: z.boolean(),
protocol: z.string(),
proxyPort: z.number().int().min(1).max(65535).optional()
proxyPort: z.number().optional(),
})
.refine(
(data) => {
if (data.http === true) {
return true;
if (!data.http) {
return z.number().int().min(1).max(65535).safeParse(data.proxyPort).success;
}
return !!data.proxyPort;
return true;
},
{
message: "Port number is required for non-HTTP resources",
path: ["proxyPort"]
message: "Invalid port number",
path: ["proxyPort"],
}
)
.refine(
(data) => {
if (data.http) {
return subdomainSchema.safeParse(data.subdomain).success;
}
return true;
},
{
message: "Invalid subdomain",
path: ["subdomain"],
}
);

View File

@@ -63,6 +63,7 @@ import {
} from "@app/components/Settings";
import { SwitchInput } from "@app/components/SwitchInput";
import { useSiteContext } from "@app/hooks/useSiteContext";
import { InfoPopup } from "@app/components/ui/info-popup";
// Regular expressions for validation
const DOMAIN_REGEX =
@@ -458,28 +459,28 @@ export default function ReverseProxyTargets(props: {
return (
<SettingsContainer>
{resource.http && (
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
SSL Configuration
</SettingsSectionTitle>
<SettingsSectionDescription>
Setup SSL to secure your connections with LetsEncrypt
certificates
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<SwitchInput
id="ssl-toggle"
label="Enable SSL (https)"
defaultChecked={resource.ssl}
onCheckedChange={async (val) => {
await saveSsl(val);
}}
/>
</SettingsSectionBody>
</SettingsSection>
)}
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
SSL Configuration
</SettingsSectionTitle>
<SettingsSectionDescription>
Setup SSL to secure your connections with
LetsEncrypt certificates
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<SwitchInput
id="ssl-toggle"
label="Enable SSL (https)"
defaultChecked={resource.ssl}
onCheckedChange={async (val) => {
await saveSsl(val);
}}
/>
</SettingsSectionBody>
</SettingsSection>
)}
{/* Targets Section */}
<SettingsSection>
<SettingsSectionHeader>
@@ -498,41 +499,45 @@ export default function ReverseProxyTargets(props: {
>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{resource.http && (
<FormField
control={addTargetForm.control}
name="method"
render={({ field }) => (
<FormItem>
<FormLabel>Method</FormLabel>
<FormControl>
<Select
value={field.value || undefined}
onValueChange={(value) => {
addTargetForm.setValue(
"method",
<FormField
control={addTargetForm.control}
name="method"
render={({ field }) => (
<FormItem>
<FormLabel>Method</FormLabel>
<FormControl>
<Select
value={
field.value ||
undefined
}
onValueChange={(
value
);
}}
>
<SelectTrigger id="method">
<SelectValue placeholder="Select method" />
</SelectTrigger>
<SelectContent>
<SelectItem value="http">
http
</SelectItem>
<SelectItem value="https">
https
</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
) => {
addTargetForm.setValue(
"method",
value
);
}}
>
<SelectTrigger id="method">
<SelectValue placeholder="Select method" />
</SelectTrigger>
<SelectContent>
<SelectItem value="http">
http
</SelectItem>
<SelectItem value="https">
https
</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<FormField
control={addTargetForm.control}
@@ -647,9 +652,9 @@ export default function ReverseProxyTargets(props: {
</TableBody>
</Table>
</TableContainer>
<SettingsSectionDescription>
Multiple targets will get load balanced by Traefik. You can use this for high availability.
</SettingsSectionDescription>
<p className="text-sm text-muted-foreground">
Adding more than one target above will enable load balancing.
</p>
</SettingsSectionBody>
<SettingsSectionFooter>
<Button

View File

@@ -36,24 +36,44 @@ import { useOrgContext } from "@app/hooks/useOrgContext";
import CustomDomainInput from "../CustomDomainInput";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { subdomainSchema } from "@server/schemas/subdomainSchema";
const GeneralFormSchema = z.object({
subdomain: z
.union([
z
.string()
.regex(
/^(?!:\/\/)([a-zA-Z0-9-_]+\.)*[a-zA-Z0-9-_]+$/,
"Invalid subdomain format"
)
.min(1, "Subdomain must be at least 1 character long")
.transform((val) => val.toLowerCase()),
z.string().optional()
])
.optional(),
name: z.string().min(1).max(255),
proxyPort: z.number().int().min(1).max(65535).optional()
});
const GeneralFormSchema = z
.object({
subdomain: z.string().optional(),
name: z.string().min(1).max(255),
proxyPort: z.number().optional(),
http: z.boolean()
})
.refine(
(data) => {
if (!data.http) {
return z
.number()
.int()
.min(1)
.max(65535)
.safeParse(data.proxyPort).success;
}
return true;
},
{
message: "Invalid port number",
path: ["proxyPort"]
}
)
.refine(
(data) => {
if (data.http) {
return subdomainSchema.safeParse(data.subdomain).success;
}
return true;
},
{
message: "Invalid subdomain",
path: ["subdomain"]
}
);
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
@@ -77,7 +97,8 @@ export default function GeneralForm() {
defaultValues: {
name: resource.name,
subdomain: resource.subdomain ? resource.subdomain : undefined,
proxyPort: resource.proxyPort ? resource.proxyPort : undefined
proxyPort: resource.proxyPort ? resource.proxyPort : undefined,
http: resource.http
},
mode: "onChange"
});