Commit graph

8 commits

Author SHA1 Message Date
3651c85f9b feat(rbac): enforce role-based access via X-Authenticated-User middleware
Backend-side enforcement поверх Caddy basic_auth — defense-in-depth для
12-юзеров на gendsgn.ru (admin + kopylov + 10 пилотных слотов).

- `auth/roles.yaml` — single source of truth: 2 роли (admin, pilot)
  + 12 user→role mappings. Bind-mounted в обоих backend контейнерах.
- `app/core/auth.py` (main + tradein-mvp mirror) — YAML loader через
  `pyyaml`, `get_role`/`get_user_scope`/`is_path_allowed` с fnmatch globs.
  Custom `_glob_to_regex` чтобы `/**` ≠ `/*` (matches multi-segment paths).
- `GET /api/v1/me` (main + tradein) — фронт читает scope при login,
  фильтрует nav по `allowed_paths`/`deny_paths`.
- `rbac_guard` middleware (main + tradein):
  * любой non-public path требует `X-Authenticated-User` → иначе 401
  * юзер должен быть в roles.yaml → иначе 403 («человек без ролей
    вообще ничего не видит» — decided 2026-05-25)
  * /api/v1/admin/* — только role=admin, иначе 403
- `Caddyfile` — `header_up X-Authenticated-User {http.auth.user.id}` в
  6 reverse_proxy блоках (gendsgn.ru main + trade-in + git.gendsgn.ru).
- Tests: 20 unit + integration в обоих стэках. Покрытие:
  no-header / unknown user / pilot / admin × admin-path / non-admin path.
  `_build_test_app()` копирует middleware inline, чтобы обойти heavy
  imports (weasyprint dlopen падает на macOS dev).

Public paths (/health, /docs, /redoc, /openapi.json) пропускаются —
X-Authenticated-User там просто не приходит из Caddy.

Frontend nav-filter + 403 fullscreen — отдельный PR B.
2026-05-26 11:14:47 +05:00
02ad9a1f58 feat(tradein): Phase 1 of #582 — address mismatch audit infrastructure
Stratified 200-house Yandex reverse-geocode audit comparing our DB's
(address, lat/lon) pair against what Yandex.Карты returns for those
coordinates. Лежит фундамент данных для Phases 2-5 (canonical = Yandex
per user decision).

What ships:
- Migration 066: address_mismatch_audit table + FDW foreign table
  gendesign_ekb_districts_geom для stratified sampling.
- Sampling SQL: 8 EKB districts × 25 houses = 200.
- Driver scripts/audit_address_mismatch.py с двумя режимами:
  API (если YANDEX_GEOCODER_API_KEY есть) и Playwright (CAPTCHA-aware,
  персистентный контекст). Resumable по --batch.
- Report SQL: p50/p75/p95 distance, top-20 outliers, per-district breakdown.
- 19 unit-тестов: normalize, distance bind, API parser, resume idempotency,
  captcha → status=blocked.

Devops note: после деплоя миграции — выдать tradein_fdw_reader SELECT на
gendesign.public.ekb_districts_geom перед первым запуском audit.

Refs #582 (Phase 1 of 5). Phase 3 backfill заблокирован до Yandex API
key (#402).
2026-05-25 12:50:30 +05:00
0767fb7401 fix(tradein-photos): sanitize uploaded images via Pillow re-encode (#511)
All checks were successful
Deploy Trade-In / changes (push) Successful in 4s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 58s
Deploy Trade-In / deploy (push) Successful in 36s
2026-05-24 11:32:04 +00:00
9643979d2c feat(tradein): Phase 3.2 — /api/v1/search endpoint + Redis cache (#479)
All checks were successful
Deploy Trade-In / deploy (push) Successful in 26s
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 56s
POST /api/v1/search reading listings_search_mv with ~20 cross-source filters, parameterized SQL (CAST(:x AS type), psycopg v3), whitelisted ORDER BY (Pydantic Literal), Redis 5min hot cache with graceful degradation (singleton pool via lru_cache).

Verified vs data/sql/050_search_optimization.sql: matview column refs (total_area, lng, house_rating, sources[], has_avito/cian/yandex), SQL injection safety, cache failure swallow, router prefix.

Deep-code-reviewer: APPROVE.
2026-05-23 14:44:22 +00:00
eb4764f75a feat(tradein): scrape_pipeline.py — Avito full orchestrator (search → houses → detail) (#451)
All checks were successful
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 56s
Deploy Trade-In / deploy (push) Successful in 28s
2026-05-23 12:55:13 +00:00
TradeIn Deploy
59ad41c183 feat(tradein): мониторинг ошибок через GlitchTip (#396)
Подключаем tradein-backend к GlitchTip (Sentry-совместимый, уже на
сервере). Необработанные исключения FastAPI улетают в дашборд —
баг estimate-500 (#388/#389) был бы виден сразу с трейсбеком.

- sentry-sdk в зависимостях.
- main.py — sentry_sdk.init(), env-gated: пусто GLITCHTIP_DSN → off.
- config.glitchtip_dsn + GLITCHTIP_DSN в docker-compose.prod.yml.
- traces_sample_rate=0 (только ошибки), send_default_pii=False.

Активация: создать проект в GlitchTip → DSN в .env.runtime.
2026-05-22 11:18:26 +05:00
TradeIn Deploy
5413cce953 feat(tradein): backend загрузки фото квартиры (#394)
Фото квартиры прикрепляются к оценке, хранятся в Postgres (bytea) —
без отдельного файлового тома, уезжают вместе с бэкапом БД.

- data/sql/007_estimate_photos.sql — таблица estimate_photos
  (FK на trade_in_estimates, ON DELETE CASCADE).
- POST /estimate/{id}/photos — multipart-загрузка (image/*, ≤10 МБ,
  ≤12 фото на оценку).
- GET  /estimate/{id}/photos — список метаданных.
- GET  /estimate/{id}/photos/{photo_id} — отдать содержимое.
- python-multipart добавлен в зависимости (FastAPI UploadFile).

Фронтенд (file-drop в форме) — отдельным PR.
2026-05-22 11:05:35 +05:00
lekss361
02267d0306 feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in)
- FastAPI backend: PostGIS estimator + 3 scrapers (Avito/Cian/Yandex)
- Next.js 15 frontend: tradein.html mockup design, basePath=/trade-in
- WeasyPrint PDF (Брусника-style 4-page report)
- Address autocomplete с typo-tolerance + 6 EKB presets
- Изолированный docker stack gendesign-tradein (отдельная postgres БД)
- Caddy inline routes: gendsgn.ru/trade-in/* и /trade-in/api/v1/*
- Forgejo Actions: .forgejo/workflows/deploy-tradein.yml (shell-based GHCR login)
- Триггер только по paths: tradein-mvp/** (не пересекается с deploy.yml)
- Образы: ghcr.io/lekss361/gendesign-tradein-{backend,frontend}:latest

Первый запуск на сервере (вручную, один раз):
  - создать /opt/gendesign/tradein-mvp/.env.runtime (postgres pwd, contact email)
  - docker network create gendesign_shared (если нет)
  - docker compose -p gendesign-tradein up -d
  - docker compose -p gendesign exec caddy caddy reload
2026-05-21 00:25:39 +03:00