feat: check custom domain before redirection and save
This commit is contained in:
parent
a311911e09
commit
23494d1941
120
showcase.json
120
showcase.json
|
|
@ -1,112 +1,12 @@
|
|||
[
|
||||
"xlog",
|
||||
"diygod",
|
||||
"rss3",
|
||||
"crossbell-blog",
|
||||
"hey",
|
||||
"song",
|
||||
"atlas-thinking",
|
||||
"joshua",
|
||||
"jeff",
|
||||
"crossbell-io-updates",
|
||||
"kanikig",
|
||||
"isheep",
|
||||
"henryqw",
|
||||
"hominsu",
|
||||
"stars",
|
||||
"somehacker",
|
||||
"novonine",
|
||||
"qfdk",
|
||||
"fblauer",
|
||||
"anxiete",
|
||||
"kumasanki",
|
||||
"jingfelix",
|
||||
"tcj",
|
||||
"atlas",
|
||||
"meow-7218",
|
||||
"erection",
|
||||
"jowno",
|
||||
"walt",
|
||||
"adazz",
|
||||
"ark",
|
||||
"hans",
|
||||
"isolcat",
|
||||
"henryq",
|
||||
"duoyu",
|
||||
"candinya",
|
||||
"shang",
|
||||
"toast",
|
||||
"pseudoyu",
|
||||
"caos",
|
||||
"nanami",
|
||||
"xbox",
|
||||
"vidorra",
|
||||
"ivyheretochill",
|
||||
"wiming",
|
||||
"zsakvo",
|
||||
"wuhang2003",
|
||||
"bladedragon",
|
||||
"qiuxuanluo",
|
||||
"saltad",
|
||||
"arthurwu",
|
||||
"laoyan",
|
||||
"baxx",
|
||||
"kmkmkm",
|
||||
"fuyuan",
|
||||
"ming5ming",
|
||||
"jameshih",
|
||||
"cryptogirls",
|
||||
"daidr",
|
||||
"mekal",
|
||||
"tommy-1329",
|
||||
"jackyoung-5042",
|
||||
"moojok",
|
||||
"maji-may-7766",
|
||||
"bocaibocaieth-1274",
|
||||
"soptq-6165",
|
||||
"yeungyeah",
|
||||
"xnor",
|
||||
"chenyyds",
|
||||
"0xcavin",
|
||||
"shuxinfeng",
|
||||
"tea-24",
|
||||
"revery",
|
||||
"hsinyau",
|
||||
"rootless",
|
||||
"haydenull",
|
||||
"walk2future",
|
||||
"liuyejinghong",
|
||||
"sion",
|
||||
"magren",
|
||||
"lolik",
|
||||
"yuhangch",
|
||||
"ssshooter",
|
||||
"baiyi-5736",
|
||||
"kendrickzou-8",
|
||||
"nafidurmus",
|
||||
"dagora",
|
||||
"aiirobyte",
|
||||
"onweb3",
|
||||
"anghunk",
|
||||
"agrislis",
|
||||
"jigsaw",
|
||||
"sakura-6601",
|
||||
"bai-2800",
|
||||
"rustynail",
|
||||
"ximu-3371",
|
||||
"xn--37q595dihas5a-4315",
|
||||
"batgirl",
|
||||
"exodus-exodus",
|
||||
"xn--psu263e-5088",
|
||||
"cryptonerdcn",
|
||||
"fzdwx-2094",
|
||||
"ovler",
|
||||
"stz",
|
||||
"monkey-1593",
|
||||
"hoodrh",
|
||||
"outtime",
|
||||
"bs10081-7290",
|
||||
"huazidev",
|
||||
"character-4568",
|
||||
"lyunvy"
|
||||
10, 33446, 572, 45, 153, 12, 506, 30, 595, 45899, 22033, 51494, 51124, 51232,
|
||||
40669, 45828, 26375, 49041, 48952, 51408, 51470, 51072, 496, 38034, 47359,
|
||||
33335, 50701, 50161, 470, 50312, 21052, 32022, 274, 77, 50168, 51145, 47328,
|
||||
630, 383, 51383, 723, 51172, 4583, 50472, 32179, 51316, 33210, 45089, 51094,
|
||||
31132, 50140, 37137, 42839, 50114, 51006, 51191, 50843, 46648, 33398, 887,
|
||||
51126, 51197, 32085, 1407, 51154, 51104, 40675, 4381, 43051, 50622, 51056,
|
||||
33483, 19, 50877, 33420, 50094, 49213, 33276, 50351, 42993, 50216, 50132,
|
||||
45763, 50143, 43754, 32168, 500, 47399, 33538, 48951, 33471, 33396, 37223,
|
||||
33458, 39133, 20004, 43625, 42787, 43258, 33462, 47109, 46939, 45701, 33233,
|
||||
37346, 40671, 33870, 33421
|
||||
]
|
||||
|
|
|
|||
|
|
@ -138,8 +138,8 @@ export const getSites = async (input: string[]) => {
|
|||
const result = await client
|
||||
.query(
|
||||
`
|
||||
query getCharacters($identities: [String!], $limit: Int) {
|
||||
characters( where: { handle: { in: $identities } }, orderBy: [{ updatedAt: desc }], take: $limit ) {
|
||||
query getCharacters($identities: [Int!], $limit: Int) {
|
||||
characters( where: { characterId: { in: $identities } }, orderBy: [{ updatedAt: desc }], take: $limit ) {
|
||||
handle
|
||||
updatedAt
|
||||
characterId
|
||||
|
|
@ -719,3 +719,31 @@ export async function getMiraBalance(address: string, contract: Contract) {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
export async function checkDomainServer(domain: string, handle: string) {
|
||||
const dns = await (
|
||||
await fetch(
|
||||
`https://cloudflare-dns.com/dns-query?name=_xlog-challenge.${domain}&type=TXT`,
|
||||
{
|
||||
headers: {
|
||||
accept: "application/dns-json",
|
||||
},
|
||||
},
|
||||
)
|
||||
).json()
|
||||
|
||||
const tenant = dns?.Answer?.[0]?.data.replace(/^"|"$/g, "")
|
||||
if (!tenant || tenant !== handle) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkDomain(domain: string, handle: string) {
|
||||
const check = await (
|
||||
await fetch(`/api/check-domain?domain=${domain}&handle=${handle}`)
|
||||
).json()
|
||||
|
||||
return check.data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
import { checkDomainServer } from "~/models/site.model"
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const { handle, domain } = req.query
|
||||
|
||||
if (!handle || !domain) {
|
||||
res.status(400).json({ error: "Missing characterId or domain" })
|
||||
return
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
data: await checkDomainServer(domain as string, handle as string),
|
||||
})
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
import { OUR_DOMAIN } from "~/lib/env"
|
||||
import { cacheGet } from "~/lib/redis.server"
|
||||
import { checkDomainServer } from "~/models/site.model"
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
|
|
@ -31,7 +32,10 @@ export default async function handler(
|
|||
char?.metadata?.content?.attributes?.find(
|
||||
(a: any) => a.trait_type === "xlog_custom_domain",
|
||||
)?.value || ""
|
||||
if (customDomain) {
|
||||
if (
|
||||
customDomain &&
|
||||
(await checkDomainServer(customDomain, subdomain))
|
||||
) {
|
||||
return {
|
||||
redirect: /^https?:\/\//.test(customDomain)
|
||||
? customDomain
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { getDefaultSlug } from "~/lib/helpers"
|
||||
import { checkDomainServer } from "~/models/site.model"
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const { characterId, noteId } = req.query
|
||||
|
||||
if (!characterId) {
|
||||
res.status(400).json({ error: "Missing characterId" })
|
||||
return
|
||||
}
|
||||
|
||||
const character = await (
|
||||
await fetch(`https://indexer.crossbell.io/v1/characters/${characterId}`)
|
||||
).json()
|
||||
|
||||
let domain = character?.metadata?.content?.attributes?.find(
|
||||
(a: any) => a.trait_type === "xlog_custom_domain",
|
||||
)?.value
|
||||
if (domain && !(await checkDomainServer(domain, character.handle))) {
|
||||
domain = undefined
|
||||
}
|
||||
let link = getSiteLink({
|
||||
domain,
|
||||
subdomain: character?.handle,
|
||||
})
|
||||
|
||||
if (noteId) {
|
||||
const note = await (
|
||||
await fetch(
|
||||
`https://indexer.crossbell.io/v1/characters/${characterId}/notes/${noteId}`,
|
||||
)
|
||||
).json()
|
||||
const slug =
|
||||
note?.metadata?.content?.attributes?.find(
|
||||
(a: any) => a.trait_type === "xlog_slug",
|
||||
)?.value ||
|
||||
getDefaultSlug(note?.metadata?.content?.title, `${characterId}-${noteId}`)
|
||||
|
||||
link += `/${slug}`
|
||||
}
|
||||
|
||||
res.redirect(link)
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useRouter } from "next/router"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import toast from "react-hot-toast"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
|
|
@ -17,6 +17,7 @@ import { useTranslation } from "next-i18next"
|
|||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { checkDomain } from "~/models/site.model"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
|
|
@ -63,7 +64,10 @@ export default function SettingsDomainsPage() {
|
|||
|
||||
const [customDomain, setCustomDomain] = useState("")
|
||||
form.register("custom_domain", {
|
||||
onChange: (e) => setCustomDomain(e.target.value),
|
||||
onChange: (e) => {
|
||||
setCustomDomain(e.target.value)
|
||||
toCheckDomain(e.target.value)
|
||||
},
|
||||
})
|
||||
|
||||
const customSubdomain = customDomain?.split(".").slice(0, -2).join(".") || ""
|
||||
|
|
@ -73,7 +77,9 @@ export default function SettingsDomainsPage() {
|
|||
if (updateSite.data?.code === 0) {
|
||||
toast.success("Saved!")
|
||||
router.replace(
|
||||
`/dashboard/${updateSite.variables?.subdomain}/settings/domains`,
|
||||
`/dashboard/${
|
||||
updateSite.variables?.subdomain || updateSite.variables?.site
|
||||
}/settings/domains`,
|
||||
)
|
||||
} else {
|
||||
toast.error("Failed to update site" + ": " + updateSite.data.message)
|
||||
|
|
@ -83,6 +89,30 @@ export default function SettingsDomainsPage() {
|
|||
}
|
||||
}, [updateSite.isSuccess])
|
||||
|
||||
const [domainCheckResult, setDomainCheckResult] = useState<{
|
||||
isLoading: boolean
|
||||
data?: boolean
|
||||
}>({
|
||||
isLoading: false,
|
||||
data: true,
|
||||
})
|
||||
const toCheckDomain = useCallback(
|
||||
(custom: string) => {
|
||||
if (custom) {
|
||||
setDomainCheckResult({
|
||||
isLoading: true,
|
||||
})
|
||||
checkDomain(custom, subdomain).then((r) =>
|
||||
setDomainCheckResult({
|
||||
isLoading: false,
|
||||
data: r,
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
[subdomain],
|
||||
)
|
||||
|
||||
const [hasSet, setHasSet] = useState(false)
|
||||
useEffect(() => {
|
||||
if (site.isSuccess && site.data && !hasSet) {
|
||||
|
|
@ -90,8 +120,9 @@ export default function SettingsDomainsPage() {
|
|||
form.setValue("subdomain", site.data.username || "")
|
||||
form.setValue("custom_domain", site.data.custom_domain || "")
|
||||
setCustomDomain(site.data.custom_domain || "")
|
||||
toCheckDomain(site.data.custom_domain || "")
|
||||
}
|
||||
}, [form, site.data, site.isSuccess, customDomain, hasSet])
|
||||
}, [form, site.data, site.isSuccess, customDomain, hasSet, toCheckDomain])
|
||||
|
||||
return (
|
||||
<SettingsLayout title={"Site Settings"} type="site">
|
||||
|
|
@ -143,14 +174,14 @@ export default function SettingsDomainsPage() {
|
|||
{...form.register("custom_domain")}
|
||||
/>
|
||||
{customDomain && (
|
||||
<div className="mt-2 text-xs">
|
||||
<p className="mb-2">
|
||||
<div className="mt-2 text-xs space-y-2">
|
||||
<p>
|
||||
{t(
|
||||
"Set the following record on your DNS provider to active your custom domain",
|
||||
)}
|
||||
:
|
||||
</p>
|
||||
<table className="">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr className="border-b">
|
||||
<th className="text-center p-3">Type</th>
|
||||
|
|
@ -173,11 +204,33 @@ export default function SettingsDomainsPage() {
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="text-sm">
|
||||
{domainCheckResult.isLoading ? (
|
||||
<span>Checking...</span>
|
||||
) : domainCheckResult.data ? (
|
||||
<span className="text-green-600">DNS check Passed.</span>
|
||||
) : (
|
||||
<span>
|
||||
<span className="text-red-600">DNS check failed.</span>
|
||||
<Button
|
||||
className="ml-4 font-media"
|
||||
variant="secondary"
|
||||
onClick={() => toCheckDomain(customDomain)}
|
||||
>
|
||||
Recheck
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Button type="submit" isLoading={updateSite.isLoading}>
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={updateSite.isLoading || domainCheckResult.isLoading}
|
||||
isDisabled={domainCheckResult?.data === false}
|
||||
>
|
||||
{t("Save")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue