sync
This commit is contained in:
@@ -1,23 +1,34 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { api, spotPath, type Activity, type DictionaryItem } from "../lib/api";
|
||||
import { api, type DictionaryItem } from "../lib/api";
|
||||
|
||||
const escapeXml = (value: string) => value.replace(/[<>&'\"]/g, character => ({ "<": "<", ">": ">", "&": "&", "'": "'", '"': """ })[character] ?? character);
|
||||
const escapeXml = (value: string) => value.replace(/[<>&'"]/g, c => ({ "<": "<", ">": ">", "&": "&", "'": "'", '"': """ })[c] ?? c);
|
||||
let lastGood: { origin: string; xml: string; at: number } | undefined;
|
||||
|
||||
export const GET: APIRoute = async ({ site }) => {
|
||||
const origin = site?.origin ?? import.meta.env.PUBLIC_SITE_URL ?? "https://rf4spotter.ru";
|
||||
const paths = new Set(["/", "/records", "/report", "/status", "/rules", "/privacy"]);
|
||||
const origin = site?.origin ?? "https://rf4spotter.ru";
|
||||
const output = (xml: string) => new Response(xml, { headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, max-age=1800" } });
|
||||
if (lastGood?.origin === origin && Date.now() - lastGood.at < 1800000) return output(lastGood.xml);
|
||||
try {
|
||||
const [activity, fishes, waters] = await Promise.all([api<Activity[]>("/api/v1/activity?hours=72&limit=100"), api<DictionaryItem[]>("/api/v1/fishes?limit=500"), api<DictionaryItem[]>("/api/v1/waterbodies?limit=500")]);
|
||||
paths.add("/fish"); paths.add("/waterbodies");
|
||||
fishes.forEach(item => paths.add(`/fish/${item.slug}`));
|
||||
waters.forEach(item => paths.add(`/waterbodies/${item.slug}`));
|
||||
activity.forEach(item => paths.add(spotPath(item)));
|
||||
activity.forEach(item => paths.add(`/waterbodies/${item.waterbody_slug}/${item.fish_slug}`));
|
||||
const paths = new Set(["/", "/records", "/report", "/status", "/rules", "/privacy", "/fish", "/waterbodies"]);
|
||||
for (const [endpoint, prefix] of [["fishes", "fish"], ["waterbodies", "waterbodies"]]) {
|
||||
for (let offset = 0; ; offset += 500) {
|
||||
const rows = await api<DictionaryItem[]>(`/api/v1/${endpoint}?limit=500&offset=${offset}`);
|
||||
rows.forEach(row => paths.add(`/${prefix}/${row.slug}`));
|
||||
if (paths.size > 49000) throw new Error("Sitemap index required");
|
||||
if (rows.length < 500) break;
|
||||
}
|
||||
}
|
||||
for (let offset = 0; ; offset += 500) {
|
||||
const rows = await api<string[]>(`/api/v1/public-spot-pages?limit=500&offset=${offset}`);
|
||||
rows.forEach(path => paths.add(path));
|
||||
if (paths.size > 49000) throw new Error("Sitemap index required");
|
||||
if (rows.length < 1000) break;
|
||||
}
|
||||
const xml = `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${[...paths].map(path => `<url><loc>${escapeXml(new URL(path, origin).toString())}</loc></url>`).join("")}</urlset>`;
|
||||
lastGood = { origin, xml, at: Date.now() };
|
||||
return output(xml);
|
||||
} catch {
|
||||
// A temporary API outage must not make the static part of the sitemap unavailable.
|
||||
if (lastGood?.origin === origin) return output(lastGood.xml);
|
||||
return new Response("Sitemap temporarily unavailable", { status: 503, headers: { "Retry-After": "60", "Cache-Control": "no-store" } });
|
||||
}
|
||||
const urls = [...paths].map(path => `<url><loc>${escapeXml(new URL(path, origin).toString())}</loc></url>`).join("");
|
||||
return new Response(`<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}</urlset>`, {
|
||||
headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, max-age=1800" },
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user