feat(legal): host the privacy policy and EULA at /privacy/ and /eula/
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Successful in 1m14s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Failing after 40s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Successful in 1m14s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Failing after 40s
Author ui/legal/{privacy,eula}_ru.md, reworked from the source documents: the seller INN is unified (290210610742), contacts unified to the Telegram bot + email + postal address, the EULA governing-law clause leads with Russian law + jurisdiction as residence tiers, the single-binding-language clause is dropped, data collection is scoped to voluntary/support provision, and "Компания" is no longer shout-cased.
Serve both as static pages through the render sidecar, reusing a shared renderLegalHtml generalised from renderOfferHtml: new GET /privacy/ and GET /eula/ (301 from the slashless form), the markdown baked into the image, no backend fetch. Route them at the edge via the @legal caddy matcher with a CI probe asserting each page's canonical URL + the seller INN, so a missing route cannot silently fall through to the landing. Add the two links to the landing footer.
Docs baked in: ARCHITECTURE (renderer legal pages + edge routing), FUNCTIONAL (+_ru) footer, renderer/README routes. Tests: renderer legal.test.mjs, ui renderLegalHtml unit, landing footer e2e.
This commit is contained in:
+10
-7
@@ -967,10 +967,12 @@ finished journal on each GET. The only degraded platform is a legacy Telegram cl
|
||||
predating `downloadFile`, where the GCG falls back to the old clipboard copy and the
|
||||
image option is not offered.
|
||||
|
||||
The same sidecar also serves the **public offer page** at `/offer/` — the one edge-exposed
|
||||
route on it (caddy routes `/offer/` here; its `/render` stays internal). It reuses the shared
|
||||
`ui/src/lib/offer.ts` renderer (again one renderer, no drift) over the owner-edited
|
||||
`ui/legal/offer_ru.md`, baked into the image, splicing in the **live price list** (§4.4): it
|
||||
The same sidecar also serves the **public legal pages** — the offer at `/offer/`, the privacy
|
||||
policy at `/privacy/` and the EULA at `/eula/` — the edge-exposed routes on it (caddy routes them
|
||||
here; its `/render` stays internal). All three reuse the shared `ui/src/lib/offer.ts`
|
||||
`renderLegalHtml` (one renderer, no drift) over their owner-edited `ui/legal/*_ru.md`, baked into
|
||||
the image. `/privacy/` and `/eula/` are static; the offer additionally splices in the **live price
|
||||
list** (§4.4): it
|
||||
fetches the two catalog tables as markdown from the backend's internal
|
||||
`/api/v1/internal/offer/pricing` at the `<#pricing_template#>` marker, then renders the page. The
|
||||
backend projects the tables from the active catalog through `payments.Money` (no float reaches the
|
||||
@@ -1446,9 +1448,10 @@ in-process (the distroless image has no `/etc/mime.types`). Hash-named `/assets/
|
||||
in-compose **caddy** is the contour's edge: it owns a single `/_gm` Basic-Auth and
|
||||
routes `/_gm/grafana/*` to **Grafana** (anonymous-admin, so the one shared login gates
|
||||
it with no per-user Grafana accounts) and the rest of `/_gm/*` to the backend-rendered
|
||||
**admin console**; `/app/`, `/telegram/`, `/vk/` and the Connect path go to the gateway; `/offer/`
|
||||
(the public offer, rendered by the `renderer` sidecar with the live catalog price list spliced in)
|
||||
goes to the render sidecar; and the catch-all — notably the landing at `/` — goes to the landing
|
||||
**admin console**; `/app/`, `/telegram/`, `/vk/` and the Connect path go to the gateway; the public
|
||||
legal pages `/offer/`, `/privacy/` and `/eula/` (rendered by the `renderer` sidecar — the offer with
|
||||
the live catalog price list spliced in) go to the render sidecar; and the catch-all — notably the
|
||||
landing at `/` — goes to the landing
|
||||
container. The
|
||||
**Telegram validator** runs as a separate container with **no public ingress**,
|
||||
answering only internal gRPC (HMAC, no Telegram egress). The **Telegram bot** holds
|
||||
|
||||
+6
-5
@@ -30,11 +30,12 @@ render with arbitrary languages, so detection would make the indexed content
|
||||
nondeterministic); a saved 🌐 choice still wins. The page carries a static Russian SEO head
|
||||
(title/description, the Open Graph card Telegram/VK link previews use, a canonical link
|
||||
pinned to the production origin, JSON-LD, the favicon set and `robots.txt`), while the SPA
|
||||
shell is `noindex` — the landing is the only indexable page. The footer links to the **public
|
||||
offer** (`/offer/`) and, beside it, **feedback** — the Telegram bot the offer names as the seller's
|
||||
contact. The offer page (the legal document a purchase accepts) is rendered on demand from
|
||||
`ui/legal/offer_ru.md` with its **price list** (§4.4) generated from the live product catalog, so
|
||||
the published prices always match what is currently on sale — no redeploy to update them.
|
||||
shell is `noindex` — the landing is the only indexable page. The footer links to the three **legal
|
||||
documents** — the **user agreement** (`/eula/`), the **privacy policy** (`/privacy/`) and the
|
||||
**public offer** (`/offer/`) — and, beside them, **feedback** (the Telegram bot the documents name as
|
||||
the seller's contact). Each is rendered on demand from its `ui/legal/*_ru.md` source by the render
|
||||
sidecar; the offer additionally splices in its **price list** (§4.4) generated from the live product
|
||||
catalog, so the published prices always match what is currently on sale — no redeploy to update them.
|
||||
|
||||
On the plain web the client is an **installable PWA**: a logged-out player sees an install
|
||||
call-to-action under the login form (and at the bottom of Settings) that installs the app to the
|
||||
|
||||
@@ -32,12 +32,13 @@ top-1 подсказку, безлимитную проверку слова с
|
||||
главнее. Страница несёт статическую русскую SEO-«шапку» (title/description, карточка
|
||||
Open Graph, которую используют превью ссылок в Telegram/VK, канонический адрес
|
||||
продакшен-домена, JSON-LD, набор favicon и `robots.txt`), а оболочка SPA помечена
|
||||
`noindex` — индексируется только посадочная страница. В подвале — ссылки на **публичную
|
||||
оферту** (`/offer/`) и рядом на **обратную связь** (тот самый Telegram-бот, который оферта
|
||||
указывает как контакт Продавца). Страница оферты (юридический документ, который принимается при
|
||||
покупке) рендерится по запросу из `ui/legal/offer_ru.md`, а её **перечень стоимости** (§4.4)
|
||||
формируется из живого каталога товаров, поэтому опубликованные цены всегда совпадают с тем, что
|
||||
сейчас в продаже — без передеплоя.
|
||||
`noindex` — индексируется только посадочная страница. В подвале — ссылки на три **юридических
|
||||
документа** — **пользовательское соглашение** (`/eula/`), **политику конфиденциальности**
|
||||
(`/privacy/`) и **публичную оферту** (`/offer/`) — и рядом на **обратную связь** (тот самый
|
||||
Telegram-бот, который документы указывают как контакт Продавца). Каждый рендерится по запросу из
|
||||
своего исходника `ui/legal/*_ru.md` сайдкаром-рендерером; оферта дополнительно подставляет
|
||||
**перечень стоимости** (§4.4), формируемый из живого каталога товаров, поэтому опубликованные цены
|
||||
всегда совпадают с тем, что сейчас в продаже — без передеплоя.
|
||||
|
||||
В обычном вебе клиент — **устанавливаемое PWA**: незалогиненный игрок видит призыв к установке
|
||||
под формой входа (и внизу «Настроек»), который в один тап ставит приложение на рабочий стол
|
||||
|
||||
Reference in New Issue
Block a user