Skip to content

Commit 79e4be5

Browse files
authored
Merge pull request #570 from trycompai/main
[comp] Production Deploy
2 parents af6093b + 882850f commit 79e4be5

File tree

17 files changed

+1342
-170
lines changed

17 files changed

+1342
-170
lines changed

apps/app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@types/d3": "^7.4.3",
5353
"@uploadthing/react": "^7.3.0",
5454
"@upstash/ratelimit": "^2.0.5",
55+
"@vercel/sdk": "^1.7.1",
5556
"ai": "^4.3.10",
5657
"argon2": "^0.43.0",
5758
"better-auth": "^1.2.7",
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"use server";
2+
3+
import { z } from "zod";
4+
import { authActionClient } from "@/actions/safe-action";
5+
import { db } from "@comp/db";
6+
import { revalidatePath, revalidateTag } from "next/cache";
7+
import { Vercel } from "@vercel/sdk";
8+
import { env } from "@/env.mjs";
9+
10+
const checkDnsSchema = z.object({
11+
domain: z
12+
.string()
13+
.min(1, "Domain cannot be empty.")
14+
.max(63, "Domain too long. Max 63 chars.")
15+
.regex(
16+
/^(?!-)[A-Za-z0-9-]+([-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,6}$/,
17+
"Invalid domain format. Use format like sub.example.com",
18+
)
19+
.trim(),
20+
});
21+
22+
const vercel = new Vercel({
23+
bearerToken: env.VERCEL_ACCESS_TOKEN,
24+
});
25+
26+
export const checkDnsRecordAction = authActionClient
27+
.schema(checkDnsSchema)
28+
.metadata({
29+
name: "check-dns-record",
30+
track: {
31+
event: "add-custom-domain",
32+
channel: "server",
33+
},
34+
})
35+
.action(async ({ parsedInput, ctx }) => {
36+
const { domain } = parsedInput;
37+
38+
if (!ctx.session.activeOrganizationId) {
39+
throw new Error("No active organization");
40+
}
41+
42+
const rootDomain = domain.split(".").slice(-2).join(".");
43+
const activeOrgId = ctx.session.activeOrganizationId;
44+
45+
const response = await fetch(
46+
`https://networkcalc.com/api/dns/lookup/${domain}`,
47+
);
48+
const txtResponse = await fetch(
49+
`https://networkcalc.com/api/dns/lookup/${rootDomain}?type=TXT`,
50+
);
51+
const data = await response.json();
52+
const txtData = await txtResponse.json();
53+
54+
if (
55+
response.status !== 200 ||
56+
data.status !== "OK" ||
57+
txtResponse.status !== 200 ||
58+
txtData.status !== "OK"
59+
) {
60+
console.error("DNS lookup failed:", data);
61+
throw new Error(
62+
data.message ||
63+
"DNS record verification failed, check the records are valid or try again later.",
64+
);
65+
}
66+
67+
const cnameRecords = data.records?.CNAME;
68+
const txtRecords = txtData.records?.TXT;
69+
70+
const expectedCnameValue = "cname.vercel-dns.com";
71+
const expectedTxtValue = `compai-domain-verification=${activeOrgId}`;
72+
73+
let isCnameVerified = false;
74+
75+
if (cnameRecords) {
76+
isCnameVerified = cnameRecords.some(
77+
(record: { address: string }) =>
78+
record.address.toLowerCase() === expectedCnameValue,
79+
);
80+
}
81+
82+
let isTxtVerified = false;
83+
84+
if (txtRecords) {
85+
isTxtVerified = txtRecords.some((record: any) => {
86+
if (typeof record === "string") {
87+
return record === expectedTxtValue;
88+
}
89+
if (record && typeof record.value === "string") {
90+
return record.value === expectedTxtValue;
91+
}
92+
if (
93+
record &&
94+
Array.isArray(record.txt) &&
95+
record.txt.length > 0
96+
) {
97+
return record.txt.includes(expectedTxtValue);
98+
}
99+
return false;
100+
});
101+
}
102+
103+
const isVerified = isCnameVerified && isTxtVerified;
104+
105+
if (!isVerified) {
106+
throw new Error(
107+
"Error verifying DNS records. Please ensure both CNAME and TXT records are correctly configured, or wait a few minutes and try again.",
108+
);
109+
}
110+
111+
if (!env.VERCEL_PROJECT_ID) {
112+
throw new Error("Vercel project ID is not set.");
113+
}
114+
115+
const isExistingRecord = await vercel.projects.getProjectDomains({
116+
idOrName: env.VERCEL_PROJECT_ID,
117+
teamId: env.VERCEL_TEAM_ID,
118+
});
119+
120+
if (isExistingRecord.domains.some((record) => record.name === domain)) {
121+
await vercel.projects.removeProjectDomain({
122+
idOrName: env.VERCEL_PROJECT_ID,
123+
teamId: env.VERCEL_TEAM_ID,
124+
domain,
125+
});
126+
}
127+
128+
const addDomainToProject = await vercel.projects
129+
.addProjectDomain({
130+
idOrName: env.VERCEL_PROJECT_ID,
131+
teamId: env.VERCEL_TEAM_ID,
132+
slug: env.VERCEL_PROJECT_ID,
133+
requestBody: {
134+
name: domain,
135+
},
136+
})
137+
.then(async (res) => {
138+
await db.trust.upsert({
139+
where: {
140+
organizationId: activeOrgId,
141+
domain,
142+
},
143+
update: {
144+
domainVerified: true,
145+
status: "published",
146+
},
147+
create: {
148+
organizationId: activeOrgId,
149+
domain,
150+
status: "published",
151+
},
152+
});
153+
});
154+
155+
revalidatePath("/settings/trust-portal");
156+
revalidateTag(`organization_${ctx.session.activeOrganizationId}`);
157+
158+
return {
159+
success: true,
160+
};
161+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// custom-domain-action.ts
2+
3+
"use server";
4+
5+
import { db } from "@comp/db";
6+
import { authActionClient } from "@/actions/safe-action";
7+
import { z } from "zod";
8+
import { revalidatePath, revalidateTag } from "next/cache";
9+
10+
const customDomainSchema = z.object({
11+
domain: z.string().min(1),
12+
});
13+
14+
export const customDomainAction = authActionClient
15+
.schema(customDomainSchema)
16+
.metadata({
17+
name: "custom-domain",
18+
track: {
19+
event: "add-custom-domain",
20+
channel: "server",
21+
},
22+
})
23+
.action(async ({ parsedInput, ctx }) => {
24+
const { domain } = parsedInput;
25+
const { activeOrganizationId } = ctx.session;
26+
27+
if (!activeOrganizationId) {
28+
throw new Error("No active organization");
29+
}
30+
31+
try {
32+
await db.trust.update({
33+
where: { organizationId: activeOrganizationId },
34+
data: { domain, domainVerified: false },
35+
});
36+
37+
revalidatePath("/settings/trust-portal");
38+
revalidateTag(`organization_${activeOrganizationId}`);
39+
40+
return {
41+
success: true,
42+
};
43+
} catch (error) {
44+
console.error(error);
45+
throw new Error("Failed to update custom domain");
46+
}
47+
});

0 commit comments

Comments
 (0)