No description
  • C# 97.1%
  • Python 2.8%
  • Dockerfile 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Anton Babichev 0378fd7a61
Some checks failed
backend-ci / build (push) Failing after 2m3s
Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	README.md
2026-10-16 23:15:56 +03:00
.github feat: implement Lab 3 fishing platform API 2026-10-16 19:52:24 +03:00
data Load real regional waters with interior geometry markers 2026-10-16 22:10:58 +03:00
scripts Load real regional waters with interior geometry markers 2026-10-16 22:10:58 +03:00
src/LigmaBa.Api fix: calibrate fishing index and synchronize configured admin password 2026-10-16 22:43:47 +03:00
tests/LigmaBa.Api.Tests fix: calibrate fishing index and synchronize configured admin password 2026-10-16 22:43:47 +03:00
.dockerignore feat: implement Lab 3 fishing platform API 2026-10-16 19:52:24 +03:00
.gitignore feat: implement Lab 3 fishing platform API 2026-10-16 19:52:24 +03:00
Dockerfile chore: bootstrap Lab 2 checkpoint 2026-10-03 09:32:27 +00:00
README.md fix: calibrate fishing index and synchronize configured admin password 2026-10-16 22:43:47 +03:00

LigmaBa LLs Backend — Lab 3

ASP.NET Core 8 API проекта «Находка для рыбака». Реализованы UC-01–UC-09 из ligmaba-docs: регистрация и JWT-сессии, каталог, Fishing Index и прогноз, избранное, чат, отчёты об улове, четыре сценария Рыбака AI, жалобы и администрирование.

Запуск

Полный стек разворачивается из соседнего ligmaba-docs/deploy по инструкции ligmaba-docs/README.md. Compose использует PostGIS/PostgreSQL 16, постоянные volumes для БД и фотографий; API выполняет миграции автоматически. /health проверяет доступность БД. Первичный OSM import работает в фоне, его сбой не останавливает карту/API.

Для запуска API без Docker нужны .NET SDK 8+ и PostgreSQL 16 с PostGIS. Задайте переменные окружения и выполните:

dotnet run --project src/LigmaBa.Api/LigmaBa.Api.csproj --urls http://localhost:8080
Переменная Назначение
ConnectionStrings__Postgres Полная строка подключения к PostgreSQL
Jwt__Key Случайный секрет HMAC длиной минимум 32 байта; обязателен
Jwt__Issuer, Jwt__Audience По умолчанию ligmaba, ligmaba-web
Admin__Email, Admin__Password, Admin__Nickname Создание первого Admin; пароль минимум 8 символов. Для существующего Admin пароль синхронизируется с конфигурацией; при фактической смене отзываются только его активные сессии. Совпадающий пароль не меняет сессии. Если email принадлежит обычному User, запуск отклоняет ошибочную конфигурацию, без повышения роли
Demo__Enabled По умолчанию false. Только явное true включает учебный каталог и детерминированную погоду с пометкой Demo
Overpass__Endpoint HTTPS endpoint выбранного Overpass-сервера; по умолчанию https://overpass-api.de/api/interpreter. При недоступности сервера можно явно выбрать совместимое зеркало; адреса с другими схемами и встроенными credentials отклоняются при старте
Overpass__SnapshotPath Резервный Overpass JSON с геометрией; по умолчанию data/moscow-water-bodies.json. Относительный путь считается от каталога приложения (AppContext.BaseDirectory), абсолютный используется напрямую. Файл включён в build/publish и Docker image
Overpass__PreferSnapshot По умолчанию true: сначала используется доступный снимок каталога. Для свежего запроса к Overpass задайте false; при сбое сети сохранённый снимок остаётся резервом
Uploads__Path Каталог фотографий; по умолчанию uploads внутри content root
Cors__Origins__0 Разрешённый frontend origin; по умолчанию http://localhost:5173

В live-режиме используются только Open-Meteo и реальный импорт OSM. Учебные водоёмы и Demo-снимки скрываются при отключении Demo даже при использовании той же БД. Пароли Admin, JWT и доступ к БД не заданы в appsettings.json.

При синхронизации существующего Admin сохраняются его ID, nickname, блокировка и история. Настройка Admin__Nickname не переименовывает существующий аккаунт.

API и модель

Base path /api/v1; актуальный контракт — ligmaba-docs/api/openapi.yaml. Ошибки имеют поля {code,message,traceId}. Гостю доступны маркеры по bbox и статус каталога; поиск, фильтры и карточки требуют входа. Logout отзывает текущую JWT-сессию. Проверка блокировки выполняется при каждом запросе с токеном. Пароли хранятся в PBKDF2-хэшах ASP.NET Identity (210000 итераций).

16 таблиц EF Core покрывают пользователей/сессии, водоёмы/виды рыб, избранное, погодные и индексные снимки, комнаты/сообщения/события чата, отчёты, жалобы, внешние ссылки, блокировки и историю импортов. Миграция создаёт PostGIS extension, вычисляемый geography(Point,4326), GiST-индекс, уникальные связи и ограничения значений. Радиус использует ST_DWithin; viewport — диапазоны координат.

