diff --git a/caddy/sites/apps.caddy b/caddy/sites/apps.caddy index b8110feb..b01dbdea 100644 --- a/caddy/sites/apps.caddy +++ b/caddy/sites/apps.caddy @@ -598,6 +598,36 @@ meraocenka.ru { } } + # og-mera.png — картинка превью ссылок (og:image). Её просят НЕ браузеры + # посетителей, а обходчики мессенджеров и соцсетей (Telegram, VK, + # WhatsApp), причём анонимно и без Referer: если путь не открыт, ссылка на + # сайт разворачивается без картинки, и понять это по логам приложения + # нельзя — запрос просто не доходит. + # + # Отдельный handle по той же причине, что у robots.txt: файл лежит в + # `public/` фронта и раздаётся Next'ом по basePath-корню + # (/trade-in/og-mera.png), а не под /trade-in/mera-public{path}, куда + # рерайтит @meraPages. Адрес намеренно короткий: он попадает в разметку + # каждой публичной страницы и расходится по чужим кэшам, так что менять + # его потом дорого. + handle /og-mera.png { + rewrite * /trade-in/og-mera.png + reverse_proxy tradein-frontend:3000 { + header_up -X-Authenticated-User + } + } + + # logo-mera.png — логотип организации из микроразметки (Organization.logo). + # Отдельный файл от og-mera.png выше: превью ссылки широкое, а логотип + # поисковик обрезает близко к квадрату. Свой handle по той же причине — + # файл лежит в `public/` и раздаётся по basePath-корню. + handle /logo-mera.png { + rewrite * /trade-in/logo-mera.png + reverse_proxy tradein-frontend:3000 { + header_up -X-Authenticated-User + } + } + # Публичный API МЕРЫ — ЕДИНСТВЕННЫЙ путь этого домена, доходящий до # бэкенда. Под /api/public/ по определению не лежит ничего закрытого: # гарантию даёт структура пакета app/api/public/, а не аккуратность этого diff --git a/scripts/smoke-mera-perimeter.sh b/scripts/smoke-mera-perimeter.sh index 2df297be..867b5f45 100644 --- a/scripts/smoke-mera-perimeter.sh +++ b/scripts/smoke-mera-perimeter.sh @@ -125,6 +125,28 @@ check_redirect_location() { esac } +# check_content_type: 200 И заявленный Content-Type начинается с ожидаемого. +# +# Заведён под og:image. Для картинки превью «200» само по себе ничего не +# доказывает: перепутанный rewrite приводит к 200 с HTML-страницей фронта, и +# обходчик мессенджера её молча отбросит — ссылка развернётся без картинки, а +# смоук останется зелёным. Проверять надо ровно то, ради чего адрес открыт. +check_content_type() { + local desc="$1" url="$2" expected_prefix="$3" + local out code ctype + out=$(curl -s -o /dev/null -D - -w '%{http_code}' --max-time 15 "$url" 2>/dev/null) + code=${out##*$' +'} + ctype=$(printf '%s' "$out" | grep -i '^content-type:' | tr -d ' +' | sed 's/^[Cc]ontent-[Tt]ype: *//' | head -1) + if [ "$code" = "200" ] && [ "${ctype#"$expected_prefix"}" != "$ctype" ]; then + echo "PASS: $desc ($url -> $code, $ctype)" + else + echo "FAIL: $desc ($url -> got '${code:-}' / '${ctype:-}', expected 200 + $expected_prefix)" + fail=1 + fi +} + echo "== МЕРА B2C perimeter smoke (ЭТАП 1) ==" # 1. Публичный домен отдаёт 200 анонимно. @@ -164,6 +186,14 @@ check "meraocenka.ru/business — public 200" "$BASE_MERA/business" 200 check "meraocenka.ru/robots.txt — public 200" "$BASE_MERA/robots.txt" 200 check "meraocenka.ru/sitemap.xml — public 200" "$BASE_MERA/sitemap.xml" 200 +# 1d4. og:image — картинка превью ссылок. Её просят обходчики мессенджеров и +# соцсетей, анонимно и без Referer; отвалится handle — ссылки на сайт +# начнут разворачиваться без картинки, и заметить это по логам +# приложения нельзя (запрос туда просто не доходит). Content-Type +# проверяется вместе с кодом: 200 с HTML вместо PNG выглядел бы так же. +check_content_type "meraocenka.ru/og-mera.png — public 200 + image/png" "$BASE_MERA/og-mera.png" "image/png" +check_content_type "meraocenka.ru/logo-mera.png — public 200 + image/png" "$BASE_MERA/logo-mera.png" "image/png" + # 1e. Длинные адреса поддерева отдают 301 на короткие: у страницы один # канонический адрес, а старые ссылки и закладки продолжают работать. # ГОЛЫЙ /trade-in/mera-public — регресс на баг 15.08.2026: прежний матчер diff --git a/tradein-mvp/frontend/public/logo-mera.png b/tradein-mvp/frontend/public/logo-mera.png new file mode 100644 index 00000000..68785168 Binary files /dev/null and b/tradein-mvp/frontend/public/logo-mera.png differ diff --git a/tradein-mvp/frontend/public/og-mera.png b/tradein-mvp/frontend/public/og-mera.png new file mode 100644 index 00000000..c03621ff Binary files /dev/null and b/tradein-mvp/frontend/public/og-mera.png differ diff --git a/tradein-mvp/frontend/scripts/og/logo-mera.html b/tradein-mvp/frontend/scripts/og/logo-mera.html new file mode 100644 index 00000000..67e03e19 --- /dev/null +++ b/tradein-mvp/frontend/scripts/og/logo-mera.html @@ -0,0 +1,87 @@ + + + + +Логотип МЕРА для микроразметки + + + + + + + +
+
+
МЕРА
+
ДЛЯ СЕБЯ
+
+ + diff --git a/tradein-mvp/frontend/scripts/og/og-mera.html b/tradein-mvp/frontend/scripts/og/og-mera.html new file mode 100644 index 00000000..867d2b01 --- /dev/null +++ b/tradein-mvp/frontend/scripts/og/og-mera.html @@ -0,0 +1,264 @@ + + + + +OG-карточка МЕРА + + + + + + + +
+ + +
+ +
Реальная цена вашей квартиры — чтобы не продешевить
+
Сделки Росреестра и объявления площадок — по вашему адресу.
+
+ +
+
ДИАПАЗОН ЦЕНЫ
+
+
+
+
+
нижевыше
+ +
Вилка, а не одно число: рядом видно, на скольких объектах она построена.
+
+ +
+
СВЕРДЛОВСКАЯ ОБЛАСТЬ
+
meraocenka.ru
+
+
+ + diff --git a/tradein-mvp/frontend/src/app/mera-public/__tests__/public-perimeter.test.ts b/tradein-mvp/frontend/src/app/mera-public/__tests__/public-perimeter.test.ts index 65e2ee83..d732f9a3 100644 --- a/tradein-mvp/frontend/src/app/mera-public/__tests__/public-perimeter.test.ts +++ b/tradein-mvp/frontend/src/app/mera-public/__tests__/public-perimeter.test.ts @@ -183,7 +183,20 @@ describe("короткие адреса публичного домена", () = * `/trade-in/...`-хопы — это внутренняя проводка basePath, а не публичная * поверхность, у них своя проверка ниже (B2B-пути обязаны отдавать 404). */ - const ROOT_HANDLES_ALLOWED = ["/metrics", "/favicon.ico", "/robots.txt"]; + const ROOT_HANDLES_ALLOWED = [ + "/metrics", + "/favicon.ico", + "/robots.txt", + // Картинка превью ссылок (og:image), 10.09.2026. Её просят обходчики + // мессенджеров и соцсетей, а не посетители; файл лежит в `public/` фронта + // и раздаётся по basePath-корню, поэтому rewrite @meraPages его не + // обслуживает — отсюда свой handle, как у robots.txt. + "/og-mera.png", + // Логотип организации из микроразметки (Organization.logo), 10.09.2026. + // Отдельный файл от превью: оно широкое, логотип поисковик обрезает + // близко к квадрату. Раздаётся так же — из `public/`, своим handle. + "/logo-mera.png", + ]; it("корневые handle публичного домена — только объявленные", () => { const declared = [...meraBlock.matchAll(/^\s*handle\s+(\/[^\s*{]+)\s*\{/gm)] diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/JsonLd.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/JsonLd.tsx new file mode 100644 index 00000000..89773dc5 --- /dev/null +++ b/tradein-mvp/frontend/src/app/mera-public/_components/JsonLd.tsx @@ -0,0 +1,18 @@ +/** + * JsonLd — серверный компонент, печатающий один узел/граф schema.org в + * `