docs: CLAUDE.md полностью под Astro v2
All checks were successful
deploy / deploy (push) Successful in 47s
All checks were successful
deploy / deploy (push) Successful in 47s
This commit is contained in:
186
CLAUDE.md
186
CLAUDE.md
@@ -1,91 +1,181 @@
|
|||||||
# pushkinohistory.ru — Vite+React v2
|
# pushkinohistory.ru — Astro v2
|
||||||
|
|
||||||
Сайт «История города Пушкино». Редизайн с WordPress (v1, контейнер `pushkinohistory-ru:4143`) на статический Vite+React+Tailwind (контейнер `pushkinohistory-ru-v2:4146`).
|
Сайт «История города Пушкино». Редизайн с WordPress (v1) на статический Astro 5 + Content Collections + markdown.
|
||||||
|
|
||||||
|
**Прод:** https://pushkinohistory.ru
|
||||||
|
**Репо:** `git.striker.su/striker/pushkinohistory-ru-v2`
|
||||||
|
**Хост:** `web.hhivp.com` (45.10.53.206 / 45.10.53.242)
|
||||||
|
**Контейнер:** `pushkinohistory-ru-v2` на `127.0.0.1:4146` (nginx:alpine + Astro SSG)
|
||||||
|
**Cutover:** 2026-05-21 со старого WP-контейнера `pushkinohistory-ru:4143`
|
||||||
|
|
||||||
## Стек
|
## Стек
|
||||||
|
|
||||||
- Vite 6 + React 18 + Tailwind 3
|
- **Astro 5** + Content Collections + markdown
|
||||||
- PT Serif (заголовки/основной текст) + IBM Plex Sans (UI)
|
- **nginx:1.29-alpine** в runtime-контейнере (статика + bind-mount для агрегатора новостей)
|
||||||
- Express 4 + compression (runtime сервер, отдаёт prerendered HTML + `/api/news.json` + `/feed/`)
|
- **sanitize-html** — очистка тела поста для RSS `<content:encoded>` (с CDATA)
|
||||||
- Puppeteer для prerender (chromium в build-стадии)
|
- **fast-xml-parser** — изолированно в `scripts/` (только для cron-агрегатора)
|
||||||
- fast-xml-parser для агрегатора внешних RSS
|
- **sharp** (devDep, опц.) — генерация OG-image PNG из SVG
|
||||||
|
- **PT Serif** (заголовки/тело статьи) + **IBM Plex Sans** (UI)
|
||||||
|
- **@astrojs/sitemap** — `sitemap-index.xml` автоматически
|
||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
App.jsx # клиентский роутер (window.history + popstate), 301-карта oldSlug → newSlug
|
├── content/
|
||||||
components/ # Header, Sidebar, Footer, PostCard
|
│ ├── posts/*.md (7 постов, мигрированы из WP DB ph_posts)
|
||||||
content/ # JSON-контент: posts, pages, partners, ads, transport, feeds
|
│ ├── pages/*.md (4 страницы: Главная-приветствие, История, Фото, Форум)
|
||||||
pages/ # Home, Post, Page, Category, News, NotFound
|
│ └── _categories.json (через categorySlugs в frontmatter)
|
||||||
server/index.js # Express: /api/health, /api/news.json, /uploads/, /feed/, статика
|
├── components/
|
||||||
|
│ ├── Header.astro (фото-шапка + аэрофото Пушкино + sepia overlay)
|
||||||
|
│ ├── Sidebar.astro (Транспорт, Рубрики с подсчётом, Партнёры, Объявления)
|
||||||
|
│ ├── Footer.astro
|
||||||
|
│ ├── PostCard.astro (featured / has-thumb / no-thumb варианты)
|
||||||
|
│ ├── CookieConsent.astro (152-ФЗ баннер, ph-consent в localStorage+cookie)
|
||||||
|
│ └── Analytics.astro (Яндекс.Метрика + GA, gating через type=text/plain)
|
||||||
|
├── layouts/BaseLayout.astro
|
||||||
|
├── pages/
|
||||||
|
│ ├── index.astro (Главная история + Ещё из истории + Хроника)
|
||||||
|
│ ├── [slug].astro (один пост или одна страница)
|
||||||
|
│ ├── cat/[slug].astro (рубрика)
|
||||||
|
│ ├── cat/[slug]/feed.xml.ts (per-category RSS)
|
||||||
|
│ ├── news.astro (агрегатор внешних RSS, фетчит /api/news.json)
|
||||||
|
│ ├── 404.astro
|
||||||
|
│ ├── privacy.astro (политика + кнопка «Отозвать согласие»)
|
||||||
|
│ ├── feed.xml.ts (общий RSS, с RSS_CUTOFF)
|
||||||
|
│ └── sitemap.txt.ts (plain-text карта)
|
||||||
|
├── lib/
|
||||||
|
│ ├── extract.ts (firstImage, plainText, formatDateRu)
|
||||||
|
│ └── rss-helpers.ts (buildRss, sanitizeForRss, cdata, plainTextExcerpt)
|
||||||
|
├── data/ (внешний контент-конфиг, JSON)
|
||||||
|
│ ├── transport.json (ссылки на Yandex.Schedules / mostransport)
|
||||||
|
│ ├── partners.json
|
||||||
|
│ ├── ads.json
|
||||||
|
│ └── feeds.json (внешние RSS-источники для cron-агрегатора)
|
||||||
|
├── styles/global.css
|
||||||
|
└── consts.ts (SITE_TITLE, MAIN_NAV, RSS_CUTOFF, ANALYTICS, plural)
|
||||||
|
|
||||||
|
public/
|
||||||
|
├── uploads/ (6 картинок, перенесены из WP /wp-content/uploads/)
|
||||||
|
├── favicon.svg
|
||||||
|
├── robots.txt
|
||||||
|
├── ai.txt
|
||||||
|
└── llms.txt
|
||||||
|
|
||||||
|
nginx/pushkinohistory.ru.conf (vhost для хост-nginx, симлинкуется в /etc/nginx/conf.d/)
|
||||||
|
|
||||||
scripts/
|
scripts/
|
||||||
convert_posts.py # WP DB → src/content/{posts,pages}.json
|
├── convert_posts.py (WP DB → src/content/posts.json + pages.json, fix WP-resized URL)
|
||||||
build-rss.js # генератор IPB-совместимого RSS (full content в CDATA)
|
├── convert_to_markdown.py (posts.json → src/content/posts/*.md с frontmatter)
|
||||||
build-sitemap.js # sitemap.xml + robots.txt
|
├── pull-external-rss.mjs (cron на хосте: feeds.json → data/news.json)
|
||||||
build-slugs.js # routes.json для prerender
|
├── install-cron.sh (установка cron на web.hhivp.com)
|
||||||
prerender.js # SPA → статичные HTML по маршрутам через puppeteer
|
└── package.json (изолированный fast-xml-parser)
|
||||||
pull-external-rss.js # cron: внешние RSS → data/news.json (агрегатор)
|
|
||||||
public/uploads/ # картинки, перенесены из WP /wp-content/uploads/
|
Dockerfile (multi-stage: node:22-alpine build → nginx:1.29-alpine serve)
|
||||||
nginx/ # vhost для прода (симлинк из /etc/nginx/conf.d/)
|
nginx.conf (внутри контейнера: gzip, кэш _astro/, MIME для RSS, /api/news.json из bind-mount)
|
||||||
|
docker-compose.yml (контейнер на 127.0.0.1:4146, bind-mount data/ → /var/lib/pushkino/data:ro)
|
||||||
|
.gitea/workflows/deploy.yml (push в main → SSH-деплой на web.hhivp.com)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Контент
|
## Контент
|
||||||
|
|
||||||
Скрейп из WP DB (`pushkinohistory_ru` на `db.hhivp.com`):
|
- **7 постов** + **4 страницы** скрейпом из WP DB `pushkinohistory_ru` на `db.hhivp.com`
|
||||||
- 7 постов + 4 страницы
|
- **URL-encoded кириллические slug'и WP** → транслитерированы (`/segodnya-nochyu-rossiyane-uvidyat-pervoe/`); старые URL → 301 через nginx map по `$uri`
|
||||||
- 6 картинок в `public/uploads/`
|
- **WP-resized URL** (`-1024x768.png`) → оригинал автоматически в `convert_posts.py:RESIZED_RE`
|
||||||
- URL-encoded кириллические slugs WP → транслитерированы (`/segodnya-nochyu-rossiyane-uvidyat-pervoe/`); старые URL → 301 через `nginx/map`
|
- **Frontmatter-флаги для иерархии главной:**
|
||||||
|
- `featured: true` + `featuredImage` — пин на верх как «Главная история» (Воронино, Старое Село)
|
||||||
|
- `hideFromList: true` — скрыть с главной (3 «технические работы»), доступ только через рубрику
|
||||||
|
- **Категория `main`** — псевдо-флаг «попадает на главную»; не показывается в плашках и в сайдбаре
|
||||||
|
|
||||||
## RSS
|
## RSS
|
||||||
|
|
||||||
- **Свой `/feed/`** — IPB-совместимый RSS 2.0 с полным HTML в `<content:encoded>`, стабильными `<guid isPermaLink>`, `<dc:creator>`, категориями. Для импорта в `forum.pushkinohistory.ru`.
|
### Свой `/feed.xml` (для IPB Importer)
|
||||||
- **Внешние фиды** — `src/content/feeds.json` (список URL), парсятся cron-скриптом `scripts/pull-external-rss.js` → `data/news.json` (bind-mount), фронт читает client-side через `/api/news.json`. Каждое добавление источника = редактирование `feeds.json` + push.
|
|
||||||
|
- Полный HTML тела поста в `<content:encoded>` с CDATA (sanitize-html → buildRss)
|
||||||
|
- Стабильные `<guid isPermaLink="true">` = URL поста (IPB дедуплицирует)
|
||||||
|
- `<dc:creator>`, `<category>`, корректный `<pubDate>` в RFC-822
|
||||||
|
- Контент-Type `application/rss+xml; charset=utf-8`
|
||||||
|
- **`RSS_CUTOFF`** в `src/consts.ts` (default `2010-01-01`) — отрезает старый архив. Чтобы IPB не флудил при следующем рестарте — поменять на новую дату и пересобрать
|
||||||
|
- Per-category RSS: `/cat/<slug>/feed.xml`
|
||||||
|
|
||||||
|
### Агрегатор внешних RSS
|
||||||
|
|
||||||
|
- `src/data/feeds.json` — список источников (`enabled: false` по умолчанию)
|
||||||
|
- `scripts/pull-external-rss.mjs` — cron на web.hhivp.com (каждый час в `:12`)
|
||||||
|
- Пишет `/opt/docker/sites/pushkinohistory-ru-v2/data/news.json` (bind-mount в контейнер как `/var/lib/pushkino/data:ro`)
|
||||||
|
- Фронт `/news/` фетчит client-side через `/api/news.json` (отдаёт nginx внутри контейнера alias на bind-mount)
|
||||||
|
- Логи `/var/log/pushkino-rss-aggregator.log` + logrotate weekly × 4
|
||||||
|
|
||||||
|
Чтобы добавить источник:
|
||||||
|
1. В `src/data/feeds.json` добавить `{name, url, enabled: true, max}`
|
||||||
|
2. Push → CI → деплой
|
||||||
|
3. На следующем cron-tick'е появится в `/news/`
|
||||||
|
|
||||||
## Деплой
|
## Деплой
|
||||||
|
|
||||||
|
### Автоматический (Gitea Actions)
|
||||||
|
|
||||||
|
Push в `main` → `.gitea/workflows/deploy.yml`:
|
||||||
|
1. SSH на `web.hhivp.com` с ключом из секрета `SSH_DEPLOY_KEY`
|
||||||
|
2. `git fetch + reset --hard origin/main`
|
||||||
|
3. `docker compose build && up -d`
|
||||||
|
4. `curl -fsS http://127.0.0.1:4146/` — health check
|
||||||
|
5. `docker image prune --filter "until=168h"`
|
||||||
|
|
||||||
|
Секреты в Gitea (`/repos/striker/pushkinohistory-ru-v2/actions/secrets`):
|
||||||
|
- `SSH_DEPLOY_KEY` — приватный ключ `~/.ssh/pushkino-v2-deploy` (генерён локально, pubkey в `striker@web:~/.ssh/authorized_keys`)
|
||||||
|
- `SSH_KNOWN_HOSTS` — fingerprint `web.hhivp.com ssh-ed25519 AAAAC3...`
|
||||||
|
|
||||||
|
### Вручную
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Локально:
|
|
||||||
cd E:\Projects\pushkinohistory-ru-v2
|
cd E:\Projects\pushkinohistory-ru-v2
|
||||||
git add . && git commit -m "..." && git push
|
git add . && git commit -m "..." && git push
|
||||||
|
# или с локалки на сервер:
|
||||||
# На web.hhivp.com:
|
ssh striker@web.hhivp.com 'cd /opt/docker/sites/pushkinohistory-ru-v2 && git pull && docker compose up -d --build'
|
||||||
ssh striker@web.hhivp.com
|
|
||||||
cd /opt/docker/sites/pushkinohistory-ru-v2
|
|
||||||
git pull && docker compose up -d --build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
CI/CD автоматизация — Gitea Actions с SSH-deploy (см. `.gitea/workflows/deploy.yml`).
|
### nginx vhost
|
||||||
|
|
||||||
## Контейнер
|
Симлинк: `/etc/nginx/conf.d/pushkinohistory.ru` → `nginx/pushkinohistory.ru.conf` в репо. После правки nginx-конфига: push → CI заберёт → `sudo nginx -t && sudo systemctl reload nginx`.
|
||||||
|
|
||||||
- Image: `pushkinohistory-ru-v2:latest` (мультистейдж: builder с puppeteer/chromium → runtime node:22-alpine + express)
|
301-редиректы со старых WP-URL — через `map $uri $legacy_redirect` (см. файл). Если нужно добавить новый редирект — отредактировать map и pushнуть.
|
||||||
- Порт: `127.0.0.1:4146`
|
|
||||||
- Bind-mounts:
|
|
||||||
- `/opt/www/pushkinohistory.ru/uploads-v2 → /app/public/uploads (ro)` — динамические uploads
|
|
||||||
- `/opt/docker/sites/pushkinohistory-ru-v2/data → /app/data (ro)` — `news.json` от cron
|
|
||||||
- nginx vhost: `/etc/nginx/conf.d/pushkinohistory.ru` → симлинк на `nginx/pushkinohistory.ru.conf` в этом репо
|
|
||||||
|
|
||||||
## Откат на WP v1
|
## Откат на WP v1
|
||||||
|
|
||||||
Старый WP в `/opt/docker/sites/pushkinohistory-ru/` (контейнер `pushkinohistory-ru:4143`) сохранён. Чтобы откатиться:
|
Старый WP-контейнер `pushkinohistory-ru:4143` + БД `pushkinohistory_ru` на `db.hhivp.com` сохранены. Откат за ~1 минуту:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh striker@web.hhivp.com
|
ssh striker@web.hhivp.com
|
||||||
sudo ln -sfn /opt/docker/sites/pushkinohistory-ru/nginx/pushkinohistory.ru.conf /etc/nginx/conf.d/pushkinohistory.ru
|
echo "Gh_lpx2017!" | sudo -S ln -sfn /opt/docker/sites/pushkinohistory-ru/nginx/pushkinohistory.ru.conf /etc/nginx/conf.d/pushkinohistory.ru
|
||||||
sudo nginx -t && sudo systemctl reload nginx
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
## БД (WP, v1)
|
После 1-2 недель стабильной работы v2 — старый WP можно удалить (контейнер + БД + репо).
|
||||||
|
|
||||||
DB на `db.hhivp.com` (45.10.53.205), `pushkinohistory_ru`/`u_pushhist`, prefix `ph_`. **Не удалять** — нужна для отката и как источник для повторного скрейпа.
|
|
||||||
|
|
||||||
## Форум
|
## Форум
|
||||||
|
|
||||||
`forum.pushkinohistory.ru` — IPB 4.x в отдельном контейнере `forum-pushkinohistory-ru:4144`. **v2 редизайном не затронут.**
|
`forum.pushkinohistory.ru` — IPB 4.x в отдельном контейнере `forum-pushkinohistory-ru:4144`. **v2 редизайном не затронут.**
|
||||||
|
|
||||||
|
## Аналитика
|
||||||
|
|
||||||
|
`ANALYTICS` в `src/consts.ts` (`yandexMetrika`, `googleGtag`) пустые — впишите ID после регистрации счётчиков. Скрипты в `Analytics.astro` имеют `type="text/plain" data-cookieconsent="statistics"` и активируются только после согласия в баннере `CookieConsent.astro`.
|
||||||
|
|
||||||
|
Согласие хранится в `localStorage` + cookie `ph-consent` (12 мес). На `/privacy/` есть кнопка «Отозвать согласие».
|
||||||
|
|
||||||
|
## Локальная разработка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev # → http://localhost:4321
|
||||||
|
npm run build # → dist/ (статика)
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Если 4321 занят — Astro сам найдёт следующий свободный (4322 и т.д.).
|
||||||
|
|
||||||
## История
|
## История
|
||||||
|
|
||||||
- 2026-05-08: v1 контейнеризован, миграция со str-u-01 на web.hhivp.com
|
- **2026-05-08:** v1 (WordPress 6.x) контейнеризован, миграция со str-u-01 на web.hhivp.com
|
||||||
- 2026-05-14: фикс trust-proxy.conf для Docker bridges (Better WP Security)
|
- **2026-05-14:** фикс trust-proxy.conf для Docker bridges (Better WP Security)
|
||||||
- 2026-05-21: v2 редизайн — Vite+React+Tailwind, отказ от WP, RSS-агрегатор внешних фидов + свой RSS для IPB
|
- **2026-05-21:** v2 редизайн — Vite+React → Astro 5 (стек как у `anotherreflections-website-v2`). Cutover, бэкап старого WP в репо `pushkinohistory-ru` + БД на `db.hhivp.com` (~2 недели на наблюдение).
|
||||||
|
|||||||
Reference in New Issue
Block a user