WayWell ecosystem WayWell

Компаньон здорового ритма жизни

Кроссплатформенное приложение на Flutter для тела, режима и бюджета: метрики, дневник активности и КБЖУ, умные напоминания, тренировки и планы питания, совместные финансы, админ-каталог и интеграции WHOOP, Telegram и OpenFoodFacts. Ниже — возможности продукта, инженерное устройство, потоки данных и архив миграции с Firebase на PostgreSQL (страница автономна, без CDN).

Flutterодин код — много платформ
PostgreSQLканон данных на waywell.ru
Realtimeпотоки и синхронизация
REFANDBDOpenAPI · PG · операции
Clean Architecture Riverpod · go_router PostgreSQL live Локально открыть файл — без интернета
Серверный контур (реализовано, 2026): сайт https://waywell.ru, приложение https://app.waywell.ru, API https://api.waywell.ru/v1. Стек: PostgreSQL, MinIO, SSE, Flutter Web без Firebase. Разделы про Firestore ниже — архив AS-IS для аудита миграции REFANDBD.

Обзор и главная цель

Продукт объединяет тело (измерения, вода, калории), режим (напоминания о движении, дыхании, воде и отдыхе для глаз), нагрузку (каталог упражнений, планы, сессии, звуки и таймеры), питание (планы приёмов пищи и справочник КБЖУ), а также деньги с совместными кошельками и связями между аккаунтами — всё через один клиентский опыт на 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-редиректов.

Страница полностью автономна: шрифты системные, CSS встроен. Откройте landing/index.html в браузере — сеть не нужна.

Что входит в репозиторий (логическая карта)

Продукт живёт не только в lib/. Рядом лежат платформенные оболочки android/ и ios/, веб-шаблоны web/, каталог telegram-bot/ для серверной стороны Telegram, папка scripts/ для сидов данных и конвейера OpenFoodFacts, а также REFANDBD/ — отдельный «пакет зрелости»: OpenAPI-черновик (тысячи строк), снимки firestore.rules и индексов, примеры nginx, docker-compose и политики операций. Такой развод удобен при продаже прав или передаче проекта: код и инженерное наследие не размазаны по чатам.

Как всё работает вместе (от запуска до сохранения)

Ниже — один связный рассказ «что вообще происходит», без проваливания в код. Если разобрать этот блок, остальные разделы страницы — просто расшифровка деталей.

  1. Пользователь запускает приложение. Flutter вызывает Firebase.initializeApp с ключами из firebase_options.dart для текущей платформы (Android, iOS, Web…). На не-web платформах включается persistence Firestore, чтобы кэшировать снимки запросов и работать офлайн с последней известной копией данных.
  2. Вход в учётную запись. Сегодня: Firebase Authentication (email/password); провайдер auth_provider.dart слушает authStateChanges — от этого дерево экранов (логин/регистрация или приложение). У залогиненного пользователя стабильный UID (ключ users/{uid} и поля владельца в коллекциях). После миграции: сессия приложения — пара JWT после обмена wsid_code на Маркбэйс id; «кто я» — claim sub и профиль с GET /v1/auth/me (см. AUTH_SYNC_SESSION_AND_CLIENT_DESIGN.md).
  3. Экран просит данные через Riverpod. Например, список напоминаний: провайдер обращается к reminder_repository.dart, тот строит запрос Firestore к коллекции reminders с фильтром по текущему или «выбранному» пользователю (см. связи ниже). Репозиторий подписывается на поток через snapshots(): при любом изменении соответствующих документов в облаке UI получает новую версию списка без опроса сервера по таймеру.
  4. Пользователь что-то меняет на экране (добавил расход, отметил воду, завершил подход тренировки). Виджет вызывает метод репозитория: документ записывается в Firestore (set, иногда с merge: true) или отправляется пакет batch — когда нужно сохранить несколько документов согласованно; ограничения Firestore по размеру батча и числу операций учитываются в реализации репозиториев. Другие устройства этого же пользователя, подписанные на тот же запрос, получают обновление через тот же механизм snapshots().
  5. Напоминания — два слоя. Правило жизни (расписание, тип напоминания) хранится в Firestore. А системное будильничье время на телефоне ставит уже notification_service.dart через пакет локальных уведомлений (иногда нужен точечный GET данных напоминания по id после срабатывания).
  6. Внешние сервисы. WHOOP живёт своим HTTPS API; приложение после OAuth держит токены и тянет историю в whoop_sync. Telegram-бот — это отдельный процесс Node.js с правами администратора Firestore и теми же именами коллекций, что клиент — он не «рисует UI», только пишет данные по командам. Продукты OpenFoodFacts в основном попадают в приложение через офлайновый конвейер и пакетную загрузку в Firestore; живой онлайновый запрос к API OpenFoodFacts на каждый поиск пользователем не является обязательным сценарием работы клиента.
  7. После перехода на свой 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.

