food-market/docs/sprint20-progress.md
2026-06-07 22:00:50 +05:00

126 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Sprint 20 — Mapster + SSO scaffolding + maintenance automation
Цель: закрыть TD-3 (Mapster вместо ручных LINQ-проекций), добавить
SSO-скелет (Google + Microsoft), включить maintenance-автоматику
(stale cleanup / VACUUM / disk / performance regression / analytics).
Старт: 2026-06-07 (после Sprint 19). Исполнитель: Claude Opus 4.7.
## Принципы
- Mapster — без AutoMapper (платный + CVE), config в `Application/Mapping/`.
- SSO — только скелет. Реальные client_id/secret не коммитим, пустые → 503.
- Hangfire jobs — идемпотентные, с лимитом на rows (не зачищать слишком много за раз).
- НЕ трогать: `global.json`, prod admin.food-market.kz, POS WPF.
## Чек-лист
- [x] **1. TD-3 Mapster**`Application/Mapping/MapsterConfig.cs` с
`TypeAdapterConfig` для Product+ProductBarcode+ProductPrice+Counterparty.
Singleton зарегистрирован в `Program.cs`. ProductsController.List/Get/
GetInternalAsync + CounterpartiesController.List/Get переведены на
`.ProjectToType<TDto>(MapsterConfig.Config)`. Inline `Projection` удалён.
- [x] **2. SSO Google + Microsoft scaffolding** — пакеты
`Microsoft.AspNetCore.Authentication.Google` 8.0.11 + `.MicrosoftAccount`
8.0.11 + `.Cookies` 2.3.0. Условная регистрация в Program.cs: если
`Authentication:{Google|Microsoft}:ClientId` пустой — провайдер не
подключается. `ExternalAuthController` с endpoint'ами:
- `GET /api/auth/external/{provider}` — Challenge или 503 с подсказкой
- `GET /api/auth/external/callback?provider=...` — 501 с email (invite-flow TODO)
- `GET /api/auth/external/providers``{google: bool, microsoft: bool}`
- `docs/sso.md` — инструкция получения keys у Google/Microsoft.
- [x] **3. Stale-data cleanup автоматика** — HousekeepingJobs расширен:
- `PruneOrgAuditLogAsync``OrgAuditLog` > `Cleanup:OrgAuditLogDays` (90)
- `PruneDraftsAsync` — Supply/RetailSale/Demand в Draft > `Cleanup:DraftDays` (30)
- `PruneRevokedRefreshTokensAsync``OpenIddictTokens` Type=refresh, Status=revoked/redeemed > `Cleanup:RevokedRefreshTokenDays` (7).
Три новых cron'a в `HangfireJobsConfigurator` (03:00 / 03:15 / 03:20 UTC).
- [x] **4. DB VACUUM automation**`DatabaseMaintenanceJobs.VacuumTopTablesAsync`:
`pg_total_relation_size` выбирает топ-`Maintenance:VacuumTopN` (5) таблиц
`VACUUM (ANALYZE) public."<table>"` per-table. Без `FULL` → не
блокирует пишущие транзакции. Логирует время per-table.
Cron еженедельно вс 04:00 UTC (`Hangfire:Cron:VacuumTopTables`).
- [x] **5. Disk usage monitoring**`DiskMonitoringJob` ежечасно
(`Hangfire:Cron:DiskMonitor`): `DriveInfo.AvailableFreeSpace` на пути
из `Monitoring:DiskPaths` (default `/opt,/var/lib/docker`). При свободе
< `Monitoring:DiskMinFreeBytes` (1GB) → Telegram-alert на
`Monitoring:SuperAdminTelegramChatIds` (CSV). Anti-spam: один alert
per mount per `Monitoring:DiskAlertCooldownHours` (6) часов (in-memory).
Prometheus-gauge `food_market_disk_free_bytes{mount="..."}` обновляется
каждым прогоном.
- [x] **6. Performance regression detection**`~/nightly-perf-check.sh`:
парсит `/metrics` stage'а, считает `db_avg_ms = sum/count` по
`food_market_db_query_duration_seconds`, сравнивает с baseline в
`~/.fm-watchdog/perf-baseline.json`. Δ>`PERF_THRESHOLD_PCT` (30%) →
Telegram-alert. Sliding window: baseline обновляется только при
«нет регрессии». Cron-устанавливаемый скрипт (запускать после
`nightly-verify.sh`).
- [x] **7. Public-site analytics placeholder** — Astro
`food-market.public/src/layouts/BaseLayout.astro` рендерит GA4
`<script async src="...gtag/js?id=...">` и Yandex.Metrika `ym(id, init, ...)`
только если заданы `PUBLIC_GA_ID` / `PUBLIC_YM_ID` env-vars. Иначе —
`<script data-analytics="..." data-id="REPLACE_ME" data-doc="docs/analytics.md">`
маркер (виден в view-source). `docs/analytics.md` — инструкция по
подключению + privacy-notes.
## Журнал
### 2026-06-07 старт
Sprint 19 закрыт (7/7 ✓ + 1 hotfix). Поехали по tech debt + maintenance.
### 2026-06-07 итог
Все 7 пунктов ✓. Stage deploy + retest:
**Recurring jobs зарегистрированы** (проверка `hangfire.hash WHERE key
LIKE 'recurring-job%'`): disk-monitor, vacuum-top-tables, prune-drafts,
prune-org-audit-log, prune-revoked-refresh-tokens — +5 к существующим
(prune-stock-movements, prune-audit-log, weekly-summary, low-stock-alert,
telegram-owner-daily-summary). Итого 10 recurring.
**Endpoint smoke** (через `s20-smoke.ts`, удалён после прогона):
| Endpoint | Статус | Результат |
|---|---|---|
| GET /api/catalog/products (Mapster) | 200 | 99ms first, avg 115ms over 5 |
| GET /api/catalog/counterparties (Mapster) | 200 | items=0 (свежий tenant) |
| GET /api/auth/external/providers | 200 | `{google:false, microsoft:false}` |
| GET /api/auth/external/google | 503 | `error: SSO для Google не настроено.` |
| GET /api/auth/external/unknown | 400 | unknown provider |
| GET /metrics | 200 | содержит `food_market_disk_free_bytes` HELP |
**Stage scenarios**: smoke 5/5 ✓, catalog 6/6 ✓.
**Perf-check**: `~/nightly-perf-check.sh` отработал в первый раз —
baseline `db_avg_ms=3.586` (sum=1.194 count=333) записан в
`~/.fm-watchdog/perf-baseline.json`. Будущие прогоны сравнят с этим
значением; >30% деградации → Telegram-alert.
## Итог
Все 7 пунктов ✓. Локальные цифры:
- **Mapster**: 4 типа в config'е, 5 sites вызовов ProjectToType, ~115ms
на список из 0 товаров (свежий tenant, сравнение «до/после» неинформативно
без нагрузки — реальный замер на нагруженном tenant'е в следующем спринте).
- **SSO**: 3 endpoint'a, 2 провайдера, conditional auth-registration.
- **Cleanup**: 3 новых job'a (org-audit / drafts / refresh-tokens) +
раздельные cron'ы 03:00-03:20 UTC.
- **VACUUM**: топ-5 таблиц еженедельно вс 04:00 UTC.
- **Disk**: ежечасно, 6h cooldown, Prom-gauge + Telegram.
- **Perf-regression**: nightly script, sliding baseline, 30% threshold.
- **Analytics**: 2 placeholder'a (GA4 + YM) в Astro layout, env-driven.
## Условия для пользователя (после Sprint 20)
Watchdog `~/.fm-watchdog/DONE` создан. Дальше нужны решения вне кода:
- **SSO**: получить OAuth-keys у Google и Microsoft, положить в
`appsettings.Production.json`.
- **Telegram**: `Monitoring:SuperAdminTelegramChatIds` — нужен список
chat-id для disk-alert'ов.
- **ОФД-keys / Webkassa**: реальные ключи у фискального оператора.
- **MoySklad-tokens**: при необходимости импорта.
- **POS WPF**: тест на Windows-машине.
- **Прод-деплой**: решение и инфраструктура для prod.
- **Реальный SMTP**: SendGrid / Mailgun / yandex300 для писем.
- **kz-перевод**: текущие i18n-строки на русском, для kz требуется
носитель.