feat: support sitemap (#559)

This commit is contained in:
Innei 2023-05-19 00:27:16 +08:00 committed by GitHub
parent 8535a5cf26
commit b420fc5882
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 10 deletions

View File

@ -0,0 +1,60 @@
import dayjs from "dayjs"
import { NextRequest } from "next/server"
import { QueryClient } from "@tanstack/react-query"
import { getSiteLink } from "~/lib/helpers"
import { NextServerResponse } from "~/lib/server-helper"
import { PageVisibilityEnum } from "~/lib/types"
import { fetchGetPagesBySite } from "~/queries/page.server"
import { fetchGetSite } from "~/queries/site.server"
export const GET = async (req: NextRequest) => {
const response = new NextServerResponse()
const queryClient = new QueryClient()
// http://localhost:2222/site/innei-4525/sitemap.xml
// http://innei-4525.localhost:2222/sitemap.xml
// find innei-5425 site handle by regexp
const domainOrSubdomain =
req.nextUrl.pathname.match(/\/site\/(.*)\/sitemap.xml/)?.[1] ||
req.headers.get("x-handle") ||
// http://innei-4525.localhost:2222/sitemap.xml
req.headers.get("host")?.split(".")[0] ||
""
if (!domainOrSubdomain) {
return response.status(400).json({
message: "domainOrSubdomain is not validate",
})
}
const site = await fetchGetSite(domainOrSubdomain, queryClient)
const pages = await fetchGetPagesBySite(
{
characterId: site?.characterId,
type: "post",
visibility: PageVisibilityEnum.Published,
},
queryClient,
)
const link = getSiteLink({
subdomain: site?.handle || "",
})
return response.headers({
"Content-Type": "text/xml",
"Access-Control-Allow-Methods": "GET",
"Access-Control-Allow-Origin": "*",
}).text(`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.list?.map((page) => {
return ` <url>
<loc>${link}/${page.metadata.content.slug}</loc>
<lastmod>${dayjs(page.updatedAt).format("YYYY-MM-DD")}</lastmod>
</url>`
}).join(`
`)}
</urlset>`)
}

View File

@ -15,6 +15,7 @@ export const getQuery = (req: Request) => {
export class NextServerResponse {
#status: number = 200
#headers = new Headers()
constructor() {}
status(status: number) {
@ -25,12 +26,9 @@ export class NextServerResponse {
json(data: any) {
const nextData = JSON.stringify(data)
return new Response(nextData, {
status: this.#status,
headers: {
"Content-Type": "application/json",
},
})
this.#headers.set("Content-Type", "application/json")
return new Response(nextData, this.makeResponseOptions())
}
send(data: any) {
@ -42,11 +40,23 @@ export class NextServerResponse {
return this.json(data)
}
return new Response(data, { status: this.#status })
return new Response(data, this.makeResponseOptions())
}
text(text: string) {
return new Response(text, this.makeResponseOptions())
}
end() {
return new Response("", { status: this.#status })
return new Response("", this.makeResponseOptions())
}
headers(headers: Record<string, string>) {
for (const [key, value] of Object.entries(headers)) {
this.#headers.set(key, value)
}
return this
}
rss(data: any, format = "json") {
@ -72,4 +82,11 @@ export class NextServerResponse {
})
}
}
private makeResponseOptions() {
return {
status: this.#status,
headers: this.#headers,
}
}
}

View File

@ -56,8 +56,7 @@ export default async function middleware(req: NextRequest) {
pathname.match(/^\/(workbox|worker|fallback)-\w+\.js(\.map)?$/) ||
pathname === "/sw.js" ||
pathname === "/sw.js.map" ||
pathname === "/robots.txt" ||
pathname === "/sitemap.xml"
pathname === "/robots.txt"
) {
return NextResponse.next()
}
@ -84,6 +83,7 @@ export default async function middleware(req: NextRequest) {
const requestHeaders = new Headers(req.headers)
requestHeaders.set("x-pathname", req.nextUrl.pathname)
requestHeaders.set("x-search", req.nextUrl.search)
requestHeaders.set("x-handle", tenant.subdomain || "")
if (tenant?.subdomain) {
const url = req.nextUrl.clone()