Масштаб кодовой базы (ориентиры из портфолио-дока)

Файлов Dart150+
Строк кода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/ (упрощённо)

lib/ ├── presentation/ страницы (auth, home, reminders, health_metrics …) и общие виджеты ├── domain/ entities — чистые модели без Firebase ├── data/ │ └── repositories/ доступ к Firestore / Storage под конкретные сценарии └── core/ providers (auth, настройки, доменные потоки), services (уведомления, WHOOP, загрузки), routes/, theme/

Такое разнесение упрощает тестирование домена отдельно от облака: в тестах подставляют фейковый репозиторий на интерфейс, не трогая виджеты.

Вход в систему, 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.

ДоменКоллекцииРепозиторий
Профиль и учётная записьusersuser_repository.dart
Расписание и история выполненийreminders, reminder_completionsreminder_*
Метрики тела по днямhealth_metricshealth_metric_repository.dart
Ежедневная активность / калории / водаactivitiesactivity_repository.dart
Каталог упражнений и историиexercises, exercise_history, completionsexercise_*
Таксономия каталогаexercise_categories, exercise_tagsexercise_meta_repository.dart
Сессии и структура тренировокworkouts, workout_sessionsworkout_repository.dart
План на календарьworkout_plansworkout_plan_repository.dart
Приёмы пищиmeal_plansmeal_plan_repository.dart
Пользовательские и референсные продуктыfood_products, food_reference_productsfood_*
Связи аккаунтовconnections (+ чтение users для отображения профилей в UI)user_connection_repository.dart
Деньгисемья finance_*finance_repository.dart, портфель
WHOOPwhoop_credentials, whoop_tokens, whoop_synccore 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.dartsnapshots() на документ 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 выполняет пакет либо целиком, либо не применяет его, что снижает риск «половины экрана устарело» после сетевого сбоя.

Цель после миграции

Flutter / Web / Telegram / CI │ ├── reverse-proxy ──► статическая сборка SPA (privacy, whoop callback HTML) ├── REST + TLS ──► приложение-сервер ──► PostgreSQL │ └── объектное хранилище MinIO или S3-совместимое └── SSE или WebSocket ◄► outbox/bus ◄► коммиты транзакций

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.dartworkouts, workout_sessionsworkouts: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
Финансовые вложения UIcoupons, 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.jsfinance_expenses, finance_categories, finance_budgets, finance_wallets, finance_mandatory_expenses, чтение части users.
handlers/sharedFinance.jsfinance_wallets, finance_budgets, finance_expenses — общие кошельки со строгими проверками членства.
handlers/health.jshealth_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 (корень репозитория).

Для связи задач кодинга ↔ REFANDBD смотри 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 млн ₽) учитывает нетривиальный охват доменов (здоровье, тренировки, питание, финансы, связи между аккаунтами), администрирование каталогов, несколько интеграций и объём документации по миграции (это существенный нематериальный актив само по себе).

Верхняя граница приближается к тому порядку, за который заказчик мог бы платить исполнителю за разработку функционального аналога с нуля средним по рынку сегментом (без маркетинга и без базы клиентов) — именно из-за экономии времени и риска «повторить тот же объём заново» новый собственник готов платить надбавку к «сырым строкам кода».

Это ознакомительная оценка для внутреннего использования; она не заменяет независимую экспертную оценку исключительных прав, налогообложение при продаже и не включает стоимость хостинга, магазинов приложений, юриста по лицензиям данных OpenFoodFacts и пользовательских соглашений. Для официальных сделок привлеките оценщика и финансового консультанта.