feat: add tackle pages to sitemap
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
import { api, type DictionaryItem } from "../lib/api";
|
import { api, type DictionaryItem, type PaginatedRigs, type PaginatedTackleItems } from "../lib/api";
|
||||||
|
|
||||||
const escapeXml = (value: string) => value.replace(/[<>&'"]/g, c => ({ "<": "<", ">": ">", "&": "&", "'": "'", '"': """ })[c] ?? c);
|
const escapeXml = (value: string) => value.replace(/[<>&'"]/g, c => ({ "<": "<", ">": ">", "&": "&", "'": "'", '"': """ })[c] ?? c);
|
||||||
let lastGood: { origin: string; xml: string; at: number } | undefined;
|
let lastGood: { origin: string; xml: string; at: number } | undefined;
|
||||||
@@ -9,7 +9,7 @@ export const GET: APIRoute = async ({ site }) => {
|
|||||||
const output = (xml: string) => new Response(xml, { headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, max-age=1800" } });
|
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);
|
if (lastGood?.origin === origin && Date.now() - lastGood.at < 1800000) return output(lastGood.xml);
|
||||||
try {
|
try {
|
||||||
const paths = new Set(["/", "/records", "/report", "/status", "/rules", "/privacy", "/fish", "/waterbodies", "/media"]);
|
const paths = new Set(["/", "/records", "/report", "/status", "/rules", "/privacy", "/fish", "/waterbodies", "/media", "/tackle"]);
|
||||||
for (const [endpoint, prefix] of [["fishes", "fish"], ["waterbodies", "waterbodies"]]) {
|
for (const [endpoint, prefix] of [["fishes", "fish"], ["waterbodies", "waterbodies"]]) {
|
||||||
for (let offset = 0; ; offset += 500) {
|
for (let offset = 0; ; offset += 500) {
|
||||||
const rows = await api<DictionaryItem[]>(`/api/v1/${endpoint}?limit=500&offset=${offset}`);
|
const rows = await api<DictionaryItem[]>(`/api/v1/${endpoint}?limit=500&offset=${offset}`);
|
||||||
@@ -18,6 +18,18 @@ export const GET: APIRoute = async ({ site }) => {
|
|||||||
if (rows.length < 500) break;
|
if (rows.length < 500) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (let offset = 0; ; offset += 100) {
|
||||||
|
const page = await api<PaginatedTackleItems>(`/api/v1/tackle/items?limit=100&offset=${offset}`);
|
||||||
|
page.items.forEach(item => paths.add(`/tackle/items/${item.id}`));
|
||||||
|
if (paths.size > 49000) throw new Error("Sitemap index required");
|
||||||
|
if (page.items.length < page.limit) break;
|
||||||
|
}
|
||||||
|
for (let offset = 0; ; offset += 100) {
|
||||||
|
const page = await api<PaginatedRigs>(`/api/v1/tackle/rigs?limit=100&offset=${offset}`);
|
||||||
|
page.items.forEach(rig => paths.add(`/tackle/rigs/${rig.id}`));
|
||||||
|
if (paths.size > 49000) throw new Error("Sitemap index required");
|
||||||
|
if (page.items.length < page.limit) break;
|
||||||
|
}
|
||||||
for (let offset = 0; ; offset += 500) {
|
for (let offset = 0; ; offset += 500) {
|
||||||
const rows = await api<string[]>(`/api/v1/public-spot-pages?limit=500&offset=${offset}`);
|
const rows = await api<string[]>(`/api/v1/public-spot-pages?limit=500&offset=${offset}`);
|
||||||
rows.forEach(path => paths.add(path));
|
rows.forEach(path => paths.add(path));
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ R-пункты уточняют критерии существующих B/G/U/
|
|||||||
- [x] **R14 · P2 · Подтверждённый каталог и demo seed.** `SEED_DEMO_DATA=false` больше не создаёт редакционные описания тестовых точек; demo-уловы имеют отдельный `seed:` namespace и catalog audit считает их отдельно от подтверждённых reports. Loaded canonical counts и external staging остаются раздельными, media count не используется как catalog count.
|
- [x] **R14 · P2 · Подтверждённый каталог и demo seed.** `SEED_DEMO_DATA=false` больше не создаёт редакционные описания тестовых точек; demo-уловы имеют отдельный `seed:` namespace и catalog audit считает их отдельно от подтверждённых reports. Loaded canonical counts и external staging остаются раздельными, media count не используется как catalog count.
|
||||||
- [x] **R15 · P2 · Восстановление пользовательских действий.** Report UI теперь различает idempotency-конфликт и обычную ошибку, восстанавливает draft и предлагает повтор с новым ключом; plan получил восьмисекундный undo после удаления с обработкой storage errors. Draft сохраняется при 422/429/timeout, повтор не требует заново заполнять весь улов.
|
- [x] **R15 · P2 · Восстановление пользовательских действий.** Report UI теперь различает idempotency-конфликт и обычную ошибку, восстанавливает draft и предлагает повтор с новым ключом; plan получил восьмисекундный undo после удаления с обработкой storage errors. Draft сохраняется при 422/429/timeout, повтор не требует заново заполнять весь улов.
|
||||||
- [ ] **R16 · P2 · Производительность реальных endpoints.** Измерить ORM/RSS/SQL и чтение manifest, затем bounded aggregation и digest/mtime cache по результатам. Критерий: воспроизводимый endpoint budget на крупных fixtures; Redis заранее не добавлять.
|
- [ ] **R16 · P2 · Производительность реальных endpoints.** Измерить ORM/RSS/SQL и чтение manifest, затем bounded aggregation и digest/mtime cache по результатам. Критерий: воспроизводимый endpoint budget на крупных fixtures; Redis заранее не добавлять.
|
||||||
- [ ] **R17 · P2 · Навигация и SEO снастей.** Обнаруживаемый каталог снастей и sitemap подтверждённых карточек; политика индексации фильтров/пустых страниц. Критерий: канонические карточки достижимы из UI и sitemap без дубликатов.
|
- [x] **R17 · P2 · Навигация и SEO снастей.** Sitemap теперь включает `/tackle`, все пагинированные canonical item/rig detail URL и не добавляет фильтры/analytics; detail layout оставляет missing/unavailable/404 страницы noindex, а каталог ведёт в canonical карточки. При недоступном API сохраняется последний корректный sitemap.
|
||||||
- [ ] **R12 · P2 · Визуальная приёмка наполненных состояний (D07/A06/U06/G09).** Изолированные данные: длинные карточки, компоненты, конфликты, ошибки, authenticated admin; light/dark mobile/desktop, keyboard/contrast и screenshots с revision. Критерий: подтверждены действия и читаемость, а не только DOM/overflow.
|
- [ ] **R12 · P2 · Визуальная приёмка наполненных состояний (D07/A06/U06/G09).** Изолированные данные: длинные карточки, компоненты, конфликты, ошибки, authenticated admin; light/dark mobile/desktop, keyboard/contrast и screenshots с revision. Критерий: подтверждены действия и читаемость, а не только DOM/overflow.
|
||||||
- [ ] **R13 · P2 · Зелёные проверки и CI artifacts.** Обновить два устаревших контракта, адресные regressions исправленных дефектов, успешные visual artifacts, Node/Python baseline и отдельный dependency audit. Критерий: suites зелёные, skip объяснён, отчёт связан с commit. Известные падения учитывать при каждом feature-коммите.
|
- [ ] **R13 · P2 · Зелёные проверки и CI artifacts.** Обновить два устаревших контракта, адресные regressions исправленных дефектов, успешные visual artifacts, Node/Python baseline и отдельный dependency audit. Критерий: suites зелёные, skip объяснён, отчёт связан с commit. Известные падения учитывать при каждом feature-коммите.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user