Fishing Index 2.0 — детерминированная эвристика условий, не вероятность улова. Веса: давление 25, ветер/порывы 20, температура воздуха 15, осадки 10, облачность 10, время суток 10, сезонность 10. Давление учитывает достаточность трёхчасовой истории; погодные кривые и часовой/месячный профили дополняются пределом при экстремальных значениях. При ограничении все семь вкладов пропорционально уменьшаются с распределением дробных остатков, сохраняя точную сумму. Коэффициенты и ограничения достоверности — в описании формулы. Ответ индекса содержит formulaVersion: "2.0", текущую точку, 48 будущих часов при наличии данных (минимум 24), семь вкладов и лучшее время ближайших суток. Сценарий AI best_time использует оставшуюся часть сегодняшнего дня по Москве.

Погодный кэш хранится в БД, повторные запросы в течение 30 минут не обращаются к провайдеру. При сбое подходят снимки не старше суток, содержащие минимум 24 ещё будущих часа и текущий сигнал не старше часа; иначе возвращается 503 IndexUnavailable. Активные индексы карты, избранного, административного списка, карточек и AI вычисляются при чтении по текущей формуле из WeatherSnapshot, с общим выбором точки CurrentSignal. FishingIndexSnapshot сохраняется как история, но старые числовые оценки не заменяют активную версию. Для маркеров не выполняется внешний запрос на каждый водоём. Фоновое обновление обрабатывает до восьми водоёмов за цикл с минутной паузой: сначала без прогноза, затем с самым старым снимком. Большой каталог не прогревается синхронно при старте API; в Demo прогреваются только учебные записи. AI ранжирует все подходящие пригодные кэшированные прогнозы; за запрос дополнительно запрашивает погоду максимум для 12 ближайших водоёмов без пригодного кэша. Скрытой подмены радиуса/рыбы нет. Для compare обязательны ровно два разных waterBodyIds.

OSM импортирует только именованные озёра, водохранилища, пруды и карьеры Москвы и Московской области. В комплект входит реальный снимок из региональных OSM PBF от 15.10.2026; источники, лицензия, контрольные суммы и команда обновления приведены в data/README.md. По умолчанию он обеспечивает первый импорт без зависимости от доступности публичного Overpass. Это снимок географического каталога; погода в live-режиме запрашивается отдельно у Open-Meteo.

При Overpass__PreferSnapshot=false синхронизация выполняет восемь последовательных запросов: четыре типа для RU-MOW и RU-MOS. Каждый успешный сегмент сохраняется отдельно; без резервного снимка сбой части запросов даёт статус partial, а не теряет уже полученные данные. Ключ (source,externalId) и учёт повторов внутри запуска предотвращают дублирование; редакционные изменения имени/типа сохраняются при синхронизации. В Demo автозапуск импорта отключён; ручной запуск использует те же настройки источника.

Координата маркера вычисляется через NetTopologySuite 2.6.0 как внутренняя точка реальной поверхности. Для multipolygon собираются внешние контуры и исключаются внутренние кольца островов. Центр bounding box не используется: он может оказаться на суше. Неполная или невалидная геометрия пропускается. Для старых объектов natural=water без water тип выводится только из однозначного названия («пруд», «озеро», «водохранилище», «карьер»); явно неизвестный подтип воды не превращается автоматически в озеро.

Чат использует REST polling. Создание CatchReport и ChatEvent атомарно; неизвестная для конкретного водоёма рыба разрешена и не меняет редакционный список. Удаление контента мягкое: событие исчезает из чата, жалоба остаётся доступной модератору. Фото: защищённый multipart upload до 5 МБ, JPEG/PNG/WebP с проверкой сигнатуры; имена файлов генерирует сервер.

Проверка

dotnet test tests/LigmaBa.Api.Tests/LigmaBa.Api.Tests.csproj --configuration Release

Тесты используют WebApplicationFactory и реляционный SQLite: гостевой доступ, регистрация/хэши, текущие и будущие часы, избранное, отдельный CatchReport/событие, неизвестная рыба, сравнение выбранной пары, жалобы, чужое удаление, блокировка действующего токена и logout. Тесты провайдеров проверяют Open-Meteo normalization, семь факторов/категории, постоянный кэш при отказе погоды, изоляцию live/Demo, ограниченное обновление старейших прогнозов и идемпотентный OSM upsert. Отдельно проверяются вогнутый водоём, составной контур с островом, отказ от bbox-центров, частичный импорт, резерв при сбое сети и весь реальный bundled-каталог с относительным путём к файлу. Тесты не требуют интернета или настоящих секретов.

CI выполняет тесты и отдельно запускает API с настоящим PostGIS/PostgreSQL, проверяя применение миграции и /health. Полный HTTP smoke для контейнеров находится в ligmaba-docs/scripts/smoke_lab3.py; он проверяет основные сценарии на PostgreSQL, включая загрузку фото и пространственный радиус.