From 07f5e8f21590922706975e2096a3ffb17281a2ac Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Mon, 20 Oct 2025 17:29:23 +0530 Subject: [PATCH] add update domain Settings for wildcard --- messages/en-US.json | 5 +- server/auth/actions.ts | 1 + server/routers/domain/index.ts | 3 +- server/routers/domain/updateDomain.ts | 161 ++++++++++++++ server/routers/external.ts | 7 + src/components/DNSRecordsDataTable.tsx | 4 +- src/components/DomainInfoCard.tsx | 291 +++++++++++++++---------- 7 files changed, 352 insertions(+), 120 deletions(-) create mode 100644 server/routers/domain/updateDomain.ts diff --git a/messages/en-US.json b/messages/en-US.json index d53765cf..c4990aae 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1906,5 +1906,8 @@ "TTL": "TTL", "howToAddRecords": "How to Add Records", "dnsRecord": "DNS Records", - "required": "Required" + "required": "Required", + "domainSettingsUpdated": "Domain settings updated successfully", + "orgOrDomainIdMissing": "Organization or Domain ID is missing", + "loadingDNSRecords": "Loading DNS records..." } diff --git a/server/auth/actions.ts b/server/auth/actions.ts index 4c442d2c..83582885 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -82,6 +82,7 @@ export enum ActionsEnum { getClient = "getClient", listOrgDomains = "listOrgDomains", getDomain = "getDomain", + updateOrgDomain = "updateOrgDomain", getDNSRecords = "getDNSRecords", createNewt = "createNewt", createIdp = "createIdp", diff --git a/server/routers/domain/index.ts b/server/routers/domain/index.ts index 0bfedb41..e7e0b555 100644 --- a/server/routers/domain/index.ts +++ b/server/routers/domain/index.ts @@ -3,4 +3,5 @@ export * from "./createOrgDomain"; export * from "./deleteOrgDomain"; export * from "./restartOrgDomain"; export * from "./getDomain"; -export * from "./getDNSRecords"; \ No newline at end of file +export * from "./getDNSRecords"; +export * from "./updateDomain"; \ No newline at end of file diff --git a/server/routers/domain/updateDomain.ts b/server/routers/domain/updateDomain.ts new file mode 100644 index 00000000..c684466e --- /dev/null +++ b/server/routers/domain/updateDomain.ts @@ -0,0 +1,161 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, domains, orgDomains } from "@server/db"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { eq, and } from "drizzle-orm"; +import { OpenAPITags, registry } from "@server/openApi"; + +const paramsSchema = z + .object({ + orgId: z.string(), + domainId: z.string() + }) + .strict(); + +const bodySchema = z + .object({ + certResolver: z.string().optional().nullable(), + preferWildcardCert: z.boolean().optional().nullable() + }) + .strict(); + +export type UpdateDomainResponse = { + domainId: string; + certResolver: string | null; + preferWildcardCert: boolean | null; +}; + + +registry.registerPath({ + method: "patch", + path: "/org/{orgId}/domain/{domainId}", + description: "Update a domain by domainId.", + tags: [OpenAPITags.Domain], + request: { + params: z.object({ + domainId: z.string(), + orgId: z.string() + }) + }, + responses: {} +}); + +export async function updateOrgDomain( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = paramsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const parsedBody = bodySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const { orgId, domainId } = parsedParams.data; + const { certResolver, preferWildcardCert } = parsedBody.data; + + const [orgDomain] = await db + .select() + .from(orgDomains) + .where( + and( + eq(orgDomains.orgId, orgId), + eq(orgDomains.domainId, domainId) + ) + ); + + if (!orgDomain) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Domain not found or does not belong to this organization" + ) + ); + } + + + const [existingDomain] = await db + .select() + .from(domains) + .where(eq(domains.domainId, domainId)); + + if (!existingDomain) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Domain not found") + ); + } + + if (existingDomain.type !== "wildcard") { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Domain settings can only be updated for wildcard domains" + ) + ); + } + + const updateData: Partial<{ + certResolver: string | null; + preferWildcardCert: boolean; + }> = {}; + + if (certResolver !== undefined) { + updateData.certResolver = certResolver; + } + + if (preferWildcardCert !== undefined && preferWildcardCert !== null) { + updateData.preferWildcardCert = preferWildcardCert; + } + + const [updatedDomain] = await db + .update(domains) + .set(updateData) + .where(eq(domains.domainId, domainId)) + .returning(); + + if (!updatedDomain) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to update domain" + ) + ); + } + + return response(res, { + data: { + domainId: updatedDomain.domainId, + certResolver: updatedDomain.certResolver, + preferWildcardCert: updatedDomain.preferWildcardCert + }, + success: true, + error: false, + message: "Domain updated successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} \ No newline at end of file diff --git a/server/routers/external.ts b/server/routers/external.ts index c00f1e9f..fcc39ded 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -309,6 +309,13 @@ authenticated.get( domain.getDomain ); +authenticated.patch( + "/org/:orgId/domain/:domainId", + verifyOrgAccess, + verifyUserHasAction(ActionsEnum.updateOrgDomain), + domain.updateOrgDomain +) + authenticated.get( "/org/:orgId/domain/:domainId/dns-records", verifyOrgAccess, diff --git a/src/components/DNSRecordsDataTable.tsx b/src/components/DNSRecordsDataTable.tsx index b29c67f8..672012ae 100644 --- a/src/components/DNSRecordsDataTable.tsx +++ b/src/components/DNSRecordsDataTable.tsx @@ -125,7 +125,7 @@ export function DNSRecordsDataTable({ {table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => ( {header.isPlaceholder @@ -165,7 +165,7 @@ export function DNSRecordsDataTable({ colSpan={columns.length} className="h-24 text-center" > - No results found. + {t("noResults")} )} diff --git a/src/components/DomainInfoCard.tsx b/src/components/DomainInfoCard.tsx index a53a2420..2465c608 100644 --- a/src/components/DomainInfoCard.tsx +++ b/src/components/DomainInfoCard.tsx @@ -105,6 +105,7 @@ export default function DomainInfoCard({ orgId, domainId }: DomainInfoCardProps) const [dnsRecords, setDnsRecords] = useState([]); const [loadingRecords, setLoadingRecords] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); + const [saveLoading, setSaveLoading] = useState(false); const form = useForm({ resolver: zodResolver(formSchema), @@ -116,6 +117,21 @@ export default function DomainInfoCard({ orgId, domainId }: DomainInfoCardProps) } }); + useEffect(() => { + if (domain.domainId) { + const certResolverValue = domain.certResolver && domain.certResolver.trim() !== "" + ? domain.certResolver + : null; + + form.reset({ + baseDomain: domain.baseDomain || "", + type: (domain.type as "ns" | "cname" | "wildcard") || "wildcard", + certResolver: certResolverValue, + preferWildcardCert: domain.preferWildcardCert || false + }); + } + }, [domain]); + const fetchDNSRecords = async (showRefreshing = false) => { if (showRefreshing) { setIsRefreshing(true); @@ -150,6 +166,49 @@ export default function DomainInfoCard({ orgId, domainId }: DomainInfoCardProps) } }, [domain.domainId]); + const onSubmit = async (values: FormValues) => { + if (!orgId || !domainId) { + toast({ + title: t("error"), + description: t("orgOrDomainIdMissing", { fallback: "Organization or Domain ID is missing" }), + variant: "destructive" + }); + return; + } + + setSaveLoading(true); + + try { + const response = await api.patch( + `/org/${orgId}/domain/${domainId}`, + { + certResolver: values.certResolver, + preferWildcardCert: values.preferWildcardCert + } + ); + + updateDomain({ + ...domain, + certResolver: values.certResolver || null, + preferWildcardCert: values.preferWildcardCert || false + }); + + toast({ + title: t("success"), + description: t("domainSettingsUpdated", { fallback: "Domain settings updated successfully" }), + variant: "default" + }); + } catch (error) { + toast({ + title: t("error"), + description: formatAxiosError(error), + variant: "destructive" + }); + } finally { + setSaveLoading(false); + } + }; + const getTypeDisplay = (type: string) => { switch (type) { case "ns": @@ -198,128 +257,128 @@ export default function DomainInfoCard({ orgId, domainId }: DomainInfoCardProps) - {loadingRecords ? ( -
- loading... -
- ) : ( - + {domain.type !== "wildcard" && ( + loadingRecords ? ( +
+ {t("loadingDNSRecords", { fallback: "Loading DNS Records..." })} +
+ ) : ( + + ) )} - {/* Domain Settings */} - {/* Add condition later to only show when domain is wildcard */} - - - - - {t("domainSetting")} - - + {/* Domain Settings - Only show for wildcard domains */} + {domain.type === "wildcard" && ( + + + + + {t("domainSetting")} + + - - -
- - ( - - {t("certResolver")} - - - - - - - {certResolverOptions.map((opt) => ( - - {opt.title} - - ))} - - - - - {field.value !== null && field.value !== "default" && ( -
- - field.onChange(e.target.value)} + onValueChange={(val) => { + if (val === "default") { + field.onChange(null); + } else if (val === "custom") { + field.onChange(""); + } else { + field.onChange(val); + } + }} + > + + + + + {certResolverOptions.map((opt) => ( + + {opt.title} + + ))} + + + + + {field.value !== null && field.value !== "default" && ( +
+ + field.onChange(e.target.value)} + /> + + ( + + +
+ + {t("preferWildcardCert")} +
+
+ + + {t("preferWildcardCertDescription")} + + +
+ )} /> - - ( - - -
- - {t("preferWildcardCert")} -
-
+
+ )} + + )} + /> + + + + - - {t("preferWildcardCertDescription")} - - - - )} - /> -
- )} -
- )} - /> - - -
-
- - - - -
-
+ + + +
+
+ )} ); } \ No newline at end of file