Обзор и главная цель
Продукт объединяет тело (измерения, вода, калории), режим (напоминания о движении, дыхании, воде и отдыхе для глаз), нагрузку (каталог упражнений, планы, сессии, звуки и таймеры), питание (планы приёмов пищи и справочник КБЖУ), а также деньги с совместными кошельками и связями между аккаунтами — всё через один клиентский опыт на Flutter.
Текущий продукт (waywell.ru): доменные данные в PostgreSQL, клиент — Flutter Web/mobile через HTTP API и SSE; вход — JWT (email/пароль) и опционально Маркбэйс id (UAM); медиа — MinIO. Ниже в разделах «архив AS-IS» сохранено описание legacy-стека Cloud Firestore для инженерного аудита.
Архив AS-IS: ранее данные лежали в Cloud Firestore с SDK Firebase и подписками snapshots();
вход выполнял Firebase Authentication, файлы — Firebase Storage, публикация web — Firebase Hosting.
Целевое состояние (REFANDBD): единый вход и регистрация через Маркбэйс id (UAM); после редиректа приложение передаёт одноразовый
wsid_code на свой backend и получает пару JWT (POST /v1/auth/uam/exchange на API api.waywell.ru).
Сквозное проектирование сессий и правил: REFANDBD/AUTH_SYNC_SESSION_AND_CLIENT_DESIGN.md,
обзор WayFit + UAM: REFANDBD/WAYSENID_UAM_PLATFORM_INTEGRATION.md, протокол SSO в корне:
AUTH_THIRD_PARTY_SITES_WAYSENID_RU.md. Полный реестр того, что ещё поправить в текстах и коде:
REFANDBD/REQUIRED_FIXES_FULL_INVENTORY.md.
Именно поэтому рядом с кодом поддерживается папка REFANDBD/ — «паспорт миграции»: инвентаризация каждого касания Firebase,
черновик REST /v1 в OpenAPI, набросок SQL-схемы Postgres, realtime-контракт (SSE/WS), операционные makefile/docker-compose/nginx,
матрицы прав и чеклисты cutover. Это связный план переезда без потери фич WHOOP/Telegram и без ломания OAuth-редиректов.
landing/index.html в браузере — сеть не нужна.
Что входит в репозиторий (логическая карта)
Продукт живёт не только в lib/. Рядом лежат платформенные оболочки android/ и ios/,
веб-шаблоны web/, каталог telegram-bot/ для серверной стороны Telegram, папка scripts/ для сидов данных и конвейера OpenFoodFacts,
а также REFANDBD/ — отдельный «пакет зрелости»: OpenAPI-черновик (тысячи строк), снимки firestore.rules и индексов,
примеры nginx, docker-compose и политики операций. Такой развод удобен при продаже прав или передаче проекта: код и инженерное наследие не размазаны по чатам.
Как всё работает вместе (от запуска до сохранения)
Ниже — один связный рассказ «что вообще происходит», без проваливания в код. Если разобрать этот блок, остальные разделы страницы — просто расшифровка деталей.
-
Пользователь запускает приложение. Flutter вызывает
Firebase.initializeAppс ключами изfirebase_options.dartдля текущей платформы (Android, iOS, Web…). На не-web платформах включается persistence Firestore, чтобы кэшировать снимки запросов и работать офлайн с последней известной копией данных. -
Вход в учётную запись. Сегодня: Firebase Authentication (email/password); провайдер
auth_provider.dartслушаетauthStateChanges— от этого дерево экранов (логин/регистрация или приложение). У залогиненного пользователя стабильный UID (ключusers/{uid}и поля владельца в коллекциях). После миграции: сессия приложения — пара JWT после обменаwsid_codeна Маркбэйс id; «кто я» — claimsubи профиль сGET /v1/auth/me(см.AUTH_SYNC_SESSION_AND_CLIENT_DESIGN.md). -
Экран просит данные через Riverpod. Например, список напоминаний: провайдер обращается к
reminder_repository.dart, тот строит запрос Firestore к коллекцииremindersс фильтром по текущему или «выбранному» пользователю (см. связи ниже). Репозиторий подписывается на поток черезsnapshots(): при любом изменении соответствующих документов в облаке UI получает новую версию списка без опроса сервера по таймеру. -
Пользователь что-то меняет на экране (добавил расход, отметил воду, завершил подход тренировки). Виджет вызывает метод репозитория:
документ записывается в Firestore (
set, иногда сmerge: true) или отправляется пакетbatch— когда нужно сохранить несколько документов согласованно; ограничения Firestore по размеру батча и числу операций учитываются в реализации репозиториев. Другие устройства этого же пользователя, подписанные на тот же запрос, получают обновление через тот же механизмsnapshots(). -
Напоминания — два слоя. Правило жизни (расписание, тип напоминания) хранится в Firestore. А системное будильничье время на телефоне
ставит уже
notification_service.dartчерез пакет локальных уведомлений (иногда нужен точечныйGETданных напоминания по id после срабатывания). -
Внешние сервисы. WHOOP живёт своим HTTPS API; приложение после OAuth держит токены и тянет историю в
whoop_sync. Telegram-бот — это отдельный процесс Node.js с правами администратора Firestore и теми же именами коллекций, что клиент — он не «рисует UI», только пишет данные по командам. Продукты OpenFoodFacts в основном попадают в приложение через офлайновый конвейер и пакетную загрузку в Firestore; живой онлайновый запрос к API OpenFoodFacts на каждый поиск пользователем не является обязательным сценарием работы клиента. -
После перехода на свой backend. Клиент вместо Firestore будет ходить в REST (
POST/GET/PATCH), а живые обновления — через SSE или WebSocket по каналам изINVENTORY_REALTIME.mdиREALTIME_SPEC.md; Postgres станет источником истины по домену.
Как пользоваться этим документом
- Сверху вниз по оглавлю «Продукт» — если нужна цельная картина: зачем приложение кому нужно и какие сценарии оно покрывает (здоровье → напоминания → спорт → деньги → админка).
- Блок «Инженерия» — если вы разработчик, интегратор или покупаете технический актив: где лежит Firebase в коде, как устроены права доступа, какие каналы realtime планируются после Postgres.
- «Стратегия» и оценка в конце — миграция, риски, офлайн, глоссарий; финальный раздел про стоимость относится только к активу репозитория, без пользовательской базы и без учёта труда авторов после передачи.
Внутри разделов технические имена помечены как путь/к файлу.dart или имяКоллекции, чтобы они совпадали с деревом проекта и с REFANDBD.
Если нужна проверка факта, приоритет у актуальных исходников и файлов индекса в REFANDBD/, а этот лендинг — связная человекочитаемая выжимка.
Для кого создан продукт
Частные пользователи
Дневник здоровья, графики, PDF-отчёты, понятная картина TDEE и баланса калорий, напоминания о воде и микродвижении.
Семьи и пары
Совместные финансовые контура, общие кошельки, синхронные изменения; при желании связи между аккаунтами с разными уровнями доступа.
Специалисты
Тренеры и диетологи опираются на планы тренировок и питания, историю выполнения и статистику прогресса по упражнениям и весам.
Администраторы
Ведение глобального каталога упражнений, меток и категорий, загрузки медиа, глобальные «плитки» и параметры приложения, обзор пользователей.
Здоровье, активность и энергобаланс
Антропометрия
Ввод и история по весу и росту; набор телесных объёмов (шея, плечи, грудь, талия, таз, бедра, конечности, запястья и др.) — с интерфейсами схемы тела и графиками динамики. Экспорт в PDF упаковывает период, таблицы и визуализации для офлайного архива или врача/тренера.
Дневник дня и КБЖУ
Учёт потребляемых и расходуемых калорий, воды с быстрыми пресетами (стакан, бутылка), текстовые заметки активности по категориям и времени начала (работа, спорт, отдых и т. д.) — с автосортировкой по времени. Отдельно ведётся база пользовательских и референсных продуктов с калорийностью и БЖУ.
Энергетический баланс (TDEE)
- BMR через формулу Mifflin–St Jeor.
- Профессия / активность дня как множители к базовым затратам.
- Шаги добавляются в калорийную математику (в т. ч. синхронно с траекторией WHOOP, где включено).
- Режимы поддержания / дефицита / профицита; целевой дневной бюджет калорий пересчитывается при смене веса или стратегии.
Справочник продуктов строится на конвейере OpenFoodFacts: огромный сырой дамп фильтруется (КБЖУ на 100 г, русскоязычные названия и пр.),
нормализуется Node.js-скриптами и загружается в Firestore в коллекцию food_reference_products —
приложение находит позиции по поиску и автозаполняет калории пользователя без обязательного онлайнового офф-сервера на каждый тап.
Напоминания: типы, расписание, уведомления
Шесть направлений
Дыхание
Техники 4-7-8, квадратное дыхание, анимации и длительность сессии.
Спорт и разминка
Лёгкие и целевые активности по зонам тела без полноценной трен-сессии.
Вода
Гидратация по расписанию; возможна связь учёта выпитого с действием из уведомления.
Глаза
Короткие упражнения для снятия зрительного напряжения.
Пользовательские типы
Свои тексты, иконки и смысл под ответственность пользователя.
Визуал на дне
Состояние «сделано / пропущено» с акцентом на сегодня и смягчением прозрачности для других дат.
Гибкая сетка времени
- Каждый час или каждые N часов (1–24).
- Фиксированный список времён (например 10:00, 14:00, 18:00).
- Выбор дней недели и «тихих» дней исключений.
- Несколько активных интервалов в один день: например 09:00–15:00 и 19:00–21:00 без ночных пингов.
Flutter Local Notifications держит реальное срабатывание с учётом часового пояса; содержание правил синхронизируется через Firestore;
журнал выполнения хранится в reminder_completions отдельно от шаблонов reminders.
Для продолжительной офлайн-работы клиент может опираться на кэш и локальную очередь, а после миграции — описанную в REFANDBD стратегию без обязательного GET к облаку на каждый будильник.
Тренировки, календарь и планы питания
Хаб активности
Единая точка входа группирует календарь тренировок, конструктор планов, библиотеку упражнений и быстрые модули (дыхание, забота о глазах, смежное). В каталоге — категории (грудь, спина, ноги, кардио, растяжка…) и маркеры оборудования; поддерживаются типы нагрузки: повторения, время, дистанция, интервалы, йога с временем позы.
Конструктор и выполнение
- Drag-and-drop порядка; подходы, вес общий или по подходу, паузы между подходами и между упражнениями.
- Суперсеты — цепочка упражнений без полноценного отдыха между ними по задумке плана.
- На экране выполнения: план против факта в каждой итерации, таймеры отдыха, голосовые/звуковые сигналы смены.
- После финиша сессии — объём работы в штуках времени × вес × повторений, суммарное время, вклад калорий в активность там, где реализована связка.
Питание по календарю
Приёмы с названием (завтрак / перекус), окном времени, списком продуктов с граммовкой и авто-КБЖУ, отметками «выполнено», заметками и напоминаниями. Календарный вид позволяет видеть занятость дней без перегруза таблицами.
Финансы, совместная экономика и матрица доверия
Модуль месячного бюджета выводит дневной остаток; «долги» начала периода сужают оставшийся люфт до конца месяца так, чтобы человек понимал реальную дисциплину расхода. Накопления в построении лимита сознательно не раздувают «сегодня можно всё»: логика сфокусирована на месячной рамке и последствиях перерасхода. Расходы категорируются, фильтруются, отображаются в календаре и секциях аналитики; опционально портфель активов дополняет картину сбережений.
Связи (connections)
Пользователь может пригласить второй аккаунт по ссылке; после акцепта формируется запись связи и набор прав в обе стороны:
только просмотр, редактирование данных, установка целей и создание планов тренировок и питания — в зависимости от выпущенных разрешений.
В приложении выбирается целевой пользователь (сам или партнёр), и все репозитории переводят запрос на эффективный userId,
если клиентская проверка PermissionChecker и правила Firestore допускают действие.
Администрирование, аналитика и выгрузки
Каталог упражнений
Создание и правка упражнений, медиафайлов, активационные флаги; метаданные категорий и тегов.
Пользователи
Просмотр с фильтром, блокировки, изменение админ-флагов, сводная активность — по реализации страниц.
Глобальные настройки
Документ app_settings/global, merge patch и связанное медиаконтент-хранилище.
Выгрузки
PDF клиентским рендером (табличные блоки и графики), JSON как машиночитаемый слепок для анализа.
Аналитический экран может делать точечные get() нескольких коллекций одновременно — такой паттерн важно учитывать при переносе на ценовые лимиты API и агрегаты в Postgres.
Масштаб кодовой базы (ориентиры из портфолио-дока)
| Файлов Dart | 150+ |
| Строк кода | 30 000+ |
| Виджетов · страниц · провайдеров | 100+ / 50+ / 50+ |
| Основных доменных модулей | 8 |
| Интеграций сервисов | WHOOP, Telegram Bot API, конвейер OpenFoodFacts |
Конкретные цифры со временем дрейфуют по мере разработки; для точности сверьте генератор статистики репозитория или последнюю редакцию файла портфолио в корне проекта.
Технологический стек
Клиент Flutter
Flutter 3.x / Dart 3.x, Riverpod как основное управление состоянием совместно с go_router для дерева экранов; Material 3, пользовательские шрифтовые и SVG-решения там, где подключены пакеты; fl_chart для графиков; flutter_local_notifications и timezone для планируемых локальных напоминаний; Hive и SharedPreferences для ключей и быстрой локальной памяти; http для внешних REST вызовов (Whoop и др.); app_links связывает OAuth и приложение с HTTPS-редиректами. Дополнительно в продукте задействуются пакеты уровня pdf / printing для отчётов, permission_handler для системных разрешений, по проекту возможен Provider рядом с Riverpod для исторических или точечных кейсов.
Серверный контур (реализовано)
PostgreSQL 16+, HTTP API /v1 на api.waywell.ru,
MinIO для медиа, SSE для realtime, nginx для Flutter Web SPA.
Telegram-бот и seed-утилиты — Node.js без firebase-admin (internal HTTP API).
Архив AS-IS: Firebase (до миграции)
Ранее в клиенте использовались firebase_core, firebase_auth,
cloud_firestore, firebase_storage — удалены из runtime (Gate W12).
Архитектура: слои как контракты
- presentation — экраны по доменам (auth, home, reminders, health_metrics, activity, workouts, meal_plans, finances, connections, admin, settings) и переиспользуемые виджеты.
- domain — типы-сущности без Firebase: напоминания, упражнения, бюджет, связи пользователей и т. д.
- data — репозитории: перевод между entity и моделью документа, запросы
where/orderBy/limit, батчи. - core — auth провайдер, глобальные настройки, sync уведомлений и WHOOP-сервисы, загрузчики медиа, тема Router.
Realtime UX на сегодня — потоковые подписки Firestore, обёрнутые в Riverpod-провайдеры; многопользовательский режим подменяет
userId в подписке на «выбранного» человека только после успешной проверки связи и матрицы разрешений.
Дерево lib/ (упрощённо)
Такое разнесение упрощает тестирование домена отдельно от облака: в тестах подставляют фейковый репозиторий на интерфейс, не трогая виджеты.
Вход в систему, UID и модель доступа
Сегодня идентичность задаётся JWT WayWell (email/пароль) и опционально Маркбэйс id (UAM).
Прикладной профиль живёт в PostgreSQL (users.id). Архив AS-IS: ранее uid совпадал с
users/{uid} Firebase Authentication. При необходимости сохраняется связь с Маркбэйс id
(uam_user_id в PG / uamUserId в JSON API).
Целевое: глобальная учётная запись и пароль (если включены) живут в UAM; WayFit хранит прикладной профиль и выпускает только производные access/refresh после POST /v1/auth/uam/exchange.
Матрица приглашений и тренерского доступа (connections, permission_checker.dart) не меняется по смыслу — меняется лишь способ доказать пользователя (Bearer JWT вместо Firebase ID token). Сервер не доверяет подставленному targetUserId без проверки связи — см. PERMISSION_MATRIX.md и AUTH_AND_SECURITY.md в REFANDBD.
Сервер Firestore Rules против логики в приложении
Rules в firestore.rules дают грубую границу: авторизован / не авторизован, свой документ / чужой.
Отдельные коллекции исторически открывались для чтения/записи широкому кругу авторизованных пользователей, рассчитывая на то, что
тонкая проверка режима «чужие данные» выполняется в клиентском permission_checker.dart.
Это уязвимость архитектуры «доверять клиенту» — именно её закрывает целевой HTTP API с проверками на каждом маршруте (см. AUTH_AND_SECURITY.md в REFANDBD).
Связи между людьми (connections)
Если пользователь А пригласил пользователя Б, между ними существует запись в connections со статусом active.
В сущности связи хранятся поля вроде inviterPermissions и inviteePermissions:
отдельно для здоровья, питания, напоминаний, активности, воды, финансов — с уровнями просмотра/редактирования;
плюс флаги «можно ли ставить цели / идеальные параметры». UI выбирает целевого пользователя и подставляет его uid в запросы репозиториев,
но в проде такой параметр после миграции нельзя принимать вслепую от клиента без серверной проверки той же матрицы, что уже описана для PermissionChecker.
Финансы и кошельки
Совместное ведение денег требует второго ограничителя: операции должны выполняться только в контексте того wallet,
где человек указан как владелец или участник массива memberIds.
Списковые эндпоинты в будущем не должны «протекать» между кошельками просто потому что кто-то подделал id в HTTP-запросе.
Bootstrap-администраторы
В эталонных правилах используется функция вроде isBootstrapAdmin() по списку email в токене:
такие люди могут менять роли пользователей, трогать app_settings/global и расширенные метаданные каталога.
На своём backend этот список должен жить в переменных окружения или таблице конфигурации, а поле role: admin нельзя давать ставить клиентскому коду напрямую.
Firestore: коллекции и репозитории (архив AS-IS)
Полный перечень полей — в REFANDBD/COLLECTION_FIELD_REFERENCE.md; индексация запросов для будущего SQL — в
REPOSITORY_QUERY_INDEX.md. Ниже — сквозная карта «домен → коллекции → файл» для чтения кода людям и генераторам чужого API.
| Домен | Коллекции | Репозиторий |
|---|---|---|
| Профиль и учётная запись | users | user_repository.dart |
| Расписание и история выполнений | reminders, reminder_completions | reminder_* |
| Метрики тела по дням | health_metrics | health_metric_repository.dart |
| Ежедневная активность / калории / вода | activities | activity_repository.dart |
| Каталог упражнений и истории | exercises, exercise_history, completions | exercise_* |
| Таксономия каталога | exercise_categories, exercise_tags | exercise_meta_repository.dart |
| Сессии и структура тренировок | workouts, workout_sessions | workout_repository.dart |
| План на календарь | workout_plans | workout_plan_repository.dart |
| Приёмы пищи | meal_plans | meal_plan_repository.dart |
| Пользовательские и референсные продукты | food_products, food_reference_products | food_* |
| Связи аккаунтов | connections (+ чтение users для отображения профилей в UI) | user_connection_repository.dart |
| Деньги | семья finance_* | finance_repository.dart, портфель |
| WHOOP | whoop_credentials, whoop_tokens, whoop_sync | core services + whoop_sync_repository.dart |
Глобальные параметры приложения — документ app_settings/global, на который подписана админ-панель и провайдер настроек.
Карта точек входа Firebase (архив AS-IS)
Чтобы понимать «где именно включён облако», ниже сводка из REFANDBD/FIREBASE_TOUCHPOINTS_INDEX.md:
каждая строка отвечает за конкретный технический аспект — от инициализации до точечного GET напоминания или загрузки файла.
Это облегчает код-ревью миграции «ничего не забыли».
| Путь в репозитории | Роль |
|---|---|
lib/main.dart | Старт Firebase, настройки экземпляра Firestore, синхронизация с FirebaseAuth.instance. |
lib/firebase_options.dart | Сгенерированная конфигурация платформ для firebase_core. |
lib/core/providers/auth_provider.dart | Сегодня: вход, регистрация, выход, поток authStateChanges. Цель: сессия JWT после UAM (REQUIRED_FIXES_FULL_INVENTORY.md). |
lib/core/providers/app_settings_provider.dart | snapshots() на документ app_settings/global. |
lib/core/providers/sync_providers.dart | Не импортирует Firebase напрямую; связывает логику синхронизации локальных уведомлений с потоком напоминаний из репозитория. |
lib/core/services/image_upload_service.dart, media_upload_service.dart | Загрузки в Firebase Storage от текущего пользователя. |
lib/core/services/notification_service.dart | Точечное чтение документа reminders/{id} при необходимости. |
lib/core/services/whoop_service.dart | Документы whoop_credentials, whoop_tokens + ключи в SharedPreferences. |
lib/data/repositories/*.dart | Коллекции из индекса репозиториев (таблица выше) — основной объём чтения и записи приложения. |
Админ-страницы admin_*_page.dart | Прямые стримы или разовые запросы к users, app_settings/global, workout_plans, многоколлекционная аналитика. |
Web (архив): ранее web/index.html подключал скрипты Firebase; сейчас удалено.
Зависимости (архив): пакеты Firebase удалены из pubspec.yaml (Gate W12).
Вне клиента: legacy Admin SDK только в quarantine seed-скриптах под scripts/REFANDBD/legacy/.
Потоки данных: от жеста до облака
Стартовая последовательность (архив AS-IS)
- Ранее: Firebase.initializeApp с
firebase_options.dart(файл удалён). - Параметры persistence/cache Firestore для офлайновых устройств.
- authStateChanges включал защищённые маршруты (заменено JWT + Riverpod).
Связка UI ⇄ поток данных
Виджет через ref.watch(streamProvider) получает поток из репозитория; тот подписан на Firestore-запрос (фильтры по пользователю, датам, кошельку и т. д.).
SDK эмитирует последовательность снимков; интерфейс обновляется без ручного опроса по таймеру (обновление по «потянуть вниз» остаётся опциональным UX).
Изменение данных пользователем
Запись идёт из UI в метод репозитория. Для одного документа обычно достаточно set с полным объектом или merge: true, когда нужно затронуть часть полей без затирания всего документа.
Если в одном жестре пользователя меняются взаимосвязанные сущности (например, закрыть тренировку и обновить статистику), часть команд собираются в транзакцию или WriteBatch — так Firestore выполняет пакет либо целиком, либо не применяет его, что снижает риск «половины экрана устарело» после сетевого сбоя.
Цель после миграции
Realtime-слой проектируется через именованные каналы (finance_wallet:{id}, workout_plans:user:{uid} и др.) и версионированное JSON-событие;
при payload: null клиент инициирует точечный REST fetch — паттерн снижает ширину канала против «тащить документ целиком на каждый чих».
См. REALTIME_SPEC.md.
Realtime сегодня (.snapshots()) и целевые каналы завтра
Любая подписка через snapshots() на запрос Firestore — это текущий «websocket-подобный» слой: сервер Google пушит изменение документа,
клиентский SDK поднимает событие в Dart Stream, Riverpod перестраивает дерево виджетов.
После перехода на свой backend эти подписки нужно сопоставить именованным каналам (SSE/WebSocket) и событию минимум с полями:
op (created | updated | deleted), id, updated_at, опционально усечённый payload.
Параллельный REST отдаёт первоначальную полноту данных после офлайна — realtime только докидывает дельты.
| Репозиторий / источник | Смысл данных | Рекомендуемое имя канала после миграции |
|---|---|---|
finance_repository.dart | Кошельки, бюджеты, расходы и связанные коллекции | finance_wallet:{walletId} или агрегат по пользователю |
reminder_repository.dart | Список напоминаний под целевого пользователя | reminders:user:{effectiveUserId} |
workout_repository.dart | workouts, workout_sessions | workouts:user:{uid}, workout_sessions:user:{uid} |
workout_plan_repository.dart | Планы по датам | workout_plans:user:{uid} |
meal_plan_repository.dart | Приёмы пищи | meal_plans:user:{uid} |
exercise_repository.dart | Каталог и история упражнений | exercises:global / exercise_history:user:{uid} |
exercise_meta_repository.dart | Категории и теги | exercise_meta (или две подтемы) |
activity_repository.dart | Дневник активности дня | activities:user:{uid} |
health_metric_repository.dart | Метрики тела | health_metrics:user:{uid} |
reminder_completion_repository.dart | Факты выполнения напоминаний | reminder_completions:user:{uid} |
user_connection_repository.dart | Приглашения и связи | connections:user:{uid} |
finance_portfolio_repository.dart | Портфель по кошельку | finance_portfolio:{walletId} |
whoop_sync_repository.dart (+ сервисы WHOOP) | Слепки интеграции Whoop под пользователя | integrations:whoop:{uid} или отдельные подканалы под тип данных |
main.dart для не-web включено persistenceEnabled: true — после ухода с Firestore нужно сознательно выбрать стратегию офлайна:
либо online-first API, либо локальный кэш (Hive/SQLite) с очередью идемпотентных мутаций. События realtime должны сливаться с кэшем по recordId и версии updated_at.
Firebase Storage: типовые префиксы объектов
Ключ нужен для понимания миграции бинарей в объектное хранилище будущего backend; список согласован с инвентаризацией touchpoints Firebase.
| Область | Префикс / путь |
|---|---|
| Медиа упражнений | exercises/images, videos, gifs |
| Иконография метаданных | exercise_meta/categories, tags |
| Финансовые вложения UI | coupons, cards |
| Прочие пользовательские вложения | папка параметром из родительского экрана (image picker widget) |
Интеграции: детальный конвейер
WHOOP
Пользователь вводит ключи приложения WHOOP Developer; они кешируются в SharedPreferences и дублируются в документы
whoop_credentials/{uid} — чувствительные поля должны переехать под шифрование at-rest после миграции.
Авторизация OAuth требует HTTPS redirect на каноническом домене (в спецификации — комбинация
nginx location /whoop-callback ↔ статическая страница ↔ разбор параметров приложением Flutter Web/Mobile через deep linking).
Authorization code от провайдера Whoop обменивается на пару access / refresh токенов; результат кладётся в whoop_tokens/{uid};
сервис синхронизации обращается к
api.prod.whoop.com и сохраняет восстановление, сон, тренировки, цикл и измерения профиля для UI и последующего зеркалирования в PostgreSQL.
Калории тренировок WHOOP синхронизируются обратно в дневник активности приложения там, где реализована эта связка домен ←→ активность дня.
OpenFoodFacts и офлайн-сидирование
Дамп данных скачивается и обрабатывается офлайн скриптами (порядок десятков гигабайт сырья), фильтруется и пакетно заливается через
Firebase Admin SDK — например сценарии scripts/build_food_reference_from_openfoodfacts.js и scripts/setup_food_reference_products.js.
Так референсный справочник в коллекции food_reference_products готов к быстрому поиску в клиенте без внешнего HTTP на каждую карточку продукта.
Скрипты тренировочного контента
Отдельный сид scripts/setup_builtin_workouts.js может наполнять встроенные планы/шаблоны в коллекцию workouts — см. индекс touchpoints.
Telegram-бот: как он подключается к тем же данным
Каталог telegram-bot/ — отдельное Node.js приложение. Переменные окружения: TELEGRAM_BOT_TOKEN (выдаёт BotFather) и
FIREBASE_ADMIN_SDK_PATH до JSON сервисного аккаунта. После появления своего API к боту добавятся API_BASE_URL
и INTERNAL_SERVICE_TOKEN для служебных маршрутов согласно черновику REFANDBD/env/.env.example.
Важно: бот не должен после миграции ходить напрямую в PostgreSQL из открытого интернета — только в приватный backend (HTTPS + service token).
Обработчики и затрагиваемые коллекции
| Модуль | Коллекции / действия |
|---|---|
handlers/auth.js | Поиск users по email; при регистрации через бота связка с Auth; документы telegram_link_requests (id = telegramId, клиентский write запрещён — только Admin); обновление профиля пользователя (Telegram id). |
handlers/finance.js | finance_expenses, finance_categories, finance_budgets, finance_wallets, finance_mandatory_expenses, чтение части users. |
handlers/sharedFinance.js | finance_wallets, finance_budgets, finance_expenses — общие кошельки со строгими проверками членства. |
handlers/health.js | health_metrics, activities — записи через set(merge: true). |
index.js + хелперы | Точечное обновление finance_expenses, обновление связанных полей users при привязке. |
Идентификация в целевой модели строится как однозначное сопоставление telegram_id ↔ пользователь в таблице Postgres; все команды бота приравниваются к REST-операциям
/v1/finance/..., /v1/health-metrics, /v1/activities от лица найденного пользователя.
Риск «бот создаёт пользователей напрямую» на не-migrated стеке нужно закрыть антиабьюзом (rate limit, капча при необходимости) на будущем endpoint.
Канонический домен, веб-хостинг и магазины
Целевая производственная картина (после перехода с Firebase-only) описана для домена waywell.ru и поддоменов типа
api.waywell.ru, отдельно — staging на staging*.waywell.ru. Только HTTPS; loopback-хосты
не задаются как «цель» клиентским контрактам в REFANDBD-политиках. Для выпусков Android нужен релизный keystore не в Git, RuStore/Google Play —
по чеклистам в том же семействе документов; возможный ребрендинг ApplicationId против текущего
etostor.ru в Gradle — это продуктовое решение (смена id даёт новую карточку магазина).
- Веб SPA — Flutter на
app.waywell.ru; лендинг и политика/privacypolicy— nginx наwaywell.ru(не Firebase Hosting). - Deep links OAuth — маршрут
/whoop-callbackсинхронизирован с Whoop Developer Console и сборкой приложения. - Сборочные ключи API — через
dart-define API_BASE_URL=…на конвейере релизов.
Локальная разработка, сборки и синхронизация команд
- Клонировать репозиторий, запустить
flutter pub get; секреты — только в.envна сервере (не Firebase JSON в git). - Скрипт
setup_local.ps1упрощает развёртывание машины нового разработчика в Windows-средах. - Android CI/signed release — отказ от debug-keystore для продукта по гайдам Flutter/Google Play/RuStore.
- Firestore rules и indexes — артефакты в snapshots; выкладывать через Firebase CLI до момента замены пайплайна.
Дополнительно в корне есть материалы о синхронизации рабочих мест (SYNC_INSTRUCTIONS.md, ветка документации @docs/),
чтобы несколько машин держали одинаковые шаги сборки без утечки секретов в git.
Стратегия REFANDBD: от клиентского Firebase к своему API
Принцип готовности: каждый клиентский вызов Firestore, перечисленный в FIREBASE_TOUCHPOINTS_INDEX.md, должен иметь осмысленный аналог —
HTTP-эндпоинт в openapi/openapi.draft.yaml и/или событие в realtime-слое. Правила из firestore.rules (эталон — REFANDBD/snapshots/firestore.rules)
переосмысливаются как проверки на сервере: кто я по JWT, имею ли право видеть чужой userId или чужой кошелёк, не подставил ли клиент лишний wallet id.
Миграция данных описывается как выгрузка Firestore → трансформация под SQL → загрузка в Postgres и поэтапное переключение клиентов по feature flags (FULL_MIGRATION_PLAYBOOK.md, DATA_EXPORT_AND_CUTOVER.md).
Параллельно зафиксированы: реестр кодов ошибок API (ERROR_CODES_REGISTRY.md), политика офлайна после ухода из кэша Firestore (OFFLINE_AND_SYNC_POLICY.md),
перенос объектов Storage (STORAGE_AND_MEDIA_SPEC.md), сценарии уведомлений без обязательного облачного GET (NOTIFICATIONS_WITHOUT_FIREBASE.md),
матрица прав (PERMISSION_MATRIX.md).
Вход и сессии: AUTH_SYNC_SESSION_AND_CLIENT_DESIGN.md, WAYSENID_UAM_PLATFORM_INTEGRATION.md,
инвентарь замены Firebase Auth в коде: AUTH_FIREBASE_TO_UAM_MIGRATION.md; протокол партнёрского SSO:
AUTH_THIRD_PARTY_SITES_WAYSENID_RU.md (корень репозитория).
PLAN_TRACEABILITY.md. Для оглавления всех Markdown в папке — MASTER_INDEX.md.
Безопасность, офлайн и наблюдаемость
Клиент никогда не считается единственным хранителем правды о правах: грубый контур дают Firestore Rules, тонкий — логика приложения; целевой API обязан проверять связи, кошельки и админство на сервере без доверия к подставленным клиентом id.
Трафик между устройством и облаком Google — поверх TLS; сервисные JSON Firebase Admin для бота и CI лежат только в секретах (LOCAL_SECRETS.md).
После миграции ожидается классическая пара access / refresh JWT (или session cookie на web), хранение refresh в хэше в БД.
Пароль учётной записи в массовом сценарии с Маркбэйс id обслуживается на стороне UAM; WayFit API не дублирует его как второй owner-контур
(см. INTEGRATION_SOURCE_OF_TRUTH_2026.md). Локальный пароль на своём backend — только осознанный fallback или полностью автономный режим.
Поле role: admin не выдаётся клиентом самовольно — только доверенными путями (AUTH_AND_SECURITY.md).
Для Whoop и прочих секретов допускается шифрование at-rest в Postgres и отсутствие cleartext логов.
Firestore Persistence даёт возможность офлайнового продолжения чтений и отложенных записей до reconnect; конфликтные патчи обычно проигрывает последняя временная метка записи — более изощрённые модели возможны уже на Postgres через версионность строк. Сообщения об ошибках в UI по возможности формулируются без сырого stack trace, ориентируясь на понятное действие пользователю (повторить, проверить сеть, обратиться в поддержку).
Сквозные сценарии (кратко как pipeline)
День напоминаний
CRUD напоминания → Firestore reminders → триггер планирования локальных уведомлений → действие пользователя → запись completions → ресинк слотов.
Синхронизация WHOOP
Credentials + OAuth → Tokens → синк-сервис → whoop_sync → отображение в профиле и производные калории активности там, где это связано в коде.
Силовой день зала
Выбор упражнения из каталога → план в workout_plans → live session workouts/sessions → графики history → вклад калорий активности при наличии.
Семейный budget
Бюджет и расходы finance_* → shared wallet члены realtime → возможный дубль через Telegram-admin.
Coach link
Приглашение принято → документ connections с матрицей прав → Provider выбирает targetUser → репозитории параметризованы effective uid.
Отчёт врачу
Выбор интервала → рендер графика в PDF-библиотеке клиента → отправка пользователем по своему каналу (не зашиваем transport).
Визуализация данных и текстовые инсайты
Графический слой охватывает вес и объём, калории приёма vs расхода, воду по дням, распределение категорий активности и финансов, долю выполненных напоминаний, прокачку упражнений во времени. Пользователь задаёт временной охват или быстрые пресеты (день, неделя, месяц, квартал, год или произвольный диапазон). Поверх кривых можно строить лёгкие эвристики: падения воды три дня подряд, перевыполнение калорий, редко выполненные блоки режима — и подсвечивать их текстовыми блоками советов там, где продукт заложил эти правила.
Надёжность, производительность и качество в конвейере
Клиент старается батчить массовые правки коллекций, избегать лишних full-scan запросов, держать индексацию Firestore в актуальных JSON.
Сервер будущего API получит качественный контур через OpenAPI lint, автопрогон миграций и smoke-тесты по staging-матрице
(STAGING_VALIDATION_MATRIX.md). Для доменной логики удобны mock-репозитории благодаря разделению слоёв.
На клиенте ошибки сети и валидации по возможности переводятся в понятный текст на экране, а детали для отладки остаются в логах сборки. Тяжёлые списки там, где это сделано в коде, режутся пагинацией или ленивой подгрузкой; при миграции на REST тот же принцип сохраняется через курсоры и лимиты страниц.
Локализация и формат региональных данных
Пользовательский интерфейс ориентирован на русский язык уведомлений и текстов приложения с форматированием дат локалью ru через intl. Расширение на несколько локалей — через стандартные механизмы Flutter переводов (assets arb / gen-l10n) без ломки структуры слоёв. Для локальных напоминаний важно согласовать часовой пояс устройства с логикой планирования, иначе «утренние» слоты смещаются при смене региона.
Глоссарий: термины без жаргона
UID / user id
Сейчас: строковый идентификатор из Firebase Auth; ключ users/{uid}. Цель: стабильный users.id в Postgres + уникальный uamUserId (Маркбэйс id); JWT содержит sub = id приложения.
Snapshot (Firestore)
Снимок результата запроса в момент времени; при подписке snapshots() приходит серия снимков при каждом изменении.
Репозиторий
Dart-класс в lib/data/repositories/, инкапсулирующий запросы к Firestore и маппинг в entity.
StreamProvider (Riverpod)
Провайдер, который отдаёт асинхронный поток данных; идеален для живых списков документов без ручного polling.
effective user / target user
Тот, чьи данные сейчас на экране: вы сами или связанный человек при активной connection и нужных правах.
Wallet (кошелёк)
Логический контейнер финансов с владельцем и членами; все расходы и бюджеты привязаны к нему во избежание пересечения чужих денег.
Bootstrap admin
Особый email из приватной конфигурации, имеющий право расширенных действий над глобальными настройками и пользователями.
SSE / WebSocket
Транспорты «живых» сообщений после миграции; заменяют клиентским кодом серию событий, которые раньше приходили из потоков Firestore.
Admin SDK
Серверная библиотека Firebase с ключом сервисного аккаунта; может обходить client rules поэтому обязана валидировать данные явно.
REFANDBD
Папка в репозитории со спецификациями миграции: OpenAPI-черновик, Postgres, операции nginx/docker, безопасность.
Итоговое резюме
WayWell — это высокосквозное приложение-wellness класса продакшена: реальные пользовательские связи финансов и здоровья, многослойная архитектура, насыщенные интеграции и понятная точка перехода к самостоятельному деплою с Postgres и управляемыми объектами данных.
- Один код на Flutter охватывает мобильные и десктопные платформы + web.
- Firebase ускоряет time-to-market, REFANDBD снимает риски vendor lock без переписывания UX с нуля.
- Внешние сервисы строго обведены документацией безопасности и повторным использованием на новом сервере.
Где искать первоисточники в репозитории
README.mdиSYNC_INSTRUCTIONS.md— установка и синхронизация машин разработки.@docs/— описание функций и инфраструктуры клиента вне деталей миграции.REFANDBD/README.mdкак вход в папку миграции, далееMASTER_INDEX.md— алфавитный указатель десятков артефактов.REFANDBD/openapi/openapi.draft.yaml— машиночитаемый чертёж HTTP API (/v1); его величина порядка тысяч строк отражает масштаб предстоящего backend.ПРОЕКТ_ОПИСАНИЕ_ДЛЯ_ПОРТФОЛИО.md— расширенное повествование для портфолио; при конфликте с кодом приоритет у исходников и индексов REFANDBD.
При противоречии между маркетинговым PDF и файлом Dart выигрывает актуальный исходник и индекс REFANDBD/FIREBASE_TOUCHPOINTS_INDEX.md.
Оценочная стоимость как готового продукта
Ниже — не бизнес-оценка с пользователями и выручкой и не калькуляция зарплат команды за всё время разработки. Это индикативная величина
программного актива в сборе: исходники клиента на Flutter, вспомогательные сервисы в репозитории (бот, скрипты), связанная документация миграции и спецификации в REFANDBD/,
в том виде как они описываются этой страницей и файлом «портфолио». Охват ориентируется на порядок ~30 тысяч строк Dart, множество экранов и доменных модулей, полный контур Firebase, админ-панель, WHOOP/Telegram/OpenFoodFacts, плюс дорожная карта отказа от Firebase.
- Итоговый ориентир (Россия, 2026, «продукт в коробке»)
- от ~6 до ~18 млн ₽
- Эквивалент условный
- порядка 70–210 тыс. USD — только для шкалы восприятия; курс валют меняется
Как читать диапазон
- Нижняя часть диапазона (~6–9 млн ₽) соответствует оценке готового кода и репозитория «как есть» для перехода прав (лицензия / отчуждение исходников) без аудита безопасности, без SLA и без гарантий коммерческой судьбы продукта.
- Середина и верх (~10–18 млн ₽) учитывает нетривиальный охват доменов (здоровье, тренировки, питание, финансы, связи между аккаунтами), администрирование каталогов, несколько интеграций и объём документации по миграции (это существенный нематериальный актив само по себе).
Верхняя граница приближается к тому порядку, за который заказчик мог бы платить исполнителю за разработку функционального аналога с нуля средним по рынку сегментом (без маркетинга и без базы клиентов) — именно из-за экономии времени и риска «повторить тот же объём заново» новый собственник готов платить надбавку к «сырым строкам кода».