Seo - generowanie schema, api lista miejscowości , sekcja z listą miejscowości
This commit is contained in:
98
src/components/sections/SectionAreaServed.astro
Normal file
98
src/components/sections/SectionAreaServed.astro
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
---
|
||||||
|
import { Image } from "astro:assets";
|
||||||
|
import type { ImageMetadata } from "astro";
|
||||||
|
import Markdown from "../../islands/Markdown.jsx";
|
||||||
|
import { findSectionImage, loadGlobalSeo } from "../../lib/astro-helpers";
|
||||||
|
|
||||||
|
const { section, index } = Astro.props;
|
||||||
|
|
||||||
|
// ✅ Załaduj global-seo.yaml
|
||||||
|
const globalSeo = loadGlobalSeo();
|
||||||
|
const cities = globalSeo?.schema?.areaServed || [];
|
||||||
|
|
||||||
|
const hasImage = !!section.image;
|
||||||
|
const reverse = index % 2 === 1;
|
||||||
|
|
||||||
|
const sectionImages = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
|
"/src/assets/sections/**/*.{png,jpg,jpeg,webp,avif}",
|
||||||
|
{ eager: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const sectionImage = section.image ? findSectionImage(sectionImages, section.image) : null;
|
||||||
|
const isAboveFold = index === 0;
|
||||||
|
|
||||||
|
// ✅ Konfiguracja wyświetlania
|
||||||
|
const showTitle = section.showTitle !== false; // domyślnie true
|
||||||
|
|
||||||
|
// ✅ Formatowanie listy miejscowości jako string
|
||||||
|
const citiesText = cities.join(", ");
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="f-section">
|
||||||
|
<div
|
||||||
|
class={`f-section-grid ${hasImage ? "md:grid-cols-2" : "md:grid-cols-1"}`}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
sectionImage && (
|
||||||
|
<Image
|
||||||
|
src={sectionImage}
|
||||||
|
alt={section.title || "Obszar działania"}
|
||||||
|
class={`f-section-image ${reverse ? "md:order-1" : "md:order-2"} ${section.dimmed ? "f-image-dimmed" : ""}`}
|
||||||
|
loading={isAboveFold ? "eager" : "lazy"}
|
||||||
|
fetchpriority={isAboveFold ? "high" : "auto"}
|
||||||
|
decoding="async"
|
||||||
|
format="webp"
|
||||||
|
widths={[480, 768, 1024, 1440]}
|
||||||
|
sizes="(min-width: 768px) 50vw, 100vw"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class={`${hasImage ? (reverse ? "md:order-2" : "md:order-1") : ""}`}>
|
||||||
|
{showTitle && section.title && (
|
||||||
|
<h2 class="f-section-title">{section.title}</h2>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{section.content && <Markdown text={section.content} />}
|
||||||
|
|
||||||
|
{/* ✅ Lista miejscowości jako paragraf */}
|
||||||
|
{cities.length > 0 && (
|
||||||
|
<p class="f-cities-paragraph">
|
||||||
|
{citiesText}. <a href={section.button.url}
|
||||||
|
class=""
|
||||||
|
title={section.button.title}
|
||||||
|
>
|
||||||
|
{section.button.text}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<!-- {
|
||||||
|
section.button && (
|
||||||
|
<div class="f-section-nav mt-6">
|
||||||
|
|
||||||
|
<a href={section.button.url}
|
||||||
|
class="btn btn-primary"
|
||||||
|
title={section.button.title}
|
||||||
|
>
|
||||||
|
{section.button.text}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
.f-cities-paragraph {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--f-text, #212529);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .f-cities-paragraph {
|
||||||
|
color: var(--f-text-dark, #f7fafc);
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
@@ -1,13 +1,28 @@
|
|||||||
---
|
---
|
||||||
import { loadYaml, processMarkdownSections } from "../../lib/astro-helpers";
|
import yaml from "js-yaml";
|
||||||
|
import fs from "fs";
|
||||||
|
import { marked } from "marked";
|
||||||
|
|
||||||
import SectionDefault from "./SectionDefault.astro";
|
import SectionDefault from "./SectionDefault.astro";
|
||||||
|
import SectionAreaServed from "./SectionAreaServed.astro"; // ✅ Import nowego komponentu
|
||||||
|
|
||||||
const { src } = Astro.props;
|
const { src } = Astro.props;
|
||||||
|
|
||||||
const data = loadYaml(src) ?? { sections: [] };
|
const data = yaml.load(fs.readFileSync(src, "utf8")) ?? { sections: [] };
|
||||||
const sections = processMarkdownSections(data.sections as any[]);
|
|
||||||
|
const sections = (data.sections as any[]).map((s: any) => ({
|
||||||
|
...s,
|
||||||
|
html: marked(s.content || "")
|
||||||
|
}));
|
||||||
---
|
---
|
||||||
|
|
||||||
{sections.map((section: any, index: number) => {
|
{sections.map((section: any, index: number) => {
|
||||||
|
const type = section.type || "default";
|
||||||
|
|
||||||
|
// ✅ Routing do właściwego komponentu
|
||||||
|
if (type === "area-served") {
|
||||||
|
return <SectionAreaServed section={section} index={index} />;
|
||||||
|
}
|
||||||
|
|
||||||
return <SectionDefault section={section} index={index} />;
|
return <SectionDefault section={section} index={index} />;
|
||||||
})}
|
})}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
page:
|
page:
|
||||||
title: "Internet Światłowodowy Wyszków - Szybkie Łącze bez Limitów | FUZ"
|
title: "Internet Światłowodowy Wyszków - Szybkie Łącze | FUZ"
|
||||||
description: "Internet światłowodowy do 1 Gb/s w Wyszkowie. Bez limitów danych, stabilne połączenie, montaż w 48h. Lokalny operator z profesjonalnym serwisem. Sprawdź ceny!"
|
description: "Internet światłowodowy w Wyszkowie. Bez limitów danych, stabilne połączenie, szybki montaż. Lokalny operator z profesjonalnym serwisem. Sprawdź ceny!"
|
||||||
image: "/og/internet-og.png"
|
image: "/og/internet-og.png"
|
||||||
url: "/internet-swiatlowodowy"
|
url: "/internet-swiatlowodowy"
|
||||||
keywords:
|
keywords:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
page:
|
page:
|
||||||
title: "Internet i Telewizja Wyszków - Pakiety Światłowodowe 2w1 | FUZ"
|
title: "Internet i Telewizja Wyszków - Pakiety Telewizyjne | FUZ"
|
||||||
description: "Pakiety Internet + TV w Wyszkowie. Światłowód do 1 Gb/s + ponad 200 kanałów. Jeden rachunek, niższa cena, lokalny serwis. Sprawdź ofertę pakietów 2w1!"
|
description: "Internet i telewizja w Wyszkowie. Jeden rachunek, niższa cena, lokalny serwis. Sprawdź ofertę pakietów internetu i telewizji!"
|
||||||
image: "/og/telewizja-og.png"
|
image: "/og/telewizja-og.png"
|
||||||
url: "/internet-telewizja"
|
url: "/internet-telewizja"
|
||||||
keywords:
|
keywords:
|
||||||
|
|||||||
12
src/content/site/area-section.yaml
Normal file
12
src/content/site/area-section.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
sections:
|
||||||
|
- type: area-served
|
||||||
|
title:
|
||||||
|
content: |
|
||||||
|
Świadczymy usługi internetu światłowodowego oraz telewizji w następujących miejscowościach
|
||||||
|
image:
|
||||||
|
showTitle: true
|
||||||
|
columns: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"
|
||||||
|
button:
|
||||||
|
text: "Sprawdź dostępność usługi pod Twoim adresem"
|
||||||
|
url: "/mapa-zasiegu"
|
||||||
|
title: "Sprawdź czy jesteś w zasięgu"
|
||||||
@@ -1,25 +1,63 @@
|
|||||||
site:
|
site:
|
||||||
name: "FUZ Adam Rojek"
|
name: FUZ Adam Rojek
|
||||||
description: "Lokalny operator internetu światłowodowego w Wyszkowie i okolicach. Stabilne łącze, szybki serwis, konkurencyjne ceny."
|
description: Lokalny operator internetu światłowodowego w Wyszkowie i okolicach. Stabilne łącze, szybki serwis, konkurencyjne ceny.
|
||||||
url: "https://www.fuz.pl"
|
url: https://www.fuz.pl
|
||||||
lang: "pl"
|
lang: pl
|
||||||
|
|
||||||
company:
|
company:
|
||||||
name: "FUZ Adam Rojek"
|
name: FUZ Adam Rojek
|
||||||
phone: "+48 29 643 80 55"
|
phone: +48 29 643 80 55
|
||||||
email: "biuro@fuz.pl"
|
email: biuro@fuz.pl
|
||||||
street: "ul. Świętojańska 46"
|
street: ul. Świętojańska 46
|
||||||
city: "Wyszków"
|
city: Wyszków
|
||||||
postal: "07-200"
|
postal: 07-200
|
||||||
country: "PL"
|
country: PL
|
||||||
lat: 52.597385
|
lat: 52.597385
|
||||||
lon: 21.456797
|
lon: 21.456797
|
||||||
logo: "/logo.webp"
|
logo: /logo.webp
|
||||||
|
|
||||||
schema:
|
schema:
|
||||||
openingHours: "Mo-Fr 09:00-17:00"
|
openingHours: Mo-Fr 09:00-17:00
|
||||||
priceRange: "$"
|
priceRange: $
|
||||||
areaServed:
|
areaServed:
|
||||||
- Wyszków
|
- Dąbrowa
|
||||||
|
- Gulczewo
|
||||||
|
- Kamieńczyk
|
||||||
|
- Komorowo
|
||||||
|
- Kręgi
|
||||||
|
- Kręgi Nowe
|
||||||
|
- Leszczydół Stary
|
||||||
|
- Leszczydół-działki
|
||||||
|
- Leszczydół-nowiny
|
||||||
|
- Leszczydół-podwielątki
|
||||||
|
- Leszczydół-pustki
|
||||||
|
- Lucynów
|
||||||
|
- Lucynów Duży
|
||||||
|
- Łosinno
|
||||||
|
- Natalin
|
||||||
|
- Nowe Kozłowo
|
||||||
|
- Nowe Wielątki
|
||||||
|
- Nowe Wypychy
|
||||||
|
- Ochudno
|
||||||
|
- Olszanka
|
||||||
|
- Ostrowy
|
||||||
|
- Porządzie
|
||||||
|
- Pułtusk
|
||||||
|
- Puste Łąki
|
||||||
|
- Rybienko Nowe
|
||||||
|
- Rybienko Stare
|
||||||
|
- Rybno
|
||||||
- Rząśnik
|
- Rząśnik
|
||||||
- Pułtusk
|
- Sitno
|
||||||
|
- Skuszew
|
||||||
|
- Stare Kozłowo
|
||||||
|
- Stary Mystkówiec
|
||||||
|
- Strachów
|
||||||
|
- Świniotop
|
||||||
|
- Tulewo
|
||||||
|
- Tulewo Górne
|
||||||
|
- Tumanek
|
||||||
|
- Wielątki
|
||||||
|
- Wielątki Rosochate
|
||||||
|
- Wielątki-folwark
|
||||||
|
- Wólka-folwark
|
||||||
|
- Wólka-przekory
|
||||||
|
- Wyszków
|
||||||
|
|||||||
@@ -111,6 +111,12 @@ export type GlobalSeo = {
|
|||||||
lat: number;
|
lat: number;
|
||||||
lon: number;
|
lon: number;
|
||||||
};
|
};
|
||||||
|
schema?: {
|
||||||
|
openingHours?: string;
|
||||||
|
priceRange?: string;
|
||||||
|
areaServed?: string[];
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ DODANY BRAKUJĄCY TYP
|
// ✅ DODANY BRAKUJĄCY TYP
|
||||||
@@ -158,6 +164,11 @@ export function buildSeoMeta(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mergedSchema = {
|
||||||
|
...globalSeo.schema, // ← Globalna schema (openingHours, priceRange, areaServed)
|
||||||
|
...page.schema, // ← Page-specific schema (override jeśli istnieje)
|
||||||
|
};
|
||||||
|
|
||||||
const schemaLocalBusiness = {
|
const schemaLocalBusiness = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "LocalBusiness",
|
"@type": "LocalBusiness",
|
||||||
@@ -178,6 +189,14 @@ export function buildSeoMeta(
|
|||||||
longitude: company.lon,
|
longitude: company.lon,
|
||||||
},
|
},
|
||||||
url: baseUrl,
|
url: baseUrl,
|
||||||
|
...(mergedSchema.openingHours && { openingHours: mergedSchema.openingHours }),
|
||||||
|
...(mergedSchema.priceRange && { priceRange: mergedSchema.priceRange }),
|
||||||
|
...(mergedSchema.areaServed && { areaServed: mergedSchema.areaServed }),
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(mergedSchema).filter(
|
||||||
|
([key]) => !['openingHours', 'priceRange', 'areaServed'].includes(key)
|
||||||
|
)
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -294,4 +313,86 @@ export const DEFAULT_FAVICON_CONFIG: FaviconConfig = {
|
|||||||
|
|
||||||
export function mergeFaviconConfig(custom?: Partial<FaviconConfig>): FaviconConfig {
|
export function mergeFaviconConfig(custom?: Partial<FaviconConfig>): FaviconConfig {
|
||||||
return { ...DEFAULT_FAVICON_CONFIG, ...custom };
|
return { ...DEFAULT_FAVICON_CONFIG, ...custom };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== LOCALE HELPERS ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title Case dla tekstu (każde słowo z wielkiej)
|
||||||
|
* UWAGA: Może mieć problemy z polskimi znakami z SQLite
|
||||||
|
*/
|
||||||
|
export function titleCaseWords(input: string): string {
|
||||||
|
return String(input ?? "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.replace(/\b\p{L}/gu, (c) => c.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizacja nazw miast dla polskiego locale
|
||||||
|
* Bezpieczne dla danych z SQLite
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* normalizeCityName("dąbrowa") => "Dąbrowa"
|
||||||
|
* normalizeCityName("DĄBROWA") => "Dąbrowa"
|
||||||
|
* normalizeCityName("DĄBrowa") => "Dąbrowa"
|
||||||
|
*/
|
||||||
|
export function normalizeCityName(input: string): string {
|
||||||
|
const text = String(input ?? "").trim();
|
||||||
|
|
||||||
|
if (!text) return "";
|
||||||
|
|
||||||
|
// 1. Normalizuj do NFD (dekompozycja znaków diakrytycznych)
|
||||||
|
const normalized = text.normalize("NFD");
|
||||||
|
|
||||||
|
// 2. Lowercase całość
|
||||||
|
const lower = normalized.toLowerCase();
|
||||||
|
|
||||||
|
// 3. Title Case słowo po słowie
|
||||||
|
const words = lower.split(/\s+/);
|
||||||
|
|
||||||
|
const titleCased = words.map(word => {
|
||||||
|
if (!word) return "";
|
||||||
|
|
||||||
|
// Pierwsza litera wielka, reszta mała
|
||||||
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||||
|
}).join(" ");
|
||||||
|
|
||||||
|
// 4. Normalizuj z powrotem do NFC (kompozycja)
|
||||||
|
return titleCased.normalize("NFC");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== ENV HELPERS ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobierz zmienną środowiskową (działa zarówno w Astro jak i Node)
|
||||||
|
* @param key - Nazwa zmiennej
|
||||||
|
* @param fallback - Wartość domyślna jeśli brak
|
||||||
|
*/
|
||||||
|
export function getEnv(key: string, fallback?: string): string | undefined {
|
||||||
|
// Próbuj import.meta.env (Astro)
|
||||||
|
if (typeof import.meta !== 'undefined' && import.meta.env?.[key]) {
|
||||||
|
return import.meta.env[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Próbuj process.env (Node.js)
|
||||||
|
if (typeof process !== 'undefined' && process.env?.[key]) {
|
||||||
|
return process.env[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobierz wymaganą zmienną środowiskową (rzuca błąd jeśli brak)
|
||||||
|
*/
|
||||||
|
export function getRequiredEnv(key: string): string {
|
||||||
|
const value = getEnv(key);
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
throw new Error(`Brak wymaganej zmiennej środowiskowej: ${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
188
src/pages/api/update-area-served.ts
Normal file
188
src/pages/api/update-area-served.ts
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import yaml from "js-yaml";
|
||||||
|
|
||||||
|
import { getDb } from "./db.js";
|
||||||
|
import { loadYaml, sortByLocale, normalizeCityName, getEnv } from "../../lib/astro-helpers";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API do automatycznej aktualizacji schema.areaServed w global-seo.yaml
|
||||||
|
*
|
||||||
|
* Wymaga: .env z AREA_ADMIN_TOKEN
|
||||||
|
* Opcjonalnie: SEO_YAML_PATH
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ==================== AUTHORIZATION ====================
|
||||||
|
|
||||||
|
function isAuthorized(request: Request): boolean {
|
||||||
|
const expected = getEnv('JAMBOX_ADMIN_TOKEN'); // ✅ Zamiast import.meta.env
|
||||||
|
|
||||||
|
if (!expected) {
|
||||||
|
console.warn("⚠️ AREA_ADMIN_TOKEN nie jest ustawiony w .env");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = request.headers.get("x-admin-token");
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
console.warn("⚠️ Brak nagłówka x-admin-token w request");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = token === expected;
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
console.warn("⚠️ Nieprawidłowy token:", {
|
||||||
|
received: token.substring(0, 10) + '...',
|
||||||
|
expected: expected.substring(0, 10) + '...'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== YAML PATH ====================
|
||||||
|
|
||||||
|
function getYamlPath(): string {
|
||||||
|
return (
|
||||||
|
getEnv('SEO_YAML_PATH') || // ✅ Zamiast import.meta.env
|
||||||
|
path.join(process.cwd(), "src", "content", "site", "global-seo.yaml")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== DATABASE QUERIES ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobierz unikalne miasta z bazy danych
|
||||||
|
* @returns Array<string> - Posortowane alfabetycznie (locale: pl)
|
||||||
|
*/
|
||||||
|
function getCitiesFromDatabase(): string[] {
|
||||||
|
const db = getDb();
|
||||||
|
|
||||||
|
const rows = db
|
||||||
|
.prepare("SELECT DISTINCT city FROM ranges WHERE city IS NOT NULL ORDER BY city")
|
||||||
|
.all() as Array<{ city: string }>;
|
||||||
|
|
||||||
|
// ✅ Używamy normalizeCityName zamiast titleCaseWords
|
||||||
|
const cities = rows
|
||||||
|
.map((r) => normalizeCityName(r.city))
|
||||||
|
.filter((s) => s.length > 0);
|
||||||
|
|
||||||
|
const uniqueCities = [...new Set(cities)];
|
||||||
|
|
||||||
|
return sortByLocale(uniqueCities, "pl");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== YAML OPERATIONS ====================
|
||||||
|
|
||||||
|
async function updateAreaServedInYaml(
|
||||||
|
yamlPath: string,
|
||||||
|
cities: string[],
|
||||||
|
dryRun: boolean
|
||||||
|
): Promise<{ changed: boolean; before: string[]; after: string[] }> {
|
||||||
|
const doc = loadYaml<any>(yamlPath) || {};
|
||||||
|
|
||||||
|
if (!doc.schema) {
|
||||||
|
doc.schema = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const before = Array.isArray(doc.schema.areaServed) ? doc.schema.areaServed : [];
|
||||||
|
const after = cities;
|
||||||
|
|
||||||
|
const changed = JSON.stringify(before) !== JSON.stringify(after);
|
||||||
|
|
||||||
|
if (!dryRun && changed) {
|
||||||
|
doc.schema.areaServed = after;
|
||||||
|
|
||||||
|
const newYaml = yaml.dump(doc, {
|
||||||
|
lineWidth: -1,
|
||||||
|
noRefs: true,
|
||||||
|
sortKeys: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.writeFile(yamlPath, newYaml, "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { changed, before, after };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== API HANDLER ====================
|
||||||
|
|
||||||
|
export async function POST({ request }: { request: Request }) {
|
||||||
|
try {
|
||||||
|
// Debug logging
|
||||||
|
console.log("📥 Request received at /api/area/update-area-served");
|
||||||
|
console.log(" Headers:", {
|
||||||
|
'content-type': request.headers.get('content-type'),
|
||||||
|
'x-admin-token': request.headers.get('x-admin-token') ? '***' : 'missing'
|
||||||
|
});
|
||||||
|
console.log(" ENV AREA_ADMIN_TOKEN:", getEnv('AREA_ADMIN_TOKEN') ? '***' : 'MISSING!');
|
||||||
|
|
||||||
|
// Authorization
|
||||||
|
if (!isAuthorized(request)) {
|
||||||
|
console.error("❌ Unauthorized request");
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ ok: false, error: "Unauthorized" }),
|
||||||
|
{
|
||||||
|
status: 401,
|
||||||
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ Authorization successful");
|
||||||
|
|
||||||
|
// Parse body
|
||||||
|
const body = await request.json().catch(() => ({}));
|
||||||
|
const dryRun = body?.dryRun === true;
|
||||||
|
|
||||||
|
console.log(" DryRun:", dryRun);
|
||||||
|
|
||||||
|
// Get cities from DB
|
||||||
|
const cities = getCitiesFromDatabase();
|
||||||
|
console.log(" Cities from DB:", cities.length);
|
||||||
|
|
||||||
|
// Update YAML
|
||||||
|
const yamlPath = getYamlPath();
|
||||||
|
console.log(" YAML path:", yamlPath);
|
||||||
|
|
||||||
|
const { changed, before, after } = await updateAreaServedInYaml(
|
||||||
|
yamlPath,
|
||||||
|
cities,
|
||||||
|
dryRun
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(" Changed:", changed);
|
||||||
|
console.log("✅ Success");
|
||||||
|
|
||||||
|
// Response
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
dryRun,
|
||||||
|
yamlPath,
|
||||||
|
changed,
|
||||||
|
count: after.length,
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ update-area-served error:", err);
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
ok: false,
|
||||||
|
error: String((err as Error)?.message ?? err),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,4 +13,5 @@ const hero = yaml.load(fs.readFileSync("./src/content/home/hero.yaml", "utf8"));
|
|||||||
<DefaultLayout seo={seo}>
|
<DefaultLayout seo={seo}>
|
||||||
<Hero {...hero} />
|
<Hero {...hero} />
|
||||||
<SectionRenderer src="./src/content/site/site.section.yaml" />
|
<SectionRenderer src="./src/content/site/site.section.yaml" />
|
||||||
|
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
|
|||||||
@@ -110,4 +110,5 @@ const addonsCenaOpis = addonsData?.cena_opis ?? cenaOpis;
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<SectionRenderer src="./src/content/internet-swiatlowodowy/section.yaml" />
|
<SectionRenderer src="./src/content/internet-swiatlowodowy/section.yaml" />
|
||||||
|
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
|
|||||||
@@ -129,4 +129,5 @@ const addonsCenaOpis = addonsYaml?.cena_opis ?? cenaOpis;
|
|||||||
</section>
|
</section>
|
||||||
<SectionChannelsSearch />
|
<SectionChannelsSearch />
|
||||||
<SectionRenderer src="./src/content/internet-telewizja/section.yaml" />
|
<SectionRenderer src="./src/content/internet-telewizja/section.yaml" />
|
||||||
|
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||||||
import MapGoogle from "../../components/maps/MapGoogle.astro";
|
import MapGoogle from "../../components/maps/MapGoogle.astro";
|
||||||
|
import SectionRenderer from "../../components/sections/SectionRenderer.astro";
|
||||||
import RangeForm from "../../islands/RangeForm.jsx";
|
import RangeForm from "../../islands/RangeForm.jsx";
|
||||||
import { loadYaml, safeArray } from "../../lib/astro-helpers";
|
import { loadYaml, safeArray } from "../../lib/astro-helpers";
|
||||||
import "../../styles/map-google.css";
|
import "../../styles/map-google.css";
|
||||||
@@ -48,7 +49,7 @@ const seo = loadYaml("./src/content/mapa-zasiegu/seo.yaml");
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<SectionRenderer src="./src/content/site/area-section.yaml" />
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
let fiberLayer = null;
|
let fiberLayer = null;
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@ const seo = loadYaml("./src/content/mapa-zasiegu/seo.yaml");
|
|||||||
|
|
||||||
map.getDiv().appendChild(overlay);
|
map.getDiv().appendChild(overlay);
|
||||||
map.panTo(pos);
|
map.panTo(pos);
|
||||||
let targetZoom = 16;
|
let targetZoom = 16;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
map.setZoom(targetZoom);
|
map.setZoom(targetZoom);
|
||||||
@@ -163,4 +164,4 @@ const seo = loadYaml("./src/content/mapa-zasiegu/seo.yaml");
|
|||||||
info.open({ map, anchor: marker });
|
info.open({ map, anchor: marker });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
|
|||||||
@@ -368,7 +368,7 @@
|
|||||||
|
|
||||||
|
|
||||||
.f-note-acc {
|
.f-note-acc {
|
||||||
@apply mx-20
|
@apply md:mx-20
|
||||||
}
|
}
|
||||||
|
|
||||||
.f-note-acc-summary {
|
.f-note-acc-summary {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
@apply items-start;
|
@apply items-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.f-contact-item {
|
.f-contact-item {
|
||||||
h3,
|
h3,
|
||||||
h4 {
|
h4 {
|
||||||
@@ -32,12 +31,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.f-contact-map {
|
.f-contact-map {
|
||||||
@apply mx-auto mt-6 w-full max-w-7xl;
|
@apply mx-auto mt-6 w-full max-w-7xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.f-toast {
|
.f-toast {
|
||||||
@apply fixed left-1/2 z-[999999] pointer-events-none;
|
@apply fixed left-1/2 z-[999999] pointer-events-none;
|
||||||
top: calc(var(--nav-height, 80px) + 20px);
|
top: calc(var(--nav-height, 80px) + 20px);
|
||||||
|
|||||||
Reference in New Issue
Block a user