-
Notifications
You must be signed in to change notification settings - Fork 307
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: get rid of vercel domains API, just check cname instead
- Loading branch information
1 parent
4e85d75
commit 6d2beaf
Showing
10 changed files
with
185 additions
and
353 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { z } from "zod"; | ||
import { Simplify } from "type-fest"; | ||
|
||
export const DomainCheckResponse = z.union([ | ||
z.object({ | ||
ok: z.literal(true), | ||
reason: z.never().optional(), | ||
}), | ||
z.object({ | ||
ok: z.literal(false), | ||
reason: z.union([z.literal("used_by_other_workspace"), z.literal("invalid_domain_name")]), | ||
cnameValue: z.never().optional(), | ||
}), | ||
z.object({ | ||
ok: z.literal(false), | ||
reason: z.literal("requires_cname_configuration"), | ||
cnameValue: z.string().optional(), | ||
}), | ||
]); | ||
|
||
export type DomainCheckResponse = Simplify<z.infer<typeof DomainCheckResponse>>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,6 @@ import { getEeClient } from "../../lib/ee-client"; | |
import { assertDefined, requireDefined } from "juava"; | ||
import { ReloadOutlined } from "@ant-design/icons"; | ||
import { confirmOp, feedbackError } from "../../lib/ui"; | ||
import type { DomainStatus } from "../../lib/server/ee"; | ||
import { getAntdModal, useAntdModal } from "../../lib/modal"; | ||
import { get } from "../../lib/useApi"; | ||
import { Activity, AlertTriangle, Check, Globe, Wrench, Zap } from "lucide-react"; | ||
|
@@ -27,6 +26,7 @@ import { useLinksQuery } from "../../lib/queries"; | |
import { toURL } from "../../lib/shared/url"; | ||
import JSON5 from "json5"; | ||
import { EditorToolbar } from "../../components/EditorToolbar/EditorToolbar"; | ||
import { DomainCheckResponse } from "../../lib/shared/domain-check-response"; | ||
|
||
const Streams: React.FC<any> = () => { | ||
return ( | ||
|
@@ -77,10 +77,10 @@ const CustomDomain: React.FC<{ domain: string; deleteDomain: () => Promise<void> | |
); | ||
const [reloadTrigger, setReloadTrigger] = useState(0); | ||
const [deleting, setDeleting] = useState(false); | ||
const { data, isLoading, error, refetch } = useQuery<DomainStatus>( | ||
const { data, isLoading, error, refetch } = useQuery<DomainCheckResponse>( | ||
["domain-status", domain.toLowerCase(), reloadTrigger], | ||
async () => { | ||
return await eeClient.attachDomain(domain); | ||
return await get(`/api/${workspace.id}/domain-check?domain=${domain.toLowerCase()}`); | ||
}, | ||
{ cacheTime: 0 } | ||
); | ||
|
@@ -94,9 +94,7 @@ const CustomDomain: React.FC<{ domain: string; deleteDomain: () => Promise<void> | |
{/*</div>*/} | ||
<div className={"text-blue-600 w-4 h-4 mr-1.5"}> | ||
<Globe | ||
className={`w-full h-full ${ | ||
error || data?.error ? "text-red-600" : data?.needsConfiguration ? "text-yellow-600" : "text-blue-600" | ||
}`} | ||
className={`w-full h-full ${error ? "text-red-600" : data?.ok ? "text-blue-600" : "text-yellow-600"}`} | ||
/> | ||
</div> | ||
<div className="font-bold text-lg">{domain}</div> | ||
|
@@ -113,14 +111,14 @@ const CustomDomain: React.FC<{ domain: string; deleteDomain: () => Promise<void> | |
<FaExternalLinkAlt /> | ||
</Button> | ||
</Tooltip> | ||
{data?.needsConfiguration && ( | ||
{!data?.ok && ( | ||
<Tooltip title="See configuration instructions"> | ||
<Button | ||
type="text" | ||
danger | ||
disabled={isLoading || deleting} | ||
onClick={() => { | ||
DomainConfigurationInstructions.show({ domain, status: data }); | ||
DomainConfigurationInstructions.show({ domain, status: data! }); | ||
}} | ||
className="border-0" | ||
> | ||
|
@@ -174,29 +172,29 @@ const CustomDomain: React.FC<{ domain: string; deleteDomain: () => Promise<void> | |
</span> | ||
</StatusBadge> | ||
); | ||
} else if (error || data?.error) { | ||
} else if (error) { | ||
return <StatusBadge status="error">ERROR</StatusBadge>; | ||
} else if (data?.needsConfiguration) { | ||
} else if (!data?.ok) { | ||
return <StatusBadge status="warning">Configuration Required</StatusBadge>; | ||
} else { | ||
return <StatusBadge status="success">OK</StatusBadge>; | ||
} | ||
})()} | ||
</div> | ||
{(error || data?.error) && ( | ||
{error && ( | ||
<div className="flex items-start mt-1"> | ||
<div className={"mr-2"}>Description:</div> | ||
<div className="">{`${data?.error || "Internal error"}`}</div> | ||
<div className="">{`${"Internal error"}`}</div> | ||
</div> | ||
)} | ||
{data?.needsConfiguration && ( | ||
{!data?.ok && ( | ||
<div className="flex items-start mt-1"> | ||
<div className={"mr-2"}>Description:</div> | ||
<div className=""> | ||
See{" "} | ||
<a | ||
className={"cursor-pointer"} | ||
onClick={() => DomainConfigurationInstructions.show({ domain, status: data })} | ||
onClick={() => DomainConfigurationInstructions.show({ domain, status: data! })} | ||
> | ||
<u>configuration instructions</u> | ||
</a> | ||
|
@@ -234,25 +232,16 @@ export const DNSRecordTable: React.FC<DNSRecordTableProps> = ({ records }) => { | |
); | ||
}; | ||
|
||
export type DomainInstructionsProps = { domain: string; status: DomainStatus }; | ||
export type DomainInstructionsProps = { domain: string; status: DomainCheckResponse }; | ||
const DomainConfigurationInstructions: React.FC<DomainInstructionsProps> & { | ||
show: (p: DomainInstructionsProps) => void; | ||
} = ({ domain, status }) => { | ||
if (status.needsConfiguration && status.configurationType === "cname") { | ||
if (status.reason === "requires_cname_configuration") { | ||
return ( | ||
<div> | ||
<h3>Set the following record on your DNS provider to continue</h3> | ||
<p className="bg-bgLight py-2 my-4"> | ||
<DNSRecordTable records={[{ type: "CNAME", domain, value: status.cnameValue }]} /> | ||
</p> | ||
</div> | ||
); | ||
} else if (status.needsConfiguration && status.configurationType == "verification") { | ||
return ( | ||
<div> | ||
<h3>Set the following record on your DNS provider to continue</h3> | ||
<p className="bg-bgLight py-2 my-4"> | ||
<DNSRecordTable records={status.verification} /> | ||
<DNSRecordTable records={[{ type: "CNAME", domain, value: status.cnameValue! }]} /> | ||
</p> | ||
</div> | ||
); | ||
|
@@ -282,10 +271,24 @@ const DomainsEditor: React.FC<CustomWidgetProps<string[]>> = props => { | |
const add = async () => { | ||
setAddPending(true); | ||
try { | ||
const { available } = await get(`/api/${workspace.id}/domain-check?domain=${addValue}`); | ||
if (!available) { | ||
feedbackError(`Domain ${addValue} is not available. It is used by other workspace`); | ||
return; | ||
const available: DomainCheckResponse = await get(`/api/${workspace.id}/domain-check?domain=${addValue}`); | ||
if (!available.ok) { | ||
if (available.reason === "used_by_other_workspace") { | ||
feedbackError( | ||
<> | ||
Domain <code>{addValue}</code> is not available. It is used by other workspace. Contact{" "} | ||
<code>[email protected]</code> if you think this is a mistake | ||
</> | ||
); | ||
return; | ||
} else if (available.reason === "invalid_domain_name") { | ||
feedbackError( | ||
<> | ||
Invalid domain name <code>{addValue}</code> | ||
</> | ||
); | ||
return; | ||
} | ||
} | ||
const newVal = [...domains, addValue as string]; | ||
setDomains(newVal); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,41 @@ | ||
import { Api, inferUrl, nextJsApiHandler, verifyAccess } from "../../../lib/api"; | ||
import { getServerLog } from "../../../lib/server/log"; | ||
|
||
import { z } from "zod"; | ||
import { isDomainAvailable } from "../../../lib/server/custom-domains"; | ||
import { customDomainCnames, isCnameValid, isDomainAvailable } from "../../../lib/server/custom-domains"; | ||
import { DomainCheckResponse } from "../../../lib/shared/domain-check-response"; | ||
import { createRoute, verifyAccess } from "../../../lib/api"; | ||
|
||
const log = getServerLog("custom-domains"); | ||
|
||
export const api: Api = { | ||
url: inferUrl(__filename), | ||
GET: { | ||
export default createRoute() | ||
.GET({ | ||
auth: true, | ||
types: { | ||
query: z.object({ | ||
workspaceId: z.string(), | ||
domain: z.string(), | ||
}), | ||
result: z.object({ | ||
available: z.boolean(), | ||
}), | ||
}, | ||
handle: async ({ user, query }) => { | ||
await verifyAccess(user, query.workspaceId); | ||
const domainAvailability = await isDomainAvailable(query.domain, query.workspaceId); | ||
if (!domainAvailability.available) { | ||
log | ||
.atWarn() | ||
.log( | ||
`Domain '${query.domain}' can't be added to workspace ${query.workspaceId}. It is used by ${domainAvailability.usedInWorkspace}` | ||
); | ||
return { available: false }; | ||
} | ||
return { available: true }; | ||
}, | ||
}, | ||
}; | ||
query: z.object({ | ||
workspaceId: z.string(), | ||
domain: z.string(), | ||
}), | ||
result: DomainCheckResponse, | ||
}) | ||
.handler(async ({ user, query: { workspaceId, domain } }) => { | ||
if (!customDomainCnames || customDomainCnames.length == 0) { | ||
throw new Error(`CUSTOM_DOMAIN_CNAMES is not set`); | ||
} | ||
await verifyAccess(user, workspaceId); | ||
const domainAvailability = await isDomainAvailable(domain, workspaceId); | ||
if (!domainAvailability.available) { | ||
log | ||
.atWarn() | ||
.log( | ||
`Domain '${domain}' can't be added to workspace ${workspaceId}. It is used by ${domainAvailability.usedInWorkspace}` | ||
); | ||
return { ok: false, reason: "used_by_other_workspace" }; | ||
} | ||
|
||
export default nextJsApiHandler(api); | ||
const cnameValid = await isCnameValid(domain); | ||
if (!cnameValid) { | ||
log.atWarn().log(`Domain ${domain} is not valid`); | ||
return { ok: false, reason: "requires_cname_configuration", cnameValue: customDomainCnames[0] }; | ||
} | ||
return { ok: true }; | ||
}) | ||
.toNextApiHandler(); |
Oops, something went wrong.
6d2beaf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
new-jitsu – ./webapps/console
ag.ru
logu.au
ozon.ru
sse.ere
erxes.io
baidu.dom
ilmiya.io
sambla.se
bobsec.com
sambla.com
agro4u.life
bluetick.ai
myilmiya.io
protontv.eu
t.quenti.io
alicesec.com
d.askloan.tw
dev.aclis.io
docs.dh19.de
docs.dh19.eu
hunterbi.com
joseviso.com
mydomain.dom
https.bluetick.ai
ji.degulesider.dk
jitsu.ivve.health
metabase.erxes.io
t.clickncruise.hu
test.d2.jitsu.com
cloud.yupaopao.com
data.investing.com
data.mycompany.com
data.usepolygon.io
demosite.jitsu.com
dev.driverdeck.app
n8n.paziresh24.com
new.enterticket.es
t-dev.papermark.io
test2.d2.jitsu.com
uniquecafes.com.br
www.sidetrekai.com
colectha.voolu.shop
crm.myguestcare.com
data.sidetrekai.com
data.timeplus.cloud
localhost.jitsu.com
report.improvado.io
trk.myguestcare.com
www.sevenbillion.co
analytics.mtrsvc.com
data.embeddables.com
dataqa.investing.com
dev.blazingboost.com
j.israeladvocate.org
mercury.stagehub.com
store.sidetrekai.com
teslahenry.github.io
data.hogarlylabs.tech
data.your-company.com
event.clickncruise.hu
event.clickncruise.ro
test-domain.jitsu.com
test.bigfootproof.com
teste.fazcomex.com.br
analytics.dev.knekt.io
loraboutiquedental.com
notion.twelftree.co.uk
dev-portal.zoopsign.com
event.tradejobsnz.co.nz
investing-poc.jitsu.dev
savvy-replay.jitsu.tech
data.analytics-smart.com
data.handelsregister.app
event.clickncruise.co.uk
jt.fairhopeweb.github.io
savvy-replay2.jitsu.tech
savvy-replay3.jitsu.tech
savvy-replay4.jitsu.tech
track.alquimiaweb.com.br
track.pressance-group.jp
track.uniquecafes.com.br
colectha.agenciavoolu.com
kolectha.agenciavoolu.com
lp.loraboutiquedental.com
stage-portal.zoopsign.com
new-jitsu-jitsu.vercel.app
lodercom-colectha.voolu.shop
warehouse1.trendstyle.com.au
d0.livingdesignsfurniture.com
ingest-load-testing.jitsu.dev
jitsu.precisaosistemas.com.br
analytics.inspiresolutions.app
betteruptime-monitoring.jitsu.dev
canvas.livingdesignsfurniture.com
analytics.dev.inspiresolutions.app
cl9vt45z50001znkunc6v8fmm.d.jitsu.com
clm2jikrm00002v6r5l6niws3.d.jitsu.com
new-jitsu-git-newjitsu-jitsu.vercel.app
3000-rajaraodv-customerdemo-nmpsqwflswt.ws-us102.gitpod.io
new.jitsu.dev
6d2beaf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
new-jitsu-ee-api – ./webapps/ee-api
onetag-ee-api.vercel.app
new-jitsu-ee-api-jitsu.vercel.app
new-jitsu-ee-api-git-newjitsu-jitsu.vercel.app
ee.jitsu.